Support the 32-bit item ID box variants for files with more than 6553… (#3357)
The encoder previously used 16-bit item IDs, so encoding a file containing
more than 65535 items (e.g. a grid image with an alpha channel) silently
wrapped the item IDs around and generated an invalid file.
Item IDs are now uint32_t internally. When any item ID exceeds UINT16_MAX,
avifEncoderFinish() and avifEncoderWriteTrackMetaBox() write the 32-bit item
ID variants of the boxes with item ID fields: 'pitm' version 1, 'iloc'
version 2, 'iinf' version 1, 'infe' version 3, 'iref' version 1 and 'ipma'
version 1. All these variants were already supported when reading.
Files with 65535 items or fewer are unchanged (byte-identical output).
avifEncoderDataCreateItem() returns NULL instead of wrapping around if the
32-bit item ID space is exhausted.
---------
Co-authored-by: krishna28238-arch <319297638+krishna28238-arch@users.noreply.github.com>
diff --git a/src/write.c b/src/write.c
index 5266ff3..2070600 100644
--- a/src/write.c
+++ b/src/write.c
@@ -163,7 +163,7 @@
// one "item" worth for encoder
typedef struct avifEncoderItem
{
- uint16_t id;
+ uint32_t id;
uint8_t type[4]; // 4-character 'item_type' field in the 'infe' (item info entry) box
avifCodec * codec; // only present on image items
avifCodecEncodeOutput * encodeOutput; // AV1 sample data
@@ -180,7 +180,7 @@
size_t infeContentTypeSize;
avifOffsetFixupArray mdatFixups;
- uint16_t irefToID; // if non-zero, make an iref from this id -> irefToID
+ uint32_t irefToID; // if non-zero, make an iref from this id -> irefToID
const char * irefType;
uint32_t gridCols; // if non-zero (legal range [1-256]), this is a grid item
@@ -192,7 +192,7 @@
uint32_t extraLayerCount; // if non-zero (legal range [1-(AVIF_MAX_AV1_LAYER_COUNT-1)]), this is a layered AV1 image
- uint16_t dimgFromID; // if non-zero, make an iref from dimgFromID -> this id
+ uint32_t dimgFromID; // if non-zero, make an iref from dimgFromID -> this id
avifItemPropertyAssociationArray associations; // 'ipma'
} avifEncoderItem;
@@ -217,7 +217,7 @@
// ---------------------------------------------------------------------------
// avifEncoderData
-AVIF_ARRAY_DECLARE(avifEncoderItemIdArray, uint16_t, itemID);
+AVIF_ARRAY_DECLARE(avifEncoderItemIdArray, uint32_t, itemID);
typedef struct avifEncoderData
{
@@ -243,8 +243,8 @@
avifImage * imageMetadata;
// Holds metadata derived from the avifGainMap struct (when present) about the alternate image
avifImage * altImageMetadata;
- uint16_t lastItemID;
- uint16_t primaryItemID;
+ uint32_t lastItemID;
+ uint32_t primaryItemID;
avifEncoderItemIdArray alternativeItemIDs; // list of item ids for an 'altr' box (group of alternatives to each other)
avifBool singleImage; // if true, the AVIF_ADD_IMAGE_FLAG_SINGLE flag was set on the first call to avifEncoderAddImage()
avifBool alphaPresent;
@@ -278,7 +278,7 @@
if (!avifArrayCreate(&data->frames, sizeof(avifEncoderFrame), 1)) {
goto error;
}
- if (!avifArrayCreate(&data->alternativeItemIDs, sizeof(uint16_t), 1)) {
+ if (!avifArrayCreate(&data->alternativeItemIDs, sizeof(uint32_t), 1)) {
goto error;
}
return data;
@@ -288,12 +288,22 @@
return NULL;
}
-static avifEncoderItem * avifEncoderDataCreateItem(avifEncoderData * data, const char * type, const char * infeName, size_t infeNameSize, uint32_t cellIndex)
+// Returns AVIF_RESULT_OUT_OF_MEMORY if a memory allocation failed, or AVIF_RESULT_INTERNAL_ERROR
+// if no item ID is left (item IDs span a 32-bit space, so the latter would require more than
+// 4 billion items).
+static avifResult avifEncoderDataCreateItem(avifEncoderData * data,
+ const char * type,
+ const char * infeName,
+ size_t infeNameSize,
+ uint32_t cellIndex,
+ avifEncoderItem ** itemPtr)
{
+ // All item IDs are taken. This is unreachable in practice but avoid a wrap-around of
+ // item IDs, which would silently truncate or duplicate them.
+ AVIF_ASSERT_OR_RETURN(data->lastItemID != UINT32_MAX);
+
avifEncoderItem * item = (avifEncoderItem *)avifArrayPush(&data->items);
- if (item == NULL) {
- return NULL;
- }
+ AVIF_CHECKERR(item != NULL, AVIF_RESULT_OUT_OF_MEMORY);
++data->lastItemID;
item->id = data->lastItemID;
memcpy(item->type, type, sizeof(item->type));
@@ -310,7 +320,8 @@
if (!avifArrayCreate(&item->associations, sizeof(avifItemPropertyAssociation), 4)) {
goto error;
}
- return item;
+ *itemPtr = item;
+ return AVIF_RESULT_OK;
error:
if (item->encodeOutput != NULL) {
@@ -319,10 +330,10 @@
avifArrayDestroy(&item->mdatFixups);
--data->lastItemID;
avifArrayPop(&data->items);
- return NULL;
+ return AVIF_RESULT_OUT_OF_MEMORY;
}
-static avifEncoderItem * avifEncoderDataFindItemByID(avifEncoderData * data, uint16_t id)
+static avifEncoderItem * avifEncoderDataFindItemByID(avifEncoderData * data, uint32_t id)
{
for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) {
avifEncoderItem * item = &data->items.item[itemIndex];
@@ -857,12 +868,17 @@
{
// Count how many non-image items (such as EXIF/XMP) are being written
uint32_t metadataItemCount = 0;
+ avifBool largeItemIDs = AVIF_FALSE; // Are any of the metadata item IDs, or the metadata item count, too large for a 16-bit field?
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (memcmp(item->type, encoder->data->imageItemType, 4) != 0) {
++metadataItemCount;
+ largeItemIDs |= (item->id > UINT16_MAX);
}
}
+ // The 'iloc' and 'iinf' variants below are also needed if the metadata item count does not
+ // fit in a 16-bit field.
+ largeItemIDs |= (metadataItemCount > UINT16_MAX);
if (metadataItemCount == 0) {
// Don't even bother writing the trak meta box
return AVIF_RESULT_OK;
@@ -874,12 +890,16 @@
AVIF_CHECKRES(avifRWStreamWriteHandlerBox(s, "pict"));
avifBoxMarker iloc;
- AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0, &iloc));
- AVIF_CHECKRES(avifRWStreamWriteBits(s, 4, /*bitCount=*/4)); // unsigned int(4) offset_size;
- AVIF_CHECKRES(avifRWStreamWriteBits(s, 4, /*bitCount=*/4)); // unsigned int(4) length_size;
- AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, /*bitCount=*/4)); // unsigned int(4) base_offset_size;
- AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, /*bitCount=*/4)); // unsigned int(4) reserved;
- AVIF_CHECKRES(avifRWStreamWriteU16(s, (uint16_t)metadataItemCount)); // unsigned int(16) item_count;
+ AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "iloc", AVIF_BOX_SIZE_TBD, largeItemIDs ? 2 : 0, 0, &iloc));
+ AVIF_CHECKRES(avifRWStreamWriteBits(s, 4, /*bitCount=*/4)); // unsigned int(4) offset_size;
+ AVIF_CHECKRES(avifRWStreamWriteBits(s, 4, /*bitCount=*/4)); // unsigned int(4) length_size;
+ AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, /*bitCount=*/4)); // unsigned int(4) base_offset_size;
+ AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, /*bitCount=*/4)); // unsigned int(4) index_size, or reserved if version < 1;
+ if (largeItemIDs) {
+ AVIF_CHECKRES(avifRWStreamWriteU32(s, metadataItemCount)); // unsigned int(32) item_count;
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(s, (uint16_t)metadataItemCount)); // unsigned int(16) item_count;
+ }
for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) {
avifEncoderItem * item = &encoder->data->items.item[trakItemIndex];
if (memcmp(item->type, encoder->data->imageItemType, 4) == 0) {
@@ -887,7 +907,13 @@
continue;
}
- AVIF_CHECKRES(avifRWStreamWriteU16(s, item->id)); // unsigned int(16) item_ID;
+ if (largeItemIDs) {
+ AVIF_CHECKRES(avifRWStreamWriteU32(s, item->id)); // unsigned int(32) item_ID;
+ AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, /*bitCount=*/12)); // unsigned int(12) reserved = 0;
+ AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, /*bitCount=*/4)); // unsigned int(4) construction_method = 0 (file offset);
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(s, (uint16_t)item->id)); // unsigned int(16) item_ID;
+ }
AVIF_CHECKRES(avifRWStreamWriteU16(s, 0)); // unsigned int(16) data_reference_index;
AVIF_CHECKRES(avifRWStreamWriteU16(s, 1)); // unsigned int(16) extent_count;
AVIF_CHECKRES(avifEncoderItemAddMdatFixup(item, s)); //
@@ -897,8 +923,12 @@
AVIF_CHECKRES(avifRWStreamFinishBox(s, iloc));
avifBoxMarker iinf;
- AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0, &iinf));
- AVIF_CHECKRES(avifRWStreamWriteU16(s, (uint16_t)metadataItemCount)); // unsigned int(16) entry_count;
+ AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "iinf", AVIF_BOX_SIZE_TBD, largeItemIDs ? 1 : 0, 0, &iinf));
+ if (largeItemIDs) {
+ AVIF_CHECKRES(avifRWStreamWriteU32(s, metadataItemCount)); // unsigned int(32) entry_count;
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(s, (uint16_t)metadataItemCount)); // unsigned int(16) entry_count;
+ }
for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) {
avifEncoderItem * item = &encoder->data->items.item[trakItemIndex];
if (memcmp(item->type, encoder->data->imageItemType, 4) == 0) {
@@ -907,8 +937,13 @@
AVIF_ASSERT_OR_RETURN(!item->hiddenImage);
avifBoxMarker infe;
- AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "infe", AVIF_BOX_SIZE_TBD, 2, 0, &infe));
- AVIF_CHECKRES(avifRWStreamWriteU16(s, item->id)); // unsigned int(16) item_ID;
+ // 'infe' version 3 contains a 32-bit item_ID field, whereas version 2 contains a 16-bit item_ID field.
+ AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "infe", AVIF_BOX_SIZE_TBD, largeItemIDs ? 3 : 2, 0, &infe));
+ if (largeItemIDs) {
+ AVIF_CHECKRES(avifRWStreamWriteU32(s, item->id)); // unsigned int(32) item_ID;
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(s, (uint16_t)item->id)); // unsigned int(16) item_ID;
+ }
AVIF_CHECKRES(avifRWStreamWriteU16(s, 0)); // unsigned int(16) item_protection_index;
AVIF_CHECKRES(avifRWStreamWrite(s, item->type, 4)); // unsigned int(32) item_type;
AVIF_CHECKRES(avifRWStreamWriteChars(s, item->infeName, item->infeNameSize)); // string item_name; (writing null terminator)
@@ -1118,10 +1153,8 @@
return result;
}
- avifEncoderItem * exifItem = avifEncoderDataCreateItem(data, "Exif", "Exif", 5, 0);
- if (!exifItem) {
- return AVIF_RESULT_OUT_OF_MEMORY;
- }
+ avifEncoderItem * exifItem;
+ AVIF_CHECKRES(avifEncoderDataCreateItem(data, "Exif", "Exif", 5, 0, &exifItem));
exifItem->irefToID = data->primaryItemID;
exifItem->irefType = "cdsc";
@@ -1134,10 +1167,8 @@
static avifResult avifEncoderDataCreateXMPItem(avifEncoderData * data, const avifRWData * xmp)
{
- avifEncoderItem * xmpItem = avifEncoderDataCreateItem(data, "mime", "XMP", 4, 0);
- if (!xmpItem) {
- return AVIF_RESULT_OUT_OF_MEMORY;
- }
+ avifEncoderItem * xmpItem;
+ AVIF_CHECKRES(avifEncoderDataCreateItem(data, "mime", "XMP", 4, 0, &xmpItem));
xmpItem->irefToID = data->primaryItemID;
xmpItem->irefType = "cdsc";
@@ -1245,7 +1276,7 @@
// Adds the items for a single cell or a grid of cells. Outputs the topLevelItemID which is
// the only item if there is exactly one cell, or the grid item for multiple cells.
-// Note: The topLevelItemID output argument has the type uint16_t* instead of avifEncoderItem** because
+// Note: The topLevelItemID output argument has the type uint32_t* instead of avifEncoderItem** because
// the avifEncoderItem pointer may be invalidated by a call to avifEncoderDataCreateItem().
static avifResult avifEncoderAddImageItems(avifEncoder * encoder,
uint32_t gridCols,
@@ -1253,14 +1284,15 @@
uint32_t gridWidth,
uint32_t gridHeight,
avifItemCategory itemCategory,
- uint16_t * topLevelItemID)
+ uint32_t * topLevelItemID)
{
const uint32_t cellCount = gridCols * gridRows;
const char * infeName = getInfeName(itemCategory);
const size_t infeNameSize = strlen(infeName) + 1;
if (cellCount > 1) {
- avifEncoderItem * gridItem = avifEncoderDataCreateItem(encoder->data, "grid", infeName, infeNameSize, 0);
+ avifEncoderItem * gridItem;
+ AVIF_CHECKRES(avifEncoderDataCreateItem(encoder->data, "grid", infeName, infeNameSize, 0, &gridItem));
AVIF_CHECKRES(avifWriteGridPayload(&gridItem->metadataPayload, gridCols, gridRows, gridWidth, gridHeight));
gridItem->itemCategory = itemCategory;
gridItem->gridCols = gridCols;
@@ -1271,9 +1303,8 @@
}
for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
- avifEncoderItem * item =
- avifEncoderDataCreateItem(encoder->data, encoder->data->imageItemType, infeName, infeNameSize, cellIndex);
- AVIF_CHECKERR(item, AVIF_RESULT_OUT_OF_MEMORY);
+ avifEncoderItem * item;
+ AVIF_CHECKRES(avifEncoderDataCreateItem(encoder->data, encoder->data->imageItemType, infeName, infeNameSize, cellIndex, &item));
AVIF_CHECKRES(avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE, &item->codec));
item->codec->csOptions = encoder->csOptions;
item->codec->diag = &encoder->diag;
@@ -1295,7 +1326,7 @@
uint32_t gridRows,
uint32_t gridWidth,
uint32_t gridHeight,
- uint16_t colorItemID)
+ uint32_t colorItemID)
{
AVIF_ASSERT_OR_RETURN(encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B ||
encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B ||
@@ -1314,24 +1345,26 @@
// and a 'sato' using the two color 'grid's as input items in this order; the primary color item
// and the 'sato' item being in an 'altr' group (backward-compatible, implemented)
// - a primary 'grid' of 'sato' cells and an auxiliary alpha 'grid' of 'sato' cells (backward-incompatible)
- avifEncoderItem * sampleTransformItem = avifEncoderDataCreateItem(encoder->data,
- "sato",
- infeNameSampleTransform,
- /*infeNameSize=*/strlen(infeNameSampleTransform) + 1,
- /*cellIndex=*/0);
+ avifEncoderItem * sampleTransformItem;
+ AVIF_CHECKRES(avifEncoderDataCreateItem(encoder->data,
+ "sato",
+ infeNameSampleTransform,
+ /*infeNameSize=*/strlen(infeNameSampleTransform) + 1,
+ /*cellIndex=*/0,
+ &sampleTransformItem));
AVIF_CHECKRES(avifEncoderWriteSampleTransformPayload(encoder, &sampleTransformItem->metadataPayload));
sampleTransformItem->itemCategory = AVIF_ITEM_SAMPLE_TRANSFORM;
- uint16_t sampleTransformItemID = sampleTransformItem->id;
+ uint32_t sampleTransformItemID = sampleTransformItem->id;
// 'altr' group
AVIF_ASSERT_OR_RETURN(encoder->data->alternativeItemIDs.count == 0);
- uint16_t * alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
+ uint32_t * alternativeItemID = (uint32_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
*alternativeItemID = sampleTransformItem->id;
- alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
+ alternativeItemID = (uint32_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
*alternativeItemID = colorItemID;
- uint16_t bitDepthExtensionColorItemId;
+ uint32_t bitDepthExtensionColorItemId;
AVIF_CHECKRES(
avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_COLOR, &bitDepthExtensionColorItemId));
avifEncoderItem * bitDepthExtensionColorItem = avifEncoderDataFindItemByID(encoder->data, bitDepthExtensionColorItemId);
@@ -1350,7 +1383,7 @@
bitDepthExtensionColorItem->dimgFromID = sampleTransformItemID;
if (encoder->data->alphaPresent) {
- uint16_t bitDepthExtensionAlphaItemId;
+ uint32_t bitDepthExtensionAlphaItemId;
AVIF_CHECKRES(
avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_ALPHA, &bitDepthExtensionAlphaItemId));
avifEncoderItem * bitDepthExtensionAlphaItem = avifEncoderDataFindItemByID(encoder->data, bitDepthExtensionAlphaItemId);
@@ -1876,7 +1909,7 @@
}
// Prepare all AV1 items
- uint16_t colorItemID;
+ uint32_t colorItemID;
AVIF_CHECKRES(avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_COLOR, &colorItemID));
encoder->data->primaryItemID = colorItemID;
@@ -1902,7 +1935,7 @@
}
if (encoder->data->alphaPresent) {
- uint16_t alphaItemID;
+ uint32_t alphaItemID;
AVIF_CHECKRES(avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_ALPHA, &alphaItemID));
avifEncoderItem * alphaItem = avifEncoderDataFindItemByID(encoder->data, alphaItemID);
AVIF_ASSERT_OR_RETURN(alphaItem);
@@ -1917,22 +1950,24 @@
}
if (firstCell->gainMap && firstCell->gainMap->image) {
- avifEncoderItem * toneMappedItem = avifEncoderDataCreateItem(encoder->data,
- "tmap",
- infeNameGainMap,
- /*infeNameSize=*/strlen(infeNameGainMap) + 1,
- /*cellIndex=*/0);
+ avifEncoderItem * toneMappedItem;
+ AVIF_CHECKRES(avifEncoderDataCreateItem(encoder->data,
+ "tmap",
+ infeNameGainMap,
+ /*infeNameSize=*/strlen(infeNameGainMap) + 1,
+ /*cellIndex=*/0,
+ &toneMappedItem));
AVIF_CHECKRES(avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, firstCell->gainMap, &encoder->diag));
// Even though the 'tmap' item is related to the gain map, it represents a color image and its metadata is more similar to the color item.
toneMappedItem->itemCategory = AVIF_ITEM_COLOR;
- uint16_t toneMappedItemID = toneMappedItem->id;
+ uint32_t toneMappedItemID = toneMappedItem->id;
AVIF_ASSERT_OR_RETURN(encoder->data->alternativeItemIDs.count == 0);
- uint16_t * alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
+ uint32_t * alternativeItemID = (uint32_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
*alternativeItemID = toneMappedItemID;
- alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
+ alternativeItemID = (uint32_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
*alternativeItemID = colorItemID;
@@ -1941,7 +1976,7 @@
const uint32_t gainMapGridHeight =
avifGridHeight(gridRows, cellImages[0]->gainMap->image, cellImages[gridCols * gridRows - 1]->gainMap->image);
- uint16_t gainMapItemID;
+ uint32_t gainMapItemID;
AVIF_CHECKRES(
avifEncoderAddImageItems(encoder, gridCols, gridRows, gainMapGridWidth, gainMapGridHeight, AVIF_ITEM_GAIN_MAP, &gainMapItemID));
avifEncoderItem * gainMapItem = avifEncoderDataFindItemByID(encoder->data, gainMapItemID);
@@ -2151,7 +2186,10 @@
avifAddImageFlags addImageFlags)
{
avifDiagnosticsClearError(&encoder->diag);
- if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) {
+ // Each cell is referenced by a 'dimg' item reference whose reference_count field is
+ // 16-bit regardless of the 'iref' box version (ISO/IEC 14496-12 Section 8.11.12), so a
+ // grid cannot contain more than 65535 cells.
+ if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256) || (gridCols * gridRows > 65535)) {
return AVIF_RESULT_INVALID_IMAGE_GRID;
}
if (gridCols * gridRows > 65535) {
@@ -3347,26 +3385,49 @@
// -----------------------------------------------------------------------
// Write pitm
+ // Use the 32-bit item ID variants of the 'pitm', 'iloc', 'iinf', 'infe', 'iref' and
+ // 'ipma' boxes below if any item ID or the item count exceeds the 16-bit space, i.e. if
+ // there are more than 65535 items (item IDs are sequential and start at 1).
+ const avifBool largeItemIDs = encoder->data->lastItemID > UINT16_MAX || encoder->data->items.count > UINT16_MAX;
+
if (encoder->data->primaryItemID != 0) {
- AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "pitm", sizeof(uint16_t), 0, 0, /*marker=*/NULL));
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, encoder->data->primaryItemID)); // unsigned int(16) item_ID;
+ if (largeItemIDs) {
+ // 'pitm' version 1 contains a 32-bit item_ID field, whereas version 0 contains a 16-bit item_ID field.
+ AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "pitm", sizeof(uint32_t), 1, 0, /*marker=*/NULL));
+ AVIF_CHECKRES(avifRWStreamWriteU32(&s, encoder->data->primaryItemID)); // unsigned int(32) item_ID;
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "pitm", sizeof(uint16_t), 0, 0, /*marker=*/NULL));
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)encoder->data->primaryItemID)); // unsigned int(16) item_ID;
+ }
}
// -----------------------------------------------------------------------
// Write iloc
avifBoxMarker iloc;
- AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0, &iloc));
- AVIF_CHECKRES(avifRWStreamWriteBits(&s, 4, /*bitCount=*/4)); // unsigned int(4) offset_size;
- AVIF_CHECKRES(avifRWStreamWriteBits(&s, 4, /*bitCount=*/4)); // unsigned int(4) length_size;
- AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/4)); // unsigned int(4) base_offset_size;
- AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/4)); // unsigned int(4) reserved;
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count)); // unsigned int(16) item_count;
+ AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iloc", AVIF_BOX_SIZE_TBD, largeItemIDs ? 2 : 0, 0, &iloc));
+ AVIF_CHECKRES(avifRWStreamWriteBits(&s, 4, /*bitCount=*/4)); // unsigned int(4) offset_size;
+ AVIF_CHECKRES(avifRWStreamWriteBits(&s, 4, /*bitCount=*/4)); // unsigned int(4) length_size;
+ AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/4)); // unsigned int(4) base_offset_size;
+ AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/4)); // unsigned int(4) index_size, or reserved if version < 1;
+ if (largeItemIDs) {
+ // 'iloc' version 2 contains a 32-bit item_count field, whereas versions 0 and 1 contain a 16-bit item_count field.
+ AVIF_CHECKRES(avifRWStreamWriteU32(&s, encoder->data->items.count)); // unsigned int(32) item_count;
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count)); // unsigned int(16) item_count;
+ }
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); // unsigned int(16) item_ID;
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // unsigned int(16) data_reference_index;
+ if (largeItemIDs) {
+ // 'iloc' version 2 contains a 32-bit item_ID field, whereas versions 0 and 1 contain a 16-bit item_ID field.
+ AVIF_CHECKRES(avifRWStreamWriteU32(&s, item->id)); // unsigned int(32) item_ID;
+ AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/12)); // unsigned int(12) reserved = 0;
+ AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/4)); // unsigned int(4) construction_method = 0 (file offset);
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)item->id)); // unsigned int(16) item_ID;
+ }
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // unsigned int(16) data_reference_index;
// Layered Image, write location for all samples
if (item->extraLayerCount > 0) {
@@ -3403,16 +3464,26 @@
// Section 8.11.6.2 of ISO/IEC 14496-12.
avifBoxMarker iinf;
- AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0, &iinf));
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count)); // unsigned int(16) entry_count;
+ // 'iinf' version 1 contains a 32-bit entry_count field, whereas version 0 contains a 16-bit entry_count field.
+ AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iinf", AVIF_BOX_SIZE_TBD, largeItemIDs ? 1 : 0, 0, &iinf));
+ if (largeItemIDs) {
+ AVIF_CHECKRES(avifRWStreamWriteU32(&s, encoder->data->items.count)); // unsigned int(32) entry_count;
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count)); // unsigned int(16) entry_count;
+ }
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
uint32_t flags = item->hiddenImage ? 1 : 0;
avifBoxMarker infe;
- AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "infe", AVIF_BOX_SIZE_TBD, 2, flags, &infe));
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); // unsigned int(16) item_ID;
+ // 'infe' version 3 contains a 32-bit item_ID field, whereas version 2 contains a 16-bit item_ID field.
+ AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "infe", AVIF_BOX_SIZE_TBD, largeItemIDs ? 3 : 2, flags, &infe));
+ if (largeItemIDs) {
+ AVIF_CHECKRES(avifRWStreamWriteU32(&s, item->id)); // unsigned int(32) item_ID;
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)item->id)); // unsigned int(16) item_ID;
+ }
AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // unsigned int(16) item_protection_index;
AVIF_CHECKRES(avifRWStreamWrite(&s, item->type, 4)); // unsigned int(32) item_type;
AVIF_CHECKRES(avifRWStreamWriteChars(&s, item->infeName, item->infeNameSize)); // utf8string item_name; (writing null terminator)
@@ -3446,16 +3517,25 @@
if (dimgCount > 0) {
if (!iref) {
- AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0, &iref));
+ // 'iref' version 1 contains 32-bit item_ID fields, whereas version 0 contains 16-bit item_ID fields.
+ AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, largeItemIDs ? 1 : 0, 0, &iref));
}
avifBoxMarker refType;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "dimg", AVIF_BOX_SIZE_TBD, &refType));
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); // unsigned int(16) from_item_ID;
+ if (largeItemIDs) {
+ AVIF_CHECKRES(avifRWStreamWriteU32(&s, item->id)); // unsigned int(32) from_item_ID;
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)item->id)); // unsigned int(16) from_item_ID;
+ }
AVIF_CHECKRES(avifRWStreamWriteU16(&s, dimgCount)); // unsigned int(16) reference_count;
for (uint32_t dimgIndex = 0; dimgIndex < encoder->data->items.count; ++dimgIndex) {
avifEncoderItem * dimgItem = &encoder->data->items.item[dimgIndex];
if (dimgItem->dimgFromID == item->id) {
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, dimgItem->id)); // unsigned int(16) to_item_ID;
+ if (largeItemIDs) {
+ AVIF_CHECKRES(avifRWStreamWriteU32(&s, dimgItem->id)); // unsigned int(32) to_item_ID;
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)dimgItem->id)); // unsigned int(16) to_item_ID;
+ }
}
}
AVIF_CHECKRES(avifRWStreamFinishBox(&s, refType));
@@ -3463,13 +3543,20 @@
if (item->irefToID != 0) {
if (!iref) {
- AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0, &iref));
+ // 'iref' version 1 contains 32-bit item_ID fields, whereas version 0 contains 16-bit item_ID fields.
+ AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, largeItemIDs ? 1 : 0, 0, &iref));
}
avifBoxMarker refType;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD, &refType));
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); // unsigned int(16) from_item_ID;
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); // unsigned int(16) reference_count;
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->irefToID)); // unsigned int(16) to_item_ID;
+ if (largeItemIDs) {
+ AVIF_CHECKRES(avifRWStreamWriteU32(&s, item->id)); // unsigned int(32) from_item_ID;
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); // unsigned int(16) reference_count;
+ AVIF_CHECKRES(avifRWStreamWriteU32(&s, item->irefToID)); // unsigned int(32) to_item_ID;
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)item->id)); // unsigned int(16) from_item_ID;
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); // unsigned int(16) reference_count;
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)item->irefToID)); // unsigned int(16) to_item_ID;
+ }
AVIF_CHECKRES(avifRWStreamFinishBox(&s, refType));
}
}
@@ -3496,7 +3583,9 @@
AVIF_CHECKRES(avifRWStreamFinishBox(&s, ipco));
avifBoxMarker ipma;
- AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "ipma", AVIF_BOX_SIZE_TBD, 0, 0, &ipma));
+ // 'ipma' version 1 contains a 32-bit item_ID field, whereas version 0 contains a 16-bit item_ID field.
+ // Note: the flags remain 0, meaning the property_index fields stay 7-bit wide.
+ AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "ipma", AVIF_BOX_SIZE_TBD, largeItemIDs ? 1 : 0, 0, &ipma));
{
uint32_t ipmaCount = 0;
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
@@ -3514,7 +3603,11 @@
continue;
}
- AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); // unsigned int(16) item_ID;
+ if (largeItemIDs) {
+ AVIF_CHECKRES(avifRWStreamWriteU32(&s, item->id)); // unsigned int(32) item_ID;
+ } else {
+ AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)item->id)); // unsigned int(16) item_ID;
+ }
AVIF_ASSERT_OR_RETURN(item->associations.count < (1 << 8));
AVIF_CHECKRES(avifRWStreamWriteU8(&s, (uint8_t)item->associations.count)); // unsigned int(8) association_count;
for (uint32_t i = 0; i < item->associations.count; ++i) {
@@ -3538,7 +3631,7 @@
// group_id value of any other EntityToGroupBox, any item_ID value of the hierarchy level
// (file, movie. or track) that contains the GroupsListBox, or any track_ID value (when the
// GroupsListBox is contained in the file level).
- AVIF_ASSERT_OR_RETURN(encoder->data->lastItemID < UINT16_MAX);
+ AVIF_ASSERT_OR_RETURN(encoder->data->lastItemID < UINT32_MAX);
++encoder->data->lastItemID;
const uint32_t groupID = encoder->data->lastItemID;
AVIF_CHECKRES(avifWriteAltrGroup(&s, groupID, &encoder->data->alternativeItemIDs));
diff --git a/tests/gtest/avifgridapitest.cc b/tests/gtest/avifgridapitest.cc
index 2168e7a..a04ae36 100644
--- a/tests/gtest/avifgridapitest.cc
+++ b/tests/gtest/avifgridapitest.cc
@@ -1,15 +1,35 @@
// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
+#include <cstring>
#include <vector>
#include "avif/avif.h"
+#include "avif/internal.h"
#include "aviftest_helpers.h"
#include "gtest/gtest.h"
namespace avif {
namespace {
+// Returns the content of the first box of the given type among the sibling
+// boxes contained in container_content, or an empty avifROData if not found.
+avifROData FindBox(avifROData container_content, const char type[4]) {
+ avifROStream stream;
+ avifROStreamStart(&stream, &container_content,
+ /*diag=*/nullptr, /*diagContext=*/nullptr);
+ avifBoxHeader header;
+ while (avifROStreamReadBoxHeader(&stream, &header)) {
+ if (!memcmp(header.type, type, 4)) {
+ return {avifROStreamCurrent(&stream), header.size};
+ }
+ if (!avifROStreamSkip(&stream, header.size)) {
+ break;
+ }
+ }
+ return {nullptr, 0};
+}
+
// One AVIF cell in an AVIF grid.
struct Cell {
int width, height; // In pixels.
@@ -221,6 +241,183 @@
//------------------------------------------------------------------------------
+TEST(GridApiTest, ColorAlphaGridExceeding16BitItemIDs) {
+ // A color grid and an alpha grid of 128x256 cells each contain 32768 cells,
+ // so encoding them requires 65538 distinct item IDs (2 grid items + 2 x 32768
+ // cell items), which does not fit in the 16-bit item ID space (item ID 0 is
+ // also invalid, see ISO/IEC 14496-12 Section 8.11.1.1). The encoder writes
+ // the 32-bit item ID variants of the boxes with item ID fields
+ // ('pitm' version 1, 'iloc' version 2, 'iinf' version 1, 'infe' version 3,
+ // 'iref' version 1 and 'ipma' version 1) so that such files are valid.
+ // The cells are 64x64 because it is the smallest size allowed by MIAF.
+ // Note: Most of the time spent in this test is the encoding of the 65536
+ // cells. The generated file is not parsed with avifDecoderParse() because
+ // that would cost minutes with the current quadratic lookups for this many
+ // items; walking the boxes below is enough to check what was written.
+ ImagePtr cell = testutil::CreateImage(
+ /*width=*/64, /*height=*/64, /*depth=*/8, AVIF_PIXEL_FORMAT_YUV400,
+ AVIF_PLANES_ALL /* the alpha channel is needed */);
+ ASSERT_NE(cell, nullptr);
+ // The pixels do not matter but avoid use-of-uninitialized-value errors.
+ testutil::FillImageGradient(cell.get());
+ const std::vector<avifImage*> cell_image_ptrs(128 * 256, cell.get());
+
+ EncoderPtr encoder(avifEncoderCreate());
+ ASSERT_NE(encoder, nullptr);
+ // Worst quality to keep the encoding of the 65536 cells as fast as possible.
+ encoder->speed = AVIF_SPEED_FASTEST;
+ encoder->quality = AVIF_QUALITY_WORST;
+ ASSERT_EQ(avifEncoderAddImageGrid(encoder.get(), /*gridCols=*/128,
+ /*gridRows=*/256, cell_image_ptrs.data(),
+ AVIF_ADD_IMAGE_FLAG_SINGLE),
+ AVIF_RESULT_OK);
+ testutil::AvifRwData encoded_avif;
+ ASSERT_EQ(avifEncoderFinish(encoder.get(), &encoded_avif), AVIF_RESULT_OK);
+
+ // The color grid item, the color cell items, the alpha grid item, the alpha
+ // cell items and possibly some metadata items are given sequential item IDs,
+ // starting with the primary item (the color grid item).
+ constexpr uint32_t kExpectedItemCount = 65538;
+
+ const avifROData file = {encoded_avif.data, encoded_avif.size};
+ avifROData meta = FindBox(file, "meta");
+ ASSERT_NE(meta.data, nullptr);
+ // The 'meta' box is a FullBox: its version and flags come before its child
+ // boxes.
+ const avifROData meta_children = {meta.data + 4, meta.size - 4};
+
+ // 'pitm' version 1 and a 32-bit primary item ID.
+ avifROData pitm = FindBox(meta_children, "pitm");
+ ASSERT_NE(pitm.data, nullptr);
+ {
+ avifROStream stream;
+ avifROStreamStart(&stream, &pitm, /*diag=*/nullptr,
+ /*diagContext=*/nullptr);
+ uint8_t version;
+ uint32_t flags;
+ ASSERT_TRUE(avifROStreamReadVersionAndFlags(&stream, &version, &flags));
+ EXPECT_EQ(version, 1);
+ uint32_t item_id;
+ ASSERT_TRUE(avifROStreamReadU32(&stream, &item_id));
+ EXPECT_EQ(item_id, 1);
+ }
+
+ // 'iloc' version 2 and a 32-bit item_count.
+ avifROData iloc = FindBox(meta_children, "iloc");
+ ASSERT_NE(iloc.data, nullptr);
+ {
+ avifROStream stream;
+ avifROStreamStart(&stream, &iloc, /*diag=*/nullptr,
+ /*diagContext=*/nullptr);
+ uint8_t version;
+ ASSERT_TRUE(avifROStreamReadVersionAndFlags(&stream, &version, nullptr));
+ EXPECT_EQ(version, 2);
+ ASSERT_TRUE(avifROStreamSkip(
+ &stream,
+ 2)); // offset_size, length_size, base_offset_size and index_size
+ uint32_t item_count;
+ ASSERT_TRUE(avifROStreamReadU32(&stream, &item_count));
+ EXPECT_EQ(item_count, kExpectedItemCount);
+ }
+
+ // 'iinf' version 1, a 32-bit entry_count, and 'infe' entries with version 3.
+ avifROData iinf = FindBox(meta_children, "iinf");
+ ASSERT_NE(iinf.data, nullptr);
+ {
+ avifROStream stream;
+ avifROStreamStart(&stream, &iinf, /*diag=*/nullptr,
+ /*diagContext=*/nullptr);
+ uint8_t version;
+ ASSERT_TRUE(avifROStreamReadVersionAndFlags(&stream, &version, nullptr));
+ EXPECT_EQ(version, 1);
+ uint32_t entry_count;
+ ASSERT_TRUE(avifROStreamReadU32(&stream, &entry_count));
+ EXPECT_EQ(entry_count, kExpectedItemCount);
+ // The 'infe' child boxes follow the version, flags and entry_count fields.
+ avifROData iinf_children = {iinf.data + 8, iinf.size - 8};
+ avifROData infe = FindBox(iinf_children, "infe");
+ ASSERT_NE(infe.data, nullptr);
+ avifROStream infe_stream;
+ avifROStreamStart(&infe_stream, &infe, /*diag=*/nullptr,
+ /*diagContext=*/nullptr);
+ ASSERT_TRUE(
+ avifROStreamReadVersionAndFlags(&infe_stream, &version, nullptr));
+ EXPECT_EQ(version, 3);
+ uint32_t item_id;
+ ASSERT_TRUE(avifROStreamReadU32(&infe_stream, &item_id));
+ EXPECT_EQ(item_id, 1);
+ }
+
+ // 'iref' version 1 and 32-bit item_ID fields in the 'dimg' references.
+ avifROData iref = FindBox(meta_children, "iref");
+ ASSERT_NE(iref.data, nullptr);
+ {
+ avifROStream stream;
+ avifROStreamStart(&stream, &iref, /*diag=*/nullptr,
+ /*diagContext=*/nullptr);
+ uint8_t version;
+ ASSERT_TRUE(avifROStreamReadVersionAndFlags(&stream, &version, nullptr));
+ EXPECT_EQ(version, 1);
+ // The SingleItemTypeReferenceBox child boxes follow the version and flags.
+ avifROData iref_children = {iref.data + 4, iref.size - 4};
+ avifROData dimg = FindBox(iref_children, "dimg");
+ ASSERT_NE(dimg.data, nullptr);
+ avifROStream dimg_stream;
+ avifROStreamStart(&dimg_stream, &dimg, /*diag=*/nullptr,
+ /*diagContext=*/nullptr);
+ uint32_t from_item_id;
+ ASSERT_TRUE(avifROStreamReadU32(&dimg_stream, &from_item_id));
+ EXPECT_EQ(from_item_id, 1);
+ uint16_t reference_count;
+ ASSERT_TRUE(avifROStreamReadU16(&dimg_stream, &reference_count));
+ EXPECT_EQ(reference_count, 32768);
+ }
+
+ // 'ipma' version 1 and a 32-bit item_ID field per entry.
+ const avifROData iprp = FindBox(meta_children, "iprp");
+ ASSERT_NE(iprp.data, nullptr);
+ avifROData ipma = FindBox(iprp, "ipma");
+ ASSERT_NE(ipma.data, nullptr);
+ {
+ avifROStream stream;
+ avifROStreamStart(&stream, &ipma, /*diag=*/nullptr,
+ /*diagContext=*/nullptr);
+ uint8_t version;
+ uint32_t flags;
+ ASSERT_TRUE(avifROStreamReadVersionAndFlags(&stream, &version, &flags));
+ EXPECT_EQ(version, 1);
+ EXPECT_EQ(flags, 0); // The property_index fields are 7-bit wide.
+ uint32_t entry_count;
+ ASSERT_TRUE(avifROStreamReadU32(&stream, &entry_count));
+ EXPECT_EQ(entry_count, kExpectedItemCount);
+ uint32_t item_id;
+ ASSERT_TRUE(avifROStreamReadU32(&stream, &item_id));
+ EXPECT_EQ(item_id, 1);
+ }
+}
+
+TEST(GridApiTest, CellCountExceeding16BitReferenceCount) {
+ // A 256x256 grid contains 65536 cells, which does not fit in the 16-bit
+ // reference_count field of a 'dimg' item reference (ISO/IEC 14496-12
+ // Section 8.11.12), so avifEncoderAddImageGrid() refuses it. Note that the
+ // maximum is a single 256x256 grid; any other combination of grid
+ // dimensions is at most 256x255 = 65280 cells.
+ ImagePtr cell = testutil::CreateImage(
+ /*width=*/64, /*height=*/64, /*depth=*/8, AVIF_PIXEL_FORMAT_YUV400,
+ AVIF_PLANES_ALL);
+ ASSERT_NE(cell, nullptr);
+ const std::vector<avifImage*> cell_image_ptrs(256 * 256, cell.get());
+
+ EncoderPtr encoder(avifEncoderCreate());
+ ASSERT_NE(encoder, nullptr);
+ ASSERT_EQ(avifEncoderAddImageGrid(encoder.get(), /*gridCols=*/256,
+ /*gridRows=*/256, cell_image_ptrs.data(),
+ AVIF_ADD_IMAGE_FLAG_SINGLE),
+ AVIF_RESULT_INVALID_IMAGE_GRID);
+}
+
+//------------------------------------------------------------------------------
+
TEST(GridApiTest, SameMatrixCoefficients) {
ImagePtr cell_0 = testutil::CreateImage(
64, 64, /*depth=*/8, AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL);