Image grid support (Summer_in_Tomsk_720p_5x4_grid)
diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a16904..2f2db93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ ### Added - libgav1 decode codec support. (wantehchang @Google) - Expose codec selection to avifdec/avifenc, speed to avifenc +- Image grid support (Summer_in_Tomsk_720p_5x4_grid) ### Changed - AppVeyor builds now compile with dav1d (EwoutH)
diff --git a/include/avif/avif.h b/include/avif/avif.h index 09e56ad..b00a3ca 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h
@@ -90,7 +90,8 @@ AVIF_RESULT_ISPE_SIZE_MISMATCH, AVIF_RESULT_NO_CODEC_AVAILABLE, AVIF_RESULT_NO_IMAGES_REMAINING, - AVIF_RESULT_INVALID_EXIF_PAYLOAD + AVIF_RESULT_INVALID_EXIF_PAYLOAD, + AVIF_RESULT_INVALID_IMAGE_GRID } avifResult; const char * avifResultToString(avifResult result); @@ -322,6 +323,7 @@ void avifImageAllocatePlanes(avifImage * image, uint32_t planes); // Ignores any pre-existing planes void avifImageFreePlanes(avifImage * image, uint32_t planes); // Ignores already-freed planes +void avifImageStealPlanes(avifImage * dstImage, avifImage * srcImage, uint32_t planes); // Optional YUV<->RGB support avifResult avifImageRGBToYUV(avifImage * image);
diff --git a/src/avif.c b/src/avif.c index 33153a1..3557762 100644 --- a/src/avif.c +++ b/src/avif.c
@@ -88,6 +88,7 @@ case AVIF_RESULT_NO_CODEC_AVAILABLE: return "No codec available"; case AVIF_RESULT_NO_IMAGES_REMAINING: return "No images remaining"; case AVIF_RESULT_INVALID_EXIF_PAYLOAD: return "Invalid Exif payload"; + case AVIF_RESULT_INVALID_IMAGE_GRID: return "Invalid image grid"; case AVIF_RESULT_UNKNOWN_ERROR: default: break; @@ -345,6 +346,57 @@ } } +void avifImageStealPlanes(avifImage * dstImage, avifImage * srcImage, uint32_t planes) +{ + avifImageFreePlanes(dstImage, planes); + + if (planes & AVIF_PLANES_RGB) { + dstImage->rgbPlanes[AVIF_CHAN_R] = srcImage->rgbPlanes[AVIF_CHAN_R]; + dstImage->rgbRowBytes[AVIF_CHAN_R] = srcImage->rgbRowBytes[AVIF_CHAN_R]; + dstImage->rgbPlanes[AVIF_CHAN_G] = srcImage->rgbPlanes[AVIF_CHAN_G]; + dstImage->rgbRowBytes[AVIF_CHAN_G] = srcImage->rgbRowBytes[AVIF_CHAN_G]; + dstImage->rgbPlanes[AVIF_CHAN_B] = srcImage->rgbPlanes[AVIF_CHAN_B]; + dstImage->rgbRowBytes[AVIF_CHAN_B] = srcImage->rgbRowBytes[AVIF_CHAN_B]; + + srcImage->rgbPlanes[AVIF_CHAN_R] = NULL; + srcImage->rgbRowBytes[AVIF_CHAN_R] = 0; + srcImage->rgbPlanes[AVIF_CHAN_G] = NULL; + srcImage->rgbRowBytes[AVIF_CHAN_G] = 0; + srcImage->rgbPlanes[AVIF_CHAN_B] = NULL; + srcImage->rgbRowBytes[AVIF_CHAN_B] = 0; + } + if (planes & AVIF_PLANES_YUV) { + dstImage->yuvPlanes[AVIF_CHAN_Y] = srcImage->yuvPlanes[AVIF_CHAN_Y]; + dstImage->yuvRowBytes[AVIF_CHAN_Y] = srcImage->yuvRowBytes[AVIF_CHAN_Y]; + dstImage->yuvPlanes[AVIF_CHAN_U] = srcImage->yuvPlanes[AVIF_CHAN_U]; + dstImage->yuvRowBytes[AVIF_CHAN_U] = srcImage->yuvRowBytes[AVIF_CHAN_U]; + dstImage->yuvPlanes[AVIF_CHAN_V] = srcImage->yuvPlanes[AVIF_CHAN_V]; + dstImage->yuvRowBytes[AVIF_CHAN_V] = srcImage->yuvRowBytes[AVIF_CHAN_V]; + + srcImage->yuvPlanes[AVIF_CHAN_Y] = NULL; + srcImage->yuvRowBytes[AVIF_CHAN_Y] = 0; + srcImage->yuvPlanes[AVIF_CHAN_U] = NULL; + srcImage->yuvRowBytes[AVIF_CHAN_U] = 0; + srcImage->yuvPlanes[AVIF_CHAN_V] = NULL; + srcImage->yuvRowBytes[AVIF_CHAN_V] = 0; + + dstImage->yuvFormat = srcImage->yuvFormat; + dstImage->yuvRange = srcImage->yuvRange; + dstImage->decoderOwnsYUVPlanes = srcImage->decoderOwnsYUVPlanes; + srcImage->decoderOwnsYUVPlanes = AVIF_FALSE; + } + if (planes & AVIF_PLANES_A) { + dstImage->alphaPlane = srcImage->alphaPlane; + dstImage->alphaRowBytes = srcImage->alphaRowBytes; + + srcImage->alphaPlane = NULL; + srcImage->alphaRowBytes = 0; + + dstImage->decoderOwnsAlphaPlane = srcImage->decoderOwnsAlphaPlane; + srcImage->decoderOwnsAlphaPlane = AVIF_FALSE; + } +} + avifBool avifImageUsesU16(avifImage * image) { return (image->depth > 8) ? AVIF_TRUE : AVIF_FALSE;
diff --git a/src/codec_aom.c b/src/codec_aom.c index 38244ca..a42ecd0 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c
@@ -137,10 +137,10 @@ avifImageFreePlanes(image, AVIF_PLANES_ALL); } } - image->width = codec->internal->image->d_w; image->height = codec->internal->image->d_h; image->depth = codec->internal->image->bit_depth; + image->yuvFormat = yuvFormat; image->yuvRange = (codec->internal->image->range == AOM_CR_STUDIO_RANGE) ? AVIF_RANGE_LIMITED : AVIF_RANGE_FULL; @@ -173,10 +173,16 @@ } else { // Alpha plane - ensure image is correct size, fill color - if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) || - (image->depth != codec->internal->image->bit_depth)) { - return AVIF_FALSE; + if (image->width && image->height) { + if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) || + (image->depth != codec->internal->image->bit_depth)) { + // Alpha plane doesn't match previous alpha plane decode, bail out + return AVIF_FALSE; + } } + image->width = codec->internal->image->d_w; + image->height = codec->internal->image->d_h; + image->depth = codec->internal->image->bit_depth; avifImageFreePlanes(image, AVIF_PLANES_A); image->alphaPlane = codec->internal->image->planes[0];
diff --git a/src/codec_dav1d.c b/src/codec_dav1d.c index e3b95bf..813ae0a 100644 --- a/src/codec_dav1d.c +++ b/src/codec_dav1d.c
@@ -144,10 +144,10 @@ avifImageFreePlanes(image, AVIF_PLANES_ALL); } } - image->width = dav1dImage->p.w; image->height = dav1dImage->p.h; image->depth = dav1dImage->p.bpc; + image->yuvFormat = yuvFormat; image->yuvRange = codec->internal->colorRange; @@ -173,10 +173,16 @@ } else { // Alpha plane - ensure image is correct size, fill color - if (!image->width || !image->height || (image->width != (uint32_t)dav1dImage->p.w) || - (image->height != (uint32_t)dav1dImage->p.h) || (image->depth != (uint32_t)dav1dImage->p.bpc)) { - return AVIF_FALSE; + if (image->width && image->height) { + if ((image->width != (uint32_t)dav1dImage->p.w) || (image->height != (uint32_t)dav1dImage->p.h) || + (image->depth != (uint32_t)dav1dImage->p.bpc)) { + // Alpha plane doesn't match previous alpha plane decode, bail out + return AVIF_FALSE; + } } + image->width = dav1dImage->p.w; + image->height = dav1dImage->p.h; + image->depth = dav1dImage->p.bpc; avifImageFreePlanes(image, AVIF_PLANES_A); image->alphaPlane = dav1dImage->data[0];
diff --git a/src/codec_libgav1.c b/src/codec_libgav1.c index 8f47fe9..5d124e7 100644 --- a/src/codec_libgav1.c +++ b/src/codec_libgav1.c
@@ -49,7 +49,8 @@ // Feed another sample avifSample * sample = &codec->decodeInput->samples.sample[codec->internal->inputSampleIndex]; ++codec->internal->inputSampleIndex; - if (Libgav1DecoderEnqueueFrame(codec->internal->gav1Decoder, sample->data.data, sample->data.size, /*user_private_data=*/0) != kLibgav1StatusOk) { + if (Libgav1DecoderEnqueueFrame(codec->internal->gav1Decoder, sample->data.data, sample->data.size, /*user_private_data=*/0) != + kLibgav1StatusOk) { return AVIF_FALSE; } // Each Libgav1DecoderDequeueFrame() call invalidates the output frame @@ -94,17 +95,16 @@ if (image->width && image->height) { if ((image->width != (uint32_t)gav1Image->displayed_width[0]) || - (image->height != (uint32_t)gav1Image->displayed_height[0]) || - (image->depth != (uint32_t)gav1Image->bitdepth) || + (image->height != (uint32_t)gav1Image->displayed_height[0]) || (image->depth != (uint32_t)gav1Image->bitdepth) || (image->yuvFormat != yuvFormat)) { // Throw it all out avifImageFreePlanes(image, AVIF_PLANES_ALL); } } - image->width = gav1Image->displayed_width[0]; image->height = gav1Image->displayed_height[0]; image->depth = gav1Image->bitdepth; + image->yuvFormat = yuvFormat; image->yuvRange = codec->internal->colorRange; @@ -131,12 +131,16 @@ } else { // Alpha plane - ensure image is correct size, fill color - if (!image->width || !image->height || - (image->width != (uint32_t)gav1Image->displayed_width[0]) || - (image->height != (uint32_t)gav1Image->displayed_height[0]) || - (image->depth != (uint32_t)gav1Image->bitdepth)) { - return AVIF_FALSE; + if (image->width && image->height) { + if ((image->width != (uint32_t)gav1Image->displayed_width[0]) || + (image->height != (uint32_t)gav1Image->displayed_height[0]) || (image->depth != (uint32_t)gav1Image->bitdepth)) { + // Alpha plane doesn't match previous alpha plane decode, bail out + return AVIF_FALSE; + } } + image->width = gav1Image->displayed_width[0]; + image->height = gav1Image->displayed_height[0]; + image->depth = gav1Image->bitdepth; avifImageFreePlanes(image, AVIF_PLANES_A); image->alphaPlane = gav1Image->plane[0];
diff --git a/src/read.c b/src/read.c index 5bbd6bf..3982dbe 100644 --- a/src/read.c +++ b/src/read.c
@@ -107,6 +107,7 @@ uint32_t thumbnailForID; // if non-zero, this item is a thumbnail for Item #{thumbnailForID} uint32_t auxForID; // if non-zero, this item is an auxC plane for Item #{auxForID} uint32_t descForID; // if non-zero, this item is a content description for Item #{descForID} + uint32_t dimgForID; // if non-zero, this item is a derived image for Item #{dimgForID} } avifItem; AVIF_ARRAY_DECLARE(avifItemArray, avifItem, item); @@ -129,6 +130,15 @@ } avifItemData; AVIF_ARRAY_DECLARE(avifItemDataArray, avifItemData, idat); +// grid storage +typedef struct avifImageGrid +{ + uint8_t rows; + uint8_t columns; + uint32_t outputWidth; + uint32_t outputHeight; +} avifImageGrid; + // --------------------------------------------------------------------------- // avifTrack @@ -348,6 +358,14 @@ // --------------------------------------------------------------------------- // avifData +typedef struct avifTile +{ + avifCodecDecodeInput * input; + struct avifCodec * codec; + avifImage * image; +} avifTile; +AVIF_ARRAY_DECLARE(avifTileArray, avifTile, tile); + typedef struct avifData { avifFileType ftyp; @@ -356,13 +374,15 @@ avifItemDataArray idats; avifTrackArray tracks; avifROData rawInput; - avifCodecDecodeInput * colorInput; - avifCodecDecodeInput * alphaInput; + avifTileArray tiles; + unsigned int colorTileCount; + unsigned int alphaTileCount; + avifImageGrid colorGrid; + avifImageGrid alphaGrid; avifDecoderSource source; avifSampleTable * sourceSampleTable; // NULL unless (source == AVIF_DECODER_SOURCE_TRACKS), owned by an avifTrack uint32_t primaryItemID; uint32_t metaBoxID; // Ever-incrementing ID for tracking which 'meta' box contains an idat, and which idat an iloc might refer to - struct avifCodec * codec[AVIF_CODEC_PLANES_COUNT]; } avifData; static avifData * avifDataCreate() @@ -373,22 +393,53 @@ avifArrayCreate(&data->properties, sizeof(avifProperty), 16); avifArrayCreate(&data->idats, sizeof(avifItemData), 1); avifArrayCreate(&data->tracks, sizeof(avifTrack), 2); + avifArrayCreate(&data->tiles, sizeof(avifTile), 8); return data; } static void avifDataResetCodec(avifData * data) { - for (int i = 0; i < AVIF_CODEC_PLANES_COUNT; ++i) { - if (data->codec[i]) { - avifCodecDestroy(data->codec[i]); - data->codec[i] = NULL; + for (unsigned int i = 0; i < data->tiles.count; ++i) { + avifTile * tile = &data->tiles.tile[i]; + if (tile->codec) { + avifCodecDestroy(tile->codec); + tile->codec = NULL; } } } +static avifTile * avifDataNewTile(avifData * data) +{ + avifTile * tile = (avifTile *)avifArrayPushPtr(&data->tiles); + tile->image = avifImageCreateEmpty(); + tile->input = avifCodecDecodeInputCreate(); + return tile; +} + +static void avifDataClearTiles(avifData * data) +{ + for (unsigned int i = 0; i < data->tiles.count; ++i) { + avifTile * tile = &data->tiles.tile[i]; + if (tile->input) { + avifCodecDecodeInputDestroy(tile->input); + tile->input = NULL; + } + if (tile->codec) { + avifCodecDestroy(tile->codec); + tile->codec = NULL; + } + if (tile->image) { + avifImageDestroy(tile->image); + tile->image = NULL; + } + } + data->tiles.count = 0; + data->colorTileCount = 0; + data->alphaTileCount = 0; +} + static void avifDataDestroy(avifData * data) { - avifDataResetCodec(data); avifArrayDestroy(&data->items); avifArrayDestroy(&data->properties); avifArrayDestroy(&data->idats); @@ -398,12 +449,8 @@ } } avifArrayDestroy(&data->tracks); - if (data->colorInput) { - avifCodecDecodeInputDestroy(data->colorInput); - } - if (data->alphaInput) { - avifCodecDecodeInputDestroy(data->alphaInput); - } + avifDataClearTiles(data); + avifArrayDestroy(&data->tiles); avifFree(data); } @@ -458,6 +505,161 @@ return offsetBuffer->data + item->offset; } +avifBool avifDataGenerateImageGridTiles(avifData * data, avifImageGrid * grid, avifItem * gridItem, avifBool alpha) +{ + unsigned int tilesRequested = (unsigned int)grid->rows * (unsigned int)grid->columns; + + // Count number of dimg for this item, bail out if it doesn't match perfectly + unsigned int tilesAvailable = 0; + for (uint32_t i = 0; i < data->items.count; ++i) { + avifItem * item = &data->items.item[i]; + if (item->dimgForID == gridItem->id) { + if (memcmp(item->type, "av01", 4)) { + continue; + } + + ++tilesAvailable; + } + } + + if (tilesRequested != tilesAvailable) { + return AVIF_FALSE; + } + + for (uint32_t i = 0; i < data->items.count; ++i) { + avifItem * item = &data->items.item[i]; + if (item->dimgForID == gridItem->id) { + if (memcmp(item->type, "av01", 4)) { + continue; + } + + avifTile * tile = avifDataNewTile(data); + avifSample * sample = (avifSample *)avifArrayPushPtr(&tile->input->samples); + sample->data.data = avifDataCalcItemPtr(data, item); + sample->data.size = item->size; + sample->sync = AVIF_TRUE; + tile->input->alpha = alpha; + } + } + return AVIF_TRUE; +} + +avifBool avifDataFillImageGrid(avifData * data, + avifImageGrid * grid, + avifImage * dstImage, + unsigned int firstTileIndex, + unsigned int tileCount, + avifBool alpha) +{ + if (tileCount == 0) { + return AVIF_FALSE; + } + + avifTile * firstTile = &data->tiles.tile[firstTileIndex]; + unsigned int tileWidth = firstTile->image->width; + unsigned int tileHeight = firstTile->image->height; + unsigned int tileDepth = firstTile->image->depth; + avifPixelFormat tileFormat = firstTile->image->yuvFormat; + avifRange tileRange = firstTile->image->yuvRange; + avifBool tileUVPresent = (firstTile->image->yuvPlanes[AVIF_CHAN_U] && firstTile->image->yuvPlanes[AVIF_CHAN_V]) ? AVIF_TRUE + : AVIF_FALSE; + + for (unsigned int i = 1; i < tileCount; ++i) { + avifTile * tile = &data->tiles.tile[firstTileIndex + i]; + avifBool uvPresent = (tile->image->yuvPlanes[AVIF_CHAN_U] && tile->image->yuvPlanes[AVIF_CHAN_V]) ? AVIF_TRUE : AVIF_FALSE; + if ((tile->image->width != tileWidth) || (tile->image->height != tileHeight) || (tile->image->depth != tileDepth) || + (tile->image->yuvFormat != tileFormat) || (tile->image->yuvRange != tileRange) || (uvPresent != tileUVPresent)) { + return AVIF_FALSE; + } + } + + if ((dstImage->width != grid->outputWidth) || (dstImage->height != grid->outputHeight) || (dstImage->depth != tileDepth) || + (dstImage->yuvFormat != tileFormat)) { + if (alpha) { + // Alpha doesn't match size, just bail out + return AVIF_FALSE; + } + + avifImageFreePlanes(dstImage, AVIF_PLANES_ALL); + dstImage->width = grid->outputWidth; + dstImage->height = grid->outputHeight; + dstImage->depth = tileDepth; + dstImage->yuvFormat = tileFormat; + dstImage->yuvRange = tileRange; + } + + avifImageAllocatePlanes(dstImage, alpha ? AVIF_PLANES_A : AVIF_PLANES_YUV); + + avifPixelFormatInfo formatInfo; + avifGetPixelFormatInfo(tileFormat, &formatInfo); + + unsigned int tileIndex = firstTileIndex; + size_t pixelBytes = avifImageUsesU16(dstImage) ? 2 : 1; + for (unsigned int rowIndex = 0; rowIndex < grid->rows; ++rowIndex) { + for (unsigned int colIndex = 0; colIndex < grid->columns; ++colIndex, ++tileIndex) { + avifTile * tile = &data->tiles.tile[tileIndex]; + + unsigned int widthToCopy = tileWidth; + unsigned int maxX = tileWidth * (colIndex + 1); + if (maxX > grid->outputWidth) { + widthToCopy -= maxX - grid->outputWidth; + } + + unsigned int heightToCopy = tileHeight; + unsigned int maxY = tileHeight * (rowIndex + 1); + if (maxY > grid->outputHeight) { + heightToCopy -= maxY - grid->outputHeight; + } + + // Y and A channels + size_t yaColOffset = colIndex * tileWidth; + size_t yaRowOffset = rowIndex * tileHeight; + size_t yaRowBytes = widthToCopy * pixelBytes; + + if (alpha) { + // A + for (unsigned int j = 0; j < heightToCopy; ++j) { + uint8_t * src = &tile->image->alphaPlane[j * tile->image->alphaRowBytes]; + uint8_t * dst = &dstImage->alphaPlane[(yaColOffset * pixelBytes) + ((yaRowOffset + j) * dstImage->alphaRowBytes)]; + memcpy(dst, src, yaRowBytes); + } + } else { + // Y + for (unsigned int j = 0; j < heightToCopy; ++j) { + uint8_t * src = &tile->image->yuvPlanes[AVIF_CHAN_Y][j * tile->image->yuvRowBytes[AVIF_CHAN_Y]]; + uint8_t * dst = + &dstImage->yuvPlanes[AVIF_CHAN_Y][(yaColOffset * pixelBytes) + ((yaRowOffset + j) * dstImage->yuvRowBytes[AVIF_CHAN_Y])]; + memcpy(dst, src, yaRowBytes); + } + + if (!tileUVPresent) { + continue; + } + + // UV + widthToCopy >>= formatInfo.chromaShiftX; + heightToCopy >>= formatInfo.chromaShiftY; + size_t uvColOffset = yaColOffset >> formatInfo.chromaShiftX; + size_t uvRowOffset = yaRowOffset >> formatInfo.chromaShiftY; + size_t uvRowBytes = yaRowBytes >> formatInfo.chromaShiftX; + for (unsigned int j = 0; j < heightToCopy; ++j) { + uint8_t * srcU = &tile->image->yuvPlanes[AVIF_CHAN_U][j * tile->image->yuvRowBytes[AVIF_CHAN_U]]; + uint8_t * dstU = + &dstImage->yuvPlanes[AVIF_CHAN_U][(uvColOffset * pixelBytes) + ((uvRowOffset + j) * dstImage->yuvRowBytes[AVIF_CHAN_U])]; + memcpy(dstU, srcU, uvRowBytes); + + uint8_t * srcV = &tile->image->yuvPlanes[AVIF_CHAN_V][j * tile->image->yuvRowBytes[AVIF_CHAN_V]]; + uint8_t * dstV = + &dstImage->yuvPlanes[AVIF_CHAN_V][(uvColOffset * pixelBytes) + ((uvRowOffset + j) * dstImage->yuvRowBytes[AVIF_CHAN_V])]; + memcpy(dstV, srcV, uvRowBytes); + } + } + } + } + + return AVIF_TRUE; +} + // --------------------------------------------------------------------------- // URN @@ -574,6 +776,39 @@ return AVIF_TRUE; } +static avifBool avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw, size_t rawLen) +{ + BEGIN_STREAM(s, raw, rawLen); + + uint8_t version, flags; + CHECK(avifROStreamRead(&s, &version, 1)); // unsigned int(8) version = 0; + if (version != 0) { + return AVIF_FALSE; + } + CHECK(avifROStreamRead(&s, &flags, 1)); // unsigned int(8) flags; + CHECK(avifROStreamRead(&s, &grid->rows, 1)); // unsigned int(8) rows_minus_one; + CHECK(avifROStreamRead(&s, &grid->columns, 1)); // unsigned int(8) columns_minus_one; + ++grid->rows; + ++grid->columns; + + uint32_t fieldLength = ((flags & 1) + 1) * 16; + if (fieldLength == 16) { + uint16_t outputWidth16, outputHeight16; + CHECK(avifROStreamReadU16(&s, &outputWidth16)); // unsigned int(FieldLength) output_width; + CHECK(avifROStreamReadU16(&s, &outputHeight16)); // unsigned int(FieldLength) output_height; + grid->outputWidth = outputWidth16; + grid->outputHeight = outputHeight16; + } else { + if (fieldLength != 32) { + // This should be impossible + return AVIF_FALSE; + } + CHECK(avifROStreamReadU32(&s, &grid->outputWidth)); // unsigned int(FieldLength) output_width; + CHECK(avifROStreamReadU32(&s, &grid->outputHeight)); // unsigned int(FieldLength) output_height; + } + return AVIF_TRUE; +} + static avifBool avifParseImageSpatialExtentsProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex) { BEGIN_STREAM(s, raw, rawLen); @@ -946,6 +1181,15 @@ if (!memcmp(irefHeader.type, "cdsc", 4)) { item->descForID = toID; } + if (!memcmp(irefHeader.type, "dimg", 4)) { + // derived images refer in the opposite direction + avifItem * dimg = avifDataFindItem(data, toID); + if (!dimg) { + return AVIF_FALSE; + } + + dimg->dimgForID = fromID; + } } } } @@ -1524,21 +1768,14 @@ { avifDataResetCodec(decoder->data); - decoder->data->codec[AVIF_CODEC_PLANES_COLOR] = avifCodecCreateInternal(decoder->codecChoice, decoder->data->colorInput); - if (!decoder->data->codec[AVIF_CODEC_PLANES_COLOR]) { - return AVIF_RESULT_NO_CODEC_AVAILABLE; - } - if (!decoder->data->codec[AVIF_CODEC_PLANES_COLOR]->open(decoder->data->codec[AVIF_CODEC_PLANES_COLOR], decoder->imageIndex + 1)) { - return AVIF_RESULT_DECODE_COLOR_FAILED; - } - - if (decoder->data->alphaInput) { - decoder->data->codec[AVIF_CODEC_PLANES_ALPHA] = avifCodecCreateInternal(decoder->codecChoice, decoder->data->alphaInput); - if (!decoder->data->codec[AVIF_CODEC_PLANES_ALPHA]) { + for (unsigned int i = 0; i < decoder->data->tiles.count; ++i) { + avifTile * tile = &decoder->data->tiles.tile[i]; + tile->codec = avifCodecCreateInternal(decoder->codecChoice, tile->input); + if (!tile->codec) { return AVIF_RESULT_NO_CODEC_AVAILABLE; } - if (!decoder->data->codec[AVIF_CODEC_PLANES_ALPHA]->open(decoder->data->codec[AVIF_CODEC_PLANES_ALPHA], decoder->imageIndex + 1)) { - return AVIF_RESULT_DECODE_ALPHA_FAILED; + if (!tile->codec->open(tile->codec, decoder->imageIndex + 1)) { + return AVIF_RESULT_DECODE_COLOR_FAILED; } } return AVIF_RESULT_OK; @@ -1552,20 +1789,13 @@ return AVIF_RESULT_OK; } - avifDataResetCodec(data); + memset(&data->colorGrid, 0, sizeof(data->colorGrid)); + memset(&data->alphaGrid, 0, sizeof(data->alphaGrid)); + avifDataClearTiles(data); if (!decoder->image) { decoder->image = avifImageCreateEmpty(); } - if (data->colorInput) { - avifCodecDecodeInputDestroy(data->colorInput); - data->colorInput = NULL; - } - if (data->alphaInput) { - avifCodecDecodeInputDestroy(data->alphaInput); - data->alphaInput = NULL; - } - memset(&decoder->ioStats, 0, sizeof(decoder->ioStats)); // ----------------------------------------------------------------------- @@ -1632,19 +1862,20 @@ alphaTrack = &decoder->data->tracks.track[alphaTrackIndex]; } - // TODO: We must get color profile information from somewhere; likely the color OBU as a fallback - - data->colorInput = avifCodecDecodeInputCreate(); - if (!avifCodecDecodeInputGetSamples(data->colorInput, colorTrack->sampleTable, &decoder->data->rawInput)) { + avifTile * colorTile = avifDataNewTile(decoder->data); + if (!avifCodecDecodeInputGetSamples(colorTile->input, colorTrack->sampleTable, &decoder->data->rawInput)) { return AVIF_RESULT_BMFF_PARSE_FAILED; } + decoder->data->colorTileCount = 1; + avifTile * alphaTile = NULL; if (alphaTrack) { - data->alphaInput = avifCodecDecodeInputCreate(); - if (!avifCodecDecodeInputGetSamples(data->alphaInput, alphaTrack->sampleTable, &decoder->data->rawInput)) { + alphaTile = avifDataNewTile(decoder->data); + if (!avifCodecDecodeInputGetSamples(alphaTile->input, alphaTrack->sampleTable, &decoder->data->rawInput)) { return AVIF_RESULT_BMFF_PARSE_FAILED; } - data->alphaInput->alpha = AVIF_TRUE; + alphaTile->input->alpha = AVIF_TRUE; + decoder->data->alphaTileCount = 1; } // Stash off sample table for future timing information @@ -1652,7 +1883,7 @@ // Image sequence timing decoder->imageIndex = -1; - decoder->imageCount = data->colorInput->samples.count; + decoder->imageCount = colorTile->input->samples.count; decoder->timescale = colorTrack->mediaTimescale; decoder->durationInTimescales = colorTrack->mediaDuration; if (colorTrack->mediaTimescale) { @@ -1673,6 +1904,7 @@ avifROData exifData = AVIF_DATA_EMPTY; avifROData xmpData = AVIF_DATA_EMPTY; avifItem * colorOBUItem = NULL; + avifItem * alphaOBUItem = NULL; // Find the colorOBU (primary) item for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) { @@ -1680,7 +1912,8 @@ if (!item->id || !item->size) { break; } - if (memcmp(item->type, "av01", 4)) { + avifBool isGrid = (memcmp(item->type, "grid", 4) == 0) ? AVIF_TRUE : AVIF_FALSE; + if (memcmp(item->type, "av01", 4) && !isGrid) { // probably exif or some other data continue; } @@ -1693,67 +1926,124 @@ continue; } + if (isGrid) { + const uint8_t * itemPtr = avifDataCalcItemPtr(data, item); + if (itemPtr == NULL) { + return AVIF_RESULT_BMFF_PARSE_FAILED; + } + if (!avifParseImageGridBox(&data->colorGrid, itemPtr, item->size)) { + return AVIF_RESULT_INVALID_IMAGE_GRID; + } + } else { + colorOBU.data = avifDataCalcItemPtr(data, item); + colorOBU.size = item->size; + } + colorOBUItem = item; - colorOBU.data = avifDataCalcItemPtr(data, item); - colorOBU.size = item->size; break; } - if (colorOBUItem) { - // Find the alphaOBU item, if any - for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) { - avifItem * item = &data->items.item[itemIndex]; - if (!item->id || !item->size) { - break; - } - if (memcmp(item->type, "av01", 4)) { - // probably exif or some other data - continue; - } - if (item->thumbnailForID != 0) { - // It's a thumbnail, skip it - continue; - } + if (!colorOBUItem) { + return AVIF_RESULT_NO_AV1_ITEMS_FOUND; + } - if (isAlphaURN(item->auxC.auxType) && (item->auxForID == colorOBUItem->id)) { - alphaOBU.data = avifDataCalcItemPtr(data, item); - alphaOBU.size = item->size; - break; - } + // Find the alphaOBU item, if any + for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) { + avifItem * item = &data->items.item[itemIndex]; + if (!item->id || !item->size) { + break; + } + avifBool isGrid = (memcmp(item->type, "grid", 4) == 0) ? AVIF_TRUE : AVIF_FALSE; + if (memcmp(item->type, "av01", 4) && !isGrid) { + // probably exif or some other data + continue; + } + if (item->thumbnailForID != 0) { + // It's a thumbnail, skip it + continue; } - // Find Exif and/or XMP metadata, if any - for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) { - avifItem * item = &data->items.item[itemIndex]; - if (!item->id || !item->size) { - break; + if (isAlphaURN(item->auxC.auxType) && (item->auxForID == colorOBUItem->id)) { + if (isGrid) { + const uint8_t * itemPtr = avifDataCalcItemPtr(data, item); + if (itemPtr == NULL) { + return AVIF_RESULT_BMFF_PARSE_FAILED; + } + if (!avifParseImageGridBox(&data->alphaGrid, itemPtr, item->size)) { + return AVIF_RESULT_INVALID_IMAGE_GRID; + } + } else { + alphaOBU.data = avifDataCalcItemPtr(data, item); + alphaOBU.size = item->size; } - if (item->descForID != colorOBUItem->id) { - // Not a content description (metadata) for the colorOBU, skip it - continue; - } - - if (!memcmp(item->type, "Exif", 4)) { - // Advance past Annex A.2.1's header - const uint8_t * boxPtr = avifDataCalcItemPtr(data, item); - BEGIN_STREAM(exifBoxStream, boxPtr, item->size); - uint32_t exifTiffHeaderOffset; - CHECK(avifROStreamReadU32(&exifBoxStream, &exifTiffHeaderOffset)); // unsigned int(32) exif_tiff_header_offset; - - exifData.data = avifROStreamCurrent(&exifBoxStream); - exifData.size = avifROStreamRemainingBytes(&exifBoxStream); - } - - if (!memcmp(item->type, "mime", 4) && !memcmp(item->contentType.contentType, xmpContentType, xmpContentTypeSize)) { - xmpData.data = avifDataCalcItemPtr(data, item); - xmpData.size = item->size; - } + alphaOBUItem = item; + break; } } - if (colorOBU.size == 0) { - return AVIF_RESULT_NO_AV1_ITEMS_FOUND; + // Find Exif and/or XMP metadata, if any + for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) { + avifItem * item = &data->items.item[itemIndex]; + if (!item->id || !item->size) { + break; + } + + if (item->descForID != colorOBUItem->id) { + // Not a content description (metadata) for the colorOBU, skip it + continue; + } + + if (!memcmp(item->type, "Exif", 4)) { + // Advance past Annex A.2.1's header + const uint8_t * boxPtr = avifDataCalcItemPtr(data, item); + BEGIN_STREAM(exifBoxStream, boxPtr, item->size); + uint32_t exifTiffHeaderOffset; + CHECK(avifROStreamReadU32(&exifBoxStream, &exifTiffHeaderOffset)); // unsigned int(32) exif_tiff_header_offset; + + exifData.data = avifROStreamCurrent(&exifBoxStream); + exifData.size = avifROStreamRemainingBytes(&exifBoxStream); + } + + if (!memcmp(item->type, "mime", 4) && !memcmp(item->contentType.contentType, xmpContentType, xmpContentTypeSize)) { + xmpData.data = avifDataCalcItemPtr(data, item); + xmpData.size = item->size; + } + } + + if ((data->colorGrid.rows > 0) && (data->colorGrid.columns > 0)) { + if (!avifDataGenerateImageGridTiles(data, &data->colorGrid, colorOBUItem, AVIF_FALSE)) { + return AVIF_RESULT_INVALID_IMAGE_GRID; + } + data->colorTileCount = data->tiles.count; + } else { + if (colorOBU.size == 0) { + return AVIF_RESULT_NO_AV1_ITEMS_FOUND; + } + + avifTile * colorTile = avifDataNewTile(decoder->data); + avifSample * colorSample = (avifSample *)avifArrayPushPtr(&colorTile->input->samples); + memcpy(&colorSample->data, &colorOBU, sizeof(avifROData)); + colorSample->sync = AVIF_TRUE; + decoder->data->colorTileCount = 1; + } + + if ((data->alphaGrid.rows > 0) && (data->alphaGrid.columns > 0)) { + if (!avifDataGenerateImageGridTiles(data, &data->alphaGrid, alphaOBUItem, AVIF_FALSE)) { + return AVIF_RESULT_INVALID_IMAGE_GRID; + } + data->alphaTileCount = data->tiles.count - data->colorTileCount; + } else { + avifTile * alphaTile = NULL; + if (alphaOBU.size > 0) { + alphaTile = avifDataNewTile(decoder->data); + + avifSample * alphaSample = (avifSample *)avifArrayPushPtr(&alphaTile->input->samples); + memcpy(&alphaSample->data, &alphaOBU, sizeof(avifROData)); + alphaSample->sync = AVIF_TRUE; + alphaTile->input->alpha = AVIF_TRUE; + decoder->data->alphaTileCount = 1; + } } if (colorOBUItem->colrPresent) { @@ -1771,18 +2061,6 @@ avifImageSetMetadataXMP(decoder->image, xmpData.data, xmpData.size); } - data->colorInput = avifCodecDecodeInputCreate(); - avifSample * colorSample = (avifSample *)avifArrayPushPtr(&data->colorInput->samples); - memcpy(&colorSample->data, &colorOBU, sizeof(avifROData)); - colorSample->sync = AVIF_TRUE; - if (alphaOBU.size > 0) { - data->alphaInput = avifCodecDecodeInputCreate(); - avifSample * alphaSample = (avifSample *)avifArrayPushPtr(&data->alphaInput->samples); - memcpy(&alphaSample->data, &alphaOBU, sizeof(avifROData)); - alphaSample->sync = AVIF_TRUE; - data->alphaInput->alpha = AVIF_TRUE; - } - // Set all counts and timing to safe-but-uninteresting values decoder->imageIndex = -1; decoder->imageCount = 1; @@ -1817,45 +2095,98 @@ avifResult avifDecoderNextImage(avifDecoder * decoder) { - avifCodec * colorCodec = decoder->data->codec[AVIF_CODEC_PLANES_COLOR]; - if (!colorCodec->getNextImage(colorCodec, decoder->image)) { - if (decoder->image->width) { - // We've sent at least one image, but we've run out now. - return AVIF_RESULT_NO_IMAGES_REMAINING; - } - return AVIF_RESULT_DECODE_COLOR_FAILED; - } + for (unsigned int tileIndex = 0; tileIndex < decoder->data->tiles.count; ++tileIndex) { + avifTile * tile = &decoder->data->tiles.tile[tileIndex]; - avifCodec * alphaCodec = decoder->data->codec[AVIF_CODEC_PLANES_ALPHA]; - if (alphaCodec) { - if (!alphaCodec->getNextImage(alphaCodec, decoder->image)) { - return AVIF_RESULT_DECODE_ALPHA_FAILED; + if (!tile->codec->getNextImage(tile->codec, tile->image)) { + if (tile->input->alpha) { + return AVIF_RESULT_DECODE_ALPHA_FAILED; + } else { + if (tile->image->width) { + // We've sent at least one image, but we've run out now. + return AVIF_RESULT_NO_IMAGES_REMAINING; + } + return AVIF_RESULT_DECODE_COLOR_FAILED; + } } - } else { - avifImageFreePlanes(decoder->image, AVIF_PLANES_A); - } #if defined(AVIF_FIX_STUDIO_ALPHA) - if (alphaCodec && alphaCodec->alphaLimitedRange(alphaCodec)) { - // Naughty! Alpha planes are supposed to be full range. Correct that here. - avifImageCopyDecoderAlpha(decoder->image); - if (avifImageUsesU16(decoder->image)) { - for (uint32_t j = 0; j < decoder->image->height; ++j) { - for (uint32_t i = 0; i < decoder->image->height; ++i) { - uint16_t * alpha = (uint16_t *)&decoder->image->alphaPlane[(i * 2) + (j * decoder->image->alphaRowBytes)]; - *alpha = (uint16_t)avifLimitedToFullY(decoder->image->depth, *alpha); + if (tile->input->alpha && tile->codec->alphaLimitedRange(tile->codec)) { + // Naughty! Alpha planes are supposed to be full range. Correct that here. + avifImageCopyDecoderAlpha(tile->image); + if (avifImageUsesU16(tile->image)) { + for (uint32_t j = 0; j < tile->image->height; ++j) { + for (uint32_t i = 0; i < tile->image->height; ++i) { + uint16_t * alpha = (uint16_t *)&tile->image->alphaPlane[(i * 2) + (j * tile->image->alphaRowBytes)]; + *alpha = (uint16_t)avifLimitedToFullY(tile->image->depth, *alpha); + } } - } - } else { - for (uint32_t j = 0; j < decoder->image->height; ++j) { - for (uint32_t i = 0; i < decoder->image->height; ++i) { - uint8_t * alpha = &decoder->image->alphaPlane[i + (j * decoder->image->alphaRowBytes)]; - *alpha = (uint8_t)avifLimitedToFullY(decoder->image->depth, *alpha); + } else { + for (uint32_t j = 0; j < tile->image->height; ++j) { + for (uint32_t i = 0; i < tile->image->height; ++i) { + uint8_t * alpha = &tile->image->alphaPlane[i + (j * tile->image->alphaRowBytes)]; + *alpha = (uint8_t)avifLimitedToFullY(tile->image->depth, *alpha); + } } } } - } #endif + } + + if (decoder->data->tiles.count != (decoder->data->colorTileCount + decoder->data->alphaTileCount)) { + // TODO: assert here? This should be impossible. + return AVIF_RESULT_UNKNOWN_ERROR; + } + + if ((decoder->data->colorGrid.rows > 0) || (decoder->data->colorGrid.columns > 0)) { + if (!avifDataFillImageGrid(decoder->data, &decoder->data->colorGrid, decoder->image, 0, decoder->data->colorTileCount, AVIF_FALSE)) { + return AVIF_RESULT_INVALID_IMAGE_GRID; + } + } else { + // Normal (most common) non-grid path. Just steal the planes from the only "tile". + + if (decoder->data->colorTileCount != 1) { + return AVIF_RESULT_DECODE_COLOR_FAILED; + } + + avifImage * srcColor = decoder->data->tiles.tile[0].image; + + if ((decoder->image->width != srcColor->width) || (decoder->image->height != srcColor->height) || + (decoder->image->depth != srcColor->depth)) { + avifImageFreePlanes(decoder->image, AVIF_PLANES_ALL); + + decoder->image->width = srcColor->width; + decoder->image->height = srcColor->height; + decoder->image->depth = srcColor->depth; + } + + avifImageStealPlanes(decoder->image, srcColor, AVIF_PLANES_YUV); + } + + if ((decoder->data->alphaGrid.rows > 0) || (decoder->data->alphaGrid.columns > 0)) { + if (!avifDataFillImageGrid( + decoder->data, &decoder->data->alphaGrid, decoder->image, decoder->data->colorTileCount, decoder->data->alphaTileCount, AVIF_TRUE)) { + return AVIF_RESULT_INVALID_IMAGE_GRID; + } + } else { + // Normal (most common) non-grid path. Just steal the planes from the only "tile". + + if (decoder->data->alphaTileCount == 0) { + avifImageFreePlanes(decoder->image, AVIF_PLANES_A); // no alpha + } else { + if (decoder->data->alphaTileCount != 1) { + return AVIF_RESULT_DECODE_ALPHA_FAILED; + } + + avifImage * srcAlpha = decoder->data->tiles.tile[decoder->data->colorTileCount].image; + if ((decoder->image->width != srcAlpha->width) || (decoder->image->height != srcAlpha->height) || + (decoder->image->depth != srcAlpha->depth)) { + return AVIF_RESULT_DECODE_ALPHA_FAILED; + } + + avifImageStealPlanes(decoder->image, srcAlpha, AVIF_PLANES_A); + } + } ++decoder->imageIndex; if (decoder->data->sourceSampleTable) { @@ -1915,9 +2246,9 @@ avifBool avifDecoderIsKeyframe(avifDecoder * decoder, uint32_t frameIndex) { - if (decoder->data->colorInput) { - if (frameIndex < decoder->data->colorInput->samples.count) { - return decoder->data->colorInput->samples.sample[frameIndex].sync; + if ((decoder->data->tiles.count > 0) && decoder->data->tiles.tile[0].input) { + if (frameIndex < decoder->data->tiles.tile[0].input->samples.count) { + return decoder->data->tiles.tile[0].input->samples.sample[frameIndex].sync; } } return AVIF_FALSE;