Refactor read.c with avifTileInfo

diff --git a/src/read.c b/src/read.c
index d115e23..352eec6 100644
--- a/src/read.c
+++ b/src/read.c
@@ -763,17 +763,22 @@
     return NULL;
 }
 
+// A group of AVIF tiles in an image item, such as a single tile or a grid of multiple tiles.
+typedef struct avifTileInfo
+{
+    unsigned int tileCount;
+    unsigned int decodedTileCount;
+    unsigned int firstTileIndex; // Within avifDecoderData.tiles.
+    avifImageGrid grid;
+} avifTileInfo;
+
 typedef struct avifDecoderData
 {
     avifMeta * meta; // The root-level meta box
     avifTrackArray tracks;
     avifTileArray tiles;
-    unsigned int colorTileCount;
-    unsigned int alphaTileCount;
-    unsigned int decodedColorTileCount;
-    unsigned int decodedAlphaTileCount;
-    avifImageGrid colorGrid;
-    avifImageGrid alphaGrid;
+    avifTileInfo color;
+    avifTileInfo alpha;
     avifDecoderSource source;
     uint8_t majorBrand[4];                     // From the file's ftyp, used by AVIF_DECODER_SOURCE_AUTO
     avifDiagnostics * diag;                    // Shallow copy; owned by avifDecoder
@@ -818,8 +823,8 @@
             tile->codec = NULL;
         }
     }
-    data->decodedColorTileCount = 0;
-    data->decodedAlphaTileCount = 0;
+    data->color.decodedTileCount = 0;
+    data->alpha.decodedTileCount = 0;
 }
 
 static avifTile * avifDecoderDataCreateTile(avifDecoderData * data, uint32_t width, uint32_t height, uint8_t operatingPoint)
@@ -874,10 +879,10 @@
         }
     }
     data->tiles.count = 0;
-    data->colorTileCount = 0;
-    data->alphaTileCount = 0;
-    data->decodedColorTileCount = 0;
-    data->decodedAlphaTileCount = 0;
+    data->color.tileCount = 0;
+    data->color.decodedTileCount = 0;
+    data->alpha.tileCount = 0;
+    data->alpha.decodedTileCount = 0;
 }
 
 static void avifDecoderDataDestroy(avifDecoderData * data)
@@ -1284,21 +1289,20 @@
 // dstImage. Only the freshly decoded tiles are considered, skipping the already
 // copied or not-yet-decoded tiles.
 static avifBool avifDecoderDataFillImageGrid(avifDecoderData * data,
-                                             avifImageGrid * grid,
                                              avifImage * dstImage,
-                                             unsigned int firstTileIndex,
+                                             const avifTileInfo * info,
                                              unsigned int oldDecodedTileCount,
-                                             unsigned int decodedTileCount,
                                              avifBool alpha)
 {
-    assert(decodedTileCount > oldDecodedTileCount);
+    assert(info->decodedTileCount > oldDecodedTileCount);
+    const avifImageGrid * grid = &info->grid;
 
-    avifTile * firstTile = &data->tiles.tile[firstTileIndex];
+    avifTile * firstTile = &data->tiles.tile[info->firstTileIndex];
     avifBool firstTileUVPresent = (firstTile->image->yuvPlanes[AVIF_CHAN_U] && firstTile->image->yuvPlanes[AVIF_CHAN_V]);
 
     // Check for tile consistency: All tiles in a grid image should match in the properties checked below.
-    for (unsigned int i = AVIF_MAX(1, oldDecodedTileCount); i < decodedTileCount; ++i) {
-        avifTile * tile = &data->tiles.tile[firstTileIndex + i];
+    for (unsigned int i = AVIF_MAX(1, oldDecodedTileCount); i < info->decodedTileCount; ++i) {
+        avifTile * tile = &data->tiles.tile[info->firstTileIndex + i];
         avifBool uvPresent = (tile->image->yuvPlanes[AVIF_CHAN_U] && tile->image->yuvPlanes[AVIF_CHAN_V]);
         if ((tile->image->width != firstTile->image->width) || (tile->image->height != firstTile->image->height) ||
             (tile->image->depth != firstTile->image->depth) || (tile->image->yuvFormat != firstTile->image->yuvFormat) ||
@@ -1385,11 +1389,11 @@
     avifImageSetDefaults(&dstView);
     for (; rowIndex < grid->rows; ++rowIndex, colIndex = 0) {
         for (; colIndex < grid->columns; ++colIndex, ++tileIndex) {
-            if (tileIndex >= decodedTileCount) {
+            if (tileIndex >= info->decodedTileCount) {
                 // Tile is not ready yet.
                 return AVIF_TRUE;
             }
-            avifTile * tile = &data->tiles.tile[firstTileIndex + tileIndex];
+            avifTile * tile = &data->tiles.tile[info->firstTileIndex + tileIndex];
 
             avifCropRect dstViewRect = {
                 firstTile->image->width * colIndex, firstTile->image->height * rowIndex, firstTile->image->width, firstTile->image->height
@@ -3390,6 +3394,29 @@
     return NULL;
 }
 
+static avifResult avifDecoderGenerateImageTiles(avifDecoder * decoder, avifTileInfo * info, avifDecoderItem * item, avifBool alpha)
+{
+    const uint32_t previousTileCount = decoder->data->tiles.count;
+    if ((info->grid.rows > 0) && (info->grid.columns > 0)) {
+        AVIF_CHECKERR(avifDecoderGenerateImageGridTiles(decoder, &info->grid, item, alpha), AVIF_RESULT_INVALID_IMAGE_GRID);
+    } else {
+        AVIF_CHECKERR(item->size != 0, AVIF_RESULT_NO_AV1_ITEMS_FOUND);
+
+        avifTile * tile = avifDecoderDataCreateTile(decoder->data, item->width, item->height, avifDecoderItemOperatingPoint(item));
+        AVIF_CHECKERR(tile, AVIF_RESULT_OUT_OF_MEMORY);
+        AVIF_CHECKERR(avifCodecDecodeInputFillFromDecoderItem(tile->input,
+                                                              item,
+                                                              decoder->allowProgressive,
+                                                              decoder->imageCountLimit,
+                                                              decoder->io->sizeHint,
+                                                              &decoder->diag),
+                      AVIF_RESULT_BMFF_PARSE_FAILED);
+        tile->input->alpha = alpha;
+    }
+    info->tileCount = decoder->data->tiles.count - previousTileCount;
+    return AVIF_RESULT_OK;
+}
+
 avifResult avifDecoderReset(avifDecoder * decoder)
 {
     avifDiagnosticsClearError(&decoder->diag);
@@ -3400,8 +3427,8 @@
         return AVIF_RESULT_OK;
     }
 
-    memset(&data->colorGrid, 0, sizeof(data->colorGrid));
-    memset(&data->alphaGrid, 0, sizeof(data->alphaGrid));
+    memset(&data->color.grid, 0, sizeof(data->color.grid));
+    memset(&data->alpha.grid, 0, sizeof(data->alpha.grid));
     avifDecoderDataClearTiles(data);
 
     // Prepare / cleanup decoded image state
@@ -3518,7 +3545,7 @@
                                                      data->diag)) {
             return AVIF_RESULT_BMFF_PARSE_FAILED;
         }
-        data->colorTileCount = 1;
+        data->color.tileCount = 1;
 
         if (alphaTrack) {
             avifTile * alphaTile = avifDecoderDataCreateTile(data, alphaTrack->width, alphaTrack->height, 0); // No way to set operating point via tracks
@@ -3533,7 +3560,7 @@
                 return AVIF_RESULT_BMFF_PARSE_FAILED;
             }
             alphaTile->input->alpha = AVIF_TRUE;
-            data->alphaTileCount = 1;
+            data->alpha.tileCount = 1;
         }
 
         // Stash off sample table for future timing information
@@ -3541,7 +3568,7 @@
 
         // Image sequence timing
         decoder->imageIndex = -1;
-        decoder->imageCount = colorTile->input->samples.count;
+        decoder->imageCount = (int)colorTile->input->samples.count;
         decoder->timescale = colorTrack->mediaTimescale;
         decoder->durationInTimescales = colorTrack->mediaDuration;
         if (colorTrack->mediaTimescale) {
@@ -3577,7 +3604,7 @@
         if (!memcmp(colorItem->type, "grid", 4)) {
             avifROData readData;
             AVIF_CHECKRES(avifDecoderItemRead(colorItem, decoder->io, &readData, 0, 0, data->diag));
-            AVIF_CHECKERR(avifParseImageGridBox(&data->colorGrid,
+            AVIF_CHECKERR(avifParseImageGridBox(&data->color.grid,
                                                 readData.data,
                                                 readData.size,
                                                 decoder->imageSizeLimit,
@@ -3590,7 +3617,7 @@
         if (alphaItem && !memcmp(alphaItem->type, "grid", 4)) {
             avifROData readData;
             AVIF_CHECKRES(avifDecoderItemRead(alphaItem, decoder->io, &readData, 0, 0, data->diag));
-            AVIF_CHECKERR(avifParseImageGridBox(&data->alphaGrid,
+            AVIF_CHECKERR(avifParseImageGridBox(&data->alpha.grid,
                                                 readData.data,
                                                 readData.size,
                                                 decoder->imageSizeLimit,
@@ -3600,10 +3627,7 @@
         }
 
         // Find Exif and/or XMP metadata, if any
-        avifResult findResult = avifDecoderFindMetadata(decoder, data->meta, decoder->image, colorItem->id);
-        if (findResult != AVIF_RESULT_OK) {
-            return findResult;
-        }
+        AVIF_CHECKRES(avifDecoderFindMetadata(decoder, data->meta, decoder->image, colorItem->id));
 
         // Set all counts and timing to safe-but-uninteresting values
         decoder->imageIndex = -1;
@@ -3617,36 +3641,14 @@
         decoder->duration = 1;
         decoder->durationInTimescales = 1;
 
-        if ((data->colorGrid.rows > 0) && (data->colorGrid.columns > 0)) {
-            if (!avifDecoderGenerateImageGridTiles(decoder, &data->colorGrid, colorItem, AVIF_FALSE)) {
-                return AVIF_RESULT_INVALID_IMAGE_GRID;
-            }
-            data->colorTileCount = data->tiles.count;
-        } else {
-            if (colorItem->size == 0) {
-                return AVIF_RESULT_NO_AV1_ITEMS_FOUND;
-            }
-
-            avifTile * colorTile =
-                avifDecoderDataCreateTile(data, colorItem->width, colorItem->height, avifDecoderItemOperatingPoint(colorItem));
-            if (!colorTile) {
-                return AVIF_RESULT_OUT_OF_MEMORY;
-            }
-            if (!avifCodecDecodeInputFillFromDecoderItem(colorTile->input,
-                                                         colorItem,
-                                                         decoder->allowProgressive,
-                                                         decoder->imageCountLimit,
-                                                         decoder->io->sizeHint,
-                                                         &decoder->diag)) {
-                return AVIF_RESULT_BMFF_PARSE_FAILED;
-            }
-            data->colorTileCount = 1;
-
+        AVIF_CHECKRES(avifDecoderGenerateImageTiles(decoder, &data->color, colorItem, /*alpha=*/AVIF_FALSE));
+        if ((data->color.grid.rows == 0) || (data->color.grid.columns == 0)) {
             if (colorItem->progressive) {
                 decoder->progressiveState = AVIF_PROGRESSIVE_STATE_AVAILABLE;
+                const avifTile * colorTile = &data->tiles.tile[0];
                 if (colorTile->input->samples.count > 1) {
                     decoder->progressiveState = AVIF_PROGRESSIVE_STATE_ACTIVE;
-                    decoder->imageCount = colorTile->input->samples.count;
+                    decoder->imageCount = (int)colorTile->input->samples.count;
                 }
             }
         }
@@ -3658,33 +3660,7 @@
                 alphaItem->width = colorItem->width;
                 alphaItem->height = colorItem->height;
             }
-
-            if ((data->alphaGrid.rows > 0) && (data->alphaGrid.columns > 0)) {
-                if (!avifDecoderGenerateImageGridTiles(decoder, &data->alphaGrid, alphaItem, AVIF_TRUE)) {
-                    return AVIF_RESULT_INVALID_IMAGE_GRID;
-                }
-                data->alphaTileCount = data->tiles.count - data->colorTileCount;
-            } else {
-                if (alphaItem->size == 0) {
-                    return AVIF_RESULT_NO_AV1_ITEMS_FOUND;
-                }
-
-                avifTile * alphaTile =
-                    avifDecoderDataCreateTile(data, alphaItem->width, alphaItem->height, avifDecoderItemOperatingPoint(alphaItem));
-                if (!alphaTile) {
-                    return AVIF_RESULT_OUT_OF_MEMORY;
-                }
-                if (!avifCodecDecodeInputFillFromDecoderItem(alphaTile->input,
-                                                             alphaItem,
-                                                             decoder->allowProgressive,
-                                                             decoder->imageCountLimit,
-                                                             decoder->io->sizeHint,
-                                                             &decoder->diag)) {
-                    return AVIF_RESULT_BMFF_PARSE_FAILED;
-                }
-                alphaTile->input->alpha = AVIF_TRUE;
-                data->alphaTileCount = 1;
-            }
+            AVIF_CHECKRES(avifDecoderGenerateImageTiles(decoder, &data->alpha, alphaItem, /*alpha=*/AVIF_TRUE));
         }
 
         decoder->image->width = colorItem->width;
@@ -3692,18 +3668,15 @@
         decoder->alphaPresent = (alphaItem != NULL);
         decoder->image->alphaPremultiplied = decoder->alphaPresent && (colorItem->premByID == alphaItem->id);
 
-        avifResult colorItemValidationResult = avifDecoderItemValidateAV1(colorItem, &decoder->diag, decoder->strictFlags);
-        if (colorItemValidationResult != AVIF_RESULT_OK) {
-            return colorItemValidationResult;
-        }
+        AVIF_CHECKRES(avifDecoderItemValidateAV1(colorItem, &decoder->diag, decoder->strictFlags));
         if (alphaItem) {
-            avifResult alphaItemValidationResult = avifDecoderItemValidateAV1(alphaItem, &decoder->diag, decoder->strictFlags);
-            if (alphaItemValidationResult != AVIF_RESULT_OK) {
-                return alphaItemValidationResult;
-            }
+            AVIF_CHECKRES(avifDecoderItemValidateAV1(alphaItem, &decoder->diag, decoder->strictFlags));
         }
     }
 
+    decoder->data->color.firstTileIndex = 0;
+    decoder->data->alpha.firstTileIndex = decoder->data->color.tileCount;
+
     // Sanity check tiles
     for (uint32_t tileIndex = 0; tileIndex < data->tiles.count; ++tileIndex) {
         avifTile * tile = &data->tiles.tile[tileIndex];
@@ -3841,14 +3814,10 @@
     return avifDecoderFlush(decoder);
 }
 
-static avifResult avifDecoderPrepareTiles(avifDecoder * decoder,
-                                          uint32_t nextImageIndex,
-                                          unsigned int firstTileIndex,
-                                          unsigned int tileCount,
-                                          unsigned int decodedTileCount)
+static avifResult avifDecoderPrepareTiles(avifDecoder * decoder, uint32_t nextImageIndex, const avifTileInfo * info)
 {
-    for (unsigned int tileIndex = decodedTileCount; tileIndex < tileCount; ++tileIndex) {
-        avifTile * tile = &decoder->data->tiles.tile[firstTileIndex + tileIndex];
+    for (unsigned int tileIndex = info->decodedTileCount; tileIndex < info->tileCount; ++tileIndex) {
+        avifTile * tile = &decoder->data->tiles.tile[info->firstTileIndex + tileIndex];
 
         // Ensure there's an AV1 codec available before doing anything else
         if (!tile->codec) {
@@ -3910,15 +3879,11 @@
     return AVIF_RESULT_OK;
 }
 
-static avifResult avifDecoderDecodeTiles(avifDecoder * decoder,
-                                         uint32_t nextImageIndex,
-                                         unsigned int firstTileIndex,
-                                         unsigned int tileCount,
-                                         unsigned int * decodedTileCount)
+static avifResult avifDecoderDecodeTiles(avifDecoder * decoder, uint32_t nextImageIndex, avifTileInfo * info)
 {
-    const unsigned int oldDecodedTileCount = *decodedTileCount;
-    for (unsigned int tileIndex = oldDecodedTileCount; tileIndex < tileCount; ++tileIndex) {
-        avifTile * tile = &decoder->data->tiles.tile[firstTileIndex + tileIndex];
+    const unsigned int oldDecodedTileCount = info->decodedTileCount;
+    for (unsigned int tileIndex = oldDecodedTileCount; tileIndex < info->tileCount; ++tileIndex) {
+        avifTile * tile = &decoder->data->tiles.tile[info->firstTileIndex + tileIndex];
 
         const avifDecodeSample * sample = &tile->input->samples.sample[nextImageIndex];
         if (sample->data.size < sample->size) {
@@ -3958,11 +3923,56 @@
             }
         }
 
-        ++*decodedTileCount;
+        ++info->decodedTileCount;
     }
     return AVIF_RESULT_OK;
 }
 
+avifResult avifDecoderOutputDecodedTiles(avifDecoder * decoder, const avifTileInfo * info, avifBool alpha, unsigned int oldDecodedTileCount)
+{
+    if (info->decodedTileCount == oldDecodedTileCount) {
+        // Nothing changed.
+        return AVIF_RESULT_OK;
+    }
+    // There is at least one newly decoded tile.
+    assert(info->decodedTileCount > oldDecodedTileCount);
+
+    // Grid path.
+    if ((info->grid.rows > 0) && (info->grid.columns > 0)) {
+        assert(info->tileCount == (info->grid.rows * info->grid.columns));
+        AVIF_CHECKERR(avifDecoderDataFillImageGrid(decoder->data, decoder->image, info, oldDecodedTileCount, alpha),
+                      AVIF_RESULT_INVALID_IMAGE_GRID);
+        return AVIF_RESULT_OK;
+    }
+
+    // Normal (most common) non-grid path. Just steal the planes from the only "tile".
+    assert(info->tileCount == 1);
+    avifImage * src = decoder->data->tiles.tile[info->firstTileIndex].image;
+    if ((decoder->image->width != src->width) || (decoder->image->height != src->height) || (decoder->image->depth != src->depth)) {
+        if (alpha) {
+            avifDiagnosticsPrintf(&decoder->diag,
+                                  "The color image item does not match the alpha image item in width, height, or bit depth");
+            return AVIF_RESULT_DECODE_ALPHA_FAILED;
+        }
+        avifImageFreePlanes(decoder->image, AVIF_PLANES_ALL);
+
+        decoder->image->width = src->width;
+        decoder->image->height = src->height;
+        decoder->image->depth = src->depth;
+    }
+#if 0
+    // This code is currently unnecessary as the CICP is always set by the end of avifDecoderParse().
+    if (!alpha && !decoder->data->cicpSet) {
+        decoder->data->cicpSet = AVIF_TRUE;
+        decoder->image->colorPrimaries = src->colorPrimaries;
+        decoder->image->transferCharacteristics = src->transferCharacteristics;
+        decoder->image->matrixCoefficients = src->matrixCoefficients;
+    }
+#endif
+    avifImageStealPlanes(decoder->image, src, alpha ? AVIF_PLANES_A : AVIF_PLANES_YUV);
+    return AVIF_RESULT_OK;
+}
+
 avifResult avifDecoderNextImage(avifDecoder * decoder)
 {
     avifDiagnosticsClearError(&decoder->diag);
@@ -3976,128 +3986,48 @@
         return AVIF_RESULT_IO_NOT_SET;
     }
 
-    if ((decoder->data->decodedColorTileCount == decoder->data->colorTileCount) &&
-        (decoder->data->decodedAlphaTileCount == decoder->data->alphaTileCount)) {
+    if ((decoder->data->color.decodedTileCount == decoder->data->color.tileCount) &&
+        (decoder->data->alpha.decodedTileCount == decoder->data->alpha.tileCount)) {
         // A frame was decoded during the last avifDecoderNextImage() call.
-        decoder->data->decodedColorTileCount = 0;
-        decoder->data->decodedAlphaTileCount = 0;
+        decoder->data->color.decodedTileCount = 0;
+        decoder->data->alpha.decodedTileCount = 0;
     }
 
-    assert(decoder->data->tiles.count == (decoder->data->colorTileCount + decoder->data->alphaTileCount));
+    assert(decoder->data->tiles.count == (decoder->data->color.tileCount + decoder->data->alpha.tileCount));
     const uint32_t nextImageIndex = (uint32_t)(decoder->imageIndex + 1);
-    const unsigned int firstColorTileIndex = 0;
-    const unsigned int firstAlphaTileIndex = decoder->data->colorTileCount;
 
     // Acquire all sample data for the current image first, allowing for any read call to bail out
     // with AVIF_RESULT_WAITING_ON_IO harmlessly / idempotently, unless decoder->allowIncremental.
     // Start with color tiles.
-    const avifResult prepareColorTileResult =
-        avifDecoderPrepareTiles(decoder, nextImageIndex, firstColorTileIndex, decoder->data->colorTileCount, decoder->data->decodedColorTileCount);
-    if ((prepareColorTileResult != AVIF_RESULT_OK) &&
-        (!decoder->allowIncremental || (prepareColorTileResult != AVIF_RESULT_WAITING_ON_IO))) {
-        return prepareColorTileResult;
+    const avifResult prepareColorTileResult = avifDecoderPrepareTiles(decoder, nextImageIndex, &decoder->data->color);
+    if (!decoder->allowIncremental || (prepareColorTileResult != AVIF_RESULT_WAITING_ON_IO)) {
+        AVIF_CHECKRES(prepareColorTileResult);
     }
     // Do the same with alpha tiles. They are handled separately because their
     // order of appearance relative to the color tiles in the bitstream is left
     // to the encoder's choice, and decoding as many as possible of each
     // category in parallel is beneficial for incremental decoding, as pixel
     // rows need all channels to be decoded before being accessible to the user.
-    const avifResult prepareAlphaTileResult =
-        avifDecoderPrepareTiles(decoder, nextImageIndex, firstAlphaTileIndex, decoder->data->alphaTileCount, decoder->data->decodedAlphaTileCount);
-    if ((prepareAlphaTileResult != AVIF_RESULT_OK) &&
-        (!decoder->allowIncremental || (prepareAlphaTileResult != AVIF_RESULT_WAITING_ON_IO))) {
-        return prepareAlphaTileResult;
+    const avifResult prepareAlphaTileResult = avifDecoderPrepareTiles(decoder, nextImageIndex, &decoder->data->alpha);
+    if (!decoder->allowIncremental || (prepareAlphaTileResult != AVIF_RESULT_WAITING_ON_IO)) {
+        AVIF_CHECKRES(prepareAlphaTileResult);
     }
 
     // Decode all available color tiles now, then all available alpha tiles.
-    const unsigned int oldDecodedColorTileCount = decoder->data->decodedColorTileCount;
-    const avifResult decodeColorTileResult =
-        avifDecoderDecodeTiles(decoder, nextImageIndex, firstColorTileIndex, decoder->data->colorTileCount, &decoder->data->decodedColorTileCount);
-    if (decodeColorTileResult != AVIF_RESULT_OK) {
-        return decodeColorTileResult;
-    }
-    const unsigned int oldDecodedAlphaTileCount = decoder->data->decodedAlphaTileCount;
-    const avifResult decodeAlphaTileResult =
-        avifDecoderDecodeTiles(decoder, nextImageIndex, firstAlphaTileIndex, decoder->data->alphaTileCount, &decoder->data->decodedAlphaTileCount);
-    if (decodeAlphaTileResult != AVIF_RESULT_OK) {
-        return decodeAlphaTileResult;
-    }
+    const unsigned int oldDecodedColorTileCount = decoder->data->color.decodedTileCount;
+    const unsigned int oldDecodedAlphaTileCount = decoder->data->alpha.decodedTileCount;
+    AVIF_CHECKRES(avifDecoderDecodeTiles(decoder, nextImageIndex, &decoder->data->color));
+    AVIF_CHECKRES(avifDecoderDecodeTiles(decoder, nextImageIndex, &decoder->data->alpha));
 
-    if (decoder->data->decodedColorTileCount > oldDecodedColorTileCount) {
-        // There is at least one newly decoded color tile.
-        if ((decoder->data->colorGrid.rows > 0) && (decoder->data->colorGrid.columns > 0)) {
-            assert(decoder->data->colorTileCount == (decoder->data->colorGrid.rows * decoder->data->colorGrid.columns));
-            if (!avifDecoderDataFillImageGrid(decoder->data,
-                                              &decoder->data->colorGrid,
-                                              decoder->image,
-                                              firstColorTileIndex,
-                                              oldDecodedColorTileCount,
-                                              decoder->data->decodedColorTileCount,
-                                              AVIF_FALSE)) {
-                return AVIF_RESULT_INVALID_IMAGE_GRID;
-            }
-        } else {
-            // Normal (most common) non-grid path. Just steal the planes from the only "tile".
-            assert(decoder->data->colorTileCount == 1);
-            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);
+    AVIF_CHECKRES(avifDecoderOutputDecodedTiles(decoder, &decoder->data->color, /*alpha=*/AVIF_FALSE, oldDecodedColorTileCount));
+    AVIF_CHECKRES(avifDecoderOutputDecodedTiles(decoder, &decoder->data->alpha, /*alpha=*/AVIF_TRUE, oldDecodedAlphaTileCount));
 
-                decoder->image->width = srcColor->width;
-                decoder->image->height = srcColor->height;
-                decoder->image->depth = srcColor->depth;
-            }
-
-#if 0
-            // This code is currently unnecessary as the CICP is always set by the end of avifDecoderParse().
-            if (!decoder->data->cicpSet) {
-                decoder->data->cicpSet = AVIF_TRUE;
-                decoder->image->colorPrimaries = srcColor->colorPrimaries;
-                decoder->image->transferCharacteristics = srcColor->transferCharacteristics;
-                decoder->image->matrixCoefficients = srcColor->matrixCoefficients;
-            }
-#endif
-
-            avifImageStealPlanes(decoder->image, srcColor, AVIF_PLANES_YUV);
-        }
-    }
-
-    if (decoder->data->decodedAlphaTileCount > oldDecodedAlphaTileCount) {
-        // There is at least one newly decoded alpha tile.
-        if ((decoder->data->alphaGrid.rows > 0) && (decoder->data->alphaGrid.columns > 0)) {
-            assert(decoder->data->alphaTileCount == (decoder->data->alphaGrid.rows * decoder->data->alphaGrid.columns));
-            if (!avifDecoderDataFillImageGrid(decoder->data,
-                                              &decoder->data->alphaGrid,
-                                              decoder->image,
-                                              firstAlphaTileIndex,
-                                              oldDecodedAlphaTileCount,
-                                              decoder->data->decodedAlphaTileCount,
-                                              AVIF_TRUE)) {
-                return AVIF_RESULT_INVALID_IMAGE_GRID;
-            }
-        } else {
-            // Normal (most common) non-grid path. Just steal the planes from the only "tile".
-            assert(decoder->data->alphaTileCount == 1);
-            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)) {
-                avifDiagnosticsPrintf(&decoder->diag, "decoder->image does not match srcAlpha in width, height, or bit depth");
-                return AVIF_RESULT_DECODE_ALPHA_FAILED;
-            }
-
-            avifImageStealPlanes(decoder->image, srcAlpha, AVIF_PLANES_A);
-        }
-    }
-
-    if ((decoder->data->decodedColorTileCount != decoder->data->colorTileCount) ||
-        (decoder->data->decodedAlphaTileCount != decoder->data->alphaTileCount)) {
+    if ((decoder->data->color.decodedTileCount != decoder->data->color.tileCount) ||
+        (decoder->data->alpha.decodedTileCount != decoder->data->alpha.tileCount)) {
         assert(decoder->allowIncremental);
         // The image is not completely decoded. There should be no error unrelated to missing bytes,
         // and at least some missing bytes.
-        assert((prepareColorTileResult == AVIF_RESULT_OK) || (prepareColorTileResult == AVIF_RESULT_WAITING_ON_IO));
-        assert((prepareAlphaTileResult == AVIF_RESULT_OK) || (prepareAlphaTileResult == AVIF_RESULT_WAITING_ON_IO));
-        assert((prepareColorTileResult != AVIF_RESULT_OK) || (prepareAlphaTileResult != AVIF_RESULT_OK));
+        assert((prepareColorTileResult == AVIF_RESULT_WAITING_ON_IO) || (prepareAlphaTileResult == AVIF_RESULT_WAITING_ON_IO));
         // Return the "not enough bytes" status now instead of moving on to the next frame.
         return AVIF_RESULT_WAITING_ON_IO;
     }
@@ -4106,7 +4036,7 @@
     // Only advance decoder->imageIndex once the image is completely decoded, so that
     // avifDecoderNthImage(decoder, decoder->imageIndex + 1) is equivalent to avifDecoderNextImage(decoder)
     // if the previous call to avifDecoderNextImage() returned AVIF_RESULT_WAITING_ON_IO.
-    decoder->imageIndex = nextImageIndex;
+    decoder->imageIndex = (int)nextImageIndex;
     // The decoded tile counts will be reset to 0 the next time avifDecoderNextImage() is called,
     // for avifDecoderDecodedRowCount() to work until then.
     if (decoder->data->sourceSampleTable) {
@@ -4177,8 +4107,8 @@
     }
 
     if (requestedIndex == decoder->imageIndex) {
-        if ((decoder->data->decodedColorTileCount == decoder->data->colorTileCount) &&
-            (decoder->data->decodedAlphaTileCount == decoder->data->alphaTileCount)) {
+        if ((decoder->data->color.decodedTileCount == decoder->data->color.tileCount) &&
+            (decoder->data->alpha.decodedTileCount == decoder->data->alpha.tileCount)) {
             // The current fully decoded image (decoder->imageIndex) is requested, nothing to do
             return AVIF_RESULT_OK;
         }
@@ -4242,23 +4172,19 @@
 }
 
 // Returns the number of available rows in decoder->image given a color or alpha subimage.
-static uint32_t avifGetDecodedRowCount(const avifDecoder * decoder,
-                                       const avifImageGrid * grid,
-                                       unsigned int firstTileIndex,
-                                       unsigned int tileCount,
-                                       unsigned int decodedTileCount)
+static uint32_t avifGetDecodedRowCount(const avifDecoder * decoder, const avifTileInfo * info)
 {
-    if (decodedTileCount == tileCount) {
+    if (info->decodedTileCount == info->tileCount) {
         return decoder->image->height;
     }
-    if (decodedTileCount == 0) {
+    if (info->decodedTileCount == 0) {
         return 0;
     }
 
-    if ((grid->rows > 0) && (grid->columns > 0)) {
+    if ((info->grid.rows > 0) && (info->grid.columns > 0)) {
         // Grid of AVIF tiles (not to be confused with AV1 tiles).
-        const uint32_t tileHeight = decoder->data->tiles.tile[firstTileIndex].height;
-        return AVIF_MIN((decodedTileCount / grid->columns) * tileHeight, decoder->image->height);
+        const uint32_t tileHeight = decoder->data->tiles.tile[info->firstTileIndex].height;
+        return AVIF_MIN((info->decodedTileCount / info->grid.columns) * tileHeight, decoder->image->height);
     } else {
         // Non-grid image.
         return decoder->image->height;
@@ -4267,16 +4193,11 @@
 
 uint32_t avifDecoderDecodedRowCount(const avifDecoder * decoder)
 {
-    const uint32_t colorRowCount = avifGetDecodedRowCount(decoder,
-                                                          &decoder->data->colorGrid,
-                                                          /*firstTileIndex=*/0,
-                                                          decoder->data->colorTileCount,
-                                                          decoder->data->decodedColorTileCount);
-    const uint32_t alphaRowCount = avifGetDecodedRowCount(decoder,
-                                                          &decoder->data->alphaGrid,
-                                                          /*firstTileIndex=*/decoder->data->colorTileCount,
-                                                          decoder->data->alphaTileCount,
-                                                          decoder->data->decodedAlphaTileCount);
+    const uint32_t colorRowCount = avifGetDecodedRowCount(decoder, &decoder->data->color);
+    if (colorRowCount == 0) {
+        return 0;
+    }
+    const uint32_t alphaRowCount = avifGetDecodedRowCount(decoder, &decoder->data->alpha);
     return AVIF_MIN(colorRowCount, alphaRowCount);
 }