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);