Improve are_images_equal for gain maps. (#3126)

Compare the alternate image/gainmap by tone mapping to the alternate.
Explicitly compare gainmap metadata, although if the file is jpeg
(and are_images_equel doesn't actually support avif so it's gonna be jpeg), then the XMP
will also differ which is already checked for.
Add logging to tell where images differ.
Add negative tests for are_images_equal with gainmaps.
diff --git a/tests/data/README.md b/tests/data/README.md
index 1ad798b..0a370bd 100644
--- a/tests/data/README.md
+++ b/tests/data/README.md
@@ -728,6 +728,17 @@
 see https://helpx.adobe.com/camera-raw/using/hdr-output.html and
 https://gregbenzphotography.com/hdr-images/jpg-hdr-gain-maps-in-adobe-camera-raw/
 
+
+### File [seine_sdr_different_gainmap_srgb.jpg](seine_sdr_different_gainmap_srgb.jpg)
+
+![](seine_sdr_different_gainmap_srgb.jpg)
+
+License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE)
+
+Source : manually edited from seine_sdr_gainmap_srgb.jpg (extracted the gainmap
+with a hex editor, added some text on it with macOS Preview, used a hex editor 
+again to add it back to the file and update the image size in the MPF segment)
+
 ### File [seine_hdr_rec2020.avif](seine_hdr_rec2020.avif)
 
 ![](seine_hdr_rec2020.avif)
diff --git a/tests/data/seine_sdr_different_gainmap_srgb.jpg b/tests/data/seine_sdr_different_gainmap_srgb.jpg
new file mode 100644
index 0000000..17db1fb
--- /dev/null
+++ b/tests/data/seine_sdr_different_gainmap_srgb.jpg
Binary files differ
diff --git a/tests/gtest/are_images_equal.cc b/tests/gtest/are_images_equal.cc
index 02df18e..f6805d5 100644
--- a/tests/gtest/are_images_equal.cc
+++ b/tests/gtest/are_images_equal.cc
@@ -1,8 +1,20 @@
 // Copyright 2022 Google LLC
 // SPDX-License-Identifier: BSD-2-Clause
+//
 // Compares two files and returns whether they are the same once decoded.
+//
+// Usage:
+//   are_images_equal file1 file2 ignore_alpha_flag [psnr_threshold]
+//   [ignore_gain_map_flag]
+// If psnr_threshold is not provided or is 0, the images are compared for strict
+// equality, including all metadata. If psnr_threshold is greater than 0, the
+// images are compared for similarity using PSNR. Most metadata is NOT checked.
+// If a gain map is present and ignore_gain_map_flag is absent or 0, the
+// tone-mapped images are also compared using PSNR. ignore_alpha_flag and
+// ignore_gain_map_flag are booleans (0 or 1).
 
 #include <cstdint>
+#include <cstring>
 #include <iostream>
 #include <limits>
 #include <string>
@@ -11,9 +23,51 @@
 #include "avifutil.h"
 
 using avif::ImagePtr;
+using avif::RGBImageCleanup;
+
+namespace {
+
+ImagePtr ToneMapToAlternate(const ImagePtr& image) {
+  avif::testutil::AvifRgbImage toneMappedRgb(image.get(), image->depth,
+                                             AVIF_RGB_FORMAT_RGB);
+  RGBImageCleanup rgbCleanup(&toneMappedRgb);
+
+  avif::ImagePtr toneMapped(
+      avifImageCreate(toneMappedRgb.width, toneMappedRgb.height,
+                      toneMappedRgb.depth, AVIF_PIXEL_FORMAT_YUV444));
+  toneMapped->colorPrimaries = image->gainMap->altColorPrimaries;
+  toneMapped->transferCharacteristics =
+      image->gainMap->altTransferCharacteristics;
+  toneMapped->matrixCoefficients = image->gainMap->altMatrixCoefficients;
+  toneMapped->yuvRange = image->yuvRange;
+
+  float altHeadroom = 0.0f;
+  if (image->gainMap->alternateHdrHeadroom.d != 0) {
+    altHeadroom = (float)image->gainMap->alternateHdrHeadroom.n /
+                  image->gainMap->alternateHdrHeadroom.d;
+  }
+
+  avifDiagnostics diag;
+  avifResult res = avifImageApplyGainMap(
+      image.get(), image->gainMap, altHeadroom, toneMapped->colorPrimaries,
+      toneMapped->transferCharacteristics, &toneMappedRgb, &toneMapped->clli,
+      &diag);
+  if (res != AVIF_RESULT_OK) {
+    std::cerr << "Failed to apply gain map: " << diag.error << std::endl;
+    return nullptr;
+  }
+
+  res = avifImageRGBToYUV(toneMapped.get(), &toneMappedRgb);
+  if (res != AVIF_RESULT_OK) {
+    return nullptr;
+  }
+  return toneMapped;
+}
+
+}  // namespace
 
 int main(int argc, char** argv) {
-  if (argc != 4 && argc != 5 && argc != 6) {
+  if (argc < 4 || argc > 6) {
     std::cerr << "Wrong argument: " << argv[0]
               << " file1 file2 ignore_alpha_flag [psnr_threshold] "
                  "[ignore_gain_map_flag]"
@@ -21,6 +75,7 @@
     return 2;
   }
   const bool ignore_gain_map = argc > 5 && std::stoi(argv[5]) != 0;
+  const double psnr_threshold = argc > 4 ? std::stod(argv[4]) : 0;
 
   ImagePtr decoded[2] = {ImagePtr(avifImageCreateEmpty()),
                          ImagePtr(avifImageCreateEmpty())};
@@ -55,22 +110,31 @@
     return 1;
   }
 
+  if (!ignore_gain_map &&
+      ((decoded[0]->gainMap != nullptr) != (decoded[1]->gainMap != nullptr))) {
+    std::cerr << "Images " << argv[1] << " and " << argv[2]
+              << " have different gainmap presence" << std::endl;
+    return 1;
+  }
+
   bool ignore_alpha = std::stoi(argv[3]) != 0;
 
-  if (argc == 4) {
+  if (psnr_threshold <= 0) {  // Check for strict equality.
     if (!avif::testutil::AreImagesEqual(*decoded[0], *decoded[1],
                                         ignore_alpha)) {
       auto psnr =
           avif::testutil::GetPsnr(*decoded[0], *decoded[1], ignore_alpha);
       std::cerr << "Images " << argv[1] << " and " << argv[2]
-                << " are different (PSNR: " << psnr << ")." << std::endl;
+                << " are different (base image PSNR: " << psnr << ")."
+                << std::endl;
       return 1;
     }
     std::cout << "Images " << argv[1] << " and " << argv[2] << " are identical."
               << std::endl;
   } else {
+    // If there is a gain map, this is the PSNR of the base images only.
     auto psnr = avif::testutil::GetPsnr(*decoded[0], *decoded[1], ignore_alpha);
-    if (psnr < std::stod(argv[4])) {
+    if (psnr < psnr_threshold) {
       std::cerr << "PSNR: " << psnr << ", images " << argv[1] << " and "
                 << argv[2] << " are not similar enough (threshold: " << argv[4]
                 << ")." << std::endl;
@@ -78,6 +142,53 @@
     }
     std::cout << "PSNR: " << psnr << ", images " << argv[1] << " and "
               << argv[2] << " are similar." << std::endl;
+
+    // If there is a gain map, tone map both images to their alternate headroom
+    // and check the PSNR of the tone mapped images.
+    if (decoded[0]->gainMap && decoded[1]->gainMap &&
+        decoded[0]->gainMap->image && decoded[1]->gainMap->image) {
+      // We don't support ICC profiles when tonemapping, so clear them after
+      // checking for equality.
+      if (decoded[0]->icc.size != decoded[1]->icc.size ||
+          (decoded[0]->icc.size > 0 &&
+           std::memcmp(decoded[0]->icc.data, decoded[1]->icc.data,
+                       decoded[0]->icc.size) != 0)) {
+        std::cerr << "Gainmap PSNR: Images " << argv[1] << " and " << argv[2]
+                  << " have different ICC profiles." << std::endl;
+        return 1;
+      }
+      if (decoded[0]->gainMap->altICC.size !=
+              decoded[1]->gainMap->altICC.size ||
+          (decoded[0]->gainMap->altICC.size > 0 &&
+           std::memcmp(decoded[0]->gainMap->altICC.data,
+                       decoded[1]->gainMap->altICC.data,
+                       decoded[0]->gainMap->altICC.size) != 0)) {
+        std::cerr << "Gainmap PSNR: Images " << argv[1] << " and " << argv[2]
+                  << " have different ICC profiles." << std::endl;
+        return 1;
+      }
+      avifRWDataFree(&decoded[0]->icc);
+      avifRWDataFree(&decoded[1]->icc);
+      avifRWDataFree(&decoded[0]->gainMap->altICC);
+      avifRWDataFree(&decoded[1]->gainMap->altICC);
+
+      avif::ImagePtr toneMapped0 = ToneMapToAlternate(decoded[0]);
+      avif::ImagePtr toneMapped1 = ToneMapToAlternate(decoded[1]);
+      if (!toneMapped0 || !toneMapped1) {
+        std::cerr << "Failed to tone map images." << std::endl;
+        return 1;
+      }
+      psnr = avif::testutil::GetPsnr(*toneMapped0, *toneMapped1);
+      if (psnr < psnr_threshold) {
+        std::cerr << "Tone-mapped PSNR: " << psnr << ", images " << argv[1]
+                  << " and " << argv[2]
+                  << " are not similar enough (threshold: " << argv[4] << ")."
+                  << std::endl;
+        return 1;
+      }
+      std::cout << "Tone-mapped PSNR: " << psnr << ", images " << argv[1]
+                << " and " << argv[2] << " are similar." << std::endl;
+    }
   }
 
   return 0;
diff --git a/tests/gtest/aviftest_helpers.cc b/tests/gtest/aviftest_helpers.cc
index 4fd63f3..2ed0adc 100644
--- a/tests/gtest/aviftest_helpers.cc
+++ b/tests/gtest/aviftest_helpers.cc
@@ -10,6 +10,7 @@
 #include <cstdlib>
 #include <cstring>
 #include <fstream>
+#include <iostream>
 #include <limits>
 #include <string>
 #include <vector>
@@ -266,6 +267,9 @@
   if (image1.width != image2.width || image1.height != image2.height ||
       image1.depth != image2.depth || image1.yuvFormat != image2.yuvFormat ||
       image1.yuvRange != image2.yuvRange) {
+    std::cerr
+        << "Image features mismatch: dimensions, depth, yuvFormat or yuvRange"
+        << std::endl;
     return false;
   }
   assert(image1.width * image1.height > 0);
@@ -282,26 +286,35 @@
           avifImageIsOpaque(&image2)) {
         continue;
       }
+      std::cerr
+          << "Image features mismatch: plane presence differs for channel " << c
+          << std::endl;
       return false;
     }
     if (c == AVIF_CHAN_A && row1 != nullptr &&
         image1.alphaPremultiplied != image2.alphaPremultiplied &&
         !avifImageIsOpaque(&image1)) {
       // Alpha premultiplication is ignored if alpha is opaque.
+      std::cerr << "Image features mismatch: alphaPremultiplied" << std::endl;
       return false;
     }
   }
 
-  if (!AreByteSequencesEqual(image1.icc, image2.icc)) return false;
+  if (!AreByteSequencesEqual(image1.icc, image2.icc)) {
+    std::cerr << "Image features mismatch: icc" << std::endl;
+    return false;
+  }
 
   if (image1.colorPrimaries != image2.colorPrimaries ||
       image1.transferCharacteristics != image2.transferCharacteristics ||
       image1.matrixCoefficients != image2.matrixCoefficients) {
+    std::cerr << "Image features mismatch: CICP" << std::endl;
     return false;
   }
 
   if (image1.clli.maxCLL != image2.clli.maxCLL ||
       image1.clli.maxPALL != image2.clli.maxPALL) {
+    std::cerr << "Image features mismatch: clli" << std::endl;
     return false;
   }
   if (image1.transformFlags != image2.transformFlags ||
@@ -313,25 +326,41 @@
        std::memcmp(&image1.irot, &image2.irot, sizeof(image1.irot))) ||
       ((image1.transformFlags & AVIF_TRANSFORM_IMIR) &&
        std::memcmp(&image1.imir, &image2.imir, sizeof(image1.imir)))) {
+    std::cerr << "Image features mismatch: transformFlags" << std::endl;
     return false;
   }
 
-  if (!AreByteSequencesEqual(image1.exif, image2.exif)) return false;
-  if (!AreByteSequencesEqual(image1.xmp, image2.xmp)) return false;
+  if (!AreByteSequencesEqual(image1.exif, image2.exif)) {
+    std::cerr << "Image features mismatch: exif" << std::endl;
+    return false;
+  }
+  if (!AreByteSequencesEqual(image1.xmp, image2.xmp)) {
+    std::cerr << "Image features mismatch: xmp" << std::endl;
+    return false;
+  }
 
   if (!MatchEachPropertyOfFirstImageInSecondImage(image1, image2) ||
       !MatchEachPropertyOfFirstImageInSecondImage(image2, image1)) {
+    std::cerr << "Image features mismatch: properties" << std::endl;
     return false;
   }
 
-  if (!image1.gainMap != !image2.gainMap) return false;
+  if (!image1.gainMap != !image2.gainMap) {
+    std::cerr << "Image features mismatch: gainMap presence" << std::endl;
+    return false;
+  }
   if (image1.gainMap != nullptr) {
     if (!avifSameGainMapMetadata(image1.gainMap, image2.gainMap) ||
         !avifSameGainMapAltMetadata(image1.gainMap, image2.gainMap)) {
+      std::cerr << "Image features mismatch: gainMap metadata" << std::endl;
       return false;
     }
 
-    if (!image1.gainMap->image != !image2.gainMap->image) return false;
+    if (!image1.gainMap->image != !image2.gainMap->image) {
+      std::cerr << "Image features mismatch: gainMap image presence"
+                << std::endl;
+      return false;
+    }
   }
   return true;
 }
@@ -344,6 +373,12 @@
     return false;
   }
 
+  if ((image1.gainMap != nullptr) != (image2.gainMap != nullptr)) {
+    std::cerr << "AreImagesEqual failed: gain map presence differs"
+              << std::endl;
+    return false;
+  }
+
   for (avifChannelIndex c :
        {AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A}) {
     if (ignore_alpha && c == AVIF_CHAN_A) continue;
@@ -362,10 +397,14 @@
         if (!std::equal(reinterpret_cast<const uint16_t*>(row1),
                         reinterpret_cast<const uint16_t*>(row1) + plane_width,
                         reinterpret_cast<const uint16_t*>(row2))) {
+          std::cerr << "AreImagesEqual failed: pixels differ in channel " << c
+                    << " at row " << y << std::endl;
           return false;
         }
       } else {
         if (!std::equal(row1, row1 + plane_width, row2)) {
+          std::cerr << "AreImagesEqual failed: pixels differ in channel " << c
+                    << " at row " << y << std::endl;
           return false;
         }
       }
@@ -377,6 +416,7 @@
   if (image1.gainMap != nullptr && image1.gainMap->image != nullptr &&
       image2.gainMap != nullptr && image2.gainMap->image != nullptr &&
       !AreImagesEqual(*image1.gainMap->image, *image2.gainMap->image)) {
+    std::cerr << "AreImagesEqual failed: gain map images differ" << std::endl;
     return false;
   }
   return true;
@@ -420,6 +460,12 @@
   if (image1.width != image2.width || image1.height != image2.height ||
       image1.depth != image2.depth || image1.yuvFormat != image2.yuvFormat ||
       image1.yuvRange != image2.yuvRange) {
+    std::cerr << "Error: cannot compute PSNR because of dimensions or yuv "
+                 "format mismatch ("
+              << image1.width << "x" << image1.height << " format "
+              << image1.yuvFormat << " vs " << image2.width << "x"
+              << image2.height << " format " << image2.yuvFormat << ")"
+              << std::endl;
     return -1;
   }
   assert(image1.width * image1.height > 0);
@@ -514,6 +560,8 @@
       image1.depth != image2.depth || image1.format != image2.format ||
       image1.alphaPremultiplied != image2.alphaPremultiplied ||
       image1.isFloat != image2.isFloat) {
+    std::cerr << "AreImagesEqual(avifRGBImage) failed: features do not match"
+              << std::endl;
     return false;
   }
   const uint8_t* row1 = image1.pixels;
@@ -521,6 +569,8 @@
   const unsigned int row_width = image1.width * avifRGBImagePixelSize(&image1);
   for (unsigned int y = 0; y < image1.height; ++y) {
     if (!std::equal(row1, row1 + row_width, row2)) {
+      std::cerr << "AreImagesEqual(avifRGBImage) failed: pixels differ at row "
+                << y << std::endl;
       return false;
     }
     row1 += image1.rowBytes;
diff --git a/tests/gtest/aviftest_helpers.h b/tests/gtest/aviftest_helpers.h
index 866dd56..3695c64 100644
--- a/tests/gtest/aviftest_helpers.h
+++ b/tests/gtest/aviftest_helpers.h
@@ -144,6 +144,7 @@
 // Returns the Peak Signal-to-Noise Ratio of image1 compared to image2.
 // A value of 99dB means all samples are exactly the same.
 // A negative value means that the input images cannot be compared.
+// The gainmap, if any, is ignored.
 double GetPsnr(const avifImage& image1, const avifImage& image2,
                bool ignore_alpha = false);
 
diff --git a/tests/test_cmd_avifgainmaputil.sh b/tests/test_cmd_avifgainmaputil.sh
index b4c7d93..b72c473 100755
--- a/tests/test_cmd_avifgainmaputil.sh
+++ b/tests/test_cmd_avifgainmaputil.sh
@@ -17,8 +17,10 @@
 INPUT_AVIF_GAINMAP_HDR="${TESTDATA_DIR}/seine_hdr_gainmap_srgb.avif"
 INPUT_AVIF_HDR2020="${TESTDATA_DIR}/seine_hdr_rec2020.avif"
 INPUT_JPEG_GAINMAP_SDR="${TESTDATA_DIR}/seine_sdr_gainmap_srgb.jpg"
+INPUT_JPEG_DIFFERENT_GAINMAP_SDR="${TESTDATA_DIR}/seine_sdr_different_gainmap_srgb.jpg"
 INPUT_AVIF_GAINMAP_SDR_WITH_ICC="${TESTDATA_DIR}/seine_sdr_gainmap_srgb_icc.avif"
 # Output file names.
+INPUT_JPEG_GAINMAP_SDR_MODIFIED="seine_sdr_gainmap_srgb_modified.jpg"
 AVIF_OUTPUT="avif_test_cmd_avifgainmaputil_output.avif"
 JPEG_OUTPUT="avif_test_cmd_avifgainmaputil_output.jpg"
 PNG_OUTPUT="avif_test_cmd_avifgainmaputil_output.png"
@@ -51,14 +53,14 @@
       -q 90 --qgain-map 90
   "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${PNG_OUTPUT}" --headroom 0
   "${AVIFGAINMAPUTIL}" tonemap "${INPUT_AVIF_GAINMAP_SDR}" "${PNG_OUTPUT}" --headroom 0 --clli 400,500
-  "${ARE_IMAGES_EQUAL}" "${PNG_OUTPUT}" "${INPUT_JPEG_GAINMAP_SDR}" 0 40
+  "${ARE_IMAGES_EQUAL}" "${PNG_OUTPUT}" "${INPUT_JPEG_GAINMAP_SDR}" 0 40 1
 
   # Test combine with overridden cicp values. Matrix coefficient 0 (identity) makes it obvious if there is an issue.
   "${AVIFGAINMAPUTIL}" combine "${INPUT_JPEG_GAINMAP_SDR}" "${INPUT_AVIF_HDR2020}" "${AVIF_OUTPUT}" \
       -q 100 --qgain-map 100 --cicp-base 1/13/0 --ignore-profile
   # Tone map to SDR and compare with original SDR.
   "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${PNG_OUTPUT}" --headroom 0
-  "${ARE_IMAGES_EQUAL}" "${PNG_OUTPUT}" "${INPUT_JPEG_GAINMAP_SDR}" 0 99
+  "${ARE_IMAGES_EQUAL}" "${PNG_OUTPUT}" "${INPUT_JPEG_GAINMAP_SDR}" 0 99 1
   # Tone map to HDR and compare with original HDR.
   "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${AVIF_OUTPUT}.tonemapped.png" --headroom 2
   # are_images_equal doesn't support AVIF so we convert to PNG.
@@ -69,7 +71,7 @@
   "${AVIFGAINMAPUTIL}" combine "${INPUT_AVIF_HDR2020}"  "${INPUT_JPEG_GAINMAP_SDR}" "${AVIF_OUTPUT}" \
       -q 100 --qgain-map 100 --cicp-alternate 1/13/0 --ignore-profile
   "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${PNG_OUTPUT}" --headroom 0
-  "${ARE_IMAGES_EQUAL}" "${PNG_OUTPUT}" "${INPUT_JPEG_GAINMAP_SDR}" 0 50 # A bit of loss from going through gainmap
+  "${ARE_IMAGES_EQUAL}" "${PNG_OUTPUT}" "${INPUT_JPEG_GAINMAP_SDR}" 0 50 1 # A bit of loss from going through gainmap
   "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${AVIF_OUTPUT}.tonemapped.png" --headroom 2
   # are_images_equal doesn't support AVIF so we convert to PNG.
   "${AVIFDEC}" "${INPUT_AVIF_HDR2020}" "input_avif_hdr2020.png"
@@ -81,10 +83,11 @@
   "${AVIFGAINMAPUTIL}" swapbase "${INPUT_AVIF_GAINMAP_SDR_WITH_ICC}" "${AVIF_OUTPUT}" --qcolor 90 --qgain-map 90 --ignore-profile
 
    # Also test the are_images_equal binary itself with some gain maps
+  "${ARE_IMAGES_EQUAL}" "${INPUT_JPEG_GAINMAP_SDR}" "${INPUT_JPEG_GAINMAP_SDR}" 0 0 0
   "${ARE_IMAGES_EQUAL}" "${INPUT_JPEG_GAINMAP_SDR}" "${INPUT_JPEG_GAINMAP_SDR}" 0 40 0
   "${ARE_IMAGES_EQUAL}" "${INPUT_JPEG_GAINMAP_SDR}" "${INPUT_JPEG_GAINMAP_SDR}" 0 40 1
 
-  # Check if avifgainmaputil was built with libxml2.
+  # Check if avifgainmaputil was built with libxml2, allowing loading of jpeg gain maps.
   # If it was not, the 'convert' command will fail with an error message
   # containing "libxml2".
   if "${AVIFGAINMAPUTIL}" convert "${INPUT_JPEG_GAINMAP_SDR}" "${AVIF_OUTPUT}" 2>&1 | grep -q "libxml2"; then
@@ -92,11 +95,20 @@
     popd
     exit 0
   fi
+
   "${AVIFGAINMAPUTIL}" convert "${INPUT_JPEG_GAINMAP_SDR}" "${AVIF_OUTPUT}"
    # should fail because icc profiles are not supported
   "${AVIFGAINMAPUTIL}" convert "${INPUT_JPEG_GAINMAP_SDR}" "${AVIF_OUTPUT}" --swap-base && exit 1
   "${AVIFGAINMAPUTIL}" convert "${INPUT_JPEG_GAINMAP_SDR}" "${AVIF_OUTPUT}" --swap-base --ignore-profile \
       --cicp 2/3/4
+
+  # Negative tests for are_images_equal: different images should not be equal.
+  "${ARE_IMAGES_EQUAL}" "${INPUT_JPEG_GAINMAP_SDR}" "${INPUT_JPEG_DIFFERENT_GAINMAP_SDR}" 0 0 0 && exit 1
+  "${ARE_IMAGES_EQUAL}" "${INPUT_JPEG_GAINMAP_SDR}" "${INPUT_JPEG_DIFFERENT_GAINMAP_SDR}" 0 40 0 && exit 1
+  # Change just the gain map metadata in the XMP.
+  LC_ALL=C sed 's/<rdf:li>1/<rdf:li>2/' "${INPUT_JPEG_GAINMAP_SDR}" > "${INPUT_JPEG_GAINMAP_SDR_MODIFIED}"
+  "${ARE_IMAGES_EQUAL}" "${INPUT_JPEG_GAINMAP_SDR}" "${INPUT_JPEG_GAINMAP_SDR_MODIFIED}" 0 0 0 && exit 1
+  "${ARE_IMAGES_EQUAL}" "${INPUT_JPEG_GAINMAP_SDR}" "${INPUT_JPEG_GAINMAP_SDR_MODIFIED}" 0 30 0 && exit 1
 popd
 
 exit 0