diff --git a/include/avif/avif.h b/include/avif/avif.h index d2caef9353..512cc83cd8 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -834,6 +834,9 @@ typedef enum avifStrictFlag // https://crbug.com/1246678. AVIF_STRICT_ALPHA_ISPE_REQUIRED = (1 << 2), + // TODO(yguyon): Generalize AVIF_STRICT_ALPHA_ISPE_REQUIRED to bit depth extension + // or add AVIF_STRICT_SUBDEPTH_ISPE_REQUIRED + // Maximum strictness; enables all bits above. This is avifDecoder's default. AVIF_STRICT_ENABLED = AVIF_STRICT_PIXI_REQUIRED | AVIF_STRICT_CLAP_VALID | AVIF_STRICT_ALPHA_ISPE_REQUIRED } avifStrictFlag; diff --git a/include/avif/internal.h b/include/avif/internal.h index 8b58687f04..b4c87785fd 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -43,6 +43,9 @@ extern "C" { #define AVIF_URN_ALPHA0 "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha" #define AVIF_URN_ALPHA1 "urn:mpeg:hevc:2015:auxid:1" +// TODO(yguyon): Add reference to specification +#define AVIF_URN_SUBDEPTH_8LSB "urn:mpeg:mpegB:cicp:systems:auxiliary:8lsb" + #define AVIF_CONTENT_TYPE_XMP "application/rdf+xml" // --------------------------------------------------------------------------- @@ -92,7 +95,29 @@ void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage); // Copies the samples from srcImage to dstImage. dstImage must be allocated. // srcImage and dstImage must have the same width, height, and depth. // If the AVIF_PLANES_YUV bit is set in planes, then srcImage and dstImage must have the same yuvFormat and yuvRange. -void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes); +avifResult avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes); + +// --------------------------------------------------------------------------- +// Subdepth + +typedef enum avifSubdepthMode +{ + // The image can be encoded as is with an AV1 codec. + AVIF_SUBDEPTH_NONE, + // The image is split into two images because its bit depth is not supported by AV1. + AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS, + AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS +} avifSubdepthMode; + +// Same as avifImageCopySamples() with source and destination bit masks and shifts. +avifResult avifImageCopySamplesExtended(avifImage * dstImage, + enum avifSubdepthMode dstSubdepthMode, + const avifImage * srcImage, + enum avifSubdepthMode srcSubdepthMode, + avifPlanesFlags planes); + +// --------------------------------------------------------------------------- +// Alpha typedef struct avifAlphaParams { diff --git a/src/avif.c b/src/avif.c index 3a53342ed4..3d4fca2e91 100644 --- a/src/avif.c +++ b/src/avif.c @@ -184,11 +184,43 @@ void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage) dstImage->imir = srcImage->imir; } -void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes) +avifResult avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes) { - assert(srcImage->depth == dstImage->depth); - if (planes & AVIF_PLANES_YUV) { - assert((srcImage->yuvFormat == dstImage->yuvFormat) && (srcImage->yuvRange == dstImage->yuvRange)); + return avifImageCopySamplesExtended(dstImage, AVIF_SUBDEPTH_NONE, srcImage, AVIF_SUBDEPTH_NONE, planes); +} + +avifResult avifImageCopySamplesExtended(avifImage * dstImage, + avifSubdepthMode dstSubdepthMode, + const avifImage * srcImage, + avifSubdepthMode srcSubdepthMode, + avifPlanesFlags planes) +{ + if (!(planes & AVIF_PLANES_YUV) && !(planes & AVIF_PLANES_A)) { + // Early exit. + return AVIF_RESULT_OK; + } + AVIF_CHECKERR((srcSubdepthMode == AVIF_SUBDEPTH_NONE) || (dstSubdepthMode == AVIF_SUBDEPTH_NONE), AVIF_RESULT_UNSUPPORTED_DEPTH); + AVIF_CHECKERR((srcSubdepthMode == AVIF_SUBDEPTH_NONE) || (dstSubdepthMode == AVIF_SUBDEPTH_NONE), AVIF_RESULT_UNSUPPORTED_DEPTH); + if (((srcImage->depth != 8) && (srcImage->depth != 10) && (srcImage->depth != 12) && (srcImage->depth != 16)) || + ((dstImage->depth != 8) && (dstImage->depth != 10) && (dstImage->depth != 12) && (dstImage->depth != 16))) { + return AVIF_RESULT_UNSUPPORTED_DEPTH; + } + if ((srcSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) || (srcSubdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS)) { + if (srcImage->depth != 8) { + return AVIF_RESULT_INVALID_ARGUMENT; + } + if (dstImage->depth == 8) { + return avifImageCopySamplesExtended(dstImage, AVIF_SUBDEPTH_NONE, srcImage, AVIF_SUBDEPTH_NONE, planes); + } + } else if ((dstSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) || (dstSubdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS)) { + if (dstImage->depth != 8) { + return AVIF_RESULT_INVALID_ARGUMENT; + } + if (srcImage->depth == 8) { + return avifImageCopySamplesExtended(dstImage, AVIF_SUBDEPTH_NONE, srcImage, AVIF_SUBDEPTH_NONE, planes); + } + } else if ((srcSubdepthMode != AVIF_SUBDEPTH_NONE) || (dstSubdepthMode != AVIF_SUBDEPTH_NONE)) { + return AVIF_RESULT_UNSUPPORTED_DEPTH; // Only 8MSB and 8LSB are supported. } const size_t bytesPerPixel = avifImageUsesU16(srcImage) ? 2 : 1; @@ -206,20 +238,62 @@ void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avif uint8_t * dstRow = avifImagePlane(dstImage, c); const uint32_t srcRowBytes = avifImagePlaneRowBytes(srcImage, c); const uint32_t dstRowBytes = avifImagePlaneRowBytes(dstImage, c); - assert(!srcRow == !dstRow); + AVIF_CHECKERR(!srcRow == !dstRow, AVIF_RESULT_INVALID_ARGUMENT); if (!srcRow) { continue; } - assert(planeWidth == avifImagePlaneWidth(dstImage, c)); - assert(planeHeight == avifImagePlaneHeight(dstImage, c)); - - const size_t planeWidthBytes = planeWidth * bytesPerPixel; - for (uint32_t y = 0; y < planeHeight; ++y) { - memcpy(dstRow, srcRow, planeWidthBytes); - srcRow += srcRowBytes; - dstRow += dstRowBytes; + AVIF_CHECKERR(planeWidth == avifImagePlaneWidth(dstImage, c), AVIF_RESULT_INVALID_ARGUMENT); + AVIF_CHECKERR(planeHeight == avifImagePlaneHeight(dstImage, c), AVIF_RESULT_INVALID_ARGUMENT); + + if ((srcSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) || (srcSubdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS)) { + // Copy samples that represent a part of the final bits to their place in the reconstructed buffer. + uint32_t srcShift; + uint16_t dstMask; + if (srcSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) { + srcShift = dstImage->depth - 8; + dstMask = (1u << (dstImage->depth - 8)) - 1u; + } else { + srcShift = 0; + dstMask = ((1u << (dstImage->depth - 8)) - 1u) << 8; + } + for (uint32_t y = 0; y < planeHeight; ++y) { + uint16_t * dstRow16 = (uint16_t *)dstRow; + for (uint32_t x = 0; x < planeWidth; ++x) { + dstRow16[x] = (dstRow16[x] & dstMask) | (srcRow[x] << srcShift); + } + srcRow += srcRowBytes; + dstRow += dstRowBytes; + } + } else if ((dstSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) || + (dstSubdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS)) { + // Copy the most or least significant bits of the source samples to a buffer with a lesser bit depth. + uint32_t srcShift; + uint16_t srcMask; + if (dstSubdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) { + srcShift = srcImage->depth - 8; + srcMask = (1u << 8) - 1u; + } else { + srcShift = 0; + srcMask = (1u << 8) - 1u; + } + for (uint32_t y = 0; y < planeHeight; ++y) { + const uint16_t * srcRow16 = (uint16_t *)srcRow; + for (uint32_t x = 0; x < planeWidth; ++x) { + dstRow[x] = (uint8_t)((srcRow16[x] >> srcShift) & srcMask); + } + srcRow += srcRowBytes; + dstRow += dstRowBytes; + } + } else { + const size_t planeWidthBytes = planeWidth * bytesPerPixel; + for (uint32_t y = 0; y < planeHeight; ++y) { + memcpy(dstRow, srcRow, planeWidthBytes); + srcRow += srcRowBytes; + dstRow += dstRowBytes; + } } } + return AVIF_RESULT_OK; } avifResult avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes) @@ -248,7 +322,7 @@ avifResult avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifP return allocationResult; } } - avifImageCopySamples(dstImage, srcImage, planes); + AVIF_CHECKRES(avifImageCopySamples(dstImage, srcImage, planes)); return AVIF_RESULT_OK; } diff --git a/src/read.c b/src/read.c index c1f223d53b..f4061939ab 100644 --- a/src/read.c +++ b/src/read.c @@ -806,15 +806,25 @@ typedef struct avifTileInfo unsigned int decodedTileCount; unsigned int firstTileIndex; // Within avifDecoderData.tiles. avifImageGrid grid; + avifSubdepthMode subdepthMode; } avifTileInfo; +// Represents the meaning of an avifTileInfo. +typedef enum +{ + AVIF_TILE_COLOR, // Usually the primary group. + AVIF_TILE_ALPHA, // Auxiliary alpha group of the primary color group. + AVIF_TILE_SUBDEPTH_COLOR, // Auxiliary bit depth extension group of the primary color group. + AVIF_TILE_SUBDEPTH_ALPHA, // Auxiliary alpha group of the auxiliary bit depth extension group. + AVIF_TILE_TYPE_COUNT +} avifTileType; + typedef struct avifDecoderData { avifMeta * meta; // The root-level meta box avifTrackArray tracks; avifTileArray tiles; - avifTileInfo color; - avifTileInfo alpha; + avifTileInfo tileInfos[AVIF_TILE_TYPE_COUNT]; avifDecoderSource source; // When decoding AVIF images with grid, use a single decoder instance for all the tiles instead of creating a decoder instance // for each tile. If that is the case, |codec| will be used by all the tiles. @@ -876,8 +886,9 @@ static void avifDecoderDataResetCodec(avifDecoderData * data) tile->codec = NULL; } } - data->color.decodedTileCount = 0; - data->alpha.decodedTileCount = 0; + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + data->tileInfos[tileType].decodedTileCount = 0; + } if (data->codec) { avifCodecDestroy(data->codec); data->codec = NULL; @@ -944,10 +955,10 @@ static void avifDecoderDataClearTiles(avifDecoderData * data) } } data->tiles.count = 0; - data->color.tileCount = 0; - data->color.decodedTileCount = 0; - data->alpha.tileCount = 0; - data->alpha.decodedTileCount = 0; + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + data->tileInfos[tileType].tileCount = 0; + data->tileInfos[tileType].decodedTileCount = 0; + } if (data->codec) { avifCodecDestroy(data->codec); data->codec = NULL; @@ -1459,9 +1470,18 @@ static avifResult avifDecoderDataAllocateGridImagePlanes(avifDecoderData * data, return AVIF_RESULT_INVALID_IMAGE_GRID; } + uint32_t dstDepth; + if (info->subdepthMode == AVIF_SUBDEPTH_NONE) { + dstDepth = tile->image->depth; + } else { + assert((info->subdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) || + (info->subdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS)); + dstDepth = 8; + } + // Lazily populate dstImage with the new frame's properties. - if ((dstImage->width != grid->outputWidth) || (dstImage->height != grid->outputHeight) || - (dstImage->depth != tile->image->depth) || (!alpha && (dstImage->yuvFormat != tile->image->yuvFormat))) { + if ((dstImage->width != grid->outputWidth) || (dstImage->height != grid->outputHeight) || (dstImage->depth != dstDepth) || + (!alpha && (dstImage->yuvFormat != tile->image->yuvFormat))) { if (alpha) { // Alpha doesn't match size, just bail out avifDiagnosticsPrintf(data->diag, "Alpha plane dimensions do not match color plane dimensions"); @@ -1471,7 +1491,7 @@ static avifResult avifDecoderDataAllocateGridImagePlanes(avifDecoderData * data, avifImageFreePlanes(dstImage, AVIF_PLANES_ALL); dstImage->width = grid->outputWidth; dstImage->height = grid->outputHeight; - dstImage->depth = tile->image->depth; + dstImage->depth = dstDepth; dstImage->yuvFormat = tile->image->yuvFormat; dstImage->yuvRange = tile->image->yuvRange; if (!data->cicpSet) { @@ -1491,11 +1511,11 @@ static avifResult avifDecoderDataAllocateGridImagePlanes(avifDecoderData * data, // After verifying that the relevant properties of the tile match those of the first tile, copies over the pixels from the tile // into dstImage. -static avifBool avifDecoderDataCopyTileToImage(avifDecoderData * data, - const avifTileInfo * info, - avifImage * dstImage, - const avifTile * tile, - unsigned int tileIndex) +static avifResult avifDecoderDataCopyTileToImage(avifDecoderData * data, + const avifTileInfo * info, + avifImage * dstImage, + const avifTile * tile, + unsigned int tileIndex) { const avifImageGrid * grid = &info->grid; const avifTile * firstTile = &data->tiles.tile[info->firstTileIndex]; @@ -1507,7 +1527,7 @@ static avifBool avifDecoderDataCopyTileToImage(avifDecoderData * data, (tile->image->transferCharacteristics != firstTile->image->transferCharacteristics) || (tile->image->matrixCoefficients != firstTile->image->matrixCoefficients)) { avifDiagnosticsPrintf(data->diag, "Grid image contains mismatched tiles"); - return AVIF_FALSE; + return AVIF_RESULT_INVALID_IMAGE_GRID; } } @@ -1530,11 +1550,11 @@ static avifBool avifDecoderDataCopyTileToImage(avifDecoderData * data, if ((avifImageSetViewRect(&dstView, dstImage, &dstViewRect) != AVIF_RESULT_OK) || (avifImageSetViewRect(&srcView, tile->image, &srcViewRect) != AVIF_RESULT_OK)) { assert(AVIF_FALSE); - return AVIF_FALSE; } - avifImageCopySamples(&dstView, &srcView, tile->input->alpha ? AVIF_PLANES_A : AVIF_PLANES_YUV); + AVIF_CHECKRES( + avifImageCopySamplesExtended(&dstView, AVIF_SUBDEPTH_NONE, &srcView, info->subdepthMode, tile->input->alpha ? AVIF_PLANES_A : AVIF_PLANES_YUV)); - return AVIF_TRUE; + return AVIF_RESULT_OK; } // If colorId == 0 (a sentinel value as item IDs must be nonzero), accept any found EXIF/XMP metadata. Passing in 0 @@ -1598,6 +1618,11 @@ static avifBool isAlphaURN(const char * urn) return !strcmp(urn, AVIF_URN_ALPHA0) || !strcmp(urn, AVIF_URN_ALPHA1); } +static avifBool isSubdepthURN(const char * urn) +{ + return !strcmp(urn, AVIF_URN_SUBDEPTH_8LSB); +} + // --------------------------------------------------------------------------- // BMFF Parsing @@ -1800,6 +1825,38 @@ static avifBool avifParseImageGridBox(avifImageGrid * grid, return avifROStreamRemainingBytes(&s) == 0; } +static avifResult avifDecoderItemReadAndParse(avifDecoder * decoder, + avifDecoderItem * item, + avifBool isItemInInput, + avifImageGrid * grid, + avifCodecType * codecType) +{ + if (!item) { + return AVIF_RESULT_OK; + } + if (!memcmp(item->type, "grid", 4)) { + if (isItemInInput) { + avifROData readData; + AVIF_CHECKRES(avifDecoderItemRead(item, decoder->io, &readData, 0, 0, decoder->data->diag)); + AVIF_CHECKERR(avifParseImageGridBox(grid, + readData.data, + readData.size, + decoder->imageSizeLimit, + decoder->imageDimensionLimit, + decoder->data->diag), + AVIF_RESULT_INVALID_IMAGE_GRID); + } + *codecType = avifDecoderItemGetGridCodecType(item); + if (*codecType == AVIF_CODEC_TYPE_UNKNOWN) { + return AVIF_RESULT_INVALID_IMAGE_GRID; + } + } else { + *codecType = avifGetCodecType(item->type); + assert(*codecType != AVIF_CODEC_TYPE_UNKNOWN); + } + return AVIF_RESULT_OK; +} + static avifBool avifParseImageSpatialExtentsProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) { BEGIN_STREAM(s, raw, rawLen, diag, "Box[ispe]"); @@ -3501,10 +3558,11 @@ static avifResult avifCodecCreateInternal(avifCodecChoice choice, const avifTile static avifBool avifTilesCanBeDecodedWithSameCodecInstance(avifDecoderData * data) { - if (data->color.tileCount == 1 && data->alpha.tileCount == 1) { + if (data->tileInfos[AVIF_TILE_COLOR].tileCount == 1 && data->tileInfos[AVIF_TILE_ALPHA].tileCount == 1 && + data->tileInfos[AVIF_TILE_SUBDEPTH_COLOR].tileCount == 0 && data->tileInfos[AVIF_TILE_SUBDEPTH_ALPHA].tileCount == 0) { // Single tile image with single tile alpha plane. In this case each tile needs its own decoder since the planes will be // "stolen". Stealing either the color or the alpha plane will invalidate the other one when decode is called the second - // time. + // time. Bit depth extensions require buffer compositing, so no plane "stealing". return AVIF_FALSE; } const uint8_t firstTileOperatingPoint = data->tiles.tile[0].operatingPoint; @@ -3574,14 +3632,14 @@ static avifBool avifDecoderItemShouldBeSkipped(const avifDecoderItem * item) } // Returns the primary color item if found, or NULL. -static avifDecoderItem * avifDecoderDataFindColorItem(avifDecoderData * data) +static avifDecoderItem * avifMetaFindColorItem(avifMeta * meta) { - for (uint32_t itemIndex = 0; itemIndex < data->meta->items.count; ++itemIndex) { - avifDecoderItem * item = &data->meta->items.item[itemIndex]; + for (uint32_t itemIndex = 0; itemIndex < meta->items.count; ++itemIndex) { + avifDecoderItem * item = &meta->items.item[itemIndex]; if (avifDecoderItemShouldBeSkipped(item)) { continue; } - if (item->id == data->meta->primaryItemID) { + if (item->id == meta->primaryItemID) { return item; } } @@ -3604,13 +3662,15 @@ static avifBool avifDecoderItemIsAlphaAux(avifDecoderItem * item, uint32_t color // set to AVIF_FALSE. In this case, the alpha item merely exists to hold the locations of the alpha tile items. The data of this // item need not be read and the pixi property cannot be validated. Otherwise, *isAlphaItemInInput will be set to AVIF_TRUE when // *alphaItem is not NULL. -static avifResult avifDecoderDataFindAlphaItem(avifDecoderData * data, - avifDecoderItem * colorItem, - avifDecoderItem ** alphaItem, - avifBool * isAlphaItemInInput) +static avifResult avifMetaFindAlphaItem(avifMeta * meta, + const avifDecoderItem * colorItem, + const avifTileInfo * colorInfo, + avifDecoderItem ** alphaItem, + avifTileInfo * alphaInfo, + avifBool * isAlphaItemInInput) { - for (uint32_t itemIndex = 0; itemIndex < data->meta->items.count; ++itemIndex) { - avifDecoderItem * item = &data->meta->items.item[itemIndex]; + for (uint32_t itemIndex = 0; itemIndex < meta->items.count; ++itemIndex) { + avifDecoderItem * item = &meta->items.item[itemIndex]; if (avifDecoderItemShouldBeSkipped(item)) { continue; } @@ -3625,9 +3685,8 @@ static avifResult avifDecoderDataFindAlphaItem(avifDecoderData * data, *isAlphaItemInInput = AVIF_FALSE; return AVIF_RESULT_OK; } - // If color item is a grid, check if there is an alpha channel which is represented as an auxl item to each color tile - // item. - uint32_t colorItemCount = data->color.grid.rows * data->color.grid.columns; + // If color item is a grid, check if there is an alpha channel which is represented as an auxl item to each color tile item. + uint32_t colorItemCount = colorInfo->grid.rows * colorInfo->grid.columns; if (colorItemCount == 0) { *alphaItem = NULL; *isAlphaItemInInput = AVIF_FALSE; @@ -3637,14 +3696,14 @@ static avifResult avifDecoderDataFindAlphaItem(avifDecoderData * data, AVIF_CHECKERR(alphaItemIndices, AVIF_RESULT_OUT_OF_MEMORY); uint32_t alphaItemCount = 0; uint32_t maxItemID = 0; - for (uint32_t i = 0; i < colorItem->meta->items.count; ++i) { - avifDecoderItem * item = &colorItem->meta->items.item[i]; + for (uint32_t i = 0; i < meta->items.count; ++i) { + avifDecoderItem * item = &meta->items.item[i]; if (item->id > maxItemID) { maxItemID = item->id; } if (item->dimgForID == colorItem->id) { - for (uint32_t j = 0; j < colorItem->meta->items.count; ++j) { - avifDecoderItem * auxlItem = &colorItem->meta->items.item[j]; + for (uint32_t j = 0; j < meta->items.count; ++j) { + avifDecoderItem * auxlItem = &meta->items.item[j]; if (avifDecoderItemIsAlphaAux(auxlItem, item->id)) { alphaItemIndices[alphaItemCount++] = j; } @@ -3658,7 +3717,7 @@ static avifResult avifDecoderDataFindAlphaItem(avifDecoderData * data, *isAlphaItemInInput = AVIF_FALSE; return AVIF_RESULT_OK; } - *alphaItem = avifMetaFindItem(colorItem->meta, maxItemID + 1); + *alphaItem = avifMetaFindItem(meta, maxItemID + 1); if (*alphaItem == NULL) { avifFree(alphaItemIndices); *isAlphaItemInInput = AVIF_FALSE; @@ -3668,15 +3727,33 @@ static avifResult avifDecoderDataFindAlphaItem(avifDecoderData * data, (*alphaItem)->width = colorItem->width; (*alphaItem)->height = colorItem->height; for (uint32_t i = 0; i < alphaItemCount; ++i) { - avifDecoderItem * item = &colorItem->meta->items.item[alphaItemIndices[i]]; + avifDecoderItem * item = &meta->items.item[alphaItemIndices[i]]; item->dimgForID = (*alphaItem)->id; } avifFree(alphaItemIndices); *isAlphaItemInInput = AVIF_FALSE; - data->alpha.grid = data->color.grid; + alphaInfo->grid = colorInfo->grid; return AVIF_RESULT_OK; } +static avifDecoderItem * avifMetaFindBitDepthExtensionItem(avifMeta * meta, const avifDecoderItem * colorItem) +{ + for (uint32_t itemIndex = 0; itemIndex < meta->items.count; ++itemIndex) { + avifDecoderItem * item = &meta->items.item[itemIndex]; + if (avifDecoderItemShouldBeSkipped(item)) { + continue; + } + if (item->auxForID != colorItem->id) { + continue; + } + const avifProperty * auxCProp = avifPropertyArrayFind(&item->properties, "auxC"); + if (auxCProp && isSubdepthURN(auxCProp->u.auxC.auxType)) { + return item; + } + } + return NULL; +} + static avifResult avifDecoderGenerateImageTiles(avifDecoder * decoder, avifTileInfo * info, avifDecoderItem * item, avifBool alpha) { const uint32_t previousTileCount = decoder->data->tiles.count; @@ -3713,8 +3790,9 @@ avifResult avifDecoderReset(avifDecoder * decoder) return AVIF_RESULT_OK; } - memset(&data->color.grid, 0, sizeof(data->color.grid)); - memset(&data->alpha.grid, 0, sizeof(data->alpha.grid)); + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + memset(&data->tileInfos[tileType].grid, 0, sizeof(data->tileInfos[tileType].grid)); + } avifDecoderDataClearTiles(data); // Prepare / cleanup decoded image state @@ -3836,7 +3914,7 @@ avifResult avifDecoderReset(avifDecoder * decoder) data->diag)) { return AVIF_RESULT_BMFF_PARSE_FAILED; } - data->color.tileCount = 1; + data->tileInfos[AVIF_TILE_COLOR].tileCount = 1; if (alphaTrack) { avifTile * alphaTile = avifDecoderDataCreateTile(data, alphaCodecType, alphaTrack->width, alphaTrack->height, operatingPoint); @@ -3851,7 +3929,7 @@ avifResult avifDecoderReset(avifDecoder * decoder) return AVIF_RESULT_BMFF_PARSE_FAILED; } alphaTile->input->alpha = AVIF_TRUE; - data->alpha.tileCount = 1; + data->tileInfos[AVIF_TILE_ALPHA].tileCount = 1; } // Stash off sample table for future timing information @@ -3886,60 +3964,74 @@ avifResult avifDecoderReset(avifDecoder * decoder) return AVIF_RESULT_MISSING_IMAGE_ITEM; } - avifDecoderItem * colorItem = avifDecoderDataFindColorItem(data); - if (!colorItem) { + // Main item of each group category, if any. + avifDecoderItem * mainItems[AVIF_TILE_TYPE_COUNT] = { NULL, NULL, NULL, NULL }; + avifCodecType codecType[AVIF_TILE_TYPE_COUNT] = { + AVIF_CODEC_TYPE_UNKNOWN, AVIF_CODEC_TYPE_UNKNOWN, AVIF_CODEC_TYPE_UNKNOWN, AVIF_CODEC_TYPE_UNKNOWN + }; + + // Mandatory primary color item + mainItems[AVIF_TILE_COLOR] = avifMetaFindColorItem(data->meta); + if (!mainItems[AVIF_TILE_COLOR]) { avifDiagnosticsPrintf(&decoder->diag, "Primary item not found"); return AVIF_RESULT_MISSING_IMAGE_ITEM; } - colorProperties = &colorItem->properties; - if (!memcmp(colorItem->type, "grid", 4)) { - avifROData readData; - AVIF_CHECKRES(avifDecoderItemRead(colorItem, decoder->io, &readData, 0, 0, data->diag)); - AVIF_CHECKERR(avifParseImageGridBox(&data->color.grid, - readData.data, - readData.size, - decoder->imageSizeLimit, - decoder->imageDimensionLimit, - data->diag), - AVIF_RESULT_INVALID_IMAGE_GRID); - colorCodecType = avifDecoderItemGetGridCodecType(colorItem); - if (colorCodecType == AVIF_CODEC_TYPE_UNKNOWN) { - return AVIF_RESULT_INVALID_IMAGE_GRID; - } - } else { - colorCodecType = avifGetCodecType(colorItem->type); - assert(colorCodecType != AVIF_CODEC_TYPE_UNKNOWN); - } + AVIF_CHECKRES(avifDecoderItemReadAndParse(decoder, + mainItems[AVIF_TILE_COLOR], + /*isItemInInput=*/AVIF_TRUE, + &data->tileInfos[AVIF_TILE_COLOR].grid, + &codecType[AVIF_TILE_COLOR])); + colorProperties = &mainItems[AVIF_TILE_COLOR]->properties; + colorCodecType = codecType[AVIF_TILE_COLOR]; + // Optional alpha auxiliary item avifBool isAlphaItemInInput; - avifDecoderItem * alphaItem; - AVIF_CHECKRES(avifDecoderDataFindAlphaItem(data, colorItem, &alphaItem, &isAlphaItemInInput)); - avifCodecType alphaCodecType = AVIF_CODEC_TYPE_UNKNOWN; - if (alphaItem) { - if (!memcmp(alphaItem->type, "grid", 4)) { - if (isAlphaItemInInput) { - avifROData readData; - AVIF_CHECKRES(avifDecoderItemRead(alphaItem, decoder->io, &readData, 0, 0, data->diag)); - AVIF_CHECKERR(avifParseImageGridBox(&data->alpha.grid, - readData.data, - readData.size, - decoder->imageSizeLimit, - decoder->imageDimensionLimit, - data->diag), - AVIF_RESULT_INVALID_IMAGE_GRID); - } - alphaCodecType = avifDecoderItemGetGridCodecType(alphaItem); - if (alphaCodecType == AVIF_CODEC_TYPE_UNKNOWN) { - return AVIF_RESULT_INVALID_IMAGE_GRID; - } - } else { - alphaCodecType = avifGetCodecType(alphaItem->type); - assert(alphaCodecType != AVIF_CODEC_TYPE_UNKNOWN); + AVIF_CHECKRES(avifMetaFindAlphaItem(data->meta, + mainItems[AVIF_TILE_COLOR], + &data->tileInfos[AVIF_TILE_COLOR], + &mainItems[AVIF_TILE_ALPHA], + &data->tileInfos[AVIF_TILE_ALPHA], + &isAlphaItemInInput)); + AVIF_CHECKRES(avifDecoderItemReadAndParse(decoder, + mainItems[AVIF_TILE_ALPHA], + isAlphaItemInInput, + &data->tileInfos[AVIF_TILE_ALPHA].grid, + &codecType[AVIF_TILE_ALPHA])); + + // Optional bit depth extension color auxiliary item + mainItems[AVIF_TILE_SUBDEPTH_COLOR] = avifMetaFindBitDepthExtensionItem(data->meta, mainItems[AVIF_TILE_COLOR]); + AVIF_CHECKRES(avifDecoderItemReadAndParse(decoder, + mainItems[AVIF_TILE_SUBDEPTH_COLOR], + /*isItemInInput=*/AVIF_TRUE, + &data->tileInfos[AVIF_TILE_SUBDEPTH_COLOR].grid, + &codecType[AVIF_TILE_SUBDEPTH_COLOR])); + + // Optional bit depth extension alpha auxiliary item + if (mainItems[AVIF_TILE_SUBDEPTH_COLOR] && mainItems[AVIF_TILE_ALPHA]) { + // Could be a bit depth extension auxiliary item of the alpha auxiliary item + mainItems[AVIF_TILE_SUBDEPTH_ALPHA] = + avifMetaFindBitDepthExtensionItem(data->meta, /*colorItem=*/mainItems[AVIF_TILE_ALPHA]); + + // Could also be an alpha auxiliary item of the bit depth extension auxiliary item + if (!mainItems[AVIF_TILE_SUBDEPTH_ALPHA]) { + avifBool isSubdepthAlphaItemInInput; + AVIF_CHECKRES(avifMetaFindAlphaItem(data->meta, + mainItems[AVIF_TILE_SUBDEPTH_COLOR], + &data->tileInfos[AVIF_TILE_SUBDEPTH_COLOR], + &mainItems[AVIF_TILE_SUBDEPTH_ALPHA], + &data->tileInfos[AVIF_TILE_SUBDEPTH_ALPHA], + &isSubdepthAlphaItemInInput)); } + + AVIF_CHECKRES(avifDecoderItemReadAndParse(decoder, + mainItems[AVIF_TILE_SUBDEPTH_ALPHA], + /*isItemInInput=*/AVIF_TRUE, + &data->tileInfos[AVIF_TILE_SUBDEPTH_ALPHA].grid, + &codecType[AVIF_TILE_SUBDEPTH_ALPHA])); } // Find Exif and/or XMP metadata, if any - AVIF_CHECKRES(avifDecoderFindMetadata(decoder, data->meta, decoder->image, colorItem->id)); + AVIF_CHECKRES(avifDecoderFindMetadata(decoder, data->meta, decoder->image, mainItems[AVIF_TILE_COLOR]->id)); // Set all counts and timing to safe-but-uninteresting values decoder->imageIndex = -1; @@ -3953,9 +4045,10 @@ avifResult avifDecoderReset(avifDecoder * decoder) decoder->duration = 1; decoder->durationInTimescales = 1; - AVIF_CHECKRES(avifDecoderGenerateImageTiles(decoder, &data->color, colorItem, /*alpha=*/AVIF_FALSE)); - if ((data->color.grid.rows == 0) || (data->color.grid.columns == 0)) { - if (colorItem->progressive) { + AVIF_CHECKRES( + avifDecoderGenerateImageTiles(decoder, &data->tileInfos[AVIF_TILE_COLOR], mainItems[AVIF_TILE_COLOR], /*alpha=*/AVIF_FALSE)); + if ((data->tileInfos[AVIF_TILE_COLOR].grid.rows == 0) || (data->tileInfos[AVIF_TILE_COLOR].grid.columns == 0)) { + if (mainItems[AVIF_TILE_COLOR]->progressive) { decoder->progressiveState = AVIF_PROGRESSIVE_STATE_AVAILABLE; const avifTile * colorTile = &data->tiles.tile[0]; if (colorTile->input->samples.count > 1) { @@ -3965,37 +4058,51 @@ avifResult avifDecoderReset(avifDecoder * decoder) } } - if (alphaItem) { - if (!alphaItem->width && !alphaItem->height) { - // NON-STANDARD: Alpha subimage does not have an ispe property; adopt width/height from color item - assert(!(decoder->strictFlags & AVIF_STRICT_ALPHA_ISPE_REQUIRED)); - alphaItem->width = colorItem->width; - alphaItem->height = colorItem->height; + for (int tileType = AVIF_TILE_ALPHA; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + if (!mainItems[tileType]) { + continue; } - AVIF_CHECKRES(avifDecoderGenerateImageTiles(decoder, &data->alpha, alphaItem, /*alpha=*/AVIF_TRUE)); + if (!mainItems[tileType]->width && !mainItems[tileType]->height) { + if (tileType == AVIF_TILE_ALPHA) { + // NON-STANDARD: Alpha subimage does not have an ispe property; adopt width/height from color item + assert(!(decoder->strictFlags & AVIF_STRICT_ALPHA_ISPE_REQUIRED)); + // TODO(yguyon): Do the same for AVIF_TILE_SUBDEPTH. + } + mainItems[tileType]->width = mainItems[AVIF_TILE_COLOR]->width; + mainItems[tileType]->height = mainItems[AVIF_TILE_COLOR]->height; + } + const avifBool isAlpha = tileType == AVIF_TILE_ALPHA || tileType == AVIF_TILE_SUBDEPTH_ALPHA; + AVIF_CHECKRES(avifDecoderGenerateImageTiles(decoder, &data->tileInfos[tileType], mainItems[tileType], isAlpha)); } - decoder->image->width = colorItem->width; - decoder->image->height = colorItem->height; - decoder->alphaPresent = (alphaItem != NULL); - decoder->image->alphaPremultiplied = decoder->alphaPresent && (colorItem->premByID == alphaItem->id); + decoder->image->width = mainItems[AVIF_TILE_COLOR]->width; + decoder->image->height = mainItems[AVIF_TILE_COLOR]->height; + decoder->alphaPresent = (mainItems[AVIF_TILE_ALPHA] != NULL); + decoder->image->alphaPremultiplied = decoder->alphaPresent && + (mainItems[AVIF_TILE_COLOR]->premByID == mainItems[AVIF_TILE_ALPHA]->id); - AVIF_CHECKRES( - avifDecoderItemValidateProperties(colorItem, avifGetConfigurationPropertyName(colorCodecType), &decoder->diag, decoder->strictFlags)); - if (alphaItem) { + for (int tileType = AVIF_TILE_COLOR; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + if (!mainItems[tileType]) { + continue; + } avifStrictFlags strictFlags = decoder->strictFlags; - if (!isAlphaItemInInput) { + if (tileType == AVIF_TILE_ALPHA && !isAlphaItemInInput) { // In this case, the made up grid item will not have an associated pixi property. So validate everything else // but the pixi property. strictFlags &= ~AVIF_STRICT_PIXI_REQUIRED; } - AVIF_CHECKRES( - avifDecoderItemValidateProperties(alphaItem, avifGetConfigurationPropertyName(alphaCodecType), &decoder->diag, strictFlags)); + AVIF_CHECKRES(avifDecoderItemValidateProperties(mainItems[tileType], + avifGetConfigurationPropertyName(codecType[tileType]), + &decoder->diag, + strictFlags)); } } - decoder->data->color.firstTileIndex = 0; - decoder->data->alpha.firstTileIndex = decoder->data->color.tileCount; + uint32_t firstTileIndex = 0; + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + decoder->data->tileInfos[tileType].firstTileIndex = firstTileIndex; + firstTileIndex += decoder->data->tileInfos[tileType].tileCount; + } // Sanity check tiles for (uint32_t tileIndex = 0; tileIndex < data->tiles.count; ++tileIndex) { @@ -4131,6 +4238,18 @@ avifResult avifDecoderReset(avifDecoder * decoder) return AVIF_RESULT_BMFF_PARSE_FAILED; } + if (data->tileInfos[AVIF_TILE_SUBDEPTH_COLOR].tileCount) { + if (decoder->image->depth != 8) { + // Only 8 + 8-bit AVIF bit depth extension is currently supported. + return AVIF_RESULT_NOT_IMPLEMENTED; + } + decoder->image->depth = 16; + data->tileInfos[AVIF_TILE_COLOR].subdepthMode = AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS; + data->tileInfos[AVIF_TILE_ALPHA].subdepthMode = AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS; + data->tileInfos[AVIF_TILE_SUBDEPTH_COLOR].subdepthMode = AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS; + data->tileInfos[AVIF_TILE_SUBDEPTH_ALPHA].subdepthMode = AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS; + } + return AVIF_RESULT_OK; } @@ -4245,16 +4364,22 @@ static avifResult avifDecoderDecodeTiles(avifDecoder * decoder, uint32_t nextIma AVIF_CHECKRES(avifDecoderDataAllocateGridImagePlanes(decoder->data, info, decoder->image)); } - if (!avifDecoderDataCopyTileToImage(decoder->data, info, decoder->image, tile, tileIndex)) { - return AVIF_RESULT_INVALID_IMAGE_GRID; - } + AVIF_CHECKRES(avifDecoderDataCopyTileToImage(decoder->data, info, decoder->image, tile, tileIndex)); } else { - // Non-grid path. Just steal the planes from the only "tile". + // Normal (most common) non-grid path. assert(info->tileCount == 1); assert(tileIndex == 0); avifImage * src = tile->image; + uint32_t targetDepth; + if (info->subdepthMode == AVIF_SUBDEPTH_NONE) { + targetDepth = src->depth; + } else { + assert((info->subdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) || + (info->subdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS)); + targetDepth = 16; + } if ((decoder->image->width != src->width) || (decoder->image->height != src->height) || - (decoder->image->depth != src->depth)) { + (decoder->image->depth != targetDepth)) { if (tile->input->alpha) { avifDiagnosticsPrintf(&decoder->diag, "The color image item does not match the alpha image item in width, height, or bit depth"); @@ -4264,9 +4389,21 @@ static avifResult avifDecoderDecodeTiles(avifDecoder * decoder, uint32_t nextIma decoder->image->width = src->width; decoder->image->height = src->height; - decoder->image->depth = src->depth; + decoder->image->depth = targetDepth; + } + const avifPlanesFlag planes = tile->input->alpha ? AVIF_PLANES_A : AVIF_PLANES_YUV; + if (info->subdepthMode == AVIF_SUBDEPTH_NONE) { + avifImageStealPlanes(decoder->image, src, planes); + } else { + if (info->subdepthMode != AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS) { + AVIF_CHECKRES(avifImageAllocatePlanes(decoder->image, planes)); + } + // Copies the samples to the least or most significant bits of the final reconstructed buffer. + if (avifImageCopySamplesExtended(decoder->image, AVIF_SUBDEPTH_NONE, src, info->subdepthMode, planes) != AVIF_RESULT_OK) { + assert(AVIF_FALSE); + } + avifImageFreePlanes(src, planes); } - avifImageStealPlanes(decoder->image, src, tile->input->alpha ? AVIF_PLANES_A : AVIF_PLANES_YUV); } } return AVIF_RESULT_OK; @@ -4285,14 +4422,19 @@ avifResult avifDecoderNextImage(avifDecoder * decoder) return AVIF_RESULT_IO_NOT_SET; } - if ((decoder->data->color.decodedTileCount == decoder->data->color.tileCount) && - (decoder->data->alpha.decodedTileCount == decoder->data->alpha.tileCount)) { + uint32_t numDecodedTiles = 0; + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + numDecodedTiles += decoder->data->tileInfos[tileType].decodedTileCount; + } + if (numDecodedTiles == decoder->data->tiles.count) { // A frame was decoded during the last avifDecoderNextImage() call. - decoder->data->color.decodedTileCount = 0; - decoder->data->alpha.decodedTileCount = 0; + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + decoder->data->tileInfos[tileType].decodedTileCount = 0; + } } - assert(decoder->data->tiles.count == (decoder->data->color.tileCount + decoder->data->alpha.tileCount)); + assert(decoder->data->tiles.count == (decoder->data->tileInfos[AVIF_TILE_TYPE_COUNT - 1].firstTileIndex + + decoder->data->tileInfos[AVIF_TILE_TYPE_COUNT - 1].tileCount)); const uint32_t nextImageIndex = (uint32_t)(decoder->imageIndex + 1); // Ensure that we have created the codecs before proceeding with the decoding. @@ -4302,35 +4444,43 @@ avifResult avifDecoderNextImage(avifDecoder * decoder) // Acquire all sample data for the current image first, allowing for any read call to bail out // with AVIF_RESULT_WAITING_ON_IO harmlessly / idempotently, unless decoder->allowIncremental. - // Start with color tiles. - const avifResult prepareColorTileResult = avifDecoderPrepareTiles(decoder, nextImageIndex, &decoder->data->color); - if (!decoder->allowIncremental || (prepareColorTileResult != AVIF_RESULT_WAITING_ON_IO)) { - AVIF_CHECKRES(prepareColorTileResult); - } - // Do the same with alpha tiles. They are handled separately because their - // order of appearance relative to the color tiles in the bitstream is left - // to the encoder's choice, and decoding as many as possible of each - // category in parallel is beneficial for incremental decoding, as pixel - // rows need all channels to be decoded before being accessible to the user. - const avifResult prepareAlphaTileResult = avifDecoderPrepareTiles(decoder, nextImageIndex, &decoder->data->alpha); - if (!decoder->allowIncremental || (prepareAlphaTileResult != AVIF_RESULT_WAITING_ON_IO)) { - AVIF_CHECKRES(prepareAlphaTileResult); - } - - // Decode all available color tiles now, then all available alpha tiles. - AVIF_CHECKRES(avifDecoderDecodeTiles(decoder, nextImageIndex, &decoder->data->color)); - AVIF_CHECKRES(avifDecoderDecodeTiles(decoder, nextImageIndex, &decoder->data->alpha)); - - if ((decoder->data->color.decodedTileCount != decoder->data->color.tileCount) || - (decoder->data->alpha.decodedTileCount != decoder->data->alpha.tileCount)) { + avifResult prepareTileResult[AVIF_TILE_TYPE_COUNT]; + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + prepareTileResult[tileType] = avifDecoderPrepareTiles(decoder, nextImageIndex, &decoder->data->tileInfos[tileType]); + if (!decoder->allowIncremental || (prepareTileResult[tileType] != AVIF_RESULT_WAITING_ON_IO)) { + AVIF_CHECKRES(prepareTileResult[tileType]); + } + } + + // Decode all available color tiles now, then all available alpha tiles, then all available bit + // depth extension tiles. The order of appearance of the tiles in the bitstream is left to the + // encoder's choice, and decoding as many as possible of each category in parallel is beneficial + // for incremental decoding, as pixel rows need all channels to be decoded before being + // accessible to the user. + avifBool missingTile = AVIF_FALSE; + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + AVIF_CHECKRES(avifDecoderDecodeTiles(decoder, nextImageIndex, &decoder->data->tileInfos[tileType])); + missingTile |= (decoder->data->tileInfos[tileType].decodedTileCount != decoder->data->tileInfos[tileType].tileCount); + } + + if (missingTile) { assert(decoder->allowIncremental); // The image is not completely decoded. There should be no error unrelated to missing bytes, // and at least some missing bytes. - assert((prepareColorTileResult == AVIF_RESULT_WAITING_ON_IO) || (prepareAlphaTileResult == AVIF_RESULT_WAITING_ON_IO)); + avifResult firstNonOkResult = AVIF_RESULT_OK; + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + assert((prepareTileResult[tileType] == AVIF_RESULT_OK) || (prepareTileResult[tileType] == AVIF_RESULT_WAITING_ON_IO)); + if (firstNonOkResult == AVIF_RESULT_OK) { + firstNonOkResult = prepareTileResult[tileType]; + } + } + assert(firstNonOkResult != AVIF_RESULT_OK); // Return the "not enough bytes" status now instead of moving on to the next frame. return AVIF_RESULT_WAITING_ON_IO; } - assert((prepareColorTileResult == AVIF_RESULT_OK) && (prepareAlphaTileResult == AVIF_RESULT_OK)); + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + assert(prepareTileResult[tileType] == AVIF_RESULT_OK); + } // Only advance decoder->imageIndex once the image is completely decoded, so that // avifDecoderNthImage(decoder, decoder->imageIndex + 1) is equivalent to avifDecoderNextImage(decoder) @@ -4406,8 +4556,12 @@ avifResult avifDecoderNthImage(avifDecoder * decoder, uint32_t frameIndex) } if (requestedIndex == decoder->imageIndex) { - if ((decoder->data->color.decodedTileCount == decoder->data->color.tileCount) && - (decoder->data->alpha.decodedTileCount == decoder->data->alpha.tileCount)) { + avifBool missingTile = AVIF_FALSE; + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + missingTile |= (decoder->data->tileInfos[tileType].decodedTileCount != decoder->data->tileInfos[tileType].tileCount); + } + + if (!missingTile) { // The current fully decoded image (decoder->imageIndex) is requested, nothing to do return AVIF_RESULT_OK; } @@ -4496,12 +4650,12 @@ static uint32_t avifGetDecodedRowCount(const avifDecoder * decoder, const avifTi uint32_t avifDecoderDecodedRowCount(const avifDecoder * decoder) { - const uint32_t colorRowCount = avifGetDecodedRowCount(decoder, &decoder->data->color); - if (colorRowCount == 0) { - return 0; + uint32_t minRowCount = decoder->image->height; + for (int tileType = 0; tileType < AVIF_TILE_TYPE_COUNT; ++tileType) { + const uint32_t rowCount = avifGetDecodedRowCount(decoder, &decoder->data->tileInfos[tileType]); + minRowCount = AVIF_MIN(minRowCount, rowCount); } - const uint32_t alphaRowCount = avifGetDecodedRowCount(decoder, &decoder->data->alpha); - return AVIF_MIN(colorRowCount, alphaRowCount); + return minRowCount; } avifResult avifDecoderRead(avifDecoder * decoder, avifImage * image) diff --git a/src/write.c b/src/write.c index 0f757b22fa..39198539ea 100644 --- a/src/write.c +++ b/src/write.c @@ -33,6 +33,9 @@ AVIF_ARRAY_DECLARE(avifOffsetFixupArray, avifOffsetFixup, fixup); static const char alphaURN[] = AVIF_URN_ALPHA0; static const size_t alphaURNSize = sizeof(alphaURN); +static const char subdepth8lsbURN[] = AVIF_URN_SUBDEPTH_8LSB; +static const size_t subdepth8lsbURNSize = sizeof(subdepth8lsbURN); + static const char xmpContentType[] = AVIF_CONTENT_TYPE_XMP; static const size_t xmpContentTypeSize = sizeof(xmpContentType); @@ -165,6 +168,7 @@ typedef struct avifEncoderItem // TODO(yguyon): Rename or add av2C uint32_t cellIndex; // Which row-major cell index corresponds to this item. only present on image items avifBool alpha; + avifSubdepthMode subdepthMode; avifBool hiddenImage; // A hidden image item has (flags & 1) equal to 1 in its ItemInfoEntry. const char * infeName; @@ -903,16 +907,18 @@ static avifResult avifEncoderAddImageItems(avifEncoder * encoder, uint32_t gridWidth, uint32_t gridHeight, avifBool alpha, + avifSubdepthMode subdepthMode, uint16_t * topLevelItemID) { const uint32_t cellCount = gridCols * gridRows; - const char * infeName = alpha ? "Alpha" : "Color"; + const char * infeName = alpha ? "Alpha" : ((subdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS) ? "8lsb " : "Color"); const size_t infeNameSize = 6; if (cellCount > 1) { avifEncoderItem * gridItem = avifEncoderDataCreateItem(encoder->data, "grid", infeName, infeNameSize, 0); avifWriteGridPayload(&gridItem->metadataPayload, gridCols, gridRows, gridWidth, gridHeight); gridItem->alpha = alpha; + gridItem->subdepthMode = subdepthMode; gridItem->gridCols = gridCols; gridItem->gridRows = gridRows; gridItem->gridWidth = gridWidth; @@ -929,6 +935,7 @@ static avifResult avifEncoderAddImageItems(avifEncoder * encoder, item->codec->csOptions = encoder->csOptions; item->codec->diag = &encoder->diag; item->alpha = alpha; + item->subdepthMode = subdepthMode; item->extraLayerCount = encoder->extraLayerCount; if (cellCount > 1) { @@ -1007,7 +1014,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, const avifImage * firstCell = cellImages[0]; const avifImage * bottomRightCell = cellImages[cellCount - 1]; - if ((firstCell->depth != 8) && (firstCell->depth != 10) && (firstCell->depth != 12)) { + if ((firstCell->depth != 8) && (firstCell->depth != 10) && (firstCell->depth != 12) && (firstCell->depth != 16)) { return AVIF_RESULT_UNSUPPORTED_DEPTH; } if (!firstCell->width || !firstCell->height || !bottomRightCell->width || !bottomRightCell->height) { @@ -1148,9 +1155,25 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, return copyResult; } + avifSubdepthMode subdepthMode = AVIF_SUBDEPTH_NONE; + if (firstCell->depth == 16) { + // AV1 does not support 16 bits per channel per sample. + // Split the input samples into AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS items + // and AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS items. + subdepthMode = AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS; + // Check that all input images have the same depth. + for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { + avifEncoderItem * item = &encoder->data->items.item[itemIndex]; + if (item->codec && (cellImages[item->cellIndex]->depth != 16)) { + return AVIF_RESULT_INCOMPATIBLE_IMAGE; + } + } + } + // Prepare all AV1 items uint16_t colorItemID; - AVIF_CHECKRES(avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, /*alpha=*/AVIF_FALSE, &colorItemID)); + AVIF_CHECKRES( + avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, /*alpha=*/AVIF_FALSE, subdepthMode, &colorItemID)); encoder->data->primaryItemID = colorItemID; encoder->data->alphaPresent = (firstCell->alphaPlane != NULL); @@ -1176,7 +1199,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, if (encoder->data->alphaPresent) { uint16_t alphaItemID; - AVIF_CHECKRES(avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, /*alpha=*/AVIF_TRUE, &alphaItemID)); + AVIF_CHECKRES( + avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, /*alpha=*/AVIF_TRUE, subdepthMode, &alphaItemID)); avifEncoderItem * alphaItem = avifEncoderDataFindItemByID(encoder->data, alphaItemID); assert(alphaItem); alphaItem->irefType = "auxl"; @@ -1189,6 +1213,51 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, } } + if (subdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) { + // There are multiple possible grid patterns for bit depth extension items: + // - one 8lsb item per 8msb color cell and one 8lsb item per 8msb alpha cell + // - one 8lsb grid per 8msb color grid and one 8lsb item per 8msb alpha cell + // - one 8lsb grid per 8msb color grid and one 8lsb grid per 8msb alpha grid + // - one 8lsb grid per 8msb color grid and one alpha grid per 8lsb color grid (implemented) + // - etc. + uint16_t subdepthColorItemID; + AVIF_CHECKRES(avifEncoderAddImageItems(encoder, + gridCols, + gridRows, + gridWidth, + gridHeight, + /*alpha=*/AVIF_FALSE, + AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS, + &subdepthColorItemID)); + avifEncoderItem * subdepthColorItem = avifEncoderDataFindItemByID(encoder->data, subdepthColorItemID); + assert(subdepthColorItem); + subdepthColorItem->irefToID = colorItemID; + subdepthColorItem->irefType = "auxl"; + + if (encoder->data->alphaPresent) { + uint16_t subdepthAlphaItemID; + AVIF_CHECKRES(avifEncoderAddImageItems(encoder, + gridCols, + gridRows, + gridWidth, + gridHeight, + /*alpha=*/AVIF_TRUE, + AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS, + &subdepthAlphaItemID)); + avifEncoderItem * subdepthAlphaItem = avifEncoderDataFindItemByID(encoder->data, subdepthAlphaItemID); + assert(subdepthAlphaItem); + subdepthAlphaItem->irefType = "auxl"; + subdepthAlphaItem->irefToID = subdepthColorItemID; + if (encoder->data->imageMetadata->alphaPremultiplied) { + // Find it again, maybe subdepthColorItem was invalidated by avifEncoderAddImageItems(). + subdepthColorItem = avifEncoderDataFindItemByID(encoder->data, subdepthColorItemID); + assert(subdepthColorItem); + subdepthColorItem->irefType = "prem"; + subdepthColorItem->irefToID = subdepthAlphaItemID; + } + } + } + // ----------------------------------------------------------------------- // Create metadata items (Exif, XMP) @@ -1250,6 +1319,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, avifEncoderItem * item = &encoder->data->items.item[itemIndex]; if (item->codec) { const avifImage * cellImage = cellImages[item->cellIndex]; + + // Padding avifImage * paddedCellImage = NULL; if ((cellImage->width != tileWidth) || (cellImage->height != tileHeight)) { paddedCellImage = avifImageCopyAndPad(cellImage, tileWidth, tileHeight); @@ -1258,7 +1329,52 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, } cellImage = paddedCellImage; } - const int quantizer = item->alpha ? encoder->data->quantizerAlpha : encoder->data->quantizer; + + // Subdepth + avifImage * subdepthCellImage = NULL; + int * encoderMinQuantizer = item->alpha ? &encoder->minQuantizerAlpha : &encoder->minQuantizer; + int * encoderMaxQuantizer = item->alpha ? &encoder->maxQuantizerAlpha : &encoder->maxQuantizer; + int originalMinQuantizer = *encoderMinQuantizer; + int originalMaxQuantizer = *encoderMaxQuantizer; + + if (item->subdepthMode != AVIF_SUBDEPTH_NONE) { + subdepthCellImage = avifImageCreate(cellImage->width, cellImage->height, 8, cellImage->yuvFormat); + if (!subdepthCellImage) { + if (paddedCellImage) { + avifImageDestroy(paddedCellImage); + } + return AVIF_RESULT_OUT_OF_MEMORY; + } + const avifPlanesFlag planes = item->alpha ? AVIF_PLANES_A : AVIF_PLANES_YUV; + const avifResult allocationResult = avifImageAllocatePlanes(subdepthCellImage, planes); + if (allocationResult != AVIF_RESULT_OK) { + if (subdepthCellImage) { + avifImageDestroy(subdepthCellImage); + } + if (paddedCellImage) { + avifImageDestroy(paddedCellImage); + } + return allocationResult; + } + if (avifImageCopySamplesExtended(subdepthCellImage, item->subdepthMode, cellImage, AVIF_SUBDEPTH_NONE, planes) != + AVIF_RESULT_OK) { + assert(AVIF_FALSE); + } + cellImage = subdepthCellImage; + } + + int quantizer = item->alpha ? encoder->data->quantizerAlpha : encoder->data->quantizer; + if (item->subdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) { + // Encoding the least significant bits of a sample does not make any sense if the + // other bits are lossily compressed. Encode the most significant bits losslessly. + *encoderMinQuantizer = AVIF_QUANTIZER_LOSSLESS; + quantizer = AVIF_QUANTIZER_LOSSLESS; + *encoderMaxQuantizer = AVIF_QUANTIZER_LOSSLESS; + if (!avifEncoderDetectChanges(encoder, &encoderChanges)) { + assert(AVIF_FALSE); + } + } + // If alpha channel is present, set disableLaggedOutput to AVIF_TRUE. If the encoder supports it, this enables // avifEncoderDataShouldForceKeyframeForAlpha to force a keyframe in the alpha channel whenever a keyframe has been // encoded in the color channel for animated images. @@ -1273,6 +1389,15 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, /*disableLaggedOutput=*/encoder->data->alphaPresent, addImageFlags, item->encodeOutput); + if (item->subdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) { + avifEncoderBackupSettings(encoder); + // Revert overridden quality setting. + *encoderMinQuantizer = originalMinQuantizer; + *encoderMaxQuantizer = originalMaxQuantizer; + } + if (subdepthCellImage) { + avifImageDestroy(subdepthCellImage); + } if (paddedCellImage) { avifImageDestroy(paddedCellImage); } @@ -1632,10 +1757,18 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) avifItemPropertyDedupStart(dedup); uint8_t channelCount = (item->alpha || (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) ? 1 : 3; + uint8_t depth; + if ((item->subdepthMode == AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS) || + (item->subdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS)) { + depth = 8; + } else { + assert(item->subdepthMode == AVIF_SUBDEPTH_NONE); + depth = (uint8_t)imageMetadata->depth; + } avifBoxMarker pixi = avifRWStreamWriteFullBox(&dedup->s, "pixi", AVIF_BOX_SIZE_TBD, 0, 0); avifRWStreamWriteU8(&dedup->s, channelCount); // unsigned int (8) num_channels; for (uint8_t chan = 0; chan < channelCount; ++chan) { - avifRWStreamWriteU8(&dedup->s, (uint8_t)imageMetadata->depth); // unsigned int (8) bits_per_channel; + avifRWStreamWriteU8(&dedup->s, depth); // unsigned int (8) bits_per_channel; } avifRWStreamFinishBox(&dedup->s, pixi); ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE); @@ -1651,7 +1784,15 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) avifItemPropertyDedupStart(dedup); avifBoxMarker auxC = avifRWStreamWriteFullBox(&dedup->s, "auxC", AVIF_BOX_SIZE_TBD, 0, 0); - avifRWStreamWriteChars(&dedup->s, alphaURN, alphaURNSize); // string aux_type; + avifRWStreamWriteChars(&dedup->s, alphaURN, alphaURNSize); // string aux_type; + avifRWStreamFinishBox(&dedup->s, auxC); + ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE); + } else if (item->subdepthMode == AVIF_SUBDEPTH_8_LEAST_SIGNIFICANT_BITS) { + // Subdepth specific properties + + avifItemPropertyDedupStart(dedup); + avifBoxMarker auxC = avifRWStreamWriteFullBox(&dedup->s, "auxC", AVIF_BOX_SIZE_TBD, 0, 0); + avifRWStreamWriteChars(&dedup->s, subdepth8lsbURN, subdepth8lsbURNSize); // string aux_type; avifRWStreamFinishBox(&dedup->s, auxC); ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE); } else { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 391bc01c56..23159553b7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -70,6 +70,11 @@ if(AVIF_ENABLE_GTEST) find_package(GTest REQUIRED) endif() + add_executable(avif16bittest gtest/avif16bittest.cc) + target_link_libraries(avif16bittest avif_internal aviftest_helpers ${GTEST_LIBRARIES}) + target_include_directories(avif16bittest PRIVATE ${GTEST_INCLUDE_DIRS}) + add_test(NAME avif16bittest COMMAND avif16bittest ${CMAKE_CURRENT_SOURCE_DIR}/data/) + add_executable(avifallocationtest gtest/avifallocationtest.cc) target_link_libraries(avifallocationtest aviftest_helpers ${GTEST_BOTH_LIBRARIES}) target_include_directories(avifallocationtest PRIVATE ${GTEST_INCLUDE_DIRS}) diff --git a/tests/gtest/avif16bittest.cc b/tests/gtest/avif16bittest.cc new file mode 100644 index 0000000000..aa12fb0984 --- /dev/null +++ b/tests/gtest/avif16bittest.cc @@ -0,0 +1,112 @@ +// Copyright 2022 Google LLC +// SPDX-License-Identifier: BSD-2-Clause + +#include + +#include "avif/avif.h" +#include "avif/internal.h" +#include "aviftest_helpers.h" +#include "gtest/gtest.h" + +namespace libavif { +namespace { + +//------------------------------------------------------------------------------ + +// Used to pass the data folder path to the GoogleTest suites. +const char* data_path = nullptr; + +//------------------------------------------------------------------------------ + +TEST(BitDepthTest, Avif16bitLossy) { + const testutil::AvifImagePtr image = + testutil::ReadImage(data_path, "weld_16bit.png", AVIF_PIXEL_FORMAT_NONE, + /*requested_depth=*/16); + ASSERT_NE(image, nullptr); + + const testutil::AvifRwData encoded = + testutil::Encode(image.get(), AVIF_SPEED_FASTEST, AVIF_QUALITY_WORST); + const testutil::AvifImagePtr decoded = + testutil::Decode(encoded.data, encoded.size); + ASSERT_NE(decoded, nullptr); + + ASSERT_EQ(image->depth, decoded->depth); + ASSERT_EQ(image->width, decoded->width); + ASSERT_EQ(image->height, decoded->height); + EXPECT_FALSE(testutil::AreImagesEqual(*image, *decoded)); +} + +//------------------------------------------------------------------------------ + +TEST(BitDepthTest, Avif16bitLossless) { + const testutil::AvifImagePtr image = + testutil::ReadImage(data_path, "weld_16bit.png", AVIF_PIXEL_FORMAT_NONE, + /*requested_depth=*/16); + ASSERT_NE(image, nullptr); + + const testutil::AvifRwData encoded = + testutil::Encode(image.get(), AVIF_SPEED_FASTEST, AVIF_QUALITY_LOSSLESS); + const testutil::AvifImagePtr decoded = + testutil::Decode(encoded.data, encoded.size); + ASSERT_NE(decoded, nullptr); + + EXPECT_TRUE(testutil::AreImagesEqual(*image, *decoded)); +} + +//------------------------------------------------------------------------------ + +TEST(BitDepthTest, Avif16bitRetroCompatible) { + const testutil::AvifImagePtr image = + testutil::ReadImage(data_path, "weld_16bit.png", AVIF_PIXEL_FORMAT_NONE, + /*requested_depth=*/16); + ASSERT_NE(image, nullptr); + + testutil::AvifRwData encoded = + testutil::Encode(image.get(), AVIF_SPEED_FASTEST, AVIF_QUALITY_WORST); + + // Replace all subdepth tags by "zzzzzz" garbage. This simulates an old + // decoder that does not recognize the subdepth feature. + const size_t kTagLength = std::strlen(AVIF_URN_SUBDEPTH_8LSB); + for (size_t i = 0; i + kTagLength <= encoded.size; ++i) { + if (!std::memcmp(&encoded.data[i], AVIF_URN_SUBDEPTH_8LSB, kTagLength)) { + std::fill(&encoded.data[i], &encoded.data[i] + kTagLength, 'z'); + } + } + + const testutil::AvifImagePtr decoded = + testutil::Decode(encoded.data, encoded.size); + ASSERT_NE(decoded, nullptr); + + // Only the 8 most significant bits of each sample can be retrieved. + // They should be encoded losslessly no matter the quantizer settings. + testutil::AvifImagePtr image_8msb = testutil::CreateImage( + image->width, image->height, 8, image->yuvFormat, + image->alphaPlane ? AVIF_PLANES_ALL : AVIF_PLANES_YUV, image->yuvRange); + ASSERT_NE(image_8msb, nullptr); + ASSERT_EQ(avifImageCopySamplesExtended( + image_8msb.get(), AVIF_SUBDEPTH_8_MOST_SIGNIFICANT_BITS, + image.get(), AVIF_SUBDEPTH_NONE, AVIF_PLANES_ALL), + AVIF_RESULT_OK); + EXPECT_TRUE(testutil::AreImagesEqual(*image_8msb, *decoded)); +} + +//------------------------------------------------------------------------------ + +// TODO(yguyon): Test 16-bit alpha + +//------------------------------------------------------------------------------ + +} // namespace +} // namespace libavif + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + if (argc != 2) { + std::cerr << "There must be exactly one argument containing the path to " + "the test data folder" + << std::endl; + return 1; + } + libavif::data_path = argv[1]; + return RUN_ALL_TESTS(); +} diff --git a/tests/gtest/avifgridapitest.cc b/tests/gtest/avifgridapitest.cc index 4d634c2f46..61f288711d 100644 --- a/tests/gtest/avifgridapitest.cc +++ b/tests/gtest/avifgridapitest.cc @@ -91,7 +91,8 @@ avifResult EncodeDecodeGrid(const std::vector>& cell_rows, if (result != AVIF_RESULT_OK) { return result; } - avifImageCopySamples(/*dstImage=*/view.get(), it->get(), AVIF_PLANES_ALL); + AVIF_CHECKRES(avifImageCopySamples(/*dstImage=*/view.get(), it->get(), + AVIF_PLANES_ALL)); assert(!view->imageOwnsYUVPlanes); ++it; rect.x += rect.width; diff --git a/tests/gtest/aviftest_helpers.cc b/tests/gtest/aviftest_helpers.cc index c0bfa27caf..9a9cfec67f 100644 --- a/tests/gtest/aviftest_helpers.cc +++ b/tests/gtest/aviftest_helpers.cc @@ -353,10 +353,16 @@ bool WriteImage(const avifImage* image, const char* file_path) { return false; } -AvifRwData Encode(const avifImage* image, int speed) { +AvifRwData Encode(const avifImage* image, int speed, int quality) { testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy); if (!encoder) return {}; encoder->speed = speed; + if (quality == -1) { + // Leave default quantizer settings. + } else { + encoder->quality = quality; + encoder->qualityAlpha = quality; + } testutil::AvifRwData bytes; if (avifEncoderWrite(encoder.get(), image, &bytes) != AVIF_RESULT_OK) { return {}; diff --git a/tests/gtest/aviftest_helpers.h b/tests/gtest/aviftest_helpers.h index 2be4ebb85e..0112b63eac 100644 --- a/tests/gtest/aviftest_helpers.h +++ b/tests/gtest/aviftest_helpers.h @@ -97,7 +97,8 @@ bool WriteImage(const avifImage* image, const char* file_path); // Encodes the image with default parameters. // Returns an empty payload in case of error. -AvifRwData Encode(const avifImage* image, int speed = AVIF_SPEED_DEFAULT); +AvifRwData Encode(const avifImage* image, int speed = AVIF_SPEED_DEFAULT, + int quality = -1); // Decodes the bytes to an image with default parameters. // Returns nullptr in case of error.