diff --git a/apps/avifgainmaputil/printmetadata_command.cc b/apps/avifgainmaputil/printmetadata_command.cc index a993e329b1..010d566f15 100644 --- a/apps/avifgainmaputil/printmetadata_command.cc +++ b/apps/avifgainmaputil/printmetadata_command.cc @@ -91,8 +91,6 @@ avifResult PrintMetadataCommand::Run() { std::cout << " * " << std::left << std::setw(w) << "Gain Map Gamma: " << FormatFractions(metadata.gainMapGammaN, metadata.gainMapGammaD) << "\n"; - std::cout << " * " << std::left << std::setw(w) << "Backward Direction: " - << (metadata.backwardDirection ? "True" : "False") << "\n"; std::cout << " * " << std::left << std::setw(w) << "Use Base Color Space: " << (metadata.useBaseColorSpace ? "True" : "False") << "\n"; diff --git a/apps/avifgainmaputil/swapbase_command.cc b/apps/avifgainmaputil/swapbase_command.cc index e23f3e3986..a7ee05b4ae 100644 --- a/apps/avifgainmaputil/swapbase_command.cc +++ b/apps/avifgainmaputil/swapbase_command.cc @@ -98,7 +98,6 @@ avifResult ChangeBase(const avifImage& image, int depth, // Swap base and alternate in the gain map metadata. avifGainMapMetadata& metadata = swapped->gainMap->metadata; - metadata.backwardDirection = !metadata.backwardDirection; metadata.useBaseColorSpace = !metadata.useBaseColorSpace; std::swap(metadata.baseHdrHeadroomN, metadata.alternateHdrHeadroomN); std::swap(metadata.baseHdrHeadroomD, metadata.alternateHdrHeadroomD); diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c index e5cd249d88..7486cb4d61 100644 --- a/apps/shared/avifjpeg.c +++ b/apps/shared/avifjpeg.c @@ -601,7 +601,6 @@ static avifBool avifJPEGParseGainMapXMPProperties(const xmlNode * rootNode, avif avifGainMapMetadataDouble metadataDouble; // Set default values from Adobe's spec. - metadataDouble.backwardDirection = AVIF_FALSE; metadataDouble.baseHdrHeadroom = 0.0; metadataDouble.alternateHdrHeadroom = 1.0; for (int i = 0; i < 3; ++i) { @@ -637,13 +636,11 @@ static avifBool avifJPEGParseGainMapXMPProperties(const xmlNode * rootNode, avif const char * baseRenditionIsHDR; if (avifJPEGFindGainMapProperty(descNode, "BaseRenditionIsHDR", /*maxValues=*/1, &baseRenditionIsHDR, &numValues)) { if (!strcmp(baseRenditionIsHDR, "True")) { - metadataDouble.backwardDirection = AVIF_TRUE; SwapDoubles(&metadataDouble.baseHdrHeadroom, &metadataDouble.alternateHdrHeadroom); for (int c = 0; c < 3; ++c) { SwapDoubles(&metadataDouble.baseOffset[c], &metadataDouble.alternateOffset[c]); } } else if (!strcmp(baseRenditionIsHDR, "False")) { - metadataDouble.backwardDirection = AVIF_FALSE; } else { return AVIF_FALSE; // Unexpected value. } diff --git a/include/avif/avif.h b/include/avif/avif.h index 79dc86b715..305f79f3bc 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -630,17 +630,14 @@ typedef struct avifGainMapMetadata // // If 'H' is the display's current log2-encoded HDR capacity (HDR to SDR ratio), // then the weight 'w' to apply the gain map is computed as follows: - // f = clamp((H - hdrCapacityMin) / - // (hdrCapacityMax - hdrCapacityMin), 0, 1); - // w = backwardDirection ? f * -1 : f; + // f = clamp((H - baseHdrHeadroom) / + // (alternateHdrHeadroom - baseHdrHeadroom), 0, 1); + // w = sign(alternateHdrHeadroom - baseHdrHeadroom) * f uint32_t baseHdrHeadroomN; uint32_t baseHdrHeadroomD; uint32_t alternateHdrHeadroomN; uint32_t alternateHdrHeadroomD; - // True if the gain map should be applied in reverse, see weight formula above. - avifBool backwardDirection; - // True if tone mapping should be performed in the color space of the // base image. If false, the color space of the alternate image should // be used. @@ -702,7 +699,6 @@ typedef struct avifGainMapMetadataDouble double alternateOffset[3]; double baseHdrHeadroom; double alternateHdrHeadroom; - avifBool backwardDirection; avifBool useBaseColorSpace; } avifGainMapMetadataDouble; diff --git a/src/gainmap.c b/src/gainmap.c index cf45a620d0..35d9505ad9 100644 --- a/src/gainmap.c +++ b/src/gainmap.c @@ -22,7 +22,6 @@ avifBool avifGainMapMetadataDoubleToFractions(avifGainMapMetadata * dst, const a } AVIF_CHECK(avifDoubleToUnsignedFraction(src->baseHdrHeadroom, &dst->baseHdrHeadroomN, &dst->baseHdrHeadroomD)); AVIF_CHECK(avifDoubleToUnsignedFraction(src->alternateHdrHeadroom, &dst->alternateHdrHeadroomN, &dst->alternateHdrHeadroomD)); - dst->backwardDirection = src->backwardDirection; dst->useBaseColorSpace = src->useBaseColorSpace; return AVIF_TRUE; } @@ -50,7 +49,6 @@ avifBool avifGainMapMetadataFractionsToDouble(avifGainMapMetadataDouble * dst, c } dst->baseHdrHeadroom = (double)src->baseHdrHeadroomN / src->baseHdrHeadroomD; dst->alternateHdrHeadroom = (double)src->alternateHdrHeadroomN / src->alternateHdrHeadroomD; - dst->backwardDirection = src->backwardDirection; dst->useBaseColorSpace = src->useBaseColorSpace; return AVIF_TRUE; } @@ -81,11 +79,8 @@ static float avifGetGainMapWeight(float hdrHeadroom, const avifGainMapMetadataDo // This case is not handled in the specification and does not make practical sense. return 0.0f; } - float w = AVIF_CLAMP((hdrHeadroom - baseHdrHeadroom) / (alternateHdrHeadroom - baseHdrHeadroom), 0.0f, 1.0f); - if (metadata->backwardDirection) { - w *= -1.0f; - } - return w; + const float w = AVIF_CLAMP((hdrHeadroom - baseHdrHeadroom) / (alternateHdrHeadroom - baseHdrHeadroom), 0.0f, 1.0f); + return (alternateHdrHeadroom < baseHdrHeadroom) ? -w : w; } // Linear interpolation between 'a' and 'b' (returns 'a' if w == 0.0f, returns 'b' if w == 1.0f). @@ -517,8 +512,6 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, const avifBool colorSpacesDiffer = (baseColorPrimaries != altColorPrimaries); avifColorPrimaries gainMapMathPrimaries; AVIF_CHECKRES(avifChooseColorSpaceForGainMapMath(baseColorPrimaries, altColorPrimaries, &gainMapMathPrimaries)); - const avifBool useBaseColorSpace = (gainMapMathPrimaries == baseColorPrimaries); - const int width = baseRgbImage->width; const int height = baseRgbImage->height; @@ -549,6 +542,7 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, avifGainMapMetadataDouble gainMapMetadata; avifGainMapMetadataSetDefaults(&gainMapMetadata); + gainMapMetadata.useBaseColorSpace = (gainMapMathPrimaries == baseColorPrimaries); float (*baseGammaToLinear)(float) = avifTransferCharacteristicsGetGammaToLinearFunction(baseTransferCharacteristics); float (*altGammaToLinear)(float) = avifTransferCharacteristicsGetGammaToLinearFunction(altTransferCharacteristics); @@ -557,7 +551,7 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, double rgbConversionCoeffs[3][3]; if (colorSpacesDiffer) { - if (useBaseColorSpace) { + if (gainMapMetadata.useBaseColorSpace) { if (!avifColorPrimariesComputeRGBToRGBMatrix(altColorPrimaries, baseColorPrimaries, rgbConversionCoeffs)) { avifDiagnosticsPrintf(diag, "Unsupported RGB color space conversion"); res = AVIF_RESULT_NOT_IMPLEMENTED; @@ -581,11 +575,12 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, float channelMin[3] = { 0 }; for (int j = 0; j < height; ++j) { for (int i = 0; i < width; ++i) { - avifGetRGBAPixel(useBaseColorSpace ? altRgbImage : baseRgbImage, i, j, useBaseColorSpace ? &altRGBInfo : &baseRGBInfo, rgba); + avifGetRGBAPixel(gainMapMetadata.useBaseColorSpace ? altRgbImage : baseRgbImage, i, j, + gainMapMetadata.useBaseColorSpace ? &altRGBInfo : &baseRGBInfo, rgba); // Convert to linear. for (int c = 0; c < 3; ++c) { - if (useBaseColorSpace) { + if (gainMapMetadata.useBaseColorSpace) { rgba[c] = altGammaToLinear(rgba[c]); } else { rgba[c] = baseGammaToLinear(rgba[c]); @@ -604,7 +599,7 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, const float maxOffset = 0.1f; if (channelMin[c] < -kEpsilon) { // Increase the offset to avoid negative values. - if (useBaseColorSpace) { + if (gainMapMetadata.useBaseColorSpace) { gainMapMetadata.alternateOffset[c] = AVIF_MIN(gainMapMetadata.alternateOffset[c] - channelMin[c], maxOffset); } else { gainMapMetadata.baseOffset[c] = AVIF_MIN(gainMapMetadata.baseOffset[c] - channelMin[c], maxOffset); @@ -630,7 +625,7 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, } if (colorSpacesDiffer) { - if (useBaseColorSpace) { + if (gainMapMetadata.useBaseColorSpace) { // convert altRGBA to baseRGBA's color space avifLinearRGBConvertColorSpace(altRGBA, rgbConversionCoeffs); } else { @@ -660,6 +655,23 @@ 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)); + + // 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) { + for (int c = 0; c < numGainMapChannels; ++c) { + for (int j = 0; j < height; ++j) { + for (int i = 0; i < width; ++i) { + gainMapF[c][j * width + i] *= -1.f; + } + } + } + } + // Find approximate min/max for each channel, discarding outliers. float gainMapMinLog2[3] = { 0.0f, 0.0f, 0.0f }; float gainMapMaxLog2[3] = { 0.0f, 0.0f, 0.0f }; @@ -670,16 +682,14 @@ avifResult avifRGBImageComputeGainMap(const avifRGBImage * baseRgbImage, } } - // Fill in the gain map's metadata. + // 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]; - gainMapMetadata.baseHdrHeadroom = log2f(AVIF_MAX(baseMax, kEpsilon)); - gainMapMetadata.alternateHdrHeadroom = log2f(AVIF_MAX(altMax, kEpsilon)); - // baseOffset, alternateOffset and gainMapGamma are all left to their default values. - // They could be tweaked based on the images to optimize quality/compression. } - gainMapMetadata.useBaseColorSpace = useBaseColorSpace; + + // 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; diff --git a/src/read.c b/src/read.c index 2f162ac3e4..adbd5e9b00 100644 --- a/src/read.c +++ b/src/read.c @@ -1973,7 +1973,6 @@ static avifBool avifParseToneMappedImageBox(avifGainMapMetadata * metadata, cons uint8_t channelCount = (flags & 1) * 2 + 1; AVIF_ASSERT_OR_RETURN(channelCount == 1 || channelCount == 3); metadata->useBaseColorSpace = (flags & 2) != 0; - metadata->backwardDirection = (flags & 4) != 0; const avifBool useCommonDenominator = (flags & 8) != 0; if (useCommonDenominator) { diff --git a/src/write.c b/src/write.c index ccc199a757..708577e25a 100644 --- a/src/write.c +++ b/src/write.c @@ -919,9 +919,6 @@ static avifBool avifWriteToneMappedImagePayload(avifRWData * data, const avifGai if (metadata->useBaseColorSpace) { flags |= 2; } - if (metadata->backwardDirection) { - flags |= 4; - } const uint32_t denom = metadata->baseHdrHeadroomD; avifBool useCommonDenominator = metadata->baseHdrHeadroomD == denom && metadata->alternateHdrHeadroomD == denom; for (int c = 0; c < channelCount; ++c) { @@ -1609,8 +1606,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, } const avifGainMapMetadata * firstMetadata = &firstGainMap->metadata; const avifGainMapMetadata * cellMetadata = &cellGainMap->metadata; - if (cellMetadata->backwardDirection != firstMetadata->backwardDirection || - cellMetadata->baseHdrHeadroomN != firstMetadata->baseHdrHeadroomN || + if (cellMetadata->baseHdrHeadroomN != firstMetadata->baseHdrHeadroomN || cellMetadata->baseHdrHeadroomD != firstMetadata->baseHdrHeadroomD || cellMetadata->alternateHdrHeadroomN != firstMetadata->alternateHdrHeadroomN || cellMetadata->alternateHdrHeadroomD != firstMetadata->alternateHdrHeadroomD) { diff --git a/tests/gtest/avif_fuzztest_enc_dec_experimental.cc b/tests/gtest/avif_fuzztest_enc_dec_experimental.cc index 1113209928..4a54339e1a 100644 --- a/tests/gtest/avif_fuzztest_enc_dec_experimental.cc +++ b/tests/gtest/avif_fuzztest_enc_dec_experimental.cc @@ -21,10 +21,6 @@ ::testing::Environment* const kStackLimitEnv = SetStackLimitTo512x1024Bytes(); void CheckGainMapMetadataMatches(const avifGainMapMetadata& actual, const avifGainMapMetadata& expected) { - // 'expecteed' is the source struct which has arbitrary data and booleans - // values can contain any value, but the decoded ('actual') struct should - // be 0 or 1. - EXPECT_EQ(actual.backwardDirection, expected.backwardDirection ? 1 : 0); EXPECT_EQ(actual.baseHdrHeadroomN, expected.baseHdrHeadroomN); EXPECT_EQ(actual.baseHdrHeadroomD, expected.baseHdrHeadroomD); EXPECT_EQ(actual.alternateHdrHeadroomN, expected.alternateHdrHeadroomN); diff --git a/tests/gtest/avifgainmaptest.cc b/tests/gtest/avifgainmaptest.cc index 9de3ca3dd1..2c249d9961 100644 --- a/tests/gtest/avifgainmaptest.cc +++ b/tests/gtest/avifgainmaptest.cc @@ -25,7 +25,6 @@ const char* data_path = nullptr; void CheckGainMapMetadataMatches(const avifGainMapMetadata& lhs, const avifGainMapMetadata& rhs) { - EXPECT_EQ(lhs.backwardDirection, rhs.backwardDirection); EXPECT_EQ(lhs.baseHdrHeadroomN, rhs.baseHdrHeadroomN); EXPECT_EQ(lhs.baseHdrHeadroomD, rhs.baseHdrHeadroomD); EXPECT_EQ(lhs.alternateHdrHeadroomN, rhs.alternateHdrHeadroomN); @@ -47,12 +46,15 @@ void CheckGainMapMetadataMatches(const avifGainMapMetadata& lhs, avifGainMapMetadata GetTestGainMapMetadata(bool base_rendition_is_hdr) { avifGainMapMetadata metadata = {}; - metadata.backwardDirection = base_rendition_is_hdr; metadata.useBaseColorSpace = true; metadata.baseHdrHeadroomN = 0; metadata.baseHdrHeadroomD = 1; metadata.alternateHdrHeadroomN = 6; metadata.alternateHdrHeadroomD = 2; + if (base_rendition_is_hdr) { + std::swap(metadata.baseHdrHeadroomN, metadata.alternateHdrHeadroomN); + std::swap(metadata.baseHdrHeadroomD, metadata.alternateHdrHeadroomD); + } for (int c = 0; c < 3; ++c) { metadata.baseOffsetN[c] = 10 * c; metadata.baseOffsetD[c] = 1000; @@ -884,7 +886,6 @@ TEST(GainMapTest, ConvertMetadata) { metadata_double.alternateOffset[1] = 0.0; metadata_double.baseHdrHeadroom = 1.0; metadata_double.alternateHdrHeadroom = 10.0; - metadata_double.backwardDirection = AVIF_TRUE; // Convert to avifGainMapMetadata. avifGainMapMetadata metadata = {}; @@ -909,7 +910,6 @@ TEST(GainMapTest, ConvertMetadata) { EXPECT_FRACTION_NEAR(metadata.alternateHdrHeadroomN, metadata.alternateHdrHeadroomD, metadata_double.alternateHdrHeadroom); - EXPECT_EQ(metadata.backwardDirection, metadata_double.backwardDirection); // Convert back to avifGainMapMetadataDouble. avifGainMapMetadataDouble metadata_double2 = {}; @@ -933,8 +933,6 @@ TEST(GainMapTest, ConvertMetadata) { kEpsilon); EXPECT_NEAR(metadata_double2.alternateHdrHeadroom, metadata_double.alternateHdrHeadroom, kEpsilon); - EXPECT_EQ(metadata_double2.backwardDirection, - metadata_double.backwardDirection); } TEST(GainMapTest, ConvertMetadataToFractionInvalid) { @@ -955,7 +953,6 @@ TEST(GainMapTest, ConvertMetadataToDoubleInvalid) { static void SwapBaseAndAlternate(const avifImage& new_alternate, avifGainMap& gain_map) { avifGainMapMetadata& metadata = gain_map.metadata; - metadata.backwardDirection = !metadata.backwardDirection; metadata.useBaseColorSpace = !metadata.useBaseColorSpace; std::swap(metadata.baseHdrHeadroomN, metadata.alternateHdrHeadroomN); std::swap(metadata.baseHdrHeadroomD, metadata.alternateHdrHeadroomD); diff --git a/tests/gtest/avifjpeggainmaptest.cc b/tests/gtest/avifjpeggainmaptest.cc index adf3903abe..7adcc7b031 100644 --- a/tests/gtest/avifjpeggainmaptest.cc +++ b/tests/gtest/avifjpeggainmaptest.cc @@ -67,7 +67,6 @@ void CheckGainMapMetadata( EXPECT_NEAR( static_cast<double>(m.alternateHdrHeadroomN) / m.alternateHdrHeadroomD, alternate_hdr_headroom, kEpsilon); - EXPECT_EQ(m.backwardDirection, backward_direction); } TEST(JpegTest, ReadJpegWithGainMap) {