Parse the gain map version before parsing the gain map item. (#2800)

Also ignore the gain map if it points to an unsupported item.

These changes make sure unsupported gain maps are ignored instead of
causing errors.
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 5c8fa1f..1620cec 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -808,6 +808,9 @@
 // ---------------------------------------------------------------------------
 // gain maps
 
+// Initializes avifGainMap to default values.
+void avifGainMapSetDefaults(avifGainMap * gainMap);
+
 // Finds the approximate min/max values from the given gain map values, excluding outliers.
 // Uses a histogram, with outliers defined as having at least one empty bucket between them
 // and the rest of the distribution. Discards at most 0.1% of values.
diff --git a/src/avif.c b/src/avif.c
index 984ca1a..a87710b 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -1310,6 +1310,17 @@
     if (!gainMap) {
         return NULL;
     }
+    avifGainMapSetDefaults(gainMap);
+    // Note that some functions like avifDecoderFindGainMapItem() allocate avifGainMap directly on
+    // the stack instead of calling avifGainMapCreate() to simplify error handling. This works under
+    // the assumption that no complex initialization (such as dynamic allocation of fields) takes
+    // place here. If this function becomes more complex than one alloc + setDefaults, such code
+    // might need to be changed.
+    return gainMap;
+}
+
+void avifGainMapSetDefaults(avifGainMap * gainMap)
+{
     memset(gainMap, 0, sizeof(avifGainMap));
     gainMap->altColorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
     gainMap->altTransferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
@@ -1327,7 +1338,6 @@
     }
     gainMap->baseHdrHeadroom.d = 1;
     gainMap->alternateHdrHeadroom.d = 1;
-    return gainMap;
 }
 
 void avifGainMapDestroy(avifGainMap * gainMap)
diff --git a/src/gainmap.c b/src/gainmap.c
index e02087b..b8585bf 100644
--- a/src/gainmap.c
+++ b/src/gainmap.c
@@ -7,7 +7,7 @@
 #include <math.h>
 #include <string.h>
 
-static void avifGainMapSetDefaults(avifGainMap * gainMap)
+static void avifGainMapSetEncodingDefaults(avifGainMap * gainMap)
 {
     for (int i = 0; i < 3; ++i) {
         gainMap->gainMapMin[i] = (avifSignedFraction) { 1, 1 };
@@ -569,7 +569,7 @@
         }
     }
 
-    avifGainMapSetDefaults(gainMap);
+    avifGainMapSetEncodingDefaults(gainMap);
     gainMap->useBaseColorSpace = (gainMapMathPrimaries == baseColorPrimaries);
 
     float (*baseGammaToLinear)(float) = avifTransferCharacteristicsGetGammaToLinearFunction(baseTransferCharacteristics);
diff --git a/src/read.c b/src/read.c
index 07c24c5..c87ee18 100644
--- a/src/read.c
+++ b/src/read.c
@@ -2098,53 +2098,56 @@
     return AVIF_RESULT_OK;
 }
 
-static avifBool avifParseImageGridBox(avifImageGrid * grid,
-                                      const uint8_t * raw,
-                                      size_t rawLen,
-                                      uint32_t imageSizeLimit,
-                                      uint32_t imageDimensionLimit,
-                                      avifDiagnostics * diag)
+static avifResult avifParseImageGridBox(avifImageGrid * grid,
+                                        const uint8_t * raw,
+                                        size_t rawLen,
+                                        uint32_t imageSizeLimit,
+                                        uint32_t imageDimensionLimit,
+                                        avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen, diag, "Box[grid]");
 
     uint8_t version, flags;
-    AVIF_CHECK(avifROStreamRead(&s, &version, 1)); // unsigned int(8) version = 0;
+    AVIF_CHECKERR(avifROStreamRead(&s, &version, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) version = 0;
     if (version != 0) {
         avifDiagnosticsPrintf(diag, "Box[grid] has unsupported version [%u]", version);
-        return AVIF_FALSE;
+        return AVIF_RESULT_NOT_IMPLEMENTED;
     }
     uint8_t rowsMinusOne, columnsMinusOne;
-    AVIF_CHECK(avifROStreamRead(&s, &flags, 1));           // unsigned int(8) flags;
-    AVIF_CHECK(avifROStreamRead(&s, &rowsMinusOne, 1));    // unsigned int(8) rows_minus_one;
-    AVIF_CHECK(avifROStreamRead(&s, &columnsMinusOne, 1)); // unsigned int(8) columns_minus_one;
+    AVIF_CHECKERR(avifROStreamRead(&s, &flags, 1), AVIF_RESULT_BMFF_PARSE_FAILED);           // unsigned int(8) flags;
+    AVIF_CHECKERR(avifROStreamRead(&s, &rowsMinusOne, 1), AVIF_RESULT_BMFF_PARSE_FAILED);    // unsigned int(8) rows_minus_one;
+    AVIF_CHECKERR(avifROStreamRead(&s, &columnsMinusOne, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) columns_minus_one;
     grid->rows = (uint32_t)rowsMinusOne + 1;
     grid->columns = (uint32_t)columnsMinusOne + 1;
 
     uint32_t fieldLength = ((flags & 1) + 1) * 16;
     if (fieldLength == 16) {
         uint16_t outputWidth16, outputHeight16;
-        AVIF_CHECK(avifROStreamReadU16(&s, &outputWidth16));  // unsigned int(FieldLength) output_width;
-        AVIF_CHECK(avifROStreamReadU16(&s, &outputHeight16)); // unsigned int(FieldLength) output_height;
+        AVIF_CHECKERR(avifROStreamReadU16(&s, &outputWidth16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(FieldLength) output_width;
+        AVIF_CHECKERR(avifROStreamReadU16(&s, &outputHeight16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(FieldLength) output_height;
         grid->outputWidth = outputWidth16;
         grid->outputHeight = outputHeight16;
     } else {
         if (fieldLength != 32) {
             // This should be impossible
             avifDiagnosticsPrintf(diag, "Grid box contains illegal field length: [%u]", fieldLength);
-            return AVIF_FALSE;
+            return AVIF_RESULT_INVALID_IMAGE_GRID;
         }
-        AVIF_CHECK(avifROStreamReadU32(&s, &grid->outputWidth));  // unsigned int(FieldLength) output_width;
-        AVIF_CHECK(avifROStreamReadU32(&s, &grid->outputHeight)); // unsigned int(FieldLength) output_height;
+        AVIF_CHECKERR(avifROStreamReadU32(&s, &grid->outputWidth), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(FieldLength) output_width;
+        AVIF_CHECKERR(avifROStreamReadU32(&s, &grid->outputHeight), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(FieldLength) output_height;
     }
     if ((grid->outputWidth == 0) || (grid->outputHeight == 0)) {
         avifDiagnosticsPrintf(diag, "Grid box contains illegal dimensions: [%u x %u]", grid->outputWidth, grid->outputHeight);
-        return AVIF_FALSE;
+        return AVIF_RESULT_INVALID_IMAGE_GRID;
     }
     if (avifDimensionsTooLarge(grid->outputWidth, grid->outputHeight, imageSizeLimit, imageDimensionLimit)) {
         avifDiagnosticsPrintf(diag, "Grid box dimensions are too large: [%u x %u]", grid->outputWidth, grid->outputHeight);
-        return AVIF_FALSE;
+        return AVIF_RESULT_NOT_IMPLEMENTED;
     }
-    return avifROStreamRemainingBytes(&s) == 0;
+    if (avifROStreamRemainingBytes(&s) != 0) {
+        return AVIF_RESULT_BMFF_PARSE_FAILED;
+    }
+    return AVIF_RESULT_OK;
 }
 
 static avifBool avifParseGainMapMetadata(avifGainMap * gainMap, avifROStream * s)
@@ -2354,13 +2357,12 @@
         if (isItemInInput) {
             avifROData readData;
             AVIF_CHECKRES(avifDecoderItemRead(item, decoder->io, &readData, 0, 0, decoder->data->diag));
-            AVIF_CHECKERR(avifParseImageGridBox(grid,
+            AVIF_CHECKRES(avifParseImageGridBox(grid,
                                                 readData.data,
                                                 readData.size,
                                                 decoder->imageSizeLimit,
                                                 decoder->imageDimensionLimit,
-                                                decoder->data->diag),
-                          AVIF_RESULT_INVALID_IMAGE_GRID);
+                                                decoder->data->diag));
             // Validate that there are exactly the same number of dimg items to form the grid.
             uint32_t dimgItemCount = 0;
             for (uint32_t i = 0; i < item->meta->items.count; ++i) {
@@ -5638,27 +5640,26 @@
 
 // Finds a 'tmap' (tone mapped image item) box associated with the given 'colorItem',
 // then finds the associated gain map image.
-// If found, fills 'toneMappedImageItem', 'gainMapItem' and 'gainMapCodecType', and
-// allocates and fills metadata in decoder->image->gainMap.
-// Otherwise, sets 'toneMappedImageItem' and 'gainMapItem' to NULL.
+// If found, fills 'gainMapItem' and 'gainMapCodecType', and allocates and fills in
+// decoder->image->gainMap.
+// Otherwise, sets 'gainMapItem' to NULL and gainMapCodecType to AVIF_CODEC_TYPE_UNKNOWN.
 // Returns AVIF_RESULT_OK if no errors were encountered (whether or not a gain map was found).
 // Assumes that there is a single tmap item, and not, e.g., a grid of tmap items.
 static avifResult avifDecoderFindGainMapItem(const avifDecoder * decoder,
                                              const avifDecoderItem * colorItem,
-                                             avifDecoderItem ** toneMappedImageItem,
                                              avifDecoderItem ** gainMapItem,
                                              avifCodecType * gainMapCodecType)
 {
-    *toneMappedImageItem = NULL;
     *gainMapItem = NULL;
     *gainMapCodecType = AVIF_CODEC_TYPE_UNKNOWN;
 
     avifDecoderData * data = decoder->data;
 
+    // Find tmap and gain map item ids.
     uint32_t gainMapItemID;
     avifDecoderItem * toneMappedImageItemTmp;
     AVIF_CHECKRES(avifDecoderDataFindToneMappedImageItem(data, colorItem, &toneMappedImageItemTmp, &gainMapItemID));
-    if (!toneMappedImageItemTmp) {
+    if (!toneMappedImageItemTmp || !gainMapItemID) {
         return AVIF_RESULT_OK;
     }
 
@@ -5666,42 +5667,53 @@
         return AVIF_RESULT_OK;
     }
 
-    AVIF_ASSERT_OR_RETURN(gainMapItemID != 0);
+    // Parse tmap item data (containing the gain map metadata).
+    avifROData tmapData;
+    AVIF_CHECKRES(avifDecoderItemRead(toneMappedImageItemTmp, decoder->io, &tmapData, 0, 0, data->diag));
+    // Allocate avifGainMap on the stack instead of using avifGainMapCreate() to simplify error handling.
+    avifGainMap gainMapTmp;
+    avifGainMapSetDefaults(&gainMapTmp);
+    const avifResult tmapParsingRes = avifParseToneMappedImageBox(&gainMapTmp, tmapData.data, tmapData.size, data->diag);
+    if (tmapParsingRes == AVIF_RESULT_NOT_IMPLEMENTED) {
+        // Unsupported gain map version. Simply ignore the gain map.
+        return AVIF_RESULT_OK;
+    }
+    AVIF_CHECKRES(tmapParsingRes);
+
     avifDecoderItem * gainMapItemTmp;
     AVIF_CHECKRES(avifMetaFindOrCreateItem(data->meta, gainMapItemID, &gainMapItemTmp));
     if (avifDecoderItemShouldBeSkipped(gainMapItemTmp)) {
-        avifDiagnosticsPrintf(data->diag, "Box[tmap] gain map item %d is not a supported image type", gainMapItemID);
-        return AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE;
+        return AVIF_RESULT_NOT_IMPLEMENTED;
     }
 
-    AVIF_CHECKRES(avifDecoderItemReadAndParse(decoder,
-                                              gainMapItemTmp,
-                                              /*isItemInInput=*/AVIF_TRUE,
-                                              &data->tileInfos[AVIF_ITEM_GAIN_MAP].grid,
-                                              gainMapCodecType));
+    const avifResult gainMapParsingRes = avifDecoderItemReadAndParse(decoder,
+                                                                     gainMapItemTmp,
+                                                                     /*isItemInInput=*/AVIF_TRUE,
+                                                                     &data->tileInfos[AVIF_ITEM_GAIN_MAP].grid,
+                                                                     gainMapCodecType);
+    if (gainMapParsingRes == AVIF_RESULT_NOT_IMPLEMENTED) {
+        return AVIF_RESULT_OK;
+    }
+    AVIF_CHECKRES(gainMapParsingRes);
 
-    decoder->image->gainMap = avifGainMapCreate();
-    AVIF_CHECKERR(decoder->image->gainMap, AVIF_RESULT_OUT_OF_MEMORY);
-
-    avifGainMap * const gainMap = decoder->image->gainMap;
     AVIF_CHECKRES(avifReadColorProperties(decoder->io,
                                           &toneMappedImageItemTmp->properties,
-                                          &gainMap->altICC,
-                                          &gainMap->altColorPrimaries,
-                                          &gainMap->altTransferCharacteristics,
-                                          &gainMap->altMatrixCoefficients,
-                                          &gainMap->altYUVRange,
+                                          &gainMapTmp.altICC,
+                                          &gainMapTmp.altColorPrimaries,
+                                          &gainMapTmp.altTransferCharacteristics,
+                                          &gainMapTmp.altMatrixCoefficients,
+                                          &gainMapTmp.altYUVRange,
                                           /*cicpSet=*/NULL));
 
     const avifProperty * clliProp = avifPropertyArrayFind(&toneMappedImageItemTmp->properties, "clli");
     if (clliProp) {
-        gainMap->altCLLI = clliProp->u.clli;
+        gainMapTmp.altCLLI = clliProp->u.clli;
     }
 
     const avifProperty * pixiProp = avifPropertyArrayFind(&toneMappedImageItemTmp->properties, "pixi");
     if (pixiProp) {
-        gainMap->altPlaneCount = pixiProp->u.pixi.planeCount;
-        gainMap->altDepth = pixiProp->u.pixi.planeDepths[0];
+        gainMapTmp.altPlaneCount = pixiProp->u.pixi.planeCount;
+        gainMapTmp.altDepth = pixiProp->u.pixi.planeDepths[0];
     }
 
     const avifProperty * ispeProp = avifPropertyArrayFind(&toneMappedImageItemTmp->properties, "ispe");
@@ -5731,21 +5743,34 @@
         return AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE;
     }
 
-    if (decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_GAIN_MAP) {
-        gainMap->image = avifImageCreateEmpty();
-        AVIF_CHECKERR(gainMap->image, AVIF_RESULT_OUT_OF_MEMORY);
+    avifColorPrimaries colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
+    avifTransferCharacteristics transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
+    avifMatrixCoefficients matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED;
+    avifRange yuvRange = AVIF_RANGE_FULL;
+    avifBool cicpSet;
+    // Look for a colr nclx box. Other colr box types (e.g. ICC) are not supported.
+    AVIF_CHECKRES(
+        avifReadColorNclxProperty(&gainMapItemTmp->properties, &colorPrimaries, &transferCharacteristics, &matrixCoefficients, &yuvRange, &cicpSet));
 
-        // Look for a colr nclx box. Other colr box types (e.g. ICC) are not supported.
-        AVIF_CHECKRES(avifReadColorNclxProperty(&gainMapItemTmp->properties,
-                                                &gainMap->image->colorPrimaries,
-                                                &gainMap->image->transferCharacteristics,
-                                                &gainMap->image->matrixCoefficients,
-                                                &gainMap->image->yuvRange,
-                                                /*cicpSet=*/NULL));
+    // -- Everything is valid, do memory allocations and fill in output data. --
+
+    decoder->image->gainMap = avifGainMapCreate();
+    AVIF_CHECKERR(decoder->image->gainMap, AVIF_RESULT_OUT_OF_MEMORY);
+    *decoder->image->gainMap = gainMapTmp;
+
+    if (decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_GAIN_MAP) {
+        decoder->image->gainMap->image = avifImageCreateEmpty();
+        avifImage * image = decoder->image->gainMap->image;
+        AVIF_CHECKERR(image, AVIF_RESULT_OUT_OF_MEMORY);
+        if (cicpSet) {
+            image->colorPrimaries = colorPrimaries;
+            image->transferCharacteristics = transferCharacteristics;
+            image->matrixCoefficients = matrixCoefficients;
+            image->yuvRange = yuvRange;
+        }
     }
 
-    // Only set the output parameters after everything has been validated.
-    *toneMappedImageItem = toneMappedImageItemTmp;
+    // Only set the output pointer after everything has been validated.
     *gainMapItem = gainMapItemTmp;
     return AVIF_RESULT_OK;
 }
@@ -6114,29 +6139,12 @@
         // need to remove the early exit in avifParse() to check if a 'tmap' item might be present
         // further down the file. Instead, we simply ignore tmap items in files that lack the 'tmap' brand.
         if (avifBrandArrayHasBrand(&data->compatibleBrands, "tmap")) {
-            avifDecoderItem * toneMappedImageItem;
             avifDecoderItem * gainMapItem;
             avifCodecType gainMapCodecType;
-            AVIF_CHECKRES(
-                avifDecoderFindGainMapItem(decoder, mainItems[AVIF_ITEM_COLOR], &toneMappedImageItem, &gainMapItem, &gainMapCodecType));
-            if (toneMappedImageItem != NULL) {
-                // Read the gain map's metadata.
-                avifROData tmapData;
-                AVIF_CHECKRES(avifDecoderItemRead(toneMappedImageItem, decoder->io, &tmapData, 0, 0, data->diag));
-                AVIF_ASSERT_OR_RETURN(decoder->image->gainMap != NULL);
-                const avifResult tmapParsingRes =
-                    avifParseToneMappedImageBox(decoder->image->gainMap, tmapData.data, tmapData.size, data->diag);
-                if (tmapParsingRes == AVIF_RESULT_NOT_IMPLEMENTED) {
-                    // Unsupported gain map version. Simply ignore the gain map.
-                    avifGainMapDestroy(decoder->image->gainMap);
-                    decoder->image->gainMap = NULL;
-                } else {
-                    AVIF_CHECKRES(tmapParsingRes);
-                    if (decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_GAIN_MAP) {
-                        mainItems[AVIF_ITEM_GAIN_MAP] = gainMapItem;
-                        codecType[AVIF_ITEM_GAIN_MAP] = gainMapCodecType;
-                    }
-                }
+            AVIF_CHECKRES(avifDecoderFindGainMapItem(decoder, mainItems[AVIF_ITEM_COLOR], &gainMapItem, &gainMapCodecType));
+            if (gainMapItem != NULL && decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_GAIN_MAP) {
+                mainItems[AVIF_ITEM_GAIN_MAP] = gainMapItem;
+                codecType[AVIF_ITEM_GAIN_MAP] = gainMapCodecType;
             }
         }
 
diff --git a/tests/gtest/avifgainmaptest.cc b/tests/gtest/avifgainmaptest.cc
index 0c2cded..c076528 100644
--- a/tests/gtest/avifgainmaptest.cc
+++ b/tests/gtest/avifgainmaptest.cc
@@ -67,6 +67,8 @@
   if (image == nullptr) {
     return nullptr;
   }
+  image->colorPrimaries = AVIF_COLOR_PRIMARIES_BT2020;
+  image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601;
   image->transferCharacteristics =
       (avifTransferCharacteristics)(base_rendition_is_hdr
                                         ? AVIF_TRANSFER_CHARACTERISTICS_PQ
@@ -78,6 +80,9 @@
   if (gain_map == nullptr) {
     return nullptr;
   }
+  gain_map->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
+  gain_map->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT709;
+  gain_map->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
   testutil::FillImageGradient(gain_map.get());
   image->gainMap = avifGainMapCreate();
   if (image->gainMap == nullptr) {
@@ -152,6 +157,13 @@
   EXPECT_EQ(decoded->gainMap->image->width, image->gainMap->image->width);
   EXPECT_EQ(decoded->gainMap->image->height, image->gainMap->image->height);
   EXPECT_EQ(decoded->gainMap->image->depth, image->gainMap->image->depth);
+  EXPECT_EQ(decoded->gainMap->image->colorPrimaries,
+            image->gainMap->image->colorPrimaries);
+  EXPECT_EQ(decoded->gainMap->image->transferCharacteristics,
+            image->gainMap->image->transferCharacteristics);
+  EXPECT_EQ(decoded->gainMap->image->matrixCoefficients,
+            image->gainMap->image->matrixCoefficients);
+  EXPECT_EQ(decoded->gainMap->image->yuvRange, image->gainMap->image->yuvRange);
   CheckGainMapMetadataMatches(*decoded->gainMap, *image->gainMap);
 
   // Decode the image.