Add alternate output format to pdfium_diff utility

This format shows how badly mangled the image is vs. the expected,
with a uniform grey indicating no difference at a given position.

-- improve the wording of the help message to suggest why an output
   file might not always be produced.

Change-Id: Ib7ea1327ce16f2686a58116e67b9137e9f49b68b
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/82530
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/testing/image_diff/image_diff.cpp b/testing/image_diff/image_diff.cpp
index e7c5681..7257b65 100644
--- a/testing/image_diff/image_diff.cpp
+++ b/testing/image_diff/image_diff.cpp
@@ -20,6 +20,7 @@
 #include "core/fxcrt/fx_memory.h"
 #include "testing/image_diff/image_diff_png.h"
 #include "testing/utils/path_service.h"
+#include "third_party/base/numerics/ranges.h"
 #include "third_party/base/numerics/safe_conversions.h"
 
 #if defined(OS_WIN)
@@ -197,16 +198,21 @@
   fprintf(
       stderr,
       "Usage:\n"
-      "  %s OPTIONS <compare file> <reference file>\n"
-      "    Compares two files on disk, returning 0 when they are the same;\n"
+      "  %s OPTIONS <compare_file> <reference_file>\n"
+      "    Compares two files on disk, returning 0 when they are the same.\n"
       "    Passing \"--histogram\" additionally calculates a diff of the\n"
-      "    RGBA value histograms. (which is resistant to shifts in layout)\n"
-      "    Passing \"--reverse-byte-order\" additionally assumes the compare\n"
-      "    file has BGRA byte ordering.\n"
-      "  %s --diff <compare file> <reference file> <output file>\n"
-      "    Compares two files on disk, outputs an image that visualizes the\n"
-      "    difference to <output file>\n",
-      binary_name.c_str(), binary_name.c_str());
+      "    RGBA value histograms (which is resistant to shifts in layout).\n"
+      "    Passing \"--reverse-byte-order\" additionally assumes the\n"
+      "    compare file has BGRA byte ordering.\n\n"
+      "  %s --diff <compare_file> <reference_file> <output_file>\n"
+      "    Compares two files on disk, and if they differ, outputs an image\n"
+      "    to <output_file> that visualizes the differing pixels as red\n"
+      "    dots.\n\n"
+      "  %s --subtract <compare_file> <reference_file> <output_file>\n"
+      "    Compares two files on disk, and if they differ, outputs an image\n"
+      "    to <output_file> that visualizes the difference as a scaled\n"
+      "    subtraction of pixel values.\n",
+      binary_name.c_str(), binary_name.c_str(), binary_name.c_str());
 }
 
 int CompareImages(const std::string& binary_name,
@@ -279,10 +285,48 @@
   return same;
 }
 
+bool SubtractImages(const Image& image1, const Image& image2, Image* out) {
+  int w = std::min(image1.w(), image2.w());
+  int h = std::min(image1.h(), image2.h());
+  *out = Image(image1);
+  bool same = (image1.w() == image2.w()) && (image1.h() == image2.h());
+
+  for (int y = 0; y < h; ++y) {
+    for (int x = 0; x < w; ++x) {
+      uint32_t pixel1 = image1.pixel_at(x, y);
+      int32_t r1 = pixel1 & 0xff;
+      int32_t g1 = (pixel1 >> 8) & 0xff;
+      int32_t b1 = (pixel1 >> 16) & 0xff;
+
+      uint32_t pixel2 = image2.pixel_at(x, y);
+      int32_t r2 = pixel2 & 0xff;
+      int32_t g2 = (pixel2 >> 8) & 0xff;
+      int32_t b2 = (pixel2 >> 16) & 0xff;
+
+      int32_t delta_r = r1 - r2;
+      int32_t delta_g = g1 - g2;
+      int32_t delta_b = b1 - b2;
+      same &= (delta_r == 0 && delta_g == 0 && delta_b == 0);
+
+      delta_r = pdfium::clamp(128 + delta_r * 8, 0, 255);
+      delta_g = pdfium::clamp(128 + delta_g * 8, 0, 255);
+      delta_b = pdfium::clamp(128 + delta_b * 8, 0, 255);
+
+      uint32_t new_pixel = RGBA_ALPHA;
+      new_pixel |= delta_r;
+      new_pixel |= (delta_g << 8);
+      new_pixel |= (delta_b << 16);
+      out->set_pixel_at(x, y, new_pixel);
+    }
+  }
+  return same;
+}
+
 int DiffImages(const std::string& binary_name,
                const std::string& file1,
                const std::string& file2,
-               const std::string& out_file) {
+               const std::string& out_file,
+               bool do_subtraction) {
   Image actual_image;
   Image baseline_image;
 
@@ -298,7 +342,9 @@
   }
 
   Image diff_image;
-  bool same = CreateImageDiff(baseline_image, actual_image, &diff_image);
+  bool same = do_subtraction
+                  ? SubtractImages(baseline_image, actual_image, &diff_image)
+                  : CreateImageDiff(baseline_image, actual_image, &diff_image);
   if (same)
     return kStatusSame;
 
@@ -324,6 +370,7 @@
 
   bool histograms = false;
   bool produce_diff_image = false;
+  bool produce_image_subtraction = false;
   bool reverse_byte_order = false;
   std::string filename1;
   std::string filename2;
@@ -342,6 +389,8 @@
       histograms = true;
     } else if (strcmp(arg, "--diff") == 0) {
       produce_diff_image = true;
+    } else if (strcmp(arg, "--subtract") == 0) {
+      produce_image_subtraction = true;
     } else if (strcmp(arg, "--reverse-byte-order") == 0) {
       reverse_byte_order = true;
     }
@@ -353,9 +402,10 @@
   if (i < argc)
     diff_filename = argv[i++];
 
-  if (produce_diff_image) {
+  if (produce_diff_image || produce_image_subtraction) {
     if (!diff_filename.empty()) {
-      return DiffImages(binary_name, filename1, filename2, diff_filename);
+      return DiffImages(binary_name, filename1, filename2, diff_filename,
+                        produce_image_subtraction);
     }
   } else if (!filename2.empty()) {
     return CompareImages(binary_name, filename1, filename2, histograms,