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) {