diff --git a/include/avif/internal.h b/include/avif/internal.h index 5630e27f5a..b35c50c074 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -786,6 +786,8 @@ AVIF_NODISCARD avifBool avifSequenceHeaderParse(avifSequenceHeader * header, con // Removing outliers helps with accuracy/compression. avifResult avifFindMinMaxWithoutOutliers(const float * gainMapF, int numPixels, float * rangeMin, float * rangeMax); +avifResult avifGainMapMetadataValidate(const avifGainMapMetadata * metadata, avifDiagnostics * diag); + #endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP #define AVIF_INDEFINITE_DURATION64 UINT64_MAX diff --git a/src/avif.c b/src/avif.c index 6bba263ee9..defd78e84d 100644 --- a/src/avif.c +++ b/src/avif.c @@ -1193,6 +1193,16 @@ avifGainMap * avifGainMapCreate(void) gainMap->altMatrixCoefficients = AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED; gainMap->altYUVRange = AVIF_RANGE_FULL; gainMap->metadata.useBaseColorSpace = AVIF_TRUE; + // Set all denominators to valid values (1). + for (int i = 0; i < 3; ++i) { + gainMap->metadata.gainMapMinD[i] = 1; + gainMap->metadata.gainMapMaxD[i] = 1; + gainMap->metadata.gainMapGammaD[i] = 1; + gainMap->metadata.baseOffsetD[i] = 1; + gainMap->metadata.alternateOffsetD[i] = 1; + } + gainMap->metadata.baseHdrHeadroomD = 1; + gainMap->metadata.alternateHdrHeadroomD = 1; return gainMap; } diff --git a/src/gainmap.c b/src/gainmap.c index 66bda1377c..d36fe4a84c 100644 --- a/src/gainmap.c +++ b/src/gainmap.c @@ -441,6 +441,26 @@ avifResult avifFindMinMaxWithoutOutliers(const float * gainMapF, int numPixels, return AVIF_RESULT_OK; } +avifResult avifGainMapMetadataValidate(const avifGainMapMetadata * metadata, avifDiagnostics * diag) +{ + for (int i = 0; i < 3; ++i) { + if (metadata->gainMapMinD[i] == 0 || metadata->gainMapMaxD[i] == 0 || metadata->gainMapGammaD[i] == 0 || + metadata->baseOffsetD[i] == 0 || metadata->alternateOffsetD[i] == 0) { + avifDiagnosticsPrintf(diag, "Per-channel denominator is 0 in avifGainMapMetadata"); + return AVIF_RESULT_INVALID_ARGUMENT; + } + } + if (metadata->baseHdrHeadroomD == 0 || metadata->alternateHdrHeadroomD == 0) { + avifDiagnosticsPrintf(diag, "Headroom denominator is 0 in avifGainMapMetadata"); + return AVIF_RESULT_INVALID_ARGUMENT; + } + if (metadata->useBaseColorSpace != 0 && metadata->useBaseColorSpace != 1) { + avifDiagnosticsPrintf(diag, "useBaseColorSpace is %d in avifGainMapMetadata", metadata->useBaseColorSpace); + return AVIF_RESULT_INVALID_ARGUMENT; + } + return AVIF_RESULT_OK; +} + static const float kEpsilon = 1e-10f; // Decides which of 'basePrimaries' or 'altPrimaries' should be used for doing gain map math when creating a gain map. diff --git a/src/write.c b/src/write.c index 948ac9e268..92f8539602 100644 --- a/src/write.c +++ b/src/write.c @@ -914,8 +914,9 @@ static avifBool avifGainmapMetadataSize(const avifGainMapMetadata * metadata) return sizeof(uint16_t) * 2 + sizeof(uint8_t) + sizeof(uint32_t) * 4 + channelCount * sizeof(uint32_t) * 10; } -static avifResult avifWriteGainmapMetadata(avifRWStream * s, const avifGainMapMetadata * metadata) +static avifResult avifWriteGainmapMetadata(avifRWStream * s, const avifGainMapMetadata * metadata, avifDiagnostics * diag) { + AVIF_CHECKRES(avifGainMapMetadataValidate(metadata, diag)); const size_t offset = avifRWStreamOffset(s); // GainMapMetadata syntax as per clause C.2.2 of ISO 21496-1: @@ -957,7 +958,7 @@ static avifResult avifWriteGainmapMetadata(avifRWStream * s, const avifGainMapMe return AVIF_RESULT_OK; } -static avifResult avifWriteToneMappedImagePayload(avifRWData * data, const avifGainMapMetadata * metadata) +static avifResult avifWriteToneMappedImagePayload(avifRWData * data, const avifGainMapMetadata * metadata, avifDiagnostics * diag) { avifRWStream s; avifRWStreamStart(&s, data); @@ -966,7 +967,7 @@ static avifResult avifWriteToneMappedImagePayload(avifRWData * data, const avifG const uint8_t version = 0; AVIF_CHECKRES(avifRWStreamWriteU8(&s, version)); // unsigned int(8) version = 0; if (version == 0) { - AVIF_CHECKRES(avifWriteGainmapMetadata(&s, metadata)); // GainMapMetadata; + AVIF_CHECKRES(avifWriteGainmapMetadata(&s, metadata, diag)); // GainMapMetadata; } avifRWStreamFinishWrite(&s); return AVIF_RESULT_OK; @@ -1868,7 +1869,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, infeNameGainMap, /*infeNameSize=*/strlen(infeNameGainMap) + 1, /*cellIndex=*/0); - AVIF_CHECKRES(avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, &firstCell->gainMap->metadata)); + AVIF_CHECKRES( + avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, &firstCell->gainMap->metadata, &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; diff --git a/tests/gtest/avif_fuzztest_enc_dec_experimental.cc b/tests/gtest/avif_fuzztest_enc_dec_experimental.cc index a1a8646840..cb40b658ab 100644 --- a/tests/gtest/avif_fuzztest_enc_dec_experimental.cc +++ b/tests/gtest/avif_fuzztest_enc_dec_experimental.cc @@ -97,24 +97,6 @@ void EncodeDecodeValid(ImagePtr image, EncoderPtr encoder, DecoderPtr decoder) { // hard to verify so do not check it. } -// Note that avifGainMapMetadata is passed as a byte array -// because the C array fields in the struct seem to prevent fuzztest from -// handling it natively. -ImagePtr AddGainMapToImage( - ImagePtr image, ImagePtr gain_map, - const std::array& metadata) { - image->gainMap = avifGainMapCreate(); - image->gainMap->image = gain_map.release(); - std::memcpy(&image->gainMap->metadata, metadata.data(), metadata.size()); - return image; -} - -inline auto ArbitraryAvifImageWithGainMap() { - return fuzztest::Map( - AddGainMapToImage, ArbitraryAvifImage(), ArbitraryAvifImage(), - fuzztest::Arbitrary>()); -} - FUZZ_TEST(EncodeDecodeAvifFuzzTest, EncodeDecodeValid) .WithDomains(fuzztest::OneOf(ArbitraryAvifImage(), ArbitraryAvifImageWithGainMap()), diff --git a/tests/gtest/avif_fuzztest_enc_dec_incr_experimental.cc b/tests/gtest/avif_fuzztest_enc_dec_incr_experimental.cc index 2d3d8515fd..a7b410cee0 100644 --- a/tests/gtest/avif_fuzztest_enc_dec_incr_experimental.cc +++ b/tests/gtest/avif_fuzztest_enc_dec_incr_experimental.cc @@ -97,24 +97,6 @@ void EncodeDecodeGridValid(ImagePtr image, EncoderPtr encoder, ASSERT_EQ(decode_result, AVIF_RESULT_OK) << avifResultToString(decode_result); } -// Note that avifGainMapMetadata is passed as a byte array -// because the C array fields in the struct seem to prevent fuzztest from -// handling it natively. -ImagePtr AddGainMapToImage( - ImagePtr image, ImagePtr gain_map, - const std::array& metadata) { - image->gainMap = avifGainMapCreate(); - image->gainMap->image = gain_map.release(); - std::memcpy(&image->gainMap->metadata, metadata.data(), metadata.size()); - return image; -} - -inline auto ArbitraryAvifImageWithGainMap() { - return fuzztest::Map( - AddGainMapToImage, ArbitraryAvifImage(), ArbitraryAvifImage(), - fuzztest::Arbitrary>()); -} - FUZZ_TEST(EncodeDecodeAvifFuzzTest, EncodeDecodeGridValid) .WithDomains(fuzztest::OneOf(ArbitraryAvifImage(), ArbitraryAvifImageWithGainMap()), diff --git a/tests/gtest/avif_fuzztest_helpers.cc b/tests/gtest/avif_fuzztest_helpers.cc index aa78da2c92..b42919a7b5 100644 --- a/tests/gtest/avif_fuzztest_helpers.cc +++ b/tests/gtest/avif_fuzztest_helpers.cc @@ -169,6 +169,44 @@ DecoderPtr AddGainMapOptionsToDecoder(DecoderPtr decoder, // is no longer the case when this option is on. return decoder; } + +ImagePtr AddGainMapToImage( + ImagePtr image, ImagePtr gain_map, int32_t gain_map_min_n0, + int32_t gain_map_min_n1, int32_t gain_map_min_n2, uint32_t gain_map_min_d0, + uint32_t gain_map_min_d1, uint32_t gain_map_min_d2, int32_t gain_map_max_n0, + int32_t gain_map_max_n1, int32_t gain_map_max_n2, uint32_t gain_map_max_d0, + uint32_t gain_map_max_d1, uint32_t gain_map_max_d2, + uint32_t gain_map_gamma_n0, uint32_t gain_map_gamma_n1, + uint32_t gain_map_gamma_n2, uint32_t gain_map_gamma_d0, + uint32_t gain_map_gamma_d1, uint32_t gain_map_gamma_d2, + int32_t base_offset_n0, int32_t base_offset_n1, int32_t base_offset_n2, + uint32_t base_offset_d0, uint32_t base_offset_d1, uint32_t base_offset_d2, + int32_t alternate_offset_n0, int32_t alternate_offset_n1, + int32_t alternate_offset_n2, uint32_t alternate_offset_d0, + uint32_t alternate_offset_d1, uint32_t alternate_offset_d2, + uint32_t base_hdr_headroom_n, uint32_t base_hdr_headroom_d, + uint32_t alternate_hdr_headroom_n, uint32_t alternate_hdr_headroom_d, + bool use_base_color_space) { + image->gainMap = avifGainMapCreate(); + image->gainMap->image = gain_map.release(); + image->gainMap->metadata = avifGainMapMetadata{ + {gain_map_min_n0, gain_map_min_n1, gain_map_min_n2}, + {gain_map_min_d0, gain_map_min_d1, gain_map_min_d2}, + {gain_map_max_n0, gain_map_max_n1, gain_map_max_n2}, + {gain_map_max_d0, gain_map_max_d1, gain_map_max_d2}, + {gain_map_gamma_n0, gain_map_gamma_n1, gain_map_gamma_n2}, + {gain_map_gamma_d0, gain_map_gamma_d1, gain_map_gamma_d2}, + {base_offset_n0, base_offset_n1, base_offset_n2}, + {base_offset_d0, base_offset_d1, base_offset_d2}, + {alternate_offset_n0, alternate_offset_n1, alternate_offset_n2}, + {alternate_offset_d0, alternate_offset_d1, alternate_offset_d2}, + base_hdr_headroom_n, + base_hdr_headroom_d, + alternate_hdr_headroom_n, + alternate_hdr_headroom_d, + use_base_color_space}; + return image; +} #endif //------------------------------------------------------------------------------ diff --git a/tests/gtest/avif_fuzztest_helpers.h b/tests/gtest/avif_fuzztest_helpers.h index d83d080a35..6b678c30cc 100644 --- a/tests/gtest/avif_fuzztest_helpers.h +++ b/tests/gtest/avif_fuzztest_helpers.h @@ -6,6 +6,7 @@ #include #include +#include #include #include "avif/avif.h" @@ -169,6 +170,65 @@ inline auto ArbitraryAvifAnim() { return fuzztest::OneOf(ArbitraryAvifAnim8b(), ArbitraryAvifAnim16b()); } +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) +// Note that avifGainMapMetadata is passed as many individual arguments +// because the C array fields in the struct seem to prevent fuzztest from +// handling it natively. +// TODO: Try StructOf(StructOf())? +ImagePtr AddGainMapToImage( + ImagePtr image, ImagePtr gain_map, int32_t gain_map_min_n0, + int32_t gain_map_min_n1, int32_t gain_map_min_n2, uint32_t gain_map_min_d0, + uint32_t gain_map_min_d1, uint32_t gain_map_min_d2, int32_t gain_map_max_n0, + int32_t gain_map_max_n1, int32_t gain_map_max_n2, uint32_t gain_map_max_d0, + uint32_t gain_map_max_d1, uint32_t gain_map_max_d2, + uint32_t gain_map_gamma_n0, uint32_t gain_map_gamma_n1, + uint32_t gain_map_gamma_n2, uint32_t gain_map_gamma_d0, + uint32_t gain_map_gamma_d1, uint32_t gain_map_gamma_d2, + int32_t base_offset_n0, int32_t base_offset_n1, int32_t base_offset_n2, + uint32_t base_offset_d0, uint32_t base_offset_d1, uint32_t base_offset_d2, + int32_t alternate_offset_n0, int32_t alternate_offset_n1, + int32_t alternate_offset_n2, uint32_t alternate_offset_d0, + uint32_t alternate_offset_d1, uint32_t alternate_offset_d2, + uint32_t base_hdr_headroom_n, uint32_t base_hdr_headroom_d, + uint32_t alternate_hdr_headroom_n, uint32_t alternate_hdr_headroom_d, + bool use_base_color_space); + +inline auto ArbitraryAvifImageWithGainMap() { + return fuzztest::Map( + AddGainMapToImage, ArbitraryAvifImage(), ArbitraryAvifImage(), + fuzztest::Arbitrary(), fuzztest::Arbitrary(), + fuzztest::Arbitrary(), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::Arbitrary(), fuzztest::Arbitrary(), + fuzztest::Arbitrary(), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::Arbitrary(), fuzztest::Arbitrary(), + fuzztest::Arbitrary(), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::Arbitrary(), fuzztest::Arbitrary(), + fuzztest::Arbitrary(), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::Arbitrary(), fuzztest::Arbitrary(), + fuzztest::Arbitrary(), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::Arbitrary(), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::Arbitrary(), + fuzztest::InRange(1, std::numeric_limits::max()), + fuzztest::Arbitrary()); +} +#endif + // Generator for an arbitrary EncoderPtr. inline auto ArbitraryAvifEncoder() { const auto codec_choice = fuzztest::ElementOf(