Take the gain map into account in avifDecoderDecodedRowCount(). (#1599)

diff --git a/include/avif/avif.h b/include/avif/avif.h
index 53a41a0..f0de8a6 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -1225,6 +1225,12 @@
 // function can be called next to retrieve the number of top rows that can be immediately accessed
 // from the luma plane of decoder->image, and alpha if any. The corresponding rows from the chroma planes,
 // if any, can also be accessed (half rounded up if subsampled, same number of rows otherwise).
+// If a gain map is present and AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP is on and enableDecodingGainMap is also on,
+// the gain map's planes can also be accessed in the same way. If the gain map's height is different from
+// the main image, then the number of available gain map rows is at least:
+// roundf((float)decoded_row_count / decoder->image->height * decoder->image->gainMap.image->height)
+// When gain map scaling is needed, callers might choose to use a few less rows depending on how many rows
+// are needed by the scaling algorithm, to avoid the last row(s) changing when more data becomes available.
 // decoder->allowIncremental must be set to true before calling avifDecoderNextImage() or
 // avifDecoderNthImage(). Returns decoder->image->height when the last call to avifDecoderNextImage() or
 // avifDecoderNthImage() returned AVIF_RESULT_OK. Returns 0 in all other cases.
diff --git a/src/read.c b/src/read.c
index dc75d39..d6e6803 100644
--- a/src/read.c
+++ b/src/read.c
@@ -6,6 +6,7 @@
 #include <assert.h>
 #include <inttypes.h>
 #include <limits.h>
+#include <math.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -5282,10 +5283,10 @@
 }
 
 // Returns the number of available rows in decoder->image given a color or alpha subimage.
-static uint32_t avifGetDecodedRowCount(const avifDecoder * decoder, const avifTileInfo * info)
+static uint32_t avifGetDecodedRowCount(const avifDecoder * decoder, const avifTileInfo * info, const avifImage * image)
 {
     if (info->decodedTileCount == info->tileCount) {
-        return decoder->image->height;
+        return image->height;
     }
     if (info->decodedTileCount == 0) {
         return 0;
@@ -5294,21 +5295,34 @@
     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[info->firstTileIndex].height;
-        return AVIF_MIN((info->decodedTileCount / info->grid.columns) * tileHeight, decoder->image->height);
+        return AVIF_MIN((info->decodedTileCount / info->grid.columns) * tileHeight, image->height);
     } else {
         // Non-grid image.
-        return decoder->image->height;
+        return image->height;
     }
 }
 
 uint32_t avifDecoderDecodedRowCount(const avifDecoder * decoder)
 {
-    const uint32_t colorRowCount = avifGetDecodedRowCount(decoder, &decoder->data->color);
+    const uint32_t colorRowCount = avifGetDecodedRowCount(decoder, &decoder->data->color, decoder->image);
     if (colorRowCount == 0) {
         return 0;
     }
-    const uint32_t alphaRowCount = avifGetDecodedRowCount(decoder, &decoder->data->alpha);
-    return AVIF_MIN(colorRowCount, alphaRowCount);
+    const uint32_t alphaRowCount = avifGetDecodedRowCount(decoder, &decoder->data->alpha, decoder->image);
+    if (alphaRowCount == 0) {
+        return 0;
+    }
+    uint32_t gainMapRowCount = colorRowCount;
+#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
+    const avifImage * const gainMap = decoder->image->gainMap.image;
+    if (decoder->gainMapPresent && decoder->enableDecodingGainMap && gainMap != NULL && gainMap->height != 0) {
+        gainMapRowCount = avifGetDecodedRowCount(decoder, &decoder->data->gainMap, gainMap);
+        if (gainMap->height != decoder->image->height) {
+            gainMapRowCount = (uint32_t)floorf((float)gainMapRowCount / gainMap->height * decoder->image->height);
+        }
+    }
+#endif
+    return AVIF_MIN(AVIF_MIN(colorRowCount, alphaRowCount), gainMapRowCount);
 }
 
 avifResult avifDecoderRead(avifDecoder * decoder, avifImage * image)
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 044d1a3..5e70e67 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -140,7 +140,7 @@
 
     if(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
         add_executable(avifgainmaptest gtest/avifgainmaptest.cc)
-        target_link_libraries(avifgainmaptest aviftest_helpers ${GTEST_BOTH_LIBRARIES})
+        target_link_libraries(avifgainmaptest aviftest_helpers avifincrtest_helpers ${GTEST_BOTH_LIBRARIES})
         target_include_directories(avifgainmaptest PRIVATE ${GTEST_INCLUDE_DIRS})
         add_test(NAME avifgainmaptest COMMAND avifgainmaptest)
 
diff --git a/tests/gtest/avifgainmaptest.cc b/tests/gtest/avifgainmaptest.cc
index bb058d3..195cb55 100644
--- a/tests/gtest/avifgainmaptest.cc
+++ b/tests/gtest/avifgainmaptest.cc
@@ -7,6 +7,7 @@
 #include "avif/avif.h"
 #include "avif/gainmap.h"
 #include "avif/internal.h"
+#include "avifincrtest_helpers.h"
 #include "aviftest_helpers.h"
 #include "gtest/gtest.h"
 
@@ -180,20 +181,22 @@
   std::vector<const avifImage*> gain_map_ptrs;
   constexpr int kGridCols = 2;
   constexpr int kGridRows = 2;
+  constexpr int kCellWidth = 128;
+  constexpr int kCellHeight = 200;
 
   avifGainMapMetadata gain_map_metadata =
       GetTestGainMapMetadata(/*base_rendition_is_hdr=*/true);
 
   for (int i = 0; i < kGridCols * kGridRows; ++i) {
     testutil::AvifImagePtr image =
-        testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/10,
+        testutil::CreateImage(kCellWidth, kCellHeight, /*depth=*/10,
                               AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL);
     ASSERT_NE(image, nullptr);
     image->transferCharacteristics =
         AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084;  // PQ
     testutil::FillImageGradient(image.get());
     testutil::AvifImagePtr gain_map =
-        testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/8,
+        testutil::CreateImage(kCellWidth / 2, kCellHeight / 2, /*depth=*/8,
                               AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV);
     ASSERT_NE(gain_map, nullptr);
     testutil::FillImageGradient(gain_map.get());
@@ -253,6 +256,14 @@
   ASSERT_GT(testutil::GetPsnr(*merged_gain_map, *decoded->gainMap.image), 40.0);
   CheckGainMapMetadataMatches(decoded->gainMap.metadata, gain_map_metadata);
 
+  // Check that non-incremental and incremental decodings of a grid AVIF produce
+  // the same pixels.
+  testutil::DecodeNonIncrementallyAndIncrementally(
+      encoded, decoder.get(),
+      /*is_persistent=*/true, /*give_size_hint=*/true,
+      /*use_nth_image_api=*/false, kCellHeight,
+      /*enable_fine_incremental_check=*/true);
+
   // Uncomment the following to save the encoded image as an AVIF file.
   //  std::ofstream("/tmp/avifgainmaptest_grid.avif", std::ios::binary)
   //      .write(reinterpret_cast<char*>(encoded.data), encoded.size);
diff --git a/tests/gtest/avifincrtest_helpers.cc b/tests/gtest/avifincrtest_helpers.cc
index 76361fb..620c93b 100644
--- a/tests/gtest/avifincrtest_helpers.cc
+++ b/tests/gtest/avifincrtest_helpers.cc
@@ -4,6 +4,7 @@
 #include "avifincrtest_helpers.h"
 
 #include <algorithm>
+#include <cmath>
 #include <cstdint>
 #include <cstring>
 #include <memory>
@@ -62,6 +63,15 @@
       row2 += row2_bytes;
     }
   }
+
+#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
+  if (image1.gainMap.image != nullptr && image2.gainMap.image != nullptr) {
+    const uint32_t gain_map_row_count = (uint32_t)roundf(
+        (float)row_count / image1.height * image1.gainMap.image->height);
+    ComparePartialYuva(*image1.gainMap.image, *image2.gainMap.image,
+                       gain_map_row_count);
+  }
+#endif
 }
 
 // Returns the expected number of decoded rows when available_byte_count out of