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