Remove backwardDirection from avifGainMapMetadata
This did not end up in the ISO 21496-1 specification.
diff --git a/apps/avifgainmaputil/printmetadata_command.cc b/apps/avifgainmaputil/printmetadata_command.cc
index a993e32..010d566 100644
--- a/apps/avifgainmaputil/printmetadata_command.cc
+++ b/apps/avifgainmaputil/printmetadata_command.cc
@@ -91,8 +91,6 @@
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 e23f3e3..a7ee05b 100644
--- a/apps/avifgainmaputil/swapbase_command.cc
+++ b/apps/avifgainmaputil/swapbase_command.cc
@@ -98,7 +98,6 @@
// 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 e5cd249..7486cb4 100644
--- a/apps/shared/avifjpeg.c
+++ b/apps/shared/avifjpeg.c
@@ -601,7 +601,6 @@
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 @@
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 79dc86b..305f79f 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -630,17 +630,14 @@
//
// 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 @@
double alternateOffset[3];
double baseHdrHeadroom;
double alternateHdrHeadroom;
- avifBool backwardDirection;
avifBool useBaseColorSpace;
} avifGainMapMetadataDouble;
diff --git a/src/gainmap.c b/src/gainmap.c
index cf45a62..35d9505 100644
--- a/src/gainmap.c
+++ b/src/gainmap.c
@@ -22,7 +22,6 @@
}
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 @@
}
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 @@
// 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 @@
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 @@
avifGainMapMetadataDouble gainMapMetadata;
avifGainMapMetadataSetDefaults(&gainMapMetadata);
+ gainMapMetadata.useBaseColorSpace = (gainMapMathPrimaries == baseColorPrimaries);
float (*baseGammaToLinear)(float) = avifTransferCharacteristicsGetGammaToLinearFunction(baseTransferCharacteristics);
float (*altGammaToLinear)(float) = avifTransferCharacteristicsGetGammaToLinearFunction(altTransferCharacteristics);
@@ -557,7 +551,7 @@
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 @@
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 @@
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 @@
}
if (colorSpacesDiffer) {
- if (useBaseColorSpace) {
+ if (gainMapMetadata.useBaseColorSpace) {
// convert altRGBA to baseRGBA's color space
avifLinearRGBConvertColorSpace(altRGBA, rgbConversionCoeffs);
} else {
@@ -660,6 +655,23 @@
}
}
+ // 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 @@
}
}
- // 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 2f162ac..adbd5e9 100644
--- a/src/read.c
+++ b/src/read.c
@@ -1973,7 +1973,6 @@
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 ccc199a..708577e 100644
--- a/src/write.c
+++ b/src/write.c
@@ -919,9 +919,6 @@
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 @@
}
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 1113209..4a54339 100644
--- a/tests/gtest/avif_fuzztest_enc_dec_experimental.cc
+++ b/tests/gtest/avif_fuzztest_enc_dec_experimental.cc
@@ -21,10 +21,6 @@
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 9de3ca3..2c249d9 100644
--- a/tests/gtest/avifgainmaptest.cc
+++ b/tests/gtest/avifgainmaptest.cc
@@ -25,7 +25,6 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 adf3903..7adcc7b 100644
--- a/tests/gtest/avifjpeggainmaptest.cc
+++ b/tests/gtest/avifjpeggainmaptest.cc
@@ -67,7 +67,6 @@
EXPECT_NEAR(
static_cast<double>(m.alternateHdrHeadroomN) / m.alternateHdrHeadroomD,
alternate_hdr_headroom, kEpsilon);
- EXPECT_EQ(m.backwardDirection, backward_direction);
}
TEST(JpegTest, ReadJpegWithGainMap) {