From cdb89c92a1eee34a98f25bacfd02c70c3e375926 Mon Sep 17 00:00:00 2001 From: maryla-uc Date: Mon, 30 Sep 2024 14:04:00 +0200 Subject: [PATCH] Refactor gain map API (#2444) Remove avifGainMapMetadata and avifGainMapMetadataDouble. These structs were not extensible, i.e. new fields could not be added without breaking ABI backward compatibility. Instead, the fields of avifGainMapMetadata are added to avifGainMap directly (which is an extensible struct since it uses Create()/Destroy() functions). Expose avifDoubleTo(Un)SignedFraction to replace avifGainMapMetadataDoubleToFractions. --- CHANGELOG.md | 4 + apps/avifgainmaputil/convert_command.cc | 2 +- apps/avifgainmaputil/printmetadata_command.cc | 54 ++-- apps/avifgainmaputil/swapbase_command.cc | 19 +- apps/avifgainmaputil/tonemap_command.cc | 32 ++- apps/shared/avifjpeg.c | 70 ++--- apps/shared/avifjpeg.h | 2 +- apps/shared/avifutil.c | 2 +- include/avif/avif.h | 109 +++---- include/avif/avif_cxx.h | 7 + include/avif/internal.h | 9 +- src/avif.c | 30 +- src/gainmap.c | 196 ++++++------- src/read.c | 53 ++-- src/utils.c | 16 +- src/write.c | 111 ++++--- .../avif_fuzztest_enc_dec_experimental.cc | 35 ++- tests/gtest/avif_fuzztest_helpers.cc | 45 +-- tests/gtest/avif_fuzztest_helpers.h | 8 +- tests/gtest/avifgainmaptest.cc | 271 +++++------------- tests/gtest/avifjpeggainmaptest.cc | 76 ++--- tests/gtest/avifutilstest.cc | 44 ++- 22 files changed, 527 insertions(+), 668 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9101280b4e..32636083ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ The changes are relative to the previous release, unless the baseline is specifi * Write an empty HandlerBox name field instead of "libavif" (saves 7 bytes). * Update aom.cmd/LocalAom.cmake: v3.10.0 * Update svt.cmd/svt.sh/LocalSvt.cmake: v2.2.1 +* Change experimental gainmap API: remove avifGainMapMetadata and + avifGainMapMetadataDouble structs. +* Add avif(Un)SignedFraction structs and avifDoubleTo(Un)SignedFraction + utility functions. ## [1.1.1] - 2024-07-30 diff --git a/apps/avifgainmaputil/convert_command.cc b/apps/avifgainmaputil/convert_command.cc index ecb617de04..47f149c75b 100644 --- a/apps/avifgainmaputil/convert_command.cc +++ b/apps/avifgainmaputil/convert_command.cc @@ -88,7 +88,7 @@ avifResult ConvertCommand::Run() { if (arg_swap_base_) { int depth = arg_image_read_.depth; if (depth == 0) { - depth = image->gainMap->metadata.alternateHdrHeadroomN == 0 ? 8 : 10; + depth = image->gainMap->alternateHdrHeadroom.n == 0 ? 8 : 10; } ImagePtr new_base(avifImageCreateEmpty()); if (new_base == nullptr) { diff --git a/apps/avifgainmaputil/printmetadata_command.cc b/apps/avifgainmaputil/printmetadata_command.cc index 010d566f15..782469a7eb 100644 --- a/apps/avifgainmaputil/printmetadata_command.cc +++ b/apps/avifgainmaputil/printmetadata_command.cc @@ -12,23 +12,20 @@ namespace avif { namespace { template -std::string FormatFraction(T numerator, uint32_t denominator) { +std::string FormatFraction(T fraction) { std::stringstream stream; - stream << (denominator != 0 ? (double)numerator / denominator : 0) - << " (as fraction: " << numerator << "/" << denominator << ")"; + stream << (fraction->d != 0 ? (double)fraction->n / fraction->d : 0) + << " (as fraction: " << fraction->n << "/" << fraction->d << ")"; return stream.str(); } template -std::string FormatFractions(const T numerator[3], - const uint32_t denominator[3]) { +std::string FormatFractions(const T fractions[3]) { std::stringstream stream; const int w = 40; - stream << "R " << std::left << std::setw(w) - << FormatFraction(numerator[0], denominator[0]) << " G " << std::left - << std::setw(w) << FormatFraction(numerator[1], denominator[1]) - << " B " << std::left << std::setw(w) - << FormatFraction(numerator[2], denominator[2]); + stream << "R " << std::left << std::setw(w) << FormatFraction(fractions) + << " G " << std::left << std::setw(w) << FormatFraction(fractions) + << " B " << std::left << std::setw(w) << FormatFraction(fractions); return stream.str(); } } // namespace @@ -65,34 +62,27 @@ avifResult PrintMetadataCommand::Run() { } assert(decoder->image->gainMap); - const avifGainMapMetadata& metadata = decoder->image->gainMap->metadata; + const avifGainMap& gainMap = *decoder->image->gainMap; const int w = 20; - std::cout << " * " << std::left << std::setw(w) << "Base headroom: " - << FormatFraction(metadata.baseHdrHeadroomN, - metadata.baseHdrHeadroomD) + std::cout << " * " << std::left << std::setw(w) + << "Base headroom: " << FormatFraction(&gainMap.baseHdrHeadroom) << "\n"; std::cout << " * " << std::left << std::setw(w) << "Alternate headroom: " - << FormatFraction(metadata.alternateHdrHeadroomN, - metadata.alternateHdrHeadroomD) + << FormatFraction(&gainMap.alternateHdrHeadroom) << "\n"; + std::cout << " * " << std::left << std::setw(w) + << "Gain Map Min: " << FormatFractions(gainMap.gainMapMin) << "\n"; + std::cout << " * " << std::left << std::setw(w) + << "Gain Map Max: " << FormatFractions(gainMap.gainMapMax) << "\n"; + std::cout << " * " << std::left << std::setw(w) + << "Base Offset: " << FormatFractions(gainMap.baseOffset) << "\n"; + std::cout << " * " << std::left << std::setw(w) + << "Alternate Offset: " << FormatFractions(gainMap.alternateOffset) << "\n"; - std::cout << " * " << std::left << std::setw(w) << "Gain Map Min: " - << FormatFractions(metadata.gainMapMinN, metadata.gainMapMinD) - << "\n"; - std::cout << " * " << std::left << std::setw(w) << "Gain Map Max: " - << FormatFractions(metadata.gainMapMaxN, metadata.gainMapMaxD) - << "\n"; - std::cout << " * " << std::left << std::setw(w) << "Base Offset: " - << FormatFractions(metadata.baseOffsetN, metadata.baseOffsetD) - << "\n"; - std::cout << " * " << std::left << std::setw(w) << "Alternate Offset: " - << FormatFractions(metadata.alternateOffsetN, - metadata.alternateOffsetD) - << "\n"; - std::cout << " * " << std::left << std::setw(w) << "Gain Map Gamma: " - << FormatFractions(metadata.gainMapGammaN, metadata.gainMapGammaD) + std::cout << " * " << std::left << std::setw(w) + << "Gain Map Gamma: " << FormatFractions(gainMap.gainMapGamma) << "\n"; std::cout << " * " << std::left << std::setw(w) << "Use Base Color Space: " - << (metadata.useBaseColorSpace ? "True" : "False") << "\n"; + << (gainMap.useBaseColorSpace ? "True" : "False") << "\n"; return AVIF_RESULT_OK; } diff --git a/apps/avifgainmaputil/swapbase_command.cc b/apps/avifgainmaputil/swapbase_command.cc index 8cb81b3a45..3b504aedd0 100644 --- a/apps/avifgainmaputil/swapbase_command.cc +++ b/apps/avifgainmaputil/swapbase_command.cc @@ -22,9 +22,12 @@ avifResult ChangeBase(const avifImage& image, int depth, swapped->depth = depth; swapped->yuvFormat = yuvFormat; + if (image.gainMap->alternateHdrHeadroom.d == 0) { + return AVIF_RESULT_INVALID_ARGUMENT; + } const float headroom = - static_cast(image.gainMap->metadata.alternateHdrHeadroomN) / - image.gainMap->metadata.alternateHdrHeadroomD; + static_cast(image.gainMap->alternateHdrHeadroom.n) / + image.gainMap->alternateHdrHeadroom.d; const bool tone_mapping_to_sdr = (headroom == 0.0f); swapped->colorPrimaries = image.gainMap->altColorPrimaries; @@ -97,14 +100,12 @@ avifResult ChangeBase(const avifImage& image, int depth, (image.yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3; swapped->gainMap->altCLLI = image.clli; - // Swap base and alternate in the gain map metadata. - avifGainMapMetadata& metadata = swapped->gainMap->metadata; - metadata.useBaseColorSpace = !metadata.useBaseColorSpace; - std::swap(metadata.baseHdrHeadroomN, metadata.alternateHdrHeadroomN); - std::swap(metadata.baseHdrHeadroomD, metadata.alternateHdrHeadroomD); + // Swap base and alternate in the gain map + avifGainMap* gainMap = swapped->gainMap; + gainMap->useBaseColorSpace = !gainMap->useBaseColorSpace; + std::swap(gainMap->baseHdrHeadroom, gainMap->alternateHdrHeadroom); for (int c = 0; c < 3; ++c) { - std::swap(metadata.baseOffsetN, metadata.alternateOffsetN); - std::swap(metadata.baseOffsetD, metadata.alternateOffsetD); + std::swap(gainMap->baseOffset, gainMap->alternateOffset); } return AVIF_RESULT_OK; diff --git a/apps/avifgainmaputil/tonemap_command.cc b/apps/avifgainmaputil/tonemap_command.cc index 371b91ceae..d0c48e05c6 100644 --- a/apps/avifgainmaputil/tonemap_command.cc +++ b/apps/avifgainmaputil/tonemap_command.cc @@ -85,29 +85,31 @@ avifResult TonemapCommand::Run() { << " does not contain a gain map\n"; return AVIF_RESULT_INVALID_ARGUMENT; } - - avifGainMapMetadataDouble metadata; - if (!avifGainMapMetadataFractionsToDouble(&metadata, - &image->gainMap->metadata)) { - std::cerr << "Input image " << arg_input_filename_ - << " has invalid gain map metadata\n"; + if (image->gainMap->baseHdrHeadroom.d == 0 || + image->gainMap->alternateHdrHeadroom.d == 0) { return AVIF_RESULT_INVALID_ARGUMENT; } + const float base_hdr_hreadroom = + static_cast(image->gainMap->baseHdrHeadroom.n) / + image->gainMap->baseHdrHeadroom.d; + const float alternate_hdr_hreadroom = + static_cast(image->gainMap->alternateHdrHeadroom.n / + image->gainMap->alternateHdrHeadroom.d); // We are either tone mapping to the base image (i.e. leaving it as is), // or tone mapping to the alternate image (i.e. fully applying the gain map), // or tone mapping in between (partially applying the gain map). const bool tone_mapping_to_base = - (headroom <= metadata.baseHdrHeadroom && - metadata.baseHdrHeadroom <= metadata.alternateHdrHeadroom) || - (headroom >= metadata.baseHdrHeadroom && - metadata.baseHdrHeadroom >= metadata.alternateHdrHeadroom); + (headroom <= base_hdr_hreadroom && + base_hdr_hreadroom <= alternate_hdr_hreadroom) || + (headroom >= base_hdr_hreadroom && + base_hdr_hreadroom >= alternate_hdr_hreadroom); const bool tone_mapping_to_alternate = - (headroom <= metadata.alternateHdrHeadroom && - metadata.alternateHdrHeadroom <= metadata.baseHdrHeadroom) || - (headroom >= metadata.alternateHdrHeadroom && - metadata.alternateHdrHeadroom >= metadata.baseHdrHeadroom); - const bool base_is_hdr = (metadata.baseHdrHeadroom != 0.0f); + (headroom <= alternate_hdr_hreadroom && + alternate_hdr_hreadroom <= base_hdr_hreadroom) || + (headroom >= alternate_hdr_hreadroom && + alternate_hdr_hreadroom >= base_hdr_hreadroom); + const bool base_is_hdr = (base_hdr_hreadroom != 0.0f); // Determine output CICP. CicpValues cicp; diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c index d48beaaf80..43b008cc12 100644 --- a/apps/shared/avifjpeg.c +++ b/apps/shared/avifjpeg.c @@ -592,53 +592,47 @@ static inline void SwapDoubles(double * x, double * y) // Parses gain map metadata from XMP. // See https://helpx.adobe.com/camera-raw/using/gain-map.html // Returns AVIF_TRUE if the gain map metadata was successfully read. -static avifBool avifJPEGParseGainMapXMPProperties(const xmlNode * rootNode, avifGainMapMetadata * metadata) +static avifBool avifJPEGParseGainMapXMPProperties(const xmlNode * rootNode, avifGainMap * gainMap) { const xmlNode * descNode = avifJPEGFindGainMapXMPNode(rootNode); if (descNode == NULL) { return AVIF_FALSE; } - avifGainMapMetadataDouble metadataDouble; // Set default values from Adobe's spec. - metadataDouble.baseHdrHeadroom = 0.0; - metadataDouble.alternateHdrHeadroom = 1.0; - for (int i = 0; i < 3; ++i) { - metadataDouble.gainMapMin[i] = 0.0; - metadataDouble.gainMapMax[i] = 1.0; - metadataDouble.baseOffset[i] = 1.0 / 64.0; - metadataDouble.alternateOffset[i] = 1.0 / 64.0; - metadataDouble.gainMapGamma[i] = 1.0; - } - // Not in Adobe's spec but both color spaces should be the same so this value doesn't matter. - metadataDouble.useBaseColorSpace = AVIF_TRUE; - - AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "HDRCapacityMin", &metadataDouble.baseHdrHeadroom, /*numDoubles=*/1)); - AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "HDRCapacityMax", &metadataDouble.alternateHdrHeadroom, /*numDoubles=*/1)); - AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "OffsetSDR", metadataDouble.baseOffset, /*numDoubles=*/3)); - AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "OffsetHDR", metadataDouble.alternateOffset, /*numDoubles=*/3)); - AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "GainMapMin", metadataDouble.gainMapMin, /*numDoubles=*/3)); - AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "GainMapMax", metadataDouble.gainMapMax, /*numDoubles=*/3)); - AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "Gamma", metadataDouble.gainMapGamma, /*numDoubles=*/3)); + double baseHdrHeadroom = 0.0; + double alternateHdrHeadroom = 1.0; + double gainMapMin[3] = { 0.0, 0.0, 0.0 }; + double gainMapMax[3] = { 1.0, 1.0, 1.0 }; + double gainMapGamma[3] = { 1.0, 1.0, 1.0 }; + double baseOffset[3] = { 1.0 / 64.0, 1.0 / 64.0, 1.0 / 64.0 }; + double alternateOffset[3] = { 1.0 / 64.0, 1.0 / 64.0, 1.0 / 64.0 }; + AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "HDRCapacityMin", &baseHdrHeadroom, /*numDoubles=*/1)); + AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "HDRCapacityMax", &alternateHdrHeadroom, /*numDoubles=*/1)); + AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "OffsetSDR", baseOffset, /*numDoubles=*/3)); + AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "OffsetHDR", alternateOffset, /*numDoubles=*/3)); + AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "GainMapMin", gainMapMin, /*numDoubles=*/3)); + AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "GainMapMax", gainMapMax, /*numDoubles=*/3)); + AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "Gamma", gainMapGamma, /*numDoubles=*/3)); // See inequality requirements in section 'XMP Representation of Gain Map Metadata' of Adobe's gain map specification // https://helpx.adobe.com/camera-raw/using/gain-map.html - AVIF_CHECK(metadataDouble.alternateHdrHeadroom > metadataDouble.baseHdrHeadroom); - AVIF_CHECK(metadataDouble.baseHdrHeadroom >= 0); + AVIF_CHECK(alternateHdrHeadroom > baseHdrHeadroom); + AVIF_CHECK(baseHdrHeadroom >= 0); for (int i = 0; i < 3; ++i) { - AVIF_CHECK(metadataDouble.gainMapMax[i] >= metadataDouble.gainMapMin[i]); - AVIF_CHECK(metadataDouble.baseOffset[i] >= 0.0); - AVIF_CHECK(metadataDouble.alternateOffset[i] >= 0.0); - AVIF_CHECK(metadataDouble.gainMapGamma[i] > 0.0); + AVIF_CHECK(gainMapMax[i] >= gainMapMin[i]); + AVIF_CHECK(baseOffset[i] >= 0.0); + AVIF_CHECK(alternateOffset[i] >= 0.0); + AVIF_CHECK(gainMapGamma[i] > 0.0); } uint32_t numValues; const char * baseRenditionIsHDR; if (avifJPEGFindGainMapProperty(descNode, "BaseRenditionIsHDR", /*maxValues=*/1, &baseRenditionIsHDR, &numValues)) { if (!strcmp(baseRenditionIsHDR, "True")) { - SwapDoubles(&metadataDouble.baseHdrHeadroom, &metadataDouble.alternateHdrHeadroom); + SwapDoubles(&baseHdrHeadroom, &alternateHdrHeadroom); for (int c = 0; c < 3; ++c) { - SwapDoubles(&metadataDouble.baseOffset[c], &metadataDouble.alternateOffset[c]); + SwapDoubles(&baseOffset[c], &alternateOffset[c]); } } else if (!strcmp(baseRenditionIsHDR, "False")) { } else { @@ -646,21 +640,31 @@ static avifBool avifJPEGParseGainMapXMPProperties(const xmlNode * rootNode, avif } } - AVIF_CHECK(avifGainMapMetadataDoubleToFractions(metadata, &metadataDouble)); + for (int i = 0; i < 3; ++i) { + AVIF_CHECK(avifDoubleToSignedFraction(gainMapMin[i], &gainMap->gainMapMin[i])); + AVIF_CHECK(avifDoubleToSignedFraction(gainMapMax[i], &gainMap->gainMapMax[i])); + AVIF_CHECK(avifDoubleToUnsignedFraction(gainMapGamma[i], &gainMap->gainMapGamma[i])); + AVIF_CHECK(avifDoubleToSignedFraction(baseOffset[i], &gainMap->baseOffset[i])); + AVIF_CHECK(avifDoubleToSignedFraction(alternateOffset[i], &gainMap->alternateOffset[i])); + } + AVIF_CHECK(avifDoubleToUnsignedFraction(baseHdrHeadroom, &gainMap->baseHdrHeadroom)); + AVIF_CHECK(avifDoubleToUnsignedFraction(alternateHdrHeadroom, &gainMap->alternateHdrHeadroom)); + // Not in Adobe's spec but both color spaces should be the same so this value doesn't matter. + gainMap->useBaseColorSpace = AVIF_TRUE; return AVIF_TRUE; } // Parses gain map metadata from an XMP payload. // Returns AVIF_TRUE if the gain map metadata was successfully read. -avifBool avifJPEGParseGainMapXMP(const uint8_t * xmpData, size_t xmpSize, avifGainMapMetadata * metadata) +avifBool avifJPEGParseGainMapXMP(const uint8_t * xmpData, size_t xmpSize, avifGainMap * gainMap) { xmlDoc * document = xmlReadMemory((const char *)xmpData, (int)xmpSize, NULL, NULL, LIBXML2_XML_PARSING_FLAGS); if (document == NULL) { return AVIF_FALSE; // Probably an out of memory error. } xmlNode * rootNode = xmlDocGetRootElement(document); - const avifBool res = avifJPEGParseGainMapXMPProperties(rootNode, metadata); + const avifBool res = avifJPEGParseGainMapXMPProperties(rootNode, gainMap); xmlFreeDoc(document); return res; } @@ -817,7 +821,7 @@ static avifBool avifJPEGExtractGainMapImage(FILE * f, avifImageDestroy(image); return AVIF_FALSE; } - if (!avifJPEGParseGainMapXMP(image->xmp.data, image->xmp.size, &gainMap->metadata)) { + if (!avifJPEGParseGainMapXMP(image->xmp.data, image->xmp.size, gainMap)) { fprintf(stderr, "Warning: failed to parse gain map metadata\n"); avifImageDestroy(image); return AVIF_FALSE; diff --git a/apps/shared/avifjpeg.h b/apps/shared/avifjpeg.h index 16032bcfd2..ad2a3f0c97 100644 --- a/apps/shared/avifjpeg.h +++ b/apps/shared/avifjpeg.h @@ -31,7 +31,7 @@ avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int #if defined(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION) // Parses XMP gain map metadata. Visible for testing. -avifBool avifJPEGParseGainMapXMP(const uint8_t * xmpData, size_t xmpSize, avifGainMapMetadata * metadata); +avifBool avifJPEGParseGainMapXMP(const uint8_t * xmpData, size_t xmpSize, avifGainMap * gainMap); #endif #ifdef __cplusplus diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c index 72edbcab88..8006ea4f0f 100644 --- a/apps/shared/avifutil.c +++ b/apps/shared/avifutil.c @@ -141,7 +141,7 @@ static void avifImageDumpInternal(const avifImage * avif, avifPixelFormatToString(gainMapImage->yuvFormat), (gainMapImage->yuvRange == AVIF_RANGE_FULL) ? "Full" : "Limited", gainMapImage->matrixCoefficients, - (avif->gainMap->metadata.baseHdrHeadroomN == 0) ? "SDR" : "HDR"); + (avif->gainMap->baseHdrHeadroom.n == 0) ? "SDR" : "HDR"); printf(" * Alternate image:\n"); printf(" * Color Primaries: %u\n", avif->gainMap->altColorPrimaries); printf(" * Transfer Char. : %u\n", avif->gainMap->altTransferCharacteristics); diff --git a/include/avif/avif.h b/include/avif/avif.h index d9105bd621..f5ee805174 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -426,7 +426,7 @@ typedef struct avifDiagnostics AVIF_API void avifDiagnosticsClearError(avifDiagnostics * diag); // --------------------------------------------------------------------------- -// Fraction utility +// Fraction utilities typedef struct avifFraction { @@ -434,6 +434,25 @@ typedef struct avifFraction int32_t d; } avifFraction; +typedef struct avifSignedFraction +{ + int32_t n; + uint32_t d; +} avifSignedFraction; + +typedef struct avifUnsignedFraction +{ + uint32_t n; + uint32_t d; +} avifUnsignedFraction; + +// Creates an int32/uint32 fraction that is approximately equal to 'v'. +// Returns AVIF_FALSE if 'v' is NaN or abs(v) is > INT32_MAX. +AVIF_API AVIF_NODISCARD avifBool avifDoubleToSignedFraction(double v, avifSignedFraction * fraction); +// Creates a uint32/uint32 fraction that is approximately equal to 'v'. +// Returns AVIF_FALSE if 'v' is < 0 or > UINT32_MAX or NaN. +AVIF_API AVIF_NODISCARD avifBool avifDoubleToUnsignedFraction(double v, avifUnsignedFraction * fraction); + // --------------------------------------------------------------------------- // Optional transformation structs @@ -591,25 +610,32 @@ typedef struct avifContentLightLevelInformationBox struct avifImage; -// Gain map metadata, to apply the gain map. Fully applying the gain map to the base -// image results in the alternate image. -// All field pairs ending with 'N' and 'D' are fractional values (numerator and denominator). -typedef struct avifGainMapMetadata +// Gain map image and associated metadata. +// Must be allocated by calling avifGainMapCreate(). +typedef struct avifGainMap { + // Gain map pixels. + // Owned by the avifGainMap and gets freed when calling avifGainMapDestroy(). + // Used fields: width, height, depth, yuvFormat, yuvRange, + // yuvChromaSamplePosition, yuvPlanes, yuvRowBytes, imageOwnsYUVPlanes, + // matrixCoefficients. The colorPrimaries and transferCharacteristics fields + // shall be 2. Other fields are ignored. + struct avifImage * image; + + // Gain map metadata used to interpret and apply the gain map pixel data. + // When encoding an image grid, all metadata below shall be identical for all + // cells. + // Parameters for converting the gain map from its image encoding to log2 space. // gainMapLog2 = lerp(gainMapMin, gainMapMax, pow(gainMapEncoded, gainMapGamma)); // where 'lerp' is a linear interpolation function. - // Minimum value in the gain map, log2-encoded, per RGB channel. - int32_t gainMapMinN[3]; - uint32_t gainMapMinD[3]; + avifSignedFraction gainMapMin[3]; // Maximum value in the gain map, log2-encoded, per RGB channel. - int32_t gainMapMaxN[3]; - uint32_t gainMapMaxD[3]; + avifSignedFraction gainMapMax[3]; // Gain map gamma value with which the gain map was encoded, per RGB channel. // For decoding, the inverse value (1/gamma) should be used. - uint32_t gainMapGammaN[3]; - uint32_t gainMapGammaD[3]; + avifUnsignedFraction gainMapGamma[3]; // Parameters used in gain map computation/tone mapping to avoid numerical // instability. @@ -618,16 +644,9 @@ typedef struct avifGainMapMetadata // (see below). // Offset constants for the base image, per RGB channel. - int32_t baseOffsetN[3]; - uint32_t baseOffsetD[3]; + avifSignedFraction baseOffset[3]; // Offset constants for the alternate image, per RGB channel. - int32_t alternateOffsetN[3]; - uint32_t alternateOffsetD[3]; - - // ----------------------------------------------------------------------- - - // Parameters below can be manually tuned after the gain map has been - // created. + avifSignedFraction alternateOffset[3]; // Log2-encoded HDR headroom of the base and alternate images respectively. // If baseHdrHeadroom is < alternateHdrHeadroom, the result of tone mapping @@ -648,34 +667,13 @@ typedef struct avifGainMapMetadata // f = clamp((H - baseHdrHeadroom) / // (alternateHdrHeadroom - baseHdrHeadroom), 0, 1); // w = sign(alternateHdrHeadroom - baseHdrHeadroom) * f - uint32_t baseHdrHeadroomN; - uint32_t baseHdrHeadroomD; - uint32_t alternateHdrHeadroomN; - uint32_t alternateHdrHeadroomD; + avifUnsignedFraction baseHdrHeadroom; + avifUnsignedFraction alternateHdrHeadroom; // True if tone mapping should be performed in the color space of the // base image. If false, the color space of the alternate image should // be used. avifBool useBaseColorSpace; -} avifGainMapMetadata; - -// Gain map image and associated metadata. -// Must be allocated by calling avifGainMapCreate(). -typedef struct avifGainMap -{ - // Gain map pixels. - // Owned by the avifGainMap and gets freed when calling avifGainMapDestroy(). - // Used fields: width, height, depth, yuvFormat, yuvRange, - // yuvChromaSamplePosition, yuvPlanes, yuvRowBytes, imageOwnsYUVPlanes, - // matrixCoefficients. The colorPrimaries and transferCharacteristics fields - // shall be 2. Other fields are ignored. - struct avifImage * image; - - // When encoding an image grid, all metadata below shall be identical for all - // cells. - - // Gain map metadata used to interpret and apply the gain map pixel data. - avifGainMapMetadata metadata; // Colorimetry of the alternate image (ICC profile and/or CICP information // of the alternate image that the gain map was created from). @@ -702,29 +700,6 @@ AVIF_API avifGainMap * avifGainMapCreate(void); // Frees a gain map, including the 'image' field if non NULL. AVIF_API void avifGainMapDestroy(avifGainMap * gainMap); -// Same as avifGainMapMetadata, but with fields of type double instead of uint32_t fractions. -// Use avifGainMapMetadataDoubleToFractions() to convert this to a avifGainMapMetadata. -// See avifGainMapMetadata for detailed descriptions of fields. -typedef struct avifGainMapMetadataDouble -{ - double gainMapMin[3]; - double gainMapMax[3]; - double gainMapGamma[3]; - double baseOffset[3]; - double alternateOffset[3]; - double baseHdrHeadroom; - double alternateHdrHeadroom; - avifBool useBaseColorSpace; -} avifGainMapMetadataDouble; - -// Converts a avifGainMapMetadataDouble to avifGainMapMetadata by converting double values -// to the closest uint32_t fractions. -// Returns AVIF_FALSE if some field values are < 0 or > UINT32_MAX. -AVIF_NODISCARD AVIF_API avifBool avifGainMapMetadataDoubleToFractions(avifGainMapMetadata * dst, const avifGainMapMetadataDouble * src); -// Converts a avifGainMapMetadata to avifGainMapMetadataDouble by converting fractions to double values. -// Returns AVIF_FALSE if some denominators are zero. -AVIF_NODISCARD AVIF_API avifBool avifGainMapMetadataFractionsToDouble(avifGainMapMetadataDouble * dst, const avifGainMapMetadata * src); - #endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP // --------------------------------------------------------------------------- diff --git a/include/avif/avif_cxx.h b/include/avif/avif_cxx.h index 8a9fd2bfae..176157c808 100644 --- a/include/avif/avif_cxx.h +++ b/include/avif/avif_cxx.h @@ -21,6 +21,9 @@ struct UniquePtrDeleter void operator()(avifEncoder * encoder) const { avifEncoderDestroy(encoder); } void operator()(avifDecoder * decoder) const { avifDecoderDestroy(decoder); } void operator()(avifImage * image) const { avifImageDestroy(image); } +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + void operator()(avifGainMap * gainMap) const { avifGainMapDestroy(gainMap); } +#endif }; // Use these unique_ptr to ensure the structs are automatically destroyed. @@ -28,6 +31,10 @@ using EncoderPtr = std::unique_ptr; using DecoderPtr = std::unique_ptr; using ImagePtr = std::unique_ptr; +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) +using GainMapPtr = std::unique_ptr; +#endif + } // namespace avif #endif // AVIF_AVIF_CXX_H diff --git a/include/avif/internal.h b/include/avif/internal.h index b35c50c074..c044716ab6 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -139,13 +139,6 @@ AVIF_NODISCARD avifBool avifFractionCD(avifFraction * a, avifFraction * b); AVIF_NODISCARD avifBool avifFractionAdd(avifFraction a, avifFraction b, avifFraction * result); AVIF_NODISCARD avifBool avifFractionSub(avifFraction a, avifFraction b, avifFraction * result); -// Creates an int32 fraction that is approximately equal to 'v'. -// Returns AVIF_FALSE if 'v' is NaN or abs(v) is > INT32_MAX. -AVIF_NODISCARD avifBool avifDoubleToSignedFraction(double v, int32_t * numerator, uint32_t * denominator); -// Creates a uint32 fraction that is approximately equal to 'v'. -// Returns AVIF_FALSE if 'v' is < 0 or > UINT32_MAX or NaN. -AVIF_NODISCARD avifBool avifDoubleToUnsignedFraction(double v, uint32_t * numerator, uint32_t * denominator); - void avifImageSetDefaults(avifImage * image); // Copies all fields that do not need to be freed/allocated from srcImage to dstImage. void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage); @@ -786,7 +779,7 @@ 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); +avifResult avifGainMapValidateMetadata(const avifGainMap * gainMap, avifDiagnostics * diag); #endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP diff --git a/src/avif.c b/src/avif.c index defd78e84d..a81885800f 100644 --- a/src/avif.c +++ b/src/avif.c @@ -262,7 +262,16 @@ avifResult avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifP dstImage->gainMap = avifGainMapCreate(); AVIF_CHECKERR(dstImage->gainMap, AVIF_RESULT_OUT_OF_MEMORY); } - dstImage->gainMap->metadata = srcImage->gainMap->metadata; + for (int c = 0; c < 3; ++c) { + dstImage->gainMap->gainMapMin[c] = srcImage->gainMap->gainMapMin[c]; + dstImage->gainMap->gainMapMax[c] = srcImage->gainMap->gainMapMax[c]; + dstImage->gainMap->gainMapGamma[c] = srcImage->gainMap->gainMapGamma[c]; + dstImage->gainMap->baseOffset[c] = srcImage->gainMap->baseOffset[c]; + dstImage->gainMap->alternateOffset[c] = srcImage->gainMap->alternateOffset[c]; + } + dstImage->gainMap->baseHdrHeadroom = srcImage->gainMap->baseHdrHeadroom; + dstImage->gainMap->alternateHdrHeadroom = srcImage->gainMap->alternateHdrHeadroom; + dstImage->gainMap->useBaseColorSpace = srcImage->gainMap->useBaseColorSpace; AVIF_CHECKRES(avifRWDataSet(&dstImage->gainMap->altICC, srcImage->gainMap->altICC.data, srcImage->gainMap->altICC.size)); dstImage->gainMap->altColorPrimaries = srcImage->gainMap->altColorPrimaries; dstImage->gainMap->altTransferCharacteristics = srcImage->gainMap->altTransferCharacteristics; @@ -1192,17 +1201,18 @@ avifGainMap * avifGainMapCreate(void) gainMap->altTransferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED; gainMap->altMatrixCoefficients = AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED; gainMap->altYUVRange = AVIF_RANGE_FULL; - gainMap->metadata.useBaseColorSpace = AVIF_TRUE; + gainMap->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; + gainMap->gainMapMin[i].d = 1; + gainMap->gainMapMax[i].d = 1; + gainMap->gainMapGamma[i].n = 1; + gainMap->gainMapGamma[i].d = 1; + gainMap->baseOffset[i].d = 1; + gainMap->alternateOffset[i].d = 1; + } + gainMap->baseHdrHeadroom.d = 1; + gainMap->alternateHdrHeadroom.d = 1; return gainMap; } diff --git a/src/gainmap.c b/src/gainmap.c index d36fe4a84c..70c0217d81 100644 --- a/src/gainmap.c +++ b/src/gainmap.c @@ -9,71 +9,44 @@ #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) -avifBool avifGainMapMetadataDoubleToFractions(avifGainMapMetadata * dst, const avifGainMapMetadataDouble * src) +static void avifGainMapSetDefaults(avifGainMap * gainMap) { - AVIF_CHECK(dst != NULL && src != NULL); - for (int i = 0; i < 3; ++i) { - AVIF_CHECK(avifDoubleToSignedFraction(src->gainMapMin[i], &dst->gainMapMinN[i], &dst->gainMapMinD[i])); - AVIF_CHECK(avifDoubleToSignedFraction(src->gainMapMax[i], &dst->gainMapMaxN[i], &dst->gainMapMaxD[i])); - AVIF_CHECK(avifDoubleToUnsignedFraction(src->gainMapGamma[i], &dst->gainMapGammaN[i], &dst->gainMapGammaD[i])); - AVIF_CHECK(avifDoubleToSignedFraction(src->baseOffset[i], &dst->baseOffsetN[i], &dst->baseOffsetD[i])); - AVIF_CHECK(avifDoubleToSignedFraction(src->alternateOffset[i], &dst->alternateOffsetN[i], &dst->alternateOffsetD[i])); - } - AVIF_CHECK(avifDoubleToUnsignedFraction(src->baseHdrHeadroom, &dst->baseHdrHeadroomN, &dst->baseHdrHeadroomD)); - AVIF_CHECK(avifDoubleToUnsignedFraction(src->alternateHdrHeadroom, &dst->alternateHdrHeadroomN, &dst->alternateHdrHeadroomD)); - dst->useBaseColorSpace = src->useBaseColorSpace; - return AVIF_TRUE; + gainMap->gainMapMin[i] = (avifSignedFraction) { 1, 1 }; + gainMap->gainMapMax[i] = (avifSignedFraction) { 1, 1 }; + gainMap->baseOffset[i] = (avifSignedFraction) { 1, 64 }; + gainMap->alternateOffset[i] = (avifSignedFraction) { 1, 64 }; + gainMap->gainMapGamma[i] = (avifUnsignedFraction) { 1, 1 }; + } + gainMap->baseHdrHeadroom = (avifUnsignedFraction) { 0, 1 }; + gainMap->alternateHdrHeadroom = (avifUnsignedFraction) { 1, 1 }; + gainMap->useBaseColorSpace = AVIF_TRUE; } -avifBool avifGainMapMetadataFractionsToDouble(avifGainMapMetadataDouble * dst, const avifGainMapMetadata * src) +static float avifSignedFractionToFloat(avifSignedFraction f) { - AVIF_CHECK(dst != NULL && src != NULL); - - AVIF_CHECK(src->baseHdrHeadroomD != 0); - AVIF_CHECK(src->alternateHdrHeadroomD != 0); - for (int i = 0; i < 3; ++i) { - AVIF_CHECK(src->gainMapMaxD[i] != 0); - AVIF_CHECK(src->gainMapGammaD[i] != 0); - AVIF_CHECK(src->gainMapMinD[i] != 0); - AVIF_CHECK(src->baseOffsetD[i] != 0); - AVIF_CHECK(src->alternateOffsetD[i] != 0); + if (f.d == 0) { + return 0.0f; } - - for (int i = 0; i < 3; ++i) { - dst->gainMapMin[i] = (double)src->gainMapMinN[i] / src->gainMapMinD[i]; - dst->gainMapMax[i] = (double)src->gainMapMaxN[i] / src->gainMapMaxD[i]; - dst->gainMapGamma[i] = (double)src->gainMapGammaN[i] / src->gainMapGammaD[i]; - dst->baseOffset[i] = (double)src->baseOffsetN[i] / src->baseOffsetD[i]; - dst->alternateOffset[i] = (double)src->alternateOffsetN[i] / src->alternateOffsetD[i]; - } - dst->baseHdrHeadroom = (double)src->baseHdrHeadroomN / src->baseHdrHeadroomD; - dst->alternateHdrHeadroom = (double)src->alternateHdrHeadroomN / src->alternateHdrHeadroomD; - dst->useBaseColorSpace = src->useBaseColorSpace; - return AVIF_TRUE; + return (float)f.n / f.d; } -static void avifGainMapMetadataDoubleSetDefaults(avifGainMapMetadataDouble * metadata) +static float avifUnsignedFractionToFloat(avifUnsignedFraction f) { - memset(metadata, 0, sizeof(*metadata)); - for (int i = 0; i < 3; ++i) { - metadata->baseOffset[i] = 0.015625; // 1/64 - metadata->alternateOffset[i] = 0.015625; // 1/64 - metadata->gainMapGamma[i] = 1.0; + if (f.d == 0) { + return 0.0f; } - metadata->baseHdrHeadroom = 0.0; - metadata->alternateHdrHeadroom = 1.0; - metadata->useBaseColorSpace = AVIF_TRUE; + return (float)f.n / f.d; } // --------------------------------------------------------------------------- // Apply a gain map. // Returns a weight in [-1.0, 1.0] that represents how much the gain map should be applied. -static float avifGetGainMapWeight(float hdrHeadroom, const avifGainMapMetadataDouble * metadata) +static float avifGetGainMapWeight(float hdrHeadroom, const avifGainMap * gainMap) { - const float baseHdrHeadroom = (float)metadata->baseHdrHeadroom; - const float alternateHdrHeadroom = (float)metadata->alternateHdrHeadroom; + const float baseHdrHeadroom = avifUnsignedFractionToFloat(gainMap->baseHdrHeadroom); + const float alternateHdrHeadroom = avifUnsignedFractionToFloat(gainMap->alternateHdrHeadroom); if (baseHdrHeadroom == alternateHdrHeadroom) { // Do not apply the gain map if the HDR headroom is the same. // This case is not handled in the specification and does not make practical sense. @@ -104,6 +77,8 @@ avifResult avifRGBImageApplyGainMap(const avifRGBImage * baseImage, { avifDiagnosticsClearError(diag); + AVIF_CHECKRES(avifGainMapValidateMetadata(gainMap, diag)); + if (hdrHeadroom < 0.0f) { avifDiagnosticsPrintf(diag, "hdrHeadroom should be >= 0, got %f", hdrHeadroom); return AVIF_RESULT_INVALID_ARGUMENT; @@ -113,22 +88,10 @@ avifResult avifRGBImageApplyGainMap(const avifRGBImage * baseImage, return AVIF_RESULT_INVALID_ARGUMENT; } - avifGainMapMetadataDouble metadata; - if (!avifGainMapMetadataFractionsToDouble(&metadata, &gainMap->metadata)) { - avifDiagnosticsPrintf(diag, "Invalid gain map metadata, a denominator value is zero"); - return AVIF_RESULT_INVALID_ARGUMENT; - } - for (int i = 0; i < 3; ++i) { - if (metadata.gainMapGamma[i] <= 0) { - avifDiagnosticsPrintf(diag, "Invalid gain map metadata, gamma should be strictly positive"); - return AVIF_RESULT_INVALID_ARGUMENT; - } - } - const uint32_t width = baseImage->width; const uint32_t height = baseImage->height; - const avifBool useBaseColorSpace = gainMap->metadata.useBaseColorSpace; + const avifBool useBaseColorSpace = gainMap->useBaseColorSpace; const avifColorPrimaries gainMapMathPrimaries = (useBaseColorSpace || (gainMap->altColorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED)) ? baseColorPrimaries : gainMap->altColorPrimaries; @@ -147,7 +110,7 @@ avifResult avifRGBImageApplyGainMap(const avifRGBImage * baseImage, // --- After this point, the function should exit with 'goto cleanup' to free allocated pixels. - const float weight = avifGetGainMapWeight(hdrHeadroom, &metadata); + const float weight = avifGetGainMapWeight(hdrHeadroom, gainMap); // Early exit if the gain map does not need to be applied and the pixel format is the same. if (weight == 0.0f && outputTransferCharacteristics == baseTransferCharacteristics && @@ -250,9 +213,22 @@ avifResult avifRGBImageApplyGainMap(const avifRGBImage * baseImage, float rgbMaxLinear = 0; // Max tone mapped pixel value across R, G and B channels. float rgbSumLinear = 0; // Sum of max(r, g, b) for mapped pixels. - const float gammaInv[3] = { 1.0f / (float)metadata.gainMapGamma[0], - 1.0f / (float)metadata.gainMapGamma[1], - 1.0f / (float)metadata.gainMapGamma[2] }; + // The gain map metadata contains the encoding gamma, and 1/gamma should be used for decoding. + const float gammaInv[3] = { 1.0f / avifUnsignedFractionToFloat(gainMap->gainMapGamma[0]), + 1.0f / avifUnsignedFractionToFloat(gainMap->gainMapGamma[1]), + 1.0f / avifUnsignedFractionToFloat(gainMap->gainMapGamma[2]) }; + const float gainMapMin[3] = { avifSignedFractionToFloat(gainMap->gainMapMin[0]), + avifSignedFractionToFloat(gainMap->gainMapMin[1]), + avifSignedFractionToFloat(gainMap->gainMapMin[2]) }; + const float gainMapMax[3] = { avifSignedFractionToFloat(gainMap->gainMapMax[0]), + avifSignedFractionToFloat(gainMap->gainMapMax[1]), + avifSignedFractionToFloat(gainMap->gainMapMax[2]) }; + const float baseOffset[3] = { avifSignedFractionToFloat(gainMap->baseOffset[0]), + avifSignedFractionToFloat(gainMap->baseOffset[1]), + avifSignedFractionToFloat(gainMap->baseOffset[2]) }; + const float alternateOffset[3] = { avifSignedFractionToFloat(gainMap->alternateOffset[0]), + avifSignedFractionToFloat(gainMap->alternateOffset[1]), + avifSignedFractionToFloat(gainMap->alternateOffset[2]) }; for (uint32_t j = 0; j < height; ++j) { for (uint32_t i = 0; i < width; ++i) { float basePixelRGBA[4]; @@ -278,10 +254,8 @@ avifResult avifRGBImageApplyGainMap(const avifRGBImage * baseImage, const float gainMapValue = gainMapRGBA[c]; // Undo gamma & affine transform; the result is in log2 space. - const float gainMapLog2 = - lerp((float)metadata.gainMapMin[c], (float)metadata.gainMapMax[c], powf(gainMapValue, gammaInv[c])); - const float toneMappedLinear = (baseLinear + (float)metadata.baseOffset[c]) * exp2f(gainMapLog2 * weight) - - (float)metadata.alternateOffset[c]; + const float gainMapLog2 = lerp(gainMapMin[c], gainMapMax[c], powf(gainMapValue, gammaInv[c])); + const float toneMappedLinear = (baseLinear + baseOffset[c]) * exp2f(gainMapLog2 * weight) - alternateOffset[c]; if (toneMappedLinear > rgbMaxLinear) { rgbMaxLinear = toneMappedLinear; @@ -441,21 +415,25 @@ avifResult avifFindMinMaxWithoutOutliers(const float * gainMapF, int numPixels, return AVIF_RESULT_OK; } -avifResult avifGainMapMetadataValidate(const avifGainMapMetadata * metadata, avifDiagnostics * diag) +avifResult avifGainMapValidateMetadata(const avifGainMap * gainMap, 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"); + if (gainMap->gainMapMin[i].d == 0 || gainMap->gainMapMax[i].d == 0 || gainMap->gainMapGamma[i].d == 0 || + gainMap->baseOffset[i].d == 0 || gainMap->alternateOffset[i].d == 0) { + avifDiagnosticsPrintf(diag, "Per-channel denominator is 0 in gain map metadata"); + return AVIF_RESULT_INVALID_ARGUMENT; + } + if (gainMap->gainMapGamma[i].n == 0) { + avifDiagnosticsPrintf(diag, "Per-channel gamma is 0 in gain map metadata"); return AVIF_RESULT_INVALID_ARGUMENT; } } - if (metadata->baseHdrHeadroomD == 0 || metadata->alternateHdrHeadroomD == 0) { - avifDiagnosticsPrintf(diag, "Headroom denominator is 0 in avifGainMapMetadata"); + if (gainMap->baseHdrHeadroom.d == 0 || gainMap->alternateHdrHeadroom.d == 0) { + avifDiagnosticsPrintf(diag, "Headroom denominator is 0 in gain map metadata"); return AVIF_RESULT_INVALID_ARGUMENT; } - if (metadata->useBaseColorSpace != 0 && metadata->useBaseColorSpace != 1) { - avifDiagnosticsPrintf(diag, "useBaseColorSpace is %d in avifGainMapMetadata", metadata->useBaseColorSpace); + if (gainMap->useBaseColorSpace != 0 && gainMap->useBaseColorSpace != 1) { + avifDiagnosticsPrintf(diag, "useBaseColorSpace is %d in gain map metadata", gainMap->useBaseColorSpace); return AVIF_RESULT_INVALID_ARGUMENT; } return AVIF_RESULT_OK; @@ -560,9 +538,8 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, } } - avifGainMapMetadataDouble gainMapMetadata; - avifGainMapMetadataDoubleSetDefaults(&gainMapMetadata); - gainMapMetadata.useBaseColorSpace = (gainMapMathPrimaries == baseColorPrimaries); + avifGainMapSetDefaults(gainMap); + gainMap->useBaseColorSpace = (gainMapMathPrimaries == baseColorPrimaries); float (*baseGammaToLinear)(float) = avifTransferCharacteristicsGetGammaToLinearFunction(baseTransferCharacteristics); float (*altGammaToLinear)(float) = avifTransferCharacteristicsGetGammaToLinearFunction(altTransferCharacteristics); @@ -571,7 +548,7 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, double rgbConversionCoeffs[3][3]; if (colorSpacesDiffer) { - if (gainMapMetadata.useBaseColorSpace) { + if (gainMap->useBaseColorSpace) { if (!avifColorPrimariesComputeRGBToRGBMatrix(altColorPrimaries, baseColorPrimaries, rgbConversionCoeffs)) { avifDiagnosticsPrintf(diag, "Unsupported RGB color space conversion"); res = AVIF_RESULT_NOT_IMPLEMENTED; @@ -586,24 +563,31 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, } } + float baseOffset[3] = { avifSignedFractionToFloat(gainMap->baseOffset[0]), + avifSignedFractionToFloat(gainMap->baseOffset[1]), + avifSignedFractionToFloat(gainMap->baseOffset[2]) }; + float alternateOffset[3] = { avifSignedFractionToFloat(gainMap->alternateOffset[0]), + avifSignedFractionToFloat(gainMap->alternateOffset[1]), + avifSignedFractionToFloat(gainMap->alternateOffset[2]) }; + // If we are converting from one colorspace to another, some RGB values may be negative and an offset must be added to // avoid clamping (although the choice of color space to do the gain map computation with // avifChooseColorSpaceForGainMapMath() should mostly avoid this). if (colorSpacesDiffer) { // Color convert pure red, pure green and pure blue in turn and see if they result in negative values. - float rgba[4] = { 0 }; - float channelMin[3] = { 0 }; + float rgba[4] = { 0.0f }; + float channelMin[3] = { 0.0f }; for (int j = 0; j < height; ++j) { for (int i = 0; i < width; ++i) { - avifGetRGBAPixel(gainMapMetadata.useBaseColorSpace ? altRgbImage : baseRgbImage, + avifGetRGBAPixel(gainMap->useBaseColorSpace ? altRgbImage : baseRgbImage, i, j, - gainMapMetadata.useBaseColorSpace ? &altRGBInfo : &baseRGBInfo, + gainMap->useBaseColorSpace ? &altRGBInfo : &baseRGBInfo, rgba); // Convert to linear. for (int c = 0; c < 3; ++c) { - if (gainMapMetadata.useBaseColorSpace) { + if (gainMap->useBaseColorSpace) { rgba[c] = altGammaToLinear(rgba[c]); } else { rgba[c] = baseGammaToLinear(rgba[c]); @@ -622,10 +606,10 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, const float maxOffset = 0.1f; if (channelMin[c] < -kEpsilon) { // Increase the offset to avoid negative values. - if (gainMapMetadata.useBaseColorSpace) { - gainMapMetadata.alternateOffset[c] = AVIF_MIN(gainMapMetadata.alternateOffset[c] - channelMin[c], maxOffset); + if (gainMap->useBaseColorSpace) { + alternateOffset[c] = AVIF_MIN(alternateOffset[c] - channelMin[c], maxOffset); } else { - gainMapMetadata.baseOffset[c] = AVIF_MIN(gainMapMetadata.baseOffset[c] - channelMin[c], maxOffset); + baseOffset[c] = AVIF_MIN(baseOffset[c] - channelMin[c], maxOffset); } } } @@ -648,7 +632,7 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, } if (colorSpacesDiffer) { - if (gainMapMetadata.useBaseColorSpace) { + if (gainMap->useBaseColorSpace) { // convert altRGBA to baseRGBA's color space avifLinearRGBConvertColorSpace(altRGBA, rgbConversionCoeffs); } else { @@ -671,7 +655,7 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, if (alt > altMax) { altMax = alt; } - const float ratio = (alt + (float)gainMapMetadata.alternateOffset[c]) / (base + (float)gainMapMetadata.baseOffset[c]); + const float ratio = (alt + alternateOffset[c]) / (base + baseOffset[c]); const float ratioLog2 = log2f(AVIF_MAX(ratio, kEpsilon)); gainMapF[c][j * width + i] = ratioLog2; } @@ -679,13 +663,18 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, } // Populate the gain map metadata's headrooms. - gainMapMetadata.baseHdrHeadroom = log2f(AVIF_MAX(baseMax, kEpsilon)); - gainMapMetadata.alternateHdrHeadroom = log2f(AVIF_MAX(altMax, kEpsilon)); + const double baseHeadroom = log2f(AVIF_MAX(baseMax, kEpsilon)); + const double alternateHeadroom = log2f(AVIF_MAX(altMax, kEpsilon)); + if (!avifDoubleToUnsignedFraction(baseHeadroom, &gainMap->baseHdrHeadroom) || + !avifDoubleToUnsignedFraction(alternateHeadroom, &gainMap->alternateHdrHeadroom)) { + res = AVIF_RESULT_INVALID_ARGUMENT; + goto cleanup; + } // Multiply the gainmap by sign(alternateHdrHeadroom - baseHdrHeadroom), to // ensure that it stores the log-ratio of the HDR representation to the SDR // representation. - if (gainMapMetadata.alternateHdrHeadroom < gainMapMetadata.baseHdrHeadroom) { + if (alternateHeadroom < baseHeadroom) { for (int c = 0; c < numGainMapChannels; ++c) { for (int j = 0; j < height; ++j) { for (int i = 0; i < width; ++i) { @@ -707,15 +696,13 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, // Populate the gain map metadata's min and max values. for (int c = 0; c < 3; ++c) { - gainMapMetadata.gainMapMin[c] = gainMapMinLog2[singleChannel ? 0 : c]; - gainMapMetadata.gainMapMax[c] = gainMapMaxLog2[singleChannel ? 0 : c]; - } - - // All of gainMapMetadata has been populated now (except for gamma which is left to the default - // value), so convert to the fraction form in which it will be stored. - if (!avifGainMapMetadataDoubleToFractions(&gainMap->metadata, &gainMapMetadata)) { - res = AVIF_RESULT_UNKNOWN_ERROR; - goto cleanup; + if (!avifDoubleToSignedFraction(gainMapMinLog2[singleChannel ? 0 : c], &gainMap->gainMapMin[c]) || + !avifDoubleToSignedFraction(gainMapMaxLog2[singleChannel ? 0 : c], &gainMap->gainMapMax[c]) || + !avifDoubleToSignedFraction(alternateOffset[c], &gainMap->alternateOffset[c]) || + !avifDoubleToSignedFraction(baseOffset[c], &gainMap->baseOffset[c])) { + res = AVIF_RESULT_INVALID_ARGUMENT; + goto cleanup; + } } // Scale the gain map values to map [min, max] range to [0, 1]. @@ -724,12 +711,13 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, if (range <= 0.0f) { continue; } + const float gainMapGamma = avifUnsignedFractionToFloat(gainMap->gainMapGamma[c]); for (int j = 0; j < height; ++j) { for (int i = 0; i < width; ++i) { // Remap [min; max] range to [0; 1] const float v = AVIF_CLAMP(gainMapF[c][j * width + i], gainMapMinLog2[c], gainMapMaxLog2[c]); - gainMapF[c][j * width + i] = powf((v - gainMapMinLog2[c]) / range, (float)gainMapMetadata.gainMapGamma[c]); + gainMapF[c][j * width + i] = powf((v - gainMapMinLog2[c]) / range, gainMapGamma); } } } diff --git a/src/read.c b/src/read.c index 3afd4a5def..d711cb95cf 100644 --- a/src/read.c +++ b/src/read.c @@ -1958,7 +1958,7 @@ static avifBool avifParseImageGridBox(avifImageGrid * grid, #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) -static avifBool avifParseGainMapMetadata(avifGainMapMetadata * metadata, avifROStream * s) +static avifBool avifParseGainMapMetadata(avifGainMap * gainMap, avifROStream * s) { uint32_t isMultichannel; AVIF_CHECK(avifROStreamReadBitsU32(s, &isMultichannel, 1)); // unsigned int(1) is_multichannel; @@ -1966,47 +1966,42 @@ static avifBool avifParseGainMapMetadata(avifGainMapMetadata * metadata, avifROS uint32_t useBaseColorSpace; AVIF_CHECK(avifROStreamReadBitsU32(s, &useBaseColorSpace, 1)); // unsigned int(1) use_base_colour_space; - metadata->useBaseColorSpace = useBaseColorSpace ? AVIF_TRUE : AVIF_FALSE; + gainMap->useBaseColorSpace = useBaseColorSpace ? AVIF_TRUE : AVIF_FALSE; uint32_t reserved; AVIF_CHECK(avifROStreamReadBitsU32(s, &reserved, 6)); // unsigned int(6) reserved; - AVIF_CHECK(avifROStreamReadU32(s, &metadata->baseHdrHeadroomN)); // unsigned int(32) base_hdr_headroom_numerator; - AVIF_CHECK(avifROStreamReadU32(s, &metadata->baseHdrHeadroomD)); // unsigned int(32) base_hdr_headroom_denominator; - AVIF_CHECK(avifROStreamReadU32(s, &metadata->alternateHdrHeadroomN)); // unsigned int(32) alternate_hdr_headroom_numerator; - AVIF_CHECK(avifROStreamReadU32(s, &metadata->alternateHdrHeadroomD)); // unsigned int(32) alternate_hdr_headroom_denominator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->baseHdrHeadroom.n)); // unsigned int(32) base_hdr_headroom_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->baseHdrHeadroom.d)); // unsigned int(32) base_hdr_headroom_denominator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->alternateHdrHeadroom.n)); // unsigned int(32) alternate_hdr_headroom_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->alternateHdrHeadroom.d)); // unsigned int(32) alternate_hdr_headroom_denominator; for (int c = 0; c < channelCount; ++c) { - AVIF_CHECK(avifROStreamReadU32(s, (uint32_t *)&metadata->gainMapMinN[c])); // int(32) gain_map_min_numerator; - AVIF_CHECK(avifROStreamReadU32(s, &metadata->gainMapMinD[c])); // unsigned int(32) gain_map_min_denominator; - AVIF_CHECK(avifROStreamReadU32(s, (uint32_t *)&metadata->gainMapMaxN[c])); // int(32) gain_map_max_numerator; - AVIF_CHECK(avifROStreamReadU32(s, &metadata->gainMapMaxD[c])); // unsigned int(32) gain_map_max_denominator; - AVIF_CHECK(avifROStreamReadU32(s, &metadata->gainMapGammaN[c])); // unsigned int(32) gamma_numerator; - AVIF_CHECK(avifROStreamReadU32(s, &metadata->gainMapGammaD[c])); // unsigned int(32) gamma_denominator; - AVIF_CHECK(avifROStreamReadU32(s, (uint32_t *)&metadata->baseOffsetN[c])); // int(32) base_offset_numerator; - AVIF_CHECK(avifROStreamReadU32(s, &metadata->baseOffsetD[c])); // unsigned int(32) base_offset_denominator; - AVIF_CHECK(avifROStreamReadU32(s, (uint32_t *)&metadata->alternateOffsetN[c])); // int(32) alternate_offset_numerator; - AVIF_CHECK(avifROStreamReadU32(s, &metadata->alternateOffsetD[c])); // unsigned int(32) alternate_offset_denominator; + AVIF_CHECK(avifROStreamReadU32(s, (uint32_t *)&gainMap->gainMapMin[c].n)); // int(32) gain_map_min_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->gainMapMin[c].d)); // unsigned int(32) gain_map_min_denominator; + AVIF_CHECK(avifROStreamReadU32(s, (uint32_t *)&gainMap->gainMapMax[c].n)); // int(32) gain_map_max_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->gainMapMax[c].d)); // unsigned int(32) gain_map_max_denominator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->gainMapGamma[c].n)); // unsigned int(32) gamma_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->gainMapGamma[c].d)); // unsigned int(32) gamma_denominator; + AVIF_CHECK(avifROStreamReadU32(s, (uint32_t *)&gainMap->baseOffset[c].n)); // int(32) base_offset_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->baseOffset[c].d)); // unsigned int(32) base_offset_denominator; + AVIF_CHECK(avifROStreamReadU32(s, (uint32_t *)&gainMap->alternateOffset[c].n)); // int(32) alternate_offset_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->alternateOffset[c].d)); // unsigned int(32) alternate_offset_denominator; } // Fill the remaining values by copying those from the first channel. for (int c = channelCount; c < 3; ++c) { - metadata->gainMapMinN[c] = metadata->gainMapMinN[0]; - metadata->gainMapMinD[c] = metadata->gainMapMinD[0]; - metadata->gainMapMaxN[c] = metadata->gainMapMaxN[0]; - metadata->gainMapMaxD[c] = metadata->gainMapMaxD[0]; - metadata->gainMapGammaN[c] = metadata->gainMapGammaN[0]; - metadata->gainMapGammaD[c] = metadata->gainMapGammaD[0]; - metadata->baseOffsetN[c] = metadata->baseOffsetN[0]; - metadata->baseOffsetD[c] = metadata->baseOffsetD[0]; - metadata->alternateOffsetN[c] = metadata->alternateOffsetN[0]; - metadata->alternateOffsetD[c] = metadata->alternateOffsetD[0]; + gainMap->gainMapMin[c] = gainMap->gainMapMin[0]; + gainMap->gainMapMax[c] = gainMap->gainMapMax[0]; + gainMap->gainMapGamma[c] = gainMap->gainMapGamma[0]; + gainMap->baseOffset[c] = gainMap->baseOffset[0]; + gainMap->alternateOffset[c] = gainMap->alternateOffset[0]; } return AVIF_TRUE; } // If the gain map's version or minimum_version tag is not supported, returns AVIF_RESULT_NOT_IMPLEMENTED. -static avifResult avifParseToneMappedImageBox(avifGainMapMetadata * metadata, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) +static avifResult avifParseToneMappedImageBox(avifGainMap * gainMap, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) { BEGIN_STREAM(s, raw, rawLen, diag, "Box[tmap]"); @@ -2028,7 +2023,7 @@ static avifResult avifParseToneMappedImageBox(avifGainMapMetadata * metadata, co AVIF_CHECKERR(avifROStreamReadU16(&s, &writerVersion), AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE); // unsigned int(16) writer_version; AVIF_CHECKERR(writerVersion >= minimumVersion, AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE); - AVIF_CHECKERR(avifParseGainMapMetadata(metadata, &s), AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE); + AVIF_CHECKERR(avifParseGainMapMetadata(gainMap, &s), AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE); if (writerVersion <= supportedMetadataVersion) { AVIF_CHECKERR(avifROStreamRemainingBytes(&s) == 0, AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE); @@ -5487,7 +5482,7 @@ avifResult avifDecoderReset(avifDecoder * decoder) 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->metadata, tmapData.data, tmapData.size, data->diag); + 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); diff --git a/src/utils.c b/src/utils.c index bcb191ae62..8578d30631 100644 --- a/src/utils.c +++ b/src/utils.c @@ -231,7 +231,7 @@ static avifBool avifDoubleToUnsignedFractionImpl(double v, uint32_t maxNumerator } // Maximum denominator: makes sure that the numerator is <= maxNumerator and the denominator is <= UINT32_MAX. - const uint64_t maxD = (v <= 1) ? UINT32_MAX : (uint64_t)floor(maxNumerator / v); + const uint32_t maxD = (v <= 1) ? UINT32_MAX : (uint32_t)floor(maxNumerator / v); // Find the best approximation of v as a fraction using continued fractions, see // https://en.wikipedia.org/wiki/Continued_fraction @@ -252,7 +252,7 @@ static avifBool avifDoubleToUnsignedFractionImpl(double v, uint32_t maxNumerator } currentV = 1.0 / currentV; const double newD = previousD + floor(currentV) * (*denominator); - if (newD > maxD) { + if (newD > (double)maxD) { // This is the best we can do with a denominator <= max_d. return AVIF_TRUE; } @@ -269,20 +269,20 @@ static avifBool avifDoubleToUnsignedFractionImpl(double v, uint32_t maxNumerator return AVIF_TRUE; } -avifBool avifDoubleToSignedFraction(double v, int32_t * numerator, uint32_t * denominator) +avifBool avifDoubleToSignedFraction(double v, avifSignedFraction * fraction) { uint32_t positive_numerator; - if (!avifDoubleToUnsignedFractionImpl(fabs(v), INT32_MAX, &positive_numerator, denominator)) { + if (!avifDoubleToUnsignedFractionImpl(fabs(v), INT32_MAX, &positive_numerator, &fraction->d)) { return AVIF_FALSE; } - *numerator = (int32_t)positive_numerator; + fraction->n = (int32_t)positive_numerator; if (v < 0) { - *numerator *= -1; + fraction->n *= -1; } return AVIF_TRUE; } -avifBool avifDoubleToUnsignedFraction(double v, uint32_t * numerator, uint32_t * denominator) +avifBool avifDoubleToUnsignedFraction(double v, avifUnsignedFraction * fraction) { - return avifDoubleToUnsignedFractionImpl(v, UINT32_MAX, numerator, denominator); + return avifDoubleToUnsignedFractionImpl(v, UINT32_MAX, &fraction->n, &fraction->d); } diff --git a/src/write.c b/src/write.c index 92f8539602..1587586617 100644 --- a/src/write.c +++ b/src/write.c @@ -891,32 +891,32 @@ static avifResult avifWriteGridPayload(avifRWData * data, uint32_t gridCols, uin #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) -static avifBool avifGainmapMetadataIdenticalChannels(const avifGainMapMetadata * metadata) -{ - return metadata->gainMapMinN[0] == metadata->gainMapMinN[1] && metadata->gainMapMinN[0] == metadata->gainMapMinN[2] && - metadata->gainMapMinD[0] == metadata->gainMapMinD[1] && metadata->gainMapMinD[0] == metadata->gainMapMinD[2] && - metadata->gainMapMaxN[0] == metadata->gainMapMaxN[1] && metadata->gainMapMaxN[0] == metadata->gainMapMaxN[2] && - metadata->gainMapMaxD[0] == metadata->gainMapMaxD[1] && metadata->gainMapMaxD[0] == metadata->gainMapMaxD[2] && - metadata->gainMapGammaN[0] == metadata->gainMapGammaN[1] && metadata->gainMapGammaN[0] == metadata->gainMapGammaN[2] && - metadata->gainMapGammaD[0] == metadata->gainMapGammaD[1] && metadata->gainMapGammaD[0] == metadata->gainMapGammaD[2] && - metadata->baseOffsetN[0] == metadata->baseOffsetN[1] && metadata->baseOffsetN[0] == metadata->baseOffsetN[2] && - metadata->baseOffsetD[0] == metadata->baseOffsetD[1] && metadata->baseOffsetD[0] == metadata->baseOffsetD[2] && - metadata->alternateOffsetN[0] == metadata->alternateOffsetN[1] && - metadata->alternateOffsetN[0] == metadata->alternateOffsetN[2] && - metadata->alternateOffsetD[0] == metadata->alternateOffsetD[1] && - metadata->alternateOffsetD[0] == metadata->alternateOffsetD[2]; +static avifBool avifGainMapIdenticalChannels(const avifGainMap * gainMap) +{ + return gainMap->gainMapMin[0].n == gainMap->gainMapMin[1].n && gainMap->gainMapMin[0].n == gainMap->gainMapMin[2].n && + gainMap->gainMapMin[0].d == gainMap->gainMapMin[1].d && gainMap->gainMapMin[0].d == gainMap->gainMapMin[2].d && + gainMap->gainMapMax[0].n == gainMap->gainMapMax[1].n && gainMap->gainMapMax[0].n == gainMap->gainMapMax[2].n && + gainMap->gainMapMax[0].d == gainMap->gainMapMax[1].d && gainMap->gainMapMax[0].d == gainMap->gainMapMax[2].d && + gainMap->gainMapGamma[0].n == gainMap->gainMapGamma[1].n && gainMap->gainMapGamma[0].n == gainMap->gainMapGamma[2].n && + gainMap->gainMapGamma[0].d == gainMap->gainMapGamma[1].d && gainMap->gainMapGamma[0].d == gainMap->gainMapGamma[2].d && + gainMap->baseOffset[0].n == gainMap->baseOffset[1].n && gainMap->baseOffset[0].n == gainMap->baseOffset[2].n && + gainMap->baseOffset[0].d == gainMap->baseOffset[1].d && gainMap->baseOffset[0].d == gainMap->baseOffset[2].d && + gainMap->alternateOffset[0].n == gainMap->alternateOffset[1].n && + gainMap->alternateOffset[0].n == gainMap->alternateOffset[2].n && + gainMap->alternateOffset[0].d == gainMap->alternateOffset[1].d && + gainMap->alternateOffset[0].d == gainMap->alternateOffset[2].d; } // Returns the number of bytes written by avifWriteGainmapMetadata(). -static avifBool avifGainmapMetadataSize(const avifGainMapMetadata * metadata) +static avifBool avifGainMapMetadataSize(const avifGainMap * gainMap) { - const uint8_t channelCount = avifGainmapMetadataIdenticalChannels(metadata) ? 1u : 3u; + const uint8_t channelCount = avifGainMapIdenticalChannels(gainMap) ? 1u : 3u; 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, avifDiagnostics * diag) +static avifResult avifWriteGainmapMetadata(avifRWStream * s, const avifGainMap * gainMap, avifDiagnostics * diag) { - AVIF_CHECKRES(avifGainMapMetadataValidate(metadata, diag)); + AVIF_CHECKRES(avifGainMapValidateMetadata(gainMap, diag)); const size_t offset = avifRWStreamOffset(s); // GainMapMetadata syntax as per clause C.2.2 of ISO 21496-1: @@ -928,37 +928,37 @@ static avifResult avifWriteGainmapMetadata(avifRWStream * s, const avifGainMapMe AVIF_CHECKRES(avifRWStreamWriteBits(s, writerVersion, 16)); // unsigned int(16) writer_version; if (minimumVersion == 0) { - const uint8_t channelCount = avifGainmapMetadataIdenticalChannels(metadata) ? 1u : 3u; - AVIF_CHECKRES(avifRWStreamWriteBits(s, channelCount == 3, 1)); // unsigned int(1) is_multichannel; - AVIF_CHECKRES(avifRWStreamWriteBits(s, metadata->useBaseColorSpace, 1)); // unsigned int(1) use_base_colour_space; - AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 6)); // unsigned int(6) reserved; + const uint8_t channelCount = avifGainMapIdenticalChannels(gainMap) ? 1u : 3u; + AVIF_CHECKRES(avifRWStreamWriteBits(s, channelCount == 3, 1)); // unsigned int(1) is_multichannel; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->useBaseColorSpace, 1)); // unsigned int(1) use_base_colour_space; + AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 6)); // unsigned int(6) reserved; - AVIF_CHECKRES(avifRWStreamWriteBits(s, metadata->baseHdrHeadroomN, 32)); // unsigned int(32) base_hdr_headroom_numerator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, metadata->baseHdrHeadroomD, 32)); // unsigned int(32) base_hdr_headroom_denominator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, metadata->alternateHdrHeadroomN, 32)); // unsigned int(32) alternate_hdr_headroom_numerator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, metadata->alternateHdrHeadroomD, 32)); // unsigned int(32) alternate_hdr_headroom_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->baseHdrHeadroom.n, 32)); // unsigned int(32) base_hdr_headroom_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->baseHdrHeadroom.d, 32)); // unsigned int(32) base_hdr_headroom_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->alternateHdrHeadroom.n, 32)); // unsigned int(32) alternate_hdr_headroom_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->alternateHdrHeadroom.d, 32)); // unsigned int(32) alternate_hdr_headroom_denominator; // GainMapChannel channels[channel_count]; for (int c = 0; c < channelCount; ++c) { // GainMapChannel syntax as per clause C.2.2 of ISO 21496-1: - AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)metadata->gainMapMinN[c], 32)); // int(32) gain_map_min_numerator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, metadata->gainMapMinD[c], 32)); // unsigned int(32) gain_map_min_denominator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)metadata->gainMapMaxN[c], 32)); // int(32) gain_map_max_numerator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, metadata->gainMapMaxD[c], 32)); // unsigned int(32) gain_map_max_denominator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, metadata->gainMapGammaN[c], 32)); // unsigned int(32) gamma_numerator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, metadata->gainMapGammaD[c], 32)); // unsigned int(32) gamma_denominator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)metadata->baseOffsetN[c], 32)); // int(32) base_offset_numerator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, metadata->baseOffsetD[c], 32)); // unsigned int(32) base_offset_denominator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)metadata->alternateOffsetN[c], 32)); // int(32) alternate_offset_numerator; - AVIF_CHECKRES(avifRWStreamWriteBits(s, metadata->alternateOffsetD[c], 32)); // unsigned int(32) alternate_offset_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainMap->gainMapMin[c].n, 32)); // int(32) gain_map_min_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapMin[c].d, 32)); // unsigned int(32) gain_map_min_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainMap->gainMapMax[c].n, 32)); // int(32) gain_map_max_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapMax[c].d, 32)); // unsigned int(32) gain_map_max_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapGamma[c].n, 32)); // unsigned int(32) gamma_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapGamma[c].d, 32)); // unsigned int(32) gamma_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainMap->baseOffset[c].n, 32)); // int(32) base_offset_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->baseOffset[c].d, 32)); // unsigned int(32) base_offset_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainMap->alternateOffset[c].n, 32)); // int(32) alternate_offset_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->alternateOffset[c].d, 32)); // unsigned int(32) alternate_offset_denominator; } } - AVIF_ASSERT_OR_RETURN(avifRWStreamOffset(s) == offset + avifGainmapMetadataSize(metadata)); + AVIF_ASSERT_OR_RETURN(avifRWStreamOffset(s) == offset + avifGainMapMetadataSize(gainMap)); return AVIF_RESULT_OK; } -static avifResult avifWriteToneMappedImagePayload(avifRWData * data, const avifGainMapMetadata * metadata, avifDiagnostics * diag) +static avifResult avifWriteToneMappedImagePayload(avifRWData * data, const avifGainMap * gainMap, avifDiagnostics * diag) { avifRWStream s; avifRWStreamStart(&s, data); @@ -967,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, diag)); // GainMapMetadata; + AVIF_CHECKRES(avifWriteGainmapMetadata(&s, gainMap, diag)); // GainMapMetadata; } avifRWStreamFinishWrite(&s); return AVIF_RESULT_OK; @@ -1688,26 +1688,24 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, avifDiagnosticsPrintf(&encoder->diag, "all cells should have the same alternate image metadata in the gain map"); return AVIF_RESULT_INVALID_IMAGE_GRID; } - const avifGainMapMetadata * firstMetadata = &firstGainMap->metadata; - const avifGainMapMetadata * cellMetadata = &cellGainMap->metadata; - if (cellMetadata->baseHdrHeadroomN != firstMetadata->baseHdrHeadroomN || - cellMetadata->baseHdrHeadroomD != firstMetadata->baseHdrHeadroomD || - cellMetadata->alternateHdrHeadroomN != firstMetadata->alternateHdrHeadroomN || - cellMetadata->alternateHdrHeadroomD != firstMetadata->alternateHdrHeadroomD) { + if (cellGainMap->baseHdrHeadroom.n != firstGainMap->baseHdrHeadroom.n || + cellGainMap->baseHdrHeadroom.d != firstGainMap->baseHdrHeadroom.d || + cellGainMap->alternateHdrHeadroom.n != firstGainMap->alternateHdrHeadroom.n || + cellGainMap->alternateHdrHeadroom.d != firstGainMap->alternateHdrHeadroom.d) { avifDiagnosticsPrintf(&encoder->diag, "all cells should have the same gain map metadata"); return AVIF_RESULT_INVALID_IMAGE_GRID; } for (int c = 0; c < 3; ++c) { - if (cellMetadata->gainMapMinN[c] != firstMetadata->gainMapMinN[c] || - cellMetadata->gainMapMinD[c] != firstMetadata->gainMapMinD[c] || - cellMetadata->gainMapMaxN[c] != firstMetadata->gainMapMaxN[c] || - cellMetadata->gainMapMaxD[c] != firstMetadata->gainMapMaxD[c] || - cellMetadata->gainMapGammaN[c] != firstMetadata->gainMapGammaN[c] || - cellMetadata->gainMapGammaD[c] != firstMetadata->gainMapGammaD[c] || - cellMetadata->baseOffsetN[c] != firstMetadata->baseOffsetN[c] || - cellMetadata->baseOffsetD[c] != firstMetadata->baseOffsetD[c] || - cellMetadata->alternateOffsetN[c] != firstMetadata->alternateOffsetN[c] || - cellMetadata->alternateOffsetD[c] != firstMetadata->alternateOffsetD[c]) { + if (cellGainMap->gainMapMin[c].n != firstGainMap->gainMapMin[c].n || + cellGainMap->gainMapMin[c].d != firstGainMap->gainMapMin[c].d || + cellGainMap->gainMapMax[c].n != firstGainMap->gainMapMax[c].n || + cellGainMap->gainMapMax[c].d != firstGainMap->gainMapMax[c].d || + cellGainMap->gainMapGamma[c].n != firstGainMap->gainMapGamma[c].n || + cellGainMap->gainMapGamma[c].d != firstGainMap->gainMapGamma[c].d || + cellGainMap->baseOffset[c].n != firstGainMap->baseOffset[c].n || + cellGainMap->baseOffset[c].d != firstGainMap->baseOffset[c].d || + cellGainMap->alternateOffset[c].n != firstGainMap->alternateOffset[c].n || + cellGainMap->alternateOffset[c].d != firstGainMap->alternateOffset[c].d) { avifDiagnosticsPrintf(&encoder->diag, "all cells should have the same gain map metadata"); return AVIF_RESULT_INVALID_IMAGE_GRID; } @@ -1869,8 +1867,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, infeNameGainMap, /*infeNameSize=*/strlen(infeNameGainMap) + 1, /*cellIndex=*/0); - AVIF_CHECKRES( - avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, &firstCell->gainMap->metadata, &encoder->diag)); + AVIF_CHECKRES(avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, firstCell->gainMap, &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 cb40b658ab..718f53e9f6 100644 --- a/tests/gtest/avif_fuzztest_enc_dec_experimental.cc +++ b/tests/gtest/avif_fuzztest_enc_dec_experimental.cc @@ -19,24 +19,24 @@ namespace { ::testing::Environment* const kStackLimitEnv = SetStackLimitTo512x1024Bytes(); -void CheckGainMapMetadataMatches(const avifGainMapMetadata& actual, - const avifGainMapMetadata& expected) { - EXPECT_EQ(actual.baseHdrHeadroomN, expected.baseHdrHeadroomN); - EXPECT_EQ(actual.baseHdrHeadroomD, expected.baseHdrHeadroomD); - EXPECT_EQ(actual.alternateHdrHeadroomN, expected.alternateHdrHeadroomN); - EXPECT_EQ(actual.alternateHdrHeadroomD, expected.alternateHdrHeadroomD); +void CheckGainMapMetadataMatches(const avifGainMap& actual, + const avifGainMap& expected) { + EXPECT_EQ(actual.baseHdrHeadroom.n, expected.baseHdrHeadroom.n); + EXPECT_EQ(actual.baseHdrHeadroom.d, expected.baseHdrHeadroom.d); + EXPECT_EQ(actual.alternateHdrHeadroom.n, expected.alternateHdrHeadroom.n); + EXPECT_EQ(actual.alternateHdrHeadroom.d, expected.alternateHdrHeadroom.d); for (int c = 0; c < 3; ++c) { SCOPED_TRACE(c); - EXPECT_EQ(actual.baseOffsetN[c], expected.baseOffsetN[c]); - EXPECT_EQ(actual.baseOffsetD[c], expected.baseOffsetD[c]); - EXPECT_EQ(actual.alternateOffsetN[c], expected.alternateOffsetN[c]); - EXPECT_EQ(actual.alternateOffsetD[c], expected.alternateOffsetD[c]); - EXPECT_EQ(actual.gainMapGammaN[c], expected.gainMapGammaN[c]); - EXPECT_EQ(actual.gainMapGammaD[c], expected.gainMapGammaD[c]); - EXPECT_EQ(actual.gainMapMinN[c], expected.gainMapMinN[c]); - EXPECT_EQ(actual.gainMapMinD[c], expected.gainMapMinD[c]); - EXPECT_EQ(actual.gainMapMaxN[c], expected.gainMapMaxN[c]); - EXPECT_EQ(actual.gainMapMaxD[c], expected.gainMapMaxD[c]); + EXPECT_EQ(actual.baseOffset[c].n, expected.baseOffset[c].n); + EXPECT_EQ(actual.baseOffset[c].d, expected.baseOffset[c].d); + EXPECT_EQ(actual.alternateOffset[c].n, expected.alternateOffset[c].n); + EXPECT_EQ(actual.alternateOffset[c].d, expected.alternateOffset[c].d); + EXPECT_EQ(actual.gainMapGamma[c].n, expected.gainMapGamma[c].n); + EXPECT_EQ(actual.gainMapGamma[c].d, expected.gainMapGamma[c].d); + EXPECT_EQ(actual.gainMapMin[c].n, expected.gainMapMin[c].n); + EXPECT_EQ(actual.gainMapMin[c].d, expected.gainMapMin[c].d); + EXPECT_EQ(actual.gainMapMax[c].n, expected.gainMapMax[c].n); + EXPECT_EQ(actual.gainMapMax[c].d, expected.gainMapMax[c].d); } } @@ -84,8 +84,7 @@ void EncodeDecodeValid(ImagePtr image, EncoderPtr encoder, DecoderPtr decoder) { EXPECT_EQ(decoded_image->gainMap->image->alphaPlane, nullptr); if (decoder->enableParsingGainMapMetadata) { - CheckGainMapMetadataMatches(decoded_image->gainMap->metadata, - image->gainMap->metadata); + CheckGainMapMetadataMatches(*decoded_image->gainMap, *image->gainMap); } } diff --git a/tests/gtest/avif_fuzztest_helpers.cc b/tests/gtest/avif_fuzztest_helpers.cc index b42919a7b5..c4fb021c77 100644 --- a/tests/gtest/avif_fuzztest_helpers.cc +++ b/tests/gtest/avif_fuzztest_helpers.cc @@ -189,22 +189,35 @@ ImagePtr AddGainMapToImage( 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}; + + image->gainMap->gainMapMin[0] = {gain_map_min_n0, gain_map_min_d0}; + image->gainMap->gainMapMin[1] = {gain_map_min_n1, gain_map_min_d1}; + image->gainMap->gainMapMin[2] = {gain_map_min_n2, gain_map_min_d2}; + + image->gainMap->gainMapMax[0] = {gain_map_max_n0, gain_map_max_d0}; + image->gainMap->gainMapMax[1] = {gain_map_max_n1, gain_map_max_d1}; + image->gainMap->gainMapMax[2] = {gain_map_max_n2, gain_map_max_d2}; + + image->gainMap->gainMapGamma[0] = {gain_map_gamma_n0, gain_map_gamma_d0}; + image->gainMap->gainMapGamma[1] = {gain_map_gamma_n1, gain_map_gamma_d1}; + image->gainMap->gainMapGamma[2] = {gain_map_gamma_n2, gain_map_gamma_d2}; + + image->gainMap->baseOffset[0] = {base_offset_n0, base_offset_d0}; + image->gainMap->baseOffset[1] = {base_offset_n1, base_offset_d1}; + image->gainMap->baseOffset[2] = {base_offset_n2, base_offset_d2}; + + image->gainMap->alternateOffset[0] = {alternate_offset_n0, + alternate_offset_d0}; + image->gainMap->alternateOffset[1] = {alternate_offset_n1, + alternate_offset_d1}; + image->gainMap->alternateOffset[2] = {alternate_offset_n2, + alternate_offset_d2}; + + image->gainMap->baseHdrHeadroom = {base_hdr_headroom_n, base_hdr_headroom_d}; + image->gainMap->alternateHdrHeadroom = {alternate_hdr_headroom_n, + alternate_hdr_headroom_d}; + image->gainMap->useBaseColorSpace = use_base_color_space; + return image; } #endif diff --git a/tests/gtest/avif_fuzztest_helpers.h b/tests/gtest/avif_fuzztest_helpers.h index 6b678c30cc..6564bc91e4 100644 --- a/tests/gtest/avif_fuzztest_helpers.h +++ b/tests/gtest/avif_fuzztest_helpers.h @@ -171,9 +171,6 @@ inline auto ArbitraryAvifAnim() { } #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, @@ -206,8 +203,9 @@ inline auto ArbitraryAvifImageWithGainMap() { 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::InRange(1, std::numeric_limits::max()), fuzztest::InRange(1, std::numeric_limits::max()), fuzztest::InRange(1, std::numeric_limits::max()), diff --git a/tests/gtest/avifgainmaptest.cc b/tests/gtest/avifgainmaptest.cc index 98c64369dc..1dadf9605d 100644 --- a/tests/gtest/avifgainmaptest.cc +++ b/tests/gtest/avifgainmaptest.cc @@ -23,51 +23,41 @@ using ::testing::Values; // Used to pass the data folder path to the GoogleTest suites. const char* data_path = nullptr; -void CheckGainMapMetadataMatches(const avifGainMapMetadata& lhs, - const avifGainMapMetadata& rhs) { - EXPECT_EQ(lhs.baseHdrHeadroomN, rhs.baseHdrHeadroomN); - EXPECT_EQ(lhs.baseHdrHeadroomD, rhs.baseHdrHeadroomD); - EXPECT_EQ(lhs.alternateHdrHeadroomN, rhs.alternateHdrHeadroomN); - EXPECT_EQ(lhs.alternateHdrHeadroomD, rhs.alternateHdrHeadroomD); +void CheckGainMapMetadataMatches(const avifGainMap& lhs, + const avifGainMap& rhs) { + EXPECT_EQ(lhs.baseHdrHeadroom.n, rhs.baseHdrHeadroom.n); + EXPECT_EQ(lhs.baseHdrHeadroom.d, rhs.baseHdrHeadroom.d); + EXPECT_EQ(lhs.alternateHdrHeadroom.n, rhs.alternateHdrHeadroom.n); + EXPECT_EQ(lhs.alternateHdrHeadroom.d, rhs.alternateHdrHeadroom.d); for (int c = 0; c < 3; ++c) { SCOPED_TRACE(c); - EXPECT_EQ(lhs.baseOffsetN[c], rhs.baseOffsetN[c]); - EXPECT_EQ(lhs.baseOffsetD[c], rhs.baseOffsetD[c]); - EXPECT_EQ(lhs.alternateOffsetN[c], rhs.alternateOffsetN[c]); - EXPECT_EQ(lhs.alternateOffsetD[c], rhs.alternateOffsetD[c]); - EXPECT_EQ(lhs.gainMapGammaN[c], rhs.gainMapGammaN[c]); - EXPECT_EQ(lhs.gainMapGammaD[c], rhs.gainMapGammaD[c]); - EXPECT_EQ(lhs.gainMapMinN[c], rhs.gainMapMinN[c]); - EXPECT_EQ(lhs.gainMapMinD[c], rhs.gainMapMinD[c]); - EXPECT_EQ(lhs.gainMapMaxN[c], rhs.gainMapMaxN[c]); - EXPECT_EQ(lhs.gainMapMaxD[c], rhs.gainMapMaxD[c]); + EXPECT_EQ(lhs.baseOffset[c].n, rhs.baseOffset[c].n); + EXPECT_EQ(lhs.baseOffset[c].d, rhs.baseOffset[c].d); + EXPECT_EQ(lhs.alternateOffset[c].n, rhs.alternateOffset[c].n); + EXPECT_EQ(lhs.alternateOffset[c].d, rhs.alternateOffset[c].d); + EXPECT_EQ(lhs.gainMapGamma[c].n, rhs.gainMapGamma[c].n); + EXPECT_EQ(lhs.gainMapGamma[c].d, rhs.gainMapGamma[c].d); + EXPECT_EQ(lhs.gainMapMin[c].n, rhs.gainMapMin[c].n); + EXPECT_EQ(lhs.gainMapMin[c].d, rhs.gainMapMin[c].d); + EXPECT_EQ(lhs.gainMapMax[c].n, rhs.gainMapMax[c].n); + EXPECT_EQ(lhs.gainMapMax[c].d, rhs.gainMapMax[c].d); } } -avifGainMapMetadata GetTestGainMapMetadata(bool base_rendition_is_hdr) { - avifGainMapMetadata metadata = {}; - metadata.useBaseColorSpace = true; - metadata.baseHdrHeadroomN = 0; - metadata.baseHdrHeadroomD = 1; - metadata.alternateHdrHeadroomN = 6; - metadata.alternateHdrHeadroomD = 2; +void FillTestGainMapMetadata(bool base_rendition_is_hdr, avifGainMap* gainMap) { + gainMap->useBaseColorSpace = true; + gainMap->baseHdrHeadroom = {0, 1}; + gainMap->alternateHdrHeadroom = {6, 2}; if (base_rendition_is_hdr) { - std::swap(metadata.baseHdrHeadroomN, metadata.alternateHdrHeadroomN); - std::swap(metadata.baseHdrHeadroomD, metadata.alternateHdrHeadroomD); + std::swap(gainMap->baseHdrHeadroom, gainMap->alternateHdrHeadroom); } for (int c = 0; c < 3; ++c) { - metadata.baseOffsetN[c] = 10 * c; - metadata.baseOffsetD[c] = 1000; - metadata.alternateOffsetN[c] = 20 * c; - metadata.alternateOffsetD[c] = 1000; - metadata.gainMapGammaN[c] = 1; - metadata.gainMapGammaD[c] = c + 1; - metadata.gainMapMinN[c] = -1; - metadata.gainMapMinD[c] = c + 1; - metadata.gainMapMaxN[c] = 10 + c + 1; - metadata.gainMapMaxD[c] = c + 1; + gainMap->baseOffset[c] = {10 * c, 1000}; + gainMap->alternateOffset[c] = {20 * c, 1000}; + gainMap->gainMapGamma[c] = {1, static_cast(c + 1)}; + gainMap->gainMapMin[c] = {-1, static_cast(c + 1)}; + gainMap->gainMapMax[c] = {10 + c + 1, static_cast(c + 1)}; } - return metadata; } ImagePtr CreateTestImageWithGainMap(bool base_rendition_is_hdr) { @@ -94,7 +84,7 @@ ImagePtr CreateTestImageWithGainMap(bool base_rendition_is_hdr) { return nullptr; } image->gainMap->image = gain_map.release(); // 'image' now owns the gain map. - image->gainMap->metadata = GetTestGainMapMetadata(base_rendition_is_hdr); + FillTestGainMapMetadata(base_rendition_is_hdr, image->gainMap); if (base_rendition_is_hdr) { image->clli.maxCLL = 10; @@ -164,8 +154,7 @@ TEST(GainMapTest, EncodeDecodeBaseImageSdr) { 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); - CheckGainMapMetadataMatches(decoded->gainMap->metadata, - image->gainMap->metadata); + CheckGainMapMetadataMatches(*decoded->gainMap, *image->gainMap); // Decode the image. result = avifDecoderNextImage(decoder.get()); @@ -226,8 +215,7 @@ TEST(GainMapTest, EncodeDecodeBaseImageHdr) { 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); - CheckGainMapMetadataMatches(decoded->gainMap->metadata, - image->gainMap->metadata); + CheckGainMapMetadataMatches(*decoded->gainMap, *image->gainMap); // Uncomment the following to save the encoded image as an AVIF file. // std::ofstream("/tmp/avifgainmaptest_basehdr.avif", std::ios::binary) @@ -280,14 +268,14 @@ TEST(GainMapTest, EncodeDecodeMetadataSameDenominator) { ASSERT_NE(image, nullptr); const uint32_t kDenominator = 1000; - image->gainMap->metadata.baseHdrHeadroomD = kDenominator; - image->gainMap->metadata.alternateHdrHeadroomD = kDenominator; + image->gainMap->baseHdrHeadroom.d = kDenominator; + image->gainMap->alternateHdrHeadroom.d = kDenominator; for (int c = 0; c < 3; ++c) { - image->gainMap->metadata.baseOffsetD[c] = kDenominator; - image->gainMap->metadata.alternateOffsetD[c] = kDenominator; - image->gainMap->metadata.gainMapGammaD[c] = kDenominator; - image->gainMap->metadata.gainMapMinD[c] = kDenominator; - image->gainMap->metadata.gainMapMaxD[c] = kDenominator; + image->gainMap->baseOffset[c].d = kDenominator; + image->gainMap->alternateOffset[c].d = kDenominator; + image->gainMap->gainMapGamma[c].d = kDenominator; + image->gainMap->gainMapMin[c].d = kDenominator; + image->gainMap->gainMapMax[c].d = kDenominator; } EncoderPtr encoder(avifEncoderCreate()); @@ -309,8 +297,7 @@ TEST(GainMapTest, EncodeDecodeMetadataSameDenominator) { << avifResultToString(result) << " " << decoder->diag.error; // Verify that the gain map metadata matches the input. - CheckGainMapMetadataMatches(decoded->gainMap->metadata, - image->gainMap->metadata); + CheckGainMapMetadataMatches(*decoded->gainMap, *image->gainMap); } TEST(GainMapTest, EncodeDecodeMetadataAllChannelsIdentical) { @@ -318,16 +305,11 @@ TEST(GainMapTest, EncodeDecodeMetadataAllChannelsIdentical) { ASSERT_NE(image, nullptr); for (int c = 0; c < 3; ++c) { - image->gainMap->metadata.baseOffsetN[c] = 1; - image->gainMap->metadata.baseOffsetD[c] = 2; - image->gainMap->metadata.alternateOffsetN[c] = 3; - image->gainMap->metadata.alternateOffsetD[c] = 4; - image->gainMap->metadata.gainMapGammaN[c] = 5; - image->gainMap->metadata.gainMapGammaD[c] = 6; - image->gainMap->metadata.gainMapMinN[c] = 7; - image->gainMap->metadata.gainMapMinD[c] = 8; - image->gainMap->metadata.gainMapMaxN[c] = 9; - image->gainMap->metadata.gainMapMaxD[c] = 10; + image->gainMap->baseOffset[c] = {1, 2}; + image->gainMap->alternateOffset[c] = {3, 4}; + image->gainMap->gainMapGamma[c] = {5, 6}; + image->gainMap->gainMapMin[c] = {7, 8}; + image->gainMap->gainMapMax[c] = {9, 10}; } EncoderPtr encoder(avifEncoderCreate()); @@ -349,8 +331,7 @@ TEST(GainMapTest, EncodeDecodeMetadataAllChannelsIdentical) { << avifResultToString(result) << " " << decoder->diag.error; // Verify that the gain map metadata matches the input. - CheckGainMapMetadataMatches(decoded->gainMap->metadata, - image->gainMap->metadata); + CheckGainMapMetadataMatches(*decoded->gainMap, *image->gainMap); } TEST(GainMapTest, EncodeDecodeGrid) { @@ -362,9 +343,6 @@ TEST(GainMapTest, EncodeDecodeGrid) { 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) { ImagePtr image = testutil::CreateImage(kCellWidth, kCellHeight, /*depth=*/10, @@ -382,7 +360,7 @@ TEST(GainMapTest, EncodeDecodeGrid) { ASSERT_NE(image->gainMap, nullptr); image->gainMap->image = gain_map.release(); // all cells must have the same metadata - image->gainMap->metadata = gain_map_metadata; + FillTestGainMapMetadata(/*base_rendition_is_hdr=*/true, image->gainMap); cell_ptrs.push_back(image.get()); gain_map_ptrs.push_back(image->gainMap->image); @@ -435,7 +413,7 @@ TEST(GainMapTest, EncodeDecodeGrid) { ASSERT_NE(decoded->gainMap->image, nullptr); ASSERT_GT(testutil::GetPsnr(*merged_gain_map, *decoded->gainMap->image), 40.0); - CheckGainMapMetadataMatches(decoded->gainMap->metadata, gain_map_metadata); + CheckGainMapMetadataMatches(*decoded->gainMap, *cell_ptrs[0]->gainMap); // Check that non-incremental and incremental decodings of a grid AVIF produce // the same pixels. @@ -457,9 +435,6 @@ TEST(GainMapTest, InvalidGrid) { constexpr int kGridCols = 2; constexpr int kGridRows = 2; - avifGainMapMetadata gain_map_metadata = - GetTestGainMapMetadata(/*base_rendition_is_hdr=*/true); - for (int i = 0; i < kGridCols * kGridRows; ++i) { ImagePtr image = testutil::CreateImage(/*width=*/64, /*height=*/100, /*depth=*/10, @@ -477,7 +452,7 @@ TEST(GainMapTest, InvalidGrid) { ASSERT_NE(image->gainMap, nullptr); image->gainMap->image = gain_map.release(); // all cells must have the same metadata - image->gainMap->metadata = gain_map_metadata; + FillTestGainMapMetadata(/*base_rendition_is_hdr=*/true, image->gainMap); cell_ptrs.push_back(image.get()); cells.push_back(std::move(image)); @@ -508,15 +483,15 @@ TEST(GainMapTest, InvalidGrid) { << avifResultToString(result) << " " << encoder->diag.error; cells[1]->gainMap->image->depth = cells[0]->gainMap->image->depth; // Revert. - // Invalid: one cell has different gain map metadata. - cells[1]->gainMap->metadata.gainMapGammaN[0] = 42; + // Invalid: one cell has different gain map metadata + cells[1]->gainMap->gainMapGamma[0].n = 42; result = avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows, cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE); EXPECT_EQ(result, AVIF_RESULT_INVALID_IMAGE_GRID) << avifResultToString(result) << " " << encoder->diag.error; - cells[1]->gainMap->metadata.gainMapGammaN[0] = - cells[0]->gainMap->metadata.gainMapGammaN[0]; // Revert. + cells[1]->gainMap->gainMapGamma[0].n = + cells[0]->gainMap->gainMapGamma[0].n; // Revert. } TEST(GainMapTest, SequenceNotSupported) { @@ -598,7 +573,7 @@ TEST(GainMapTest, IgnoreGainMapButReadMetadata) { ASSERT_NE(decoded, nullptr); DecoderPtr decoder(avifDecoderCreate()); ASSERT_NE(decoder, nullptr); - decoder->enableParsingGainMapMetadata = AVIF_TRUE; // Read gain map metadata. + decoder->enableParsingGainMapMetadata = AVIF_TRUE; // Read gain map metadata result = avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data, encoded.size); ASSERT_EQ(result, AVIF_RESULT_OK) @@ -612,8 +587,7 @@ TEST(GainMapTest, IgnoreGainMapButReadMetadata) { // ... but not decoded because enableDecodingGainMap is false by default. EXPECT_EQ(decoded->gainMap->image, nullptr); // Check that the gain map metadata WAS populated. - CheckGainMapMetadataMatches(decoded->gainMap->metadata, - image->gainMap->metadata); + CheckGainMapMetadataMatches(*decoded->gainMap, *image->gainMap); EXPECT_EQ(decoded->gainMap->altDepth, image->gainMap->altDepth); EXPECT_EQ(decoded->gainMap->altPlaneCount, image->gainMap->altPlaneCount); EXPECT_EQ(decoded->gainMap->altColorPrimaries, @@ -688,8 +662,7 @@ TEST(GainMapTest, IgnoreColorAndAlpha) { ASSERT_NE(decoded->gainMap->image, nullptr); EXPECT_GT(testutil::GetPsnr(*image->gainMap->image, *decoded->gainMap->image), 40.0); - CheckGainMapMetadataMatches(decoded->gainMap->metadata, - image->gainMap->metadata); + CheckGainMapMetadataMatches(*decoded->gainMap, *image->gainMap); } TEST(GainMapTest, IgnoreAll) { @@ -708,7 +681,7 @@ TEST(GainMapTest, IgnoreAll) { // Ignore both the main image and the gain map. decoder->ignoreColorAndAlpha = AVIF_TRUE; decoder->enableDecodingGainMap = AVIF_FALSE; - // But do read the gain map metadata. + // But do read the gain map metadata decoder->enableParsingGainMapMetadata = AVIF_TRUE; // Parsing just the header should work. @@ -717,8 +690,7 @@ TEST(GainMapTest, IgnoreAll) { ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK); EXPECT_TRUE(decoder->gainMapPresent); - CheckGainMapMetadataMatches(decoder->image->gainMap->metadata, - image->gainMap->metadata); + CheckGainMapMetadataMatches(*decoder->image->gainMap, *image->gainMap); ASSERT_EQ(decoder->image->gainMap->image, nullptr); // But trying to access the next image should give an error because both @@ -791,8 +763,8 @@ TEST(GainMapTest, DecodeGainMapGrid) { EXPECT_EQ(decoded->gainMap->image->width, 64u * 2u); EXPECT_EQ(decoded->gainMap->image->height, 80u * 2u); EXPECT_EQ(decoded->gainMap->image->depth, 8u); - EXPECT_EQ(decoded->gainMap->metadata.baseHdrHeadroomN, 6u); - EXPECT_EQ(decoded->gainMap->metadata.baseHdrHeadroomD, 2u); + EXPECT_EQ(decoded->gainMap->baseHdrHeadroom.n, 6u); + EXPECT_EQ(decoded->gainMap->baseHdrHeadroom.d, 2u); // Decode the image. result = avifDecoderNextImage(decoder.get()); @@ -820,8 +792,8 @@ TEST(GainMapTest, DecodeColorGridGainMapNoGrid) { // Gain map: single image of size 64x80. EXPECT_EQ(decoded->gainMap->image->width, 64u); EXPECT_EQ(decoded->gainMap->image->height, 80u); - EXPECT_EQ(decoded->gainMap->metadata.baseHdrHeadroomN, 6u); - EXPECT_EQ(decoded->gainMap->metadata.baseHdrHeadroomD, 2u); + EXPECT_EQ(decoded->gainMap->baseHdrHeadroom.n, 6u); + EXPECT_EQ(decoded->gainMap->baseHdrHeadroom.d, 2u); } TEST(GainMapTest, DecodeColorNoGridGainMapGrid) { @@ -844,8 +816,8 @@ TEST(GainMapTest, DecodeColorNoGridGainMapGrid) { // Gain map: 2x2 grid of 64x80 tiles. EXPECT_EQ(decoded->gainMap->image->width, 64u * 2u); EXPECT_EQ(decoded->gainMap->image->height, 80u * 2u); - EXPECT_EQ(decoded->gainMap->metadata.baseHdrHeadroomN, 6u); - EXPECT_EQ(decoded->gainMap->metadata.baseHdrHeadroomD, 2u); + EXPECT_EQ(decoded->gainMap->baseHdrHeadroom.n, 6u); + EXPECT_EQ(decoded->gainMap->baseHdrHeadroom.d, 2u); } TEST(GainMapTest, DecodeUnsupportedVersion) { @@ -958,97 +930,14 @@ TEST(GainMapTest, DecodeInvalidFtyp) { EXPECT_NEAR(std::abs((double)numerator / denominator), expected, \ expected * 0.001); -TEST(GainMapTest, ConvertMetadata) { - avifGainMapMetadataDouble metadata_double = {}; - metadata_double.gainMapMin[0] = 1.0; - metadata_double.gainMapMin[1] = 1.1; - metadata_double.gainMapMin[2] = 1.2; - metadata_double.gainMapMax[0] = 10.0; - metadata_double.gainMapMax[1] = 10.1; - metadata_double.gainMapMax[2] = 10.2; - metadata_double.gainMapGamma[0] = 1.0; - metadata_double.gainMapGamma[1] = 1.0; - metadata_double.gainMapGamma[2] = 1.2; - metadata_double.baseOffset[0] = 1.0 / 32.0; - metadata_double.baseOffset[1] = 1.0 / 64.0; - metadata_double.baseOffset[2] = 1.0 / 128.0; - metadata_double.alternateOffset[0] = 0.004564; - metadata_double.alternateOffset[1] = 0.0; - metadata_double.baseHdrHeadroom = 1.0; - metadata_double.alternateHdrHeadroom = 10.0; - - // Convert to avifGainMapMetadata. - avifGainMapMetadata metadata = {}; - ASSERT_TRUE( - avifGainMapMetadataDoubleToFractions(&metadata, &metadata_double)); - - for (int i = 0; i < 3; ++i) { - EXPECT_FRACTION_NEAR(metadata.gainMapMinN[i], metadata.gainMapMinD[i], - metadata_double.gainMapMin[i]); - EXPECT_FRACTION_NEAR(metadata.gainMapMaxN[i], metadata.gainMapMaxD[i], - metadata_double.gainMapMax[i]); - EXPECT_FRACTION_NEAR(metadata.gainMapGammaN[i], metadata.gainMapGammaD[i], - metadata_double.gainMapGamma[i]); - EXPECT_FRACTION_NEAR(metadata.baseOffsetN[i], metadata.baseOffsetD[i], - metadata_double.baseOffset[i]); - EXPECT_FRACTION_NEAR(metadata.alternateOffsetN[i], - metadata.alternateOffsetD[i], - metadata_double.alternateOffset[i]); - } - EXPECT_FRACTION_NEAR(metadata.baseHdrHeadroomN, metadata.baseHdrHeadroomD, - metadata_double.baseHdrHeadroom); - EXPECT_FRACTION_NEAR(metadata.alternateHdrHeadroomN, - metadata.alternateHdrHeadroomD, - metadata_double.alternateHdrHeadroom); - - // Convert back to avifGainMapMetadataDouble. - avifGainMapMetadataDouble metadata_double2 = {}; - ASSERT_TRUE( - avifGainMapMetadataFractionsToDouble(&metadata_double2, &metadata)); - - constexpr double kEpsilon = 0.000001; - for (int i = 0; i < 3; ++i) { - EXPECT_NEAR(metadata_double2.gainMapMin[i], metadata_double.gainMapMin[i], - kEpsilon); - EXPECT_NEAR(metadata_double2.gainMapMax[i], metadata_double.gainMapMax[i], - kEpsilon); - EXPECT_NEAR(metadata_double2.gainMapGamma[i], - metadata_double.gainMapGamma[i], kEpsilon); - EXPECT_NEAR(metadata_double2.baseOffset[i], metadata_double.baseOffset[i], - kEpsilon); - EXPECT_NEAR(metadata_double2.alternateOffset[i], - metadata_double.alternateOffset[i], kEpsilon); - } - EXPECT_NEAR(metadata_double2.baseHdrHeadroom, metadata_double.baseHdrHeadroom, - kEpsilon); - EXPECT_NEAR(metadata_double2.alternateHdrHeadroom, - metadata_double.alternateHdrHeadroom, kEpsilon); -} - -TEST(GainMapTest, ConvertMetadataToFractionInvalid) { - avifGainMapMetadataDouble metadata_double = {}; - metadata_double.gainMapGamma[0] = -42; // A negative value is invalid! - avifGainMapMetadata metadata = {}; - ASSERT_FALSE( - avifGainMapMetadataDoubleToFractions(&metadata, &metadata_double)); -} - -TEST(GainMapTest, ConvertMetadataToDoubleInvalid) { - avifGainMapMetadata metadata = {}; // Denominators are zero. - avifGainMapMetadataDouble metadata_double = {}; - ASSERT_FALSE( - avifGainMapMetadataFractionsToDouble(&metadata_double, &metadata)); -} - static void SwapBaseAndAlternate(const avifImage& new_alternate, avifGainMap& gain_map) { - avifGainMapMetadata& metadata = gain_map.metadata; - metadata.useBaseColorSpace = !metadata.useBaseColorSpace; - std::swap(metadata.baseHdrHeadroomN, metadata.alternateHdrHeadroomN); - std::swap(metadata.baseHdrHeadroomD, metadata.alternateHdrHeadroomD); + gain_map.useBaseColorSpace = !gain_map.useBaseColorSpace; + std::swap(gain_map.baseHdrHeadroom.n, gain_map.alternateHdrHeadroom.n); + std::swap(gain_map.baseHdrHeadroom.d, gain_map.alternateHdrHeadroom.d); for (int c = 0; c < 3; ++c) { - std::swap(metadata.baseOffsetN[c], metadata.alternateOffsetN[c]); - std::swap(metadata.baseOffsetD[c], metadata.alternateOffsetD[c]); + std::swap(gain_map.baseOffset[c].n, gain_map.alternateOffset[c].n); + std::swap(gain_map.baseOffset[c].d, gain_map.alternateOffset[c].d); } gain_map.altColorPrimaries = new_alternate.colorPrimaries; gain_map.altTransferCharacteristics = new_alternate.transferCharacteristics; @@ -1383,13 +1272,10 @@ TEST(ToneMapTest, ToneMapImageSameHeadroom) { ASSERT_NE(image->gainMap->image, nullptr); // Force the alternate and base HDR headroom to the same value. - image->gainMap->metadata.baseHdrHeadroomN = - image->gainMap->metadata.alternateHdrHeadroomN; - image->gainMap->metadata.baseHdrHeadroomD = - image->gainMap->metadata.alternateHdrHeadroomD; - const float headroom = static_cast( - static_cast(image->gainMap->metadata.baseHdrHeadroomN) / - image->gainMap->metadata.baseHdrHeadroomD); + image->gainMap->baseHdrHeadroom = image->gainMap->alternateHdrHeadroom; + const float headroom = + static_cast(static_cast(image->gainMap->baseHdrHeadroom.n) / + image->gainMap->baseHdrHeadroom.d); // Check that when the two headrooms are the same, the gain map is not applied // whatever the target headroom is. @@ -1436,7 +1322,7 @@ TEST_P(CreateGainMapTest, Create) { gain_map_depth, gain_map_format); avifDiagnostics diag; - gain_map->metadata.useBaseColorSpace = true; + gain_map->useBaseColorSpace = true; avifResult result = avifImageComputeGainMap(image1.get(), image2.get(), gain_map.get(), &diag); ASSERT_EQ(result, AVIF_RESULT_OK) @@ -1445,11 +1331,10 @@ TEST_P(CreateGainMapTest, Create) { EXPECT_EQ(gain_map->image->width, gain_map_width); EXPECT_EQ(gain_map->image->height, gain_map_height); - const float image1_headroom = (float)gain_map->metadata.baseHdrHeadroomN / - gain_map->metadata.baseHdrHeadroomD; - const float image2_headroom = - (float)gain_map->metadata.alternateHdrHeadroomN / - gain_map->metadata.alternateHdrHeadroomD; + const float image1_headroom = + (float)gain_map->baseHdrHeadroom.n / gain_map->baseHdrHeadroom.d; + const float image2_headroom = (float)gain_map->alternateHdrHeadroom.n / + gain_map->alternateHdrHeadroom.d; // Tone map from image1 to image2 by applying the gainmap forward. float psnr_image1_to_image2_forward; @@ -1471,14 +1356,14 @@ TEST_P(CreateGainMapTest, Create) { // "/tmp/gain_map_image1_to_image2.png")); // Compute the gain map in the other direction (from image2 to image1). - gain_map->metadata.useBaseColorSpace = false; + gain_map->useBaseColorSpace = false; result = avifImageComputeGainMap(image2.get(), image1.get(), gain_map.get(), &diag); ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result) << " " << diag.error; - const float image2_headroom2 = (float)gain_map->metadata.baseHdrHeadroomN / - gain_map->metadata.baseHdrHeadroomD; + const float image2_headroom2 = + (float)gain_map->baseHdrHeadroom.n / gain_map->baseHdrHeadroom.d; EXPECT_NEAR(image2_headroom2, image2_headroom, 0.001); // Tone map from image2 to image1 by applying the new gainmap forward. diff --git a/tests/gtest/avifjpeggainmaptest.cc b/tests/gtest/avifjpeggainmaptest.cc index b11ee5268f..5f729f95ff 100644 --- a/tests/gtest/avifjpeggainmaptest.cc +++ b/tests/gtest/avifjpeggainmaptest.cc @@ -17,55 +17,55 @@ const char* data_path = nullptr; //------------------------------------------------------------------------------ void CheckGainMapMetadata( - const avifGainMapMetadata& m, std::array gain_map_min, + const avifGainMap& gm, std::array gain_map_min, std::array gain_map_max, std::array gain_map_gamma, std::array base_offset, std::array alternate_offset, double base_hdr_headroom, double alternate_hdr_headroom) { const double kEpsilon = 1e-8; - EXPECT_NEAR(static_cast(m.gainMapMinN[0]) / m.gainMapMinD[0], + EXPECT_NEAR(static_cast(gm.gainMapMin[0].n) / gm.gainMapMin[0].d, gain_map_min[0], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapMinN[1]) / m.gainMapMinD[1], + EXPECT_NEAR(static_cast(gm.gainMapMin[1].n) / gm.gainMapMin[1].d, gain_map_min[1], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapMinN[2]) / m.gainMapMinD[2], + EXPECT_NEAR(static_cast(gm.gainMapMin[2].n) / gm.gainMapMin[2].d, gain_map_min[2], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapMaxN[0]) / m.gainMapMaxD[0], + EXPECT_NEAR(static_cast(gm.gainMapMax[0].n) / gm.gainMapMax[0].d, gain_map_max[0], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapMaxN[1]) / m.gainMapMaxD[1], + EXPECT_NEAR(static_cast(gm.gainMapMax[1].n) / gm.gainMapMax[1].d, gain_map_max[1], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapMaxN[2]) / m.gainMapMaxD[2], + EXPECT_NEAR(static_cast(gm.gainMapMax[2].n) / gm.gainMapMax[2].d, gain_map_max[2], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapGammaN[0]) / m.gainMapGammaD[0], + EXPECT_NEAR(static_cast(gm.gainMapGamma[0].n) / gm.gainMapGamma[0].d, gain_map_gamma[0], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapGammaN[1]) / m.gainMapGammaD[1], + EXPECT_NEAR(static_cast(gm.gainMapGamma[1].n) / gm.gainMapGamma[1].d, gain_map_gamma[1], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapGammaN[2]) / m.gainMapGammaD[2], + EXPECT_NEAR(static_cast(gm.gainMapGamma[2].n) / gm.gainMapGamma[2].d, gain_map_gamma[2], kEpsilon); - EXPECT_NEAR(static_cast(m.baseOffsetN[0]) / m.baseOffsetD[0], + EXPECT_NEAR(static_cast(gm.baseOffset[0].n) / gm.baseOffset[0].d, base_offset[0], kEpsilon); - EXPECT_NEAR(static_cast(m.baseOffsetN[1]) / m.baseOffsetD[1], + EXPECT_NEAR(static_cast(gm.baseOffset[1].n) / gm.baseOffset[1].d, base_offset[1], kEpsilon); - EXPECT_NEAR(static_cast(m.baseOffsetN[2]) / m.baseOffsetD[2], + EXPECT_NEAR(static_cast(gm.baseOffset[2].n) / gm.baseOffset[2].d, base_offset[2], kEpsilon); EXPECT_NEAR( - static_cast(m.alternateOffsetN[0]) / m.alternateOffsetD[0], + static_cast(gm.alternateOffset[0].n) / gm.alternateOffset[0].d, alternate_offset[0], kEpsilon); EXPECT_NEAR( - static_cast(m.alternateOffsetN[1]) / m.alternateOffsetD[1], + static_cast(gm.alternateOffset[1].n) / gm.alternateOffset[1].d, alternate_offset[1], kEpsilon); EXPECT_NEAR( - static_cast(m.alternateOffsetN[2]) / m.alternateOffsetD[2], + static_cast(gm.alternateOffset[2].n) / gm.alternateOffset[2].d, alternate_offset[2], kEpsilon); - EXPECT_NEAR(static_cast(m.baseHdrHeadroomN) / m.baseHdrHeadroomD, + EXPECT_NEAR(static_cast(gm.baseHdrHeadroom.n) / gm.baseHdrHeadroom.d, base_hdr_headroom, kEpsilon); - EXPECT_NEAR( - static_cast(m.alternateHdrHeadroomN) / m.alternateHdrHeadroomD, - alternate_hdr_headroom, kEpsilon); + EXPECT_NEAR(static_cast(gm.alternateHdrHeadroom.n) / + gm.alternateHdrHeadroom.d, + alternate_hdr_headroom, kEpsilon); } TEST(JpegTest, ReadJpegWithGainMap) { @@ -88,7 +88,7 @@ TEST(JpegTest, ReadJpegWithGainMap) { // be read to parse the gain map. EXPECT_EQ(image->xmp.size, 0u); - CheckGainMapMetadata(image->gainMap->metadata, + CheckGainMapMetadata(*image->gainMap, /*gain_map_min=*/{0.0, 0.0, 0.0}, /*gain_map_max=*/{3.5, 3.6, 3.7}, /*gain_map_gamma=*/{1.0, 1.0, 1.0}, @@ -156,11 +156,11 @@ TEST(JpegTest, ParseXMP) { )"; - avifGainMapMetadata metadata; + GainMapPtr gainMap(avifGainMapCreate()); ASSERT_TRUE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(), - &metadata)); + gainMap.get())); - CheckGainMapMetadata(metadata, + CheckGainMapMetadata(*gainMap, /*gain_map_min=*/{0.025869, 0.075191, 0.142298}, /*gain_map_max=*/{3.527605, 2.830234, 1.537243}, /*gain_map_gamma=*/{0.506828, 0.590032, 1.517708}, @@ -181,12 +181,12 @@ TEST(JpegTest, ParseXMPAllDefaultValues) { )"; - avifGainMapMetadata metadata; + GainMapPtr gainMap(avifGainMapCreate()); ASSERT_TRUE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(), - &metadata)); + gainMap.get())); CheckGainMapMetadata( - metadata, + *gainMap, /*gain_map_min=*/{0.0, 0.0, 0.0}, /*gain_map_max=*/{1.0, 1.0, 1.0}, /*gain_map_gamma=*/{1.0, 1.0, 1.0}, @@ -220,15 +220,15 @@ TEST(JpegTest, ExtendedXmp) { )"; - avifGainMapMetadata metadata; + GainMapPtr gainMap(avifGainMapCreate()); ASSERT_TRUE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(), - &metadata)); + gainMap.get())); // Note that this test passes because the gain map metadata is in the primary // XMP. If it was in the extended part, we wouldn't detect it (but probably // should). CheckGainMapMetadata( - metadata, + *gainMap, /*gain_map_min=*/{0.0, 0.0, 0.0}, /*gain_map_max=*/{1.0, 1.0, 1.0}, /*gain_map_gamma=*/{1.0, 1.0, 1.0}, @@ -258,9 +258,9 @@ TEST(JpegTest, InvalidNumberOfValues) { )"; - avifGainMapMetadata metadata; + GainMapPtr gainMap(avifGainMapCreate()); EXPECT_FALSE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(), - &metadata)); + gainMap.get())); } TEST(JpegTest, WrongVersion) { @@ -273,9 +273,9 @@ TEST(JpegTest, WrongVersion) { )"; - avifGainMapMetadata metadata; + GainMapPtr gainMap(avifGainMapCreate()); EXPECT_FALSE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(), - &metadata)); + gainMap.get())); } TEST(JpegTest, InvalidXMP) { @@ -287,16 +287,16 @@ TEST(JpegTest, InvalidXMP) { )"; - avifGainMapMetadata metadata; + GainMapPtr gainMap(avifGainMapCreate()); EXPECT_FALSE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(), - &metadata)); + gainMap.get())); } TEST(JpegTest, EmptyXMP) { const std::string xmp = ""; - avifGainMapMetadata metadata; + GainMapPtr gainMap(avifGainMapCreate()); EXPECT_FALSE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(), - &metadata)); + gainMap.get())); } //------------------------------------------------------------------------------ diff --git a/tests/gtest/avifutilstest.cc b/tests/gtest/avifutilstest.cc index b5da9cd065..fb464e7877 100644 --- a/tests/gtest/avifutilstest.cc +++ b/tests/gtest/avifutilstest.cc @@ -10,33 +10,31 @@ namespace avif { namespace { // Converts a double value to a fraction, and checks that the difference -// between numerator/denominator and v is below relative_tolerance. +// between fraction.n/fraction.d and v is below relative_tolerance. void TestRoundTrip(double v, double relative_tolerance) { // Unsigned. if (v >= 0) { - uint32_t numerator, denominator; - ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &numerator, &denominator)) << v; - const double reconstructed = (double)numerator / denominator; + avifUnsignedFraction fraction; + ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &fraction)) << v; + const double reconstructed = (double)fraction.n / fraction.d; const double tolerance = v * relative_tolerance; EXPECT_NEAR(reconstructed, v, tolerance) - << "numerator " << (double)numerator << " denominator " - << (double)denominator; + << "fraction.n " << (double)fraction.n << " fraction.d " + << (double)fraction.d; } // Signed. if (v <= INT32_MAX) { for (double multiplier : {1.0, -1.0}) { double v2 = v * multiplier; - int32_t numerator; - uint32_t denominator; + avifSignedFraction fraction; - ASSERT_TRUE(avifDoubleToSignedFraction(v2, &numerator, &denominator)) - << v2; - const double reconstructed = (double)numerator / denominator; + ASSERT_TRUE(avifDoubleToSignedFraction(v2, &fraction)) << v2; + const double reconstructed = (double)fraction.n / fraction.d; const double tolerance = v * relative_tolerance; EXPECT_NEAR(reconstructed, v2, tolerance) - << "numerator " << (double)numerator << " denominator " - << (double)denominator; + << "fraction.n " << (double)fraction.n << " fraction.d " + << (double)fraction.d; } } } @@ -89,9 +87,9 @@ TEST(ToFractionTest, MaxDifference) { double max_relative_error_v = 0; for (uint64_t i = 0; i < UINT32_MAX; i += 1000) { const double v = i + kLotsOfDecimals; - uint32_t numerator, denominator; - ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &numerator, &denominator)) << v; - const double reconstructed = (double)numerator / denominator; + avifUnsignedFraction fraction; + ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &fraction)) << v; + const double reconstructed = (double)fraction.n / fraction.d; const double error = abs(reconstructed - v); const double relative_error = error / v; if (error > max_error) { @@ -116,9 +114,9 @@ TEST(ToFractionTest, MaxDifferenceSmall) { double max_relative_error_v = 0; for (uint64_t i = 1; i < UINT32_MAX; i += 1000) { const double v = 1.0 / (i + kLotsOfDecimals); - uint32_t numerator, denominator; - ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &numerator, &denominator)) << v; - const double reconstructed = (double)numerator / denominator; + avifUnsignedFraction fraction; + ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &fraction)) << v; + const double reconstructed = (double)fraction.n / fraction.d; const double error = abs(reconstructed - v); const double relative_error = error / v; if (error > max_error) { @@ -135,12 +133,12 @@ TEST(ToFractionTest, MaxDifferenceSmall) { } TEST(ToFractionTest, BadValues) { - uint32_t numerator, denominator; + avifUnsignedFraction fraction; // Negative value. - EXPECT_FALSE(avifDoubleToUnsignedFraction(-0.1, &numerator, &denominator)); + EXPECT_FALSE(avifDoubleToUnsignedFraction(-0.1, &fraction)); // Too large. - EXPECT_FALSE(avifDoubleToUnsignedFraction(((double)UINT32_MAX) + 1.0, - &numerator, &denominator)); + EXPECT_FALSE( + avifDoubleToUnsignedFraction(((double)UINT32_MAX) + 1.0, &fraction)); } } // namespace