diff --git a/CHANGELOG.md b/CHANGELOG.md index 9101280b4e..8cbef8ea87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ 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 avifDoubleToSignedFraction and avifDoubleToUnsignedFraction functions. ## [1.1.1] - 2024-07-30 diff --git a/apps/avifgainmaputil/convert_command.cc b/apps/avifgainmaputil/convert_command.cc index ecb617de04..c4ccf27b57 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->alternateHdrHeadroomN == 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..992016494a 100644 --- a/apps/avifgainmaputil/printmetadata_command.cc +++ b/apps/avifgainmaputil/printmetadata_command.cc @@ -65,34 +65,34 @@ 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) + << FormatFraction(gainMap.baseHdrHeadroomN, + gainMap.baseHdrHeadroomD) << "\n"; std::cout << " * " << std::left << std::setw(w) << "Alternate headroom: " - << FormatFraction(metadata.alternateHdrHeadroomN, - metadata.alternateHdrHeadroomD) + << FormatFraction(gainMap.alternateHdrHeadroomN, + gainMap.alternateHdrHeadroomD) << "\n"; std::cout << " * " << std::left << std::setw(w) << "Gain Map Min: " - << FormatFractions(metadata.gainMapMinN, metadata.gainMapMinD) + << FormatFractions(gainMap.gainMapMinN, gainMap.gainMapMinD) << "\n"; std::cout << " * " << std::left << std::setw(w) << "Gain Map Max: " - << FormatFractions(metadata.gainMapMaxN, metadata.gainMapMaxD) + << FormatFractions(gainMap.gainMapMaxN, gainMap.gainMapMaxD) << "\n"; std::cout << " * " << std::left << std::setw(w) << "Base Offset: " - << FormatFractions(metadata.baseOffsetN, metadata.baseOffsetD) + << FormatFractions(gainMap.baseOffsetN, gainMap.baseOffsetD) << "\n"; std::cout << " * " << std::left << std::setw(w) << "Alternate Offset: " - << FormatFractions(metadata.alternateOffsetN, - metadata.alternateOffsetD) + << FormatFractions(gainMap.alternateOffsetN, + gainMap.alternateOffsetD) << "\n"; std::cout << " * " << std::left << std::setw(w) << "Gain Map Gamma: " - << FormatFractions(metadata.gainMapGammaN, metadata.gainMapGammaD) + << FormatFractions(gainMap.gainMapGammaN, gainMap.gainMapGammaD) << "\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..0ef00cc3d6 100644 --- a/apps/avifgainmaputil/swapbase_command.cc +++ b/apps/avifgainmaputil/swapbase_command.cc @@ -23,8 +23,10 @@ avifResult ChangeBase(const avifImage& image, int depth, swapped->yuvFormat = yuvFormat; const float headroom = - static_cast(image.gainMap->metadata.alternateHdrHeadroomN) / - image.gainMap->metadata.alternateHdrHeadroomD; + image.gainMap->alternateHdrHeadroomD == 0 + ? 0.0f + : static_cast(image.gainMap->alternateHdrHeadroomN) / + image.gainMap->alternateHdrHeadroomD; const bool tone_mapping_to_sdr = (headroom == 0.0f); swapped->colorPrimaries = image.gainMap->altColorPrimaries; @@ -97,14 +99,14 @@ 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->baseHdrHeadroomN, gainMap->alternateHdrHeadroomN); + std::swap(gainMap->baseHdrHeadroomD, gainMap->alternateHdrHeadroomD); for (int c = 0; c < 3; ++c) { - std::swap(metadata.baseOffsetN, metadata.alternateOffsetN); - std::swap(metadata.baseOffsetD, metadata.alternateOffsetD); + std::swap(gainMap->baseOffsetN, gainMap->alternateOffsetN); + std::swap(gainMap->baseOffsetD, gainMap->alternateOffsetD); } return AVIF_RESULT_OK; diff --git a/apps/avifgainmaputil/tonemap_command.cc b/apps/avifgainmaputil/tonemap_command.cc index 371b91ceae..87ac5d5ed6 100644 --- a/apps/avifgainmaputil/tonemap_command.cc +++ b/apps/avifgainmaputil/tonemap_command.cc @@ -86,28 +86,30 @@ avifResult TonemapCommand::Run() { 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"; - return AVIF_RESULT_INVALID_ARGUMENT; - } - + const float base_hdr_hreadroom = + image->gainMap->baseHdrHeadroomD == 0 + ? 0.0f + : (float)image->gainMap->baseHdrHeadroomN / + image->gainMap->baseHdrHeadroomD; + const float alternate_hdr_hreadroom = + image->gainMap->alternateHdrHeadroomD == 0 + ? 0.0f + : (float)image->gainMap->alternateHdrHeadroomN / + image->gainMap->alternateHdrHeadroomD; // 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..06bf6be609 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->gainMapMinN[i], &gainMap->gainMapMinD[i])); + AVIF_CHECK(avifDoubleToSignedFraction(gainMapMax[i], &gainMap->gainMapMaxN[i], &gainMap->gainMapMaxD[i])); + AVIF_CHECK(avifDoubleToUnsignedFraction(gainMapGamma[i], &gainMap->gainMapGammaN[i], &gainMap->gainMapGammaD[i])); + AVIF_CHECK(avifDoubleToSignedFraction(baseOffset[i], &gainMap->baseOffsetN[i], &gainMap->baseOffsetD[i])); + AVIF_CHECK(avifDoubleToSignedFraction(alternateOffset[i], &gainMap->alternateOffsetN[i], &gainMap->alternateOffsetD[i])); + } + AVIF_CHECK(avifDoubleToUnsignedFraction(baseHdrHeadroom, &gainMap->baseHdrHeadroomN, &gainMap->baseHdrHeadroomD)); + AVIF_CHECK(avifDoubleToUnsignedFraction(alternateHdrHeadroom, &gainMap->alternateHdrHeadroomN, &gainMap->alternateHdrHeadroomD)); + // 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..20c5f19fb1 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->baseHdrHeadroomN == 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..ab47846512 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -434,6 +434,13 @@ typedef struct avifFraction int32_t d; } avifFraction; +// 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, int32_t * numerator, uint32_t * denominator); +// 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, uint32_t * numerator, uint32_t * denominator); + // --------------------------------------------------------------------------- // Optional transformation structs @@ -591,15 +598,25 @@ 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]; @@ -624,11 +641,6 @@ typedef struct avifGainMapMetadata int32_t alternateOffsetN[3]; uint32_t alternateOffsetD[3]; - // ----------------------------------------------------------------------- - - // Parameters below can be manually tuned after the gain map has been - // created. - // Log2-encoded HDR headroom of the base and alternate images respectively. // If baseHdrHeadroom is < alternateHdrHeadroom, the result of tone mapping // for a display with an HDR headroom that is <= baseHdrHeadroom is the base @@ -657,25 +669,6 @@ typedef struct avifGainMapMetadata // 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 +695,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..43f06ed13c 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -786,7 +786,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..5cbe50c312 100644 --- a/src/avif.c +++ b/src/avif.c @@ -262,7 +262,23 @@ 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->gainMapMinN[c] = srcImage->gainMap->gainMapMinN[c]; + dstImage->gainMap->gainMapMinD[c] = srcImage->gainMap->gainMapMinD[c]; + dstImage->gainMap->gainMapMaxN[c] = srcImage->gainMap->gainMapMaxN[c]; + dstImage->gainMap->gainMapMaxD[c] = srcImage->gainMap->gainMapMaxD[c]; + dstImage->gainMap->gainMapGammaN[c] = srcImage->gainMap->gainMapGammaN[c]; + dstImage->gainMap->gainMapGammaD[c] = srcImage->gainMap->gainMapGammaD[c]; + dstImage->gainMap->baseOffsetN[c] = srcImage->gainMap->baseOffsetN[c]; + dstImage->gainMap->baseOffsetD[c] = srcImage->gainMap->baseOffsetD[c]; + dstImage->gainMap->alternateOffsetN[c] = srcImage->gainMap->alternateOffsetN[c]; + dstImage->gainMap->alternateOffsetD[c] = srcImage->gainMap->alternateOffsetD[c]; + } + dstImage->gainMap->baseHdrHeadroomN = srcImage->gainMap->baseHdrHeadroomN; + dstImage->gainMap->baseHdrHeadroomD = srcImage->gainMap->baseHdrHeadroomD; + dstImage->gainMap->alternateHdrHeadroomN = srcImage->gainMap->alternateHdrHeadroomN; + dstImage->gainMap->alternateHdrHeadroomD = srcImage->gainMap->alternateHdrHeadroomD; + 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 +1208,17 @@ 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->gainMapMinD[i] = 1; + gainMap->gainMapMaxD[i] = 1; + gainMap->gainMapGammaD[i] = 1; + gainMap->baseOffsetD[i] = 1; + gainMap->alternateOffsetD[i] = 1; + } + gainMap->baseHdrHeadroomD = 1; + gainMap->alternateHdrHeadroomD = 1; return gainMap; } diff --git a/src/gainmap.c b/src/gainmap.c index d36fe4a84c..87fd4c6f8f 100644 --- a/src/gainmap.c +++ b/src/gainmap.c @@ -9,71 +9,43 @@ #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->gainMapMinN[i] = 1; + gainMap->gainMapMinD[i] = 1; + gainMap->gainMapMaxN[i] = 1; + gainMap->gainMapMaxD[i] = 1; + gainMap->baseOffsetN[i] = 1; + gainMap->baseOffsetD[i] = 64; + gainMap->alternateOffsetN[i] = 1; + gainMap->alternateOffsetD[i] = 64; + gainMap->gainMapGammaN[i] = 1; + gainMap->gainMapGammaD[i] = 1; + } + gainMap->baseHdrHeadroomN = 0; + gainMap->baseHdrHeadroomD = 1; + gainMap->alternateHdrHeadroomN = 0; + gainMap->alternateHdrHeadroomD = 1; + gainMap->useBaseColorSpace = AVIF_TRUE; } -avifBool avifGainMapMetadataFractionsToDouble(avifGainMapMetadataDouble * dst, const avifGainMapMetadata * src) +static float toFloat(int numerator, int denominator) { - 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); - } - - 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; -} - -static void avifGainMapMetadataDoubleSetDefaults(avifGainMapMetadataDouble * metadata) -{ - 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 (denominator == 0) { + return 0.0f; } - metadata->baseHdrHeadroom = 0.0; - metadata->alternateHdrHeadroom = 1.0; - metadata->useBaseColorSpace = AVIF_TRUE; + return (float)numerator / denominator; } // --------------------------------------------------------------------------- // 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 = toFloat(gainMap->baseHdrHeadroomN, gainMap->baseHdrHeadroomD); + const float alternateHdrHeadroom = toFloat(gainMap->alternateHdrHeadroomN, gainMap->alternateHdrHeadroomD); 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. @@ -113,13 +85,8 @@ 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) { + if (gainMap->gainMapGammaN[i] <= 0) { avifDiagnosticsPrintf(diag, "Invalid gain map metadata, gamma should be strictly positive"); return AVIF_RESULT_INVALID_ARGUMENT; } @@ -128,7 +95,7 @@ avifResult avifRGBImageApplyGainMap(const avifRGBImage * baseImage, 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 +114,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 +217,21 @@ 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] }; + const float gammaInv[3] = { toFloat(gainMap->gainMapGammaD[0], gainMap->gainMapGammaN[0]), + toFloat(gainMap->gainMapGammaD[1], gainMap->gainMapGammaN[1]), + toFloat(gainMap->gainMapGammaD[2], gainMap->gainMapGammaN[2]) }; + const float gainMapMin[3] = { toFloat(gainMap->gainMapMinN[0], gainMap->gainMapMinD[0]), + toFloat(gainMap->gainMapMinN[1], gainMap->gainMapMinD[1]), + toFloat(gainMap->gainMapMinN[2], gainMap->gainMapMinD[2]) }; + const float gainMapMax[3] = { toFloat(gainMap->gainMapMaxN[0], gainMap->gainMapMaxD[0]), + toFloat(gainMap->gainMapMaxN[1], gainMap->gainMapMaxD[1]), + toFloat(gainMap->gainMapMaxN[2], gainMap->gainMapMaxD[2]) }; + const float baseOffset[3] = { toFloat(gainMap->baseOffsetN[0], gainMap->baseOffsetD[0]), + toFloat(gainMap->baseOffsetN[1], gainMap->baseOffsetD[1]), + toFloat(gainMap->baseOffsetN[2], gainMap->baseOffsetD[2]) }; + const float alternateOffset[3] = { toFloat(gainMap->alternateOffsetN[0], gainMap->alternateOffsetD[0]), + toFloat(gainMap->alternateOffsetN[1], gainMap->alternateOffsetD[1]), + toFloat(gainMap->alternateOffsetN[2], gainMap->alternateOffsetD[2]) }; for (uint32_t j = 0; j < height; ++j) { for (uint32_t i = 0; i < width; ++i) { float basePixelRGBA[4]; @@ -278,10 +257,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 +418,21 @@ 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) { + if (gainMap->gainMapMinD[i] == 0 || gainMap->gainMapMaxD[i] == 0 || gainMap->gainMapGammaD[i] == 0 || + gainMap->baseOffsetD[i] == 0 || gainMap->alternateOffsetD[i] == 0) { avifDiagnosticsPrintf(diag, "Per-channel denominator is 0 in avifGainMapMetadata"); return AVIF_RESULT_INVALID_ARGUMENT; } } - if (metadata->baseHdrHeadroomD == 0 || metadata->alternateHdrHeadroomD == 0) { + if (gainMap->baseHdrHeadroomD == 0 || gainMap->alternateHdrHeadroomD == 0) { avifDiagnosticsPrintf(diag, "Headroom denominator is 0 in avifGainMapMetadata"); return AVIF_RESULT_INVALID_ARGUMENT; } - if (metadata->useBaseColorSpace != 0 && metadata->useBaseColorSpace != 1) { - avifDiagnosticsPrintf(diag, "useBaseColorSpace is %d in avifGainMapMetadata", metadata->useBaseColorSpace); + if (gainMap->useBaseColorSpace != 0 && gainMap->useBaseColorSpace != 1) { + avifDiagnosticsPrintf(diag, "useBaseColorSpace is %d in avifGainMapMetadata", gainMap->useBaseColorSpace); return AVIF_RESULT_INVALID_ARGUMENT; } return AVIF_RESULT_OK; @@ -560,9 +537,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 +547,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 +562,31 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, } } + float baseOffset[3] = { toFloat(gainMap->baseOffsetN[0], gainMap->baseOffsetD[0]), + toFloat(gainMap->baseOffsetN[1], gainMap->baseOffsetD[1]), + toFloat(gainMap->baseOffsetN[2], gainMap->baseOffsetD[2]) }; + float alternateOffset[3] = { toFloat(gainMap->alternateOffsetN[0], gainMap->alternateOffsetD[0]), + toFloat(gainMap->alternateOffsetN[1], gainMap->alternateOffsetD[1]), + toFloat(gainMap->alternateOffsetN[2], gainMap->alternateOffsetD[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 +605,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 +631,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 +654,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 +662,17 @@ 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->baseHdrHeadroomN, &gainMap->baseHdrHeadroomD) || + !avifDoubleToUnsignedFraction(alternateHeadroom, &gainMap->alternateHdrHeadroomN, &gainMap->alternateHdrHeadroomD)) { + 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 +694,12 @@ 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->gainMapMinN[c], &gainMap->gainMapMinD[c]) || + !avifDoubleToSignedFraction(gainMapMaxLog2[singleChannel ? 0 : c], &gainMap->gainMapMaxN[c], &gainMap->gainMapMaxD[c]) || + !avifDoubleToSignedFraction(alternateOffset[c], &gainMap->alternateOffsetN[c], &gainMap->alternateOffsetD[c]) || + !avifDoubleToSignedFraction(baseOffset[c], &gainMap->baseOffsetN[c], &gainMap->baseOffsetD[c])) { + goto cleanup; + } } // Scale the gain map values to map [min, max] range to [0, 1]. @@ -724,12 +708,13 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, if (range <= 0.0f) { continue; } + const float gainMapGamma = toFloat(gainMap->gainMapGammaN[c], gainMap->gainMapGammaD[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..cc192a07c1 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,47 @@ 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->baseHdrHeadroomN)); // unsigned int(32) base_hdr_headroom_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->baseHdrHeadroomD)); // unsigned int(32) base_hdr_headroom_denominator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->alternateHdrHeadroomN)); // unsigned int(32) alternate_hdr_headroom_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->alternateHdrHeadroomD)); // 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->gainMapMinN[c])); // int(32) gain_map_min_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->gainMapMinD[c])); // unsigned int(32) gain_map_min_denominator; + AVIF_CHECK(avifROStreamReadU32(s, (uint32_t *)&gainMap->gainMapMaxN[c])); // int(32) gain_map_max_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->gainMapMaxD[c])); // unsigned int(32) gain_map_max_denominator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->gainMapGammaN[c])); // unsigned int(32) gamma_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->gainMapGammaD[c])); // unsigned int(32) gamma_denominator; + AVIF_CHECK(avifROStreamReadU32(s, (uint32_t *)&gainMap->baseOffsetN[c])); // int(32) base_offset_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->baseOffsetD[c])); // unsigned int(32) base_offset_denominator; + AVIF_CHECK(avifROStreamReadU32(s, (uint32_t *)&gainMap->alternateOffsetN[c])); // int(32) alternate_offset_numerator; + AVIF_CHECK(avifROStreamReadU32(s, &gainMap->alternateOffsetD[c])); // 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->gainMapMinN[c] = gainMap->gainMapMinN[0]; + gainMap->gainMapMinD[c] = gainMap->gainMapMinD[0]; + gainMap->gainMapMaxN[c] = gainMap->gainMapMaxN[0]; + gainMap->gainMapMaxD[c] = gainMap->gainMapMaxD[0]; + gainMap->gainMapGammaN[c] = gainMap->gainMapGammaN[0]; + gainMap->gainMapGammaD[c] = gainMap->gainMapGammaD[0]; + gainMap->baseOffsetN[c] = gainMap->baseOffsetN[0]; + gainMap->baseOffsetD[c] = gainMap->baseOffsetD[0]; + gainMap->alternateOffsetN[c] = gainMap->alternateOffsetN[0]; + gainMap->alternateOffsetD[c] = gainMap->alternateOffsetD[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 +2028,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 +5487,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/write.c b/src/write.c index 92f8539602..eaf785637c 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->gainMapMinN[0] == gainMap->gainMapMinN[1] && gainMap->gainMapMinN[0] == gainMap->gainMapMinN[2] && + gainMap->gainMapMinD[0] == gainMap->gainMapMinD[1] && gainMap->gainMapMinD[0] == gainMap->gainMapMinD[2] && + gainMap->gainMapMaxN[0] == gainMap->gainMapMaxN[1] && gainMap->gainMapMaxN[0] == gainMap->gainMapMaxN[2] && + gainMap->gainMapMaxD[0] == gainMap->gainMapMaxD[1] && gainMap->gainMapMaxD[0] == gainMap->gainMapMaxD[2] && + gainMap->gainMapGammaN[0] == gainMap->gainMapGammaN[1] && gainMap->gainMapGammaN[0] == gainMap->gainMapGammaN[2] && + gainMap->gainMapGammaD[0] == gainMap->gainMapGammaD[1] && gainMap->gainMapGammaD[0] == gainMap->gainMapGammaD[2] && + gainMap->baseOffsetN[0] == gainMap->baseOffsetN[1] && gainMap->baseOffsetN[0] == gainMap->baseOffsetN[2] && + gainMap->baseOffsetD[0] == gainMap->baseOffsetD[1] && gainMap->baseOffsetD[0] == gainMap->baseOffsetD[2] && + gainMap->alternateOffsetN[0] == gainMap->alternateOffsetN[1] && + gainMap->alternateOffsetN[0] == gainMap->alternateOffsetN[2] && + gainMap->alternateOffsetD[0] == gainMap->alternateOffsetD[1] && + gainMap->alternateOffsetD[0] == gainMap->alternateOffsetD[2]; } // 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->baseHdrHeadroomN, 32)); // unsigned int(32) base_hdr_headroom_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->baseHdrHeadroomD, 32)); // unsigned int(32) base_hdr_headroom_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->alternateHdrHeadroomN, 32)); // unsigned int(32) alternate_hdr_headroom_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->alternateHdrHeadroomD, 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->gainMapMinN[c], 32)); // int(32) gain_map_min_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapMinD[c], 32)); // unsigned int(32) gain_map_min_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainMap->gainMapMaxN[c], 32)); // int(32) gain_map_max_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapMaxD[c], 32)); // unsigned int(32) gain_map_max_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapGammaN[c], 32)); // unsigned int(32) gamma_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->gainMapGammaD[c], 32)); // unsigned int(32) gamma_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainMap->baseOffsetN[c], 32)); // int(32) base_offset_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->baseOffsetD[c], 32)); // unsigned int(32) base_offset_denominator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainMap->alternateOffsetN[c], 32)); // int(32) alternate_offset_numerator; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainMap->alternateOffsetD[c], 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->baseHdrHeadroomN != firstGainMap->baseHdrHeadroomN || + cellGainMap->baseHdrHeadroomD != firstGainMap->baseHdrHeadroomD || + cellGainMap->alternateHdrHeadroomN != firstGainMap->alternateHdrHeadroomN || + cellGainMap->alternateHdrHeadroomD != firstGainMap->alternateHdrHeadroomD) { 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->gainMapMinN[c] != firstGainMap->gainMapMinN[c] || + cellGainMap->gainMapMinD[c] != firstGainMap->gainMapMinD[c] || + cellGainMap->gainMapMaxN[c] != firstGainMap->gainMapMaxN[c] || + cellGainMap->gainMapMaxD[c] != firstGainMap->gainMapMaxD[c] || + cellGainMap->gainMapGammaN[c] != firstGainMap->gainMapGammaN[c] || + cellGainMap->gainMapGammaD[c] != firstGainMap->gainMapGammaD[c] || + cellGainMap->baseOffsetN[c] != firstGainMap->baseOffsetN[c] || + cellGainMap->baseOffsetD[c] != firstGainMap->baseOffsetD[c] || + cellGainMap->alternateOffsetN[c] != firstGainMap->alternateOffsetN[c] || + cellGainMap->alternateOffsetD[c] != firstGainMap->alternateOffsetD[c]) { 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..8e677ef802 100644 --- a/tests/gtest/avif_fuzztest_enc_dec_experimental.cc +++ b/tests/gtest/avif_fuzztest_enc_dec_experimental.cc @@ -19,8 +19,8 @@ namespace { ::testing::Environment* const kStackLimitEnv = SetStackLimitTo512x1024Bytes(); -void CheckGainMapMetadataMatches(const avifGainMapMetadata& actual, - const avifGainMapMetadata& expected) { +void CheckGainMapMetadataMatches(const avifGainMap& actual, + const avifGainMap& expected) { EXPECT_EQ(actual.baseHdrHeadroomN, expected.baseHdrHeadroomN); EXPECT_EQ(actual.baseHdrHeadroomD, expected.baseHdrHeadroomD); EXPECT_EQ(actual.alternateHdrHeadroomN, expected.alternateHdrHeadroomN); @@ -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..0dbc5f39c7 100644 --- a/tests/gtest/avif_fuzztest_helpers.cc +++ b/tests/gtest/avif_fuzztest_helpers.cc @@ -189,22 +189,48 @@ 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->gainMapMinN[0] = gain_map_min_n0; + image->gainMap->gainMapMinN[1] = gain_map_min_n1; + image->gainMap->gainMapMinN[2] = gain_map_min_n2; + image->gainMap->gainMapMinD[0] = gain_map_min_d0; + image->gainMap->gainMapMinD[1] = gain_map_min_d1; + image->gainMap->gainMapMinD[2] = gain_map_min_d2; + + image->gainMap->gainMapMaxN[0] = gain_map_max_n0; + image->gainMap->gainMapMaxN[1] = gain_map_max_n1; + image->gainMap->gainMapMaxN[2] = gain_map_max_n2; + image->gainMap->gainMapMaxD[0] = gain_map_max_d0; + image->gainMap->gainMapMaxD[1] = gain_map_max_d1; + image->gainMap->gainMapMaxD[2] = gain_map_max_d2; + + image->gainMap->gainMapGammaN[0] = gain_map_gamma_n0; + image->gainMap->gainMapGammaN[1] = gain_map_gamma_n1; + image->gainMap->gainMapGammaN[2] = gain_map_gamma_n2; + image->gainMap->gainMapGammaD[0] = gain_map_gamma_d0; + image->gainMap->gainMapGammaD[1] = gain_map_gamma_d1; + image->gainMap->gainMapGammaD[2] = gain_map_gamma_d2; + + image->gainMap->baseOffsetN[0] = base_offset_n0; + image->gainMap->baseOffsetN[1] = base_offset_n1; + image->gainMap->baseOffsetN[2] = base_offset_n2; + image->gainMap->baseOffsetD[0] = base_offset_d0; + image->gainMap->baseOffsetD[1] = base_offset_d1; + image->gainMap->baseOffsetD[2] = base_offset_d2; + + image->gainMap->alternateOffsetN[0] = alternate_offset_n0; + image->gainMap->alternateOffsetN[1] = alternate_offset_n1; + image->gainMap->alternateOffsetN[2] = alternate_offset_n2; + image->gainMap->alternateOffsetD[0] = alternate_offset_d0; + image->gainMap->alternateOffsetD[1] = alternate_offset_d1; + image->gainMap->alternateOffsetD[2] = alternate_offset_d2; + + image->gainMap->baseHdrHeadroomN = base_hdr_headroom_n; + image->gainMap->baseHdrHeadroomD = base_hdr_headroom_d; + image->gainMap->alternateHdrHeadroomN = alternate_hdr_headroom_n; + image->gainMap->alternateHdrHeadroomD = alternate_hdr_headroom_d; + image->gainMap->useBaseColorSpace = use_base_color_space; + return image; } #endif diff --git a/tests/gtest/avifgainmaptest.cc b/tests/gtest/avifgainmaptest.cc index 98c64369dc..fc1494a273 100644 --- a/tests/gtest/avifgainmaptest.cc +++ b/tests/gtest/avifgainmaptest.cc @@ -23,8 +23,8 @@ 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) { +void CheckGainMapMetadataMatches(const avifGainMap& lhs, + const avifGainMap& rhs) { EXPECT_EQ(lhs.baseHdrHeadroomN, rhs.baseHdrHeadroomN); EXPECT_EQ(lhs.baseHdrHeadroomD, rhs.baseHdrHeadroomD); EXPECT_EQ(lhs.alternateHdrHeadroomN, rhs.alternateHdrHeadroomN); @@ -44,30 +44,28 @@ void CheckGainMapMetadataMatches(const avifGainMapMetadata& lhs, } } -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->baseHdrHeadroomN = 0; + gainMap->baseHdrHeadroomD = 1; + gainMap->alternateHdrHeadroomN = 6; + gainMap->alternateHdrHeadroomD = 2; if (base_rendition_is_hdr) { - std::swap(metadata.baseHdrHeadroomN, metadata.alternateHdrHeadroomN); - std::swap(metadata.baseHdrHeadroomD, metadata.alternateHdrHeadroomD); + std::swap(gainMap->baseHdrHeadroomN, gainMap->alternateHdrHeadroomN); + std::swap(gainMap->baseHdrHeadroomD, gainMap->alternateHdrHeadroomD); } 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->baseOffsetN[c] = 10 * c; + gainMap->baseOffsetD[c] = 1000; + gainMap->alternateOffsetN[c] = 20 * c; + gainMap->alternateOffsetD[c] = 1000; + gainMap->gainMapGammaN[c] = 1; + gainMap->gainMapGammaD[c] = c + 1; + gainMap->gainMapMinN[c] = -1; + gainMap->gainMapMinD[c] = c + 1; + gainMap->gainMapMaxN[c] = 10 + c + 1; + gainMap->gainMapMaxD[c] = c + 1; } - return metadata; } ImagePtr CreateTestImageWithGainMap(bool base_rendition_is_hdr) { @@ -94,7 +92,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 +162,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 +223,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 +276,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->baseHdrHeadroomD = kDenominator; + image->gainMap->alternateHdrHeadroomD = 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->baseOffsetD[c] = kDenominator; + image->gainMap->alternateOffsetD[c] = kDenominator; + image->gainMap->gainMapGammaD[c] = kDenominator; + image->gainMap->gainMapMinD[c] = kDenominator; + image->gainMap->gainMapMaxD[c] = kDenominator; } EncoderPtr encoder(avifEncoderCreate()); @@ -309,8 +305,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 +313,16 @@ 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->baseOffsetN[c] = 1; + image->gainMap->baseOffsetD[c] = 2; + image->gainMap->alternateOffsetN[c] = 3; + image->gainMap->alternateOffsetD[c] = 4; + image->gainMap->gainMapGammaN[c] = 5; + image->gainMap->gainMapGammaD[c] = 6; + image->gainMap->gainMapMinN[c] = 7; + image->gainMap->gainMapMinD[c] = 8; + image->gainMap->gainMapMaxN[c] = 9; + image->gainMap->gainMapMaxD[c] = 10; } EncoderPtr encoder(avifEncoderCreate()); @@ -349,8 +344,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 +356,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 +373,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 +426,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 +448,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 +465,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 +496,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->gainMapGammaN[0] = 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->gainMapGammaN[0] = + cells[0]->gainMap->gainMapGammaN[0]; // Revert. } TEST(GainMapTest, SequenceNotSupported) { @@ -598,7 +586,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 +600,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 +675,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 +694,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 +703,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 +776,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->baseHdrHeadroomN, 6u); + EXPECT_EQ(decoded->gainMap->baseHdrHeadroomD, 2u); // Decode the image. result = avifDecoderNextImage(decoder.get()); @@ -820,8 +805,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->baseHdrHeadroomN, 6u); + EXPECT_EQ(decoded->gainMap->baseHdrHeadroomD, 2u); } TEST(GainMapTest, DecodeColorNoGridGainMapGrid) { @@ -844,8 +829,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->baseHdrHeadroomN, 6u); + EXPECT_EQ(decoded->gainMap->baseHdrHeadroomD, 2u); } TEST(GainMapTest, DecodeUnsupportedVersion) { @@ -958,97 +943,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.baseHdrHeadroomN, gain_map.alternateHdrHeadroomN); + std::swap(gain_map.baseHdrHeadroomD, gain_map.alternateHdrHeadroomD); 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.baseOffsetN[c], gain_map.alternateOffsetN[c]); + std::swap(gain_map.baseOffsetD[c], gain_map.alternateOffsetD[c]); } gain_map.altColorPrimaries = new_alternate.colorPrimaries; gain_map.altTransferCharacteristics = new_alternate.transferCharacteristics; @@ -1383,13 +1285,11 @@ 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->baseHdrHeadroomN = image->gainMap->alternateHdrHeadroomN; + image->gainMap->baseHdrHeadroomD = image->gainMap->alternateHdrHeadroomD; + const float headroom = + static_cast(static_cast(image->gainMap->baseHdrHeadroomN) / + image->gainMap->baseHdrHeadroomD); // Check that when the two headrooms are the same, the gain map is not applied // whatever the target headroom is. @@ -1436,7 +1336,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 +1345,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 image1_headroom = + (float)gain_map->baseHdrHeadroomN / gain_map->baseHdrHeadroomD; const float image2_headroom = - (float)gain_map->metadata.alternateHdrHeadroomN / - gain_map->metadata.alternateHdrHeadroomD; + (float)gain_map->alternateHdrHeadroomN / gain_map->alternateHdrHeadroomD; // Tone map from image1 to image2 by applying the gainmap forward. float psnr_image1_to_image2_forward; @@ -1471,14 +1370,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->baseHdrHeadroomN / gain_map->baseHdrHeadroomD; 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..ac9fc9f48c 100644 --- a/tests/gtest/avifjpeggainmaptest.cc +++ b/tests/gtest/avifjpeggainmaptest.cc @@ -17,54 +17,54 @@ 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.gainMapMinN[0]) / gm.gainMapMinD[0], gain_map_min[0], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapMinN[1]) / m.gainMapMinD[1], + EXPECT_NEAR(static_cast(gm.gainMapMinN[1]) / gm.gainMapMinD[1], gain_map_min[1], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapMinN[2]) / m.gainMapMinD[2], + EXPECT_NEAR(static_cast(gm.gainMapMinN[2]) / gm.gainMapMinD[2], gain_map_min[2], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapMaxN[0]) / m.gainMapMaxD[0], + EXPECT_NEAR(static_cast(gm.gainMapMaxN[0]) / gm.gainMapMaxD[0], gain_map_max[0], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapMaxN[1]) / m.gainMapMaxD[1], + EXPECT_NEAR(static_cast(gm.gainMapMaxN[1]) / gm.gainMapMaxD[1], gain_map_max[1], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapMaxN[2]) / m.gainMapMaxD[2], + EXPECT_NEAR(static_cast(gm.gainMapMaxN[2]) / gm.gainMapMaxD[2], gain_map_max[2], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapGammaN[0]) / m.gainMapGammaD[0], + EXPECT_NEAR(static_cast(gm.gainMapGammaN[0]) / gm.gainMapGammaD[0], gain_map_gamma[0], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapGammaN[1]) / m.gainMapGammaD[1], + EXPECT_NEAR(static_cast(gm.gainMapGammaN[1]) / gm.gainMapGammaD[1], gain_map_gamma[1], kEpsilon); - EXPECT_NEAR(static_cast(m.gainMapGammaN[2]) / m.gainMapGammaD[2], + EXPECT_NEAR(static_cast(gm.gainMapGammaN[2]) / gm.gainMapGammaD[2], gain_map_gamma[2], kEpsilon); - EXPECT_NEAR(static_cast(m.baseOffsetN[0]) / m.baseOffsetD[0], + EXPECT_NEAR(static_cast(gm.baseOffsetN[0]) / gm.baseOffsetD[0], base_offset[0], kEpsilon); - EXPECT_NEAR(static_cast(m.baseOffsetN[1]) / m.baseOffsetD[1], + EXPECT_NEAR(static_cast(gm.baseOffsetN[1]) / gm.baseOffsetD[1], base_offset[1], kEpsilon); - EXPECT_NEAR(static_cast(m.baseOffsetN[2]) / m.baseOffsetD[2], + EXPECT_NEAR(static_cast(gm.baseOffsetN[2]) / gm.baseOffsetD[2], base_offset[2], kEpsilon); EXPECT_NEAR( - static_cast(m.alternateOffsetN[0]) / m.alternateOffsetD[0], + static_cast(gm.alternateOffsetN[0]) / gm.alternateOffsetD[0], alternate_offset[0], kEpsilon); EXPECT_NEAR( - static_cast(m.alternateOffsetN[1]) / m.alternateOffsetD[1], + static_cast(gm.alternateOffsetN[1]) / gm.alternateOffsetD[1], alternate_offset[1], kEpsilon); EXPECT_NEAR( - static_cast(m.alternateOffsetN[2]) / m.alternateOffsetD[2], + static_cast(gm.alternateOffsetN[2]) / gm.alternateOffsetD[2], alternate_offset[2], kEpsilon); - EXPECT_NEAR(static_cast(m.baseHdrHeadroomN) / m.baseHdrHeadroomD, + EXPECT_NEAR(static_cast(gm.baseHdrHeadroomN) / gm.baseHdrHeadroomD, base_hdr_headroom, kEpsilon); EXPECT_NEAR( - static_cast(m.alternateHdrHeadroomN) / m.alternateHdrHeadroomD, + static_cast(gm.alternateHdrHeadroomN) / gm.alternateHdrHeadroomD, alternate_hdr_headroom, kEpsilon); } @@ -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())); } //------------------------------------------------------------------------------