Add ignore exif/xmp to avifginmaputil (#3300)

Fixes #3297
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8af3be1..218207e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 * Support ignoring alpha in avifDecoder::imageContentToDecode.
 * avifenc: add --ignore-alpha flag to discard alpha channel on encode
 * avifgainmaputil: add --ignore-alpha flag to discard alpha channel
+* avifgainmaputil: add --ignore-exif and --ignore-xmp flags
 
 ### Changed since 1.4.2
 
diff --git a/apps/avifgainmaputil/combine_command.cc b/apps/avifgainmaputil/combine_command.cc
index 420a193..b58b73b 100644
--- a/apps/avifgainmaputil/combine_command.cc
+++ b/apps/avifgainmaputil/combine_command.cc
@@ -98,7 +98,8 @@
   }
   avifResult result = ReadImage(
       base_image.get(), arg_base_filename_, pixel_format, arg_image_read_.depth,
-      arg_image_read_.ignore_profile, arg_image_read_.ignore_alpha,
+      arg_image_read_.ignore_profile, arg_image_read_.ignore_exif,
+      arg_image_read_.ignore_xmp, arg_image_read_.ignore_alpha,
       /*ignore_gain_map=*/true, arg_jobs_.jobs.value());
   if (result != AVIF_RESULT_OK) {
     std::cout << "Failed to read base image: " << avifResultToString(result)
@@ -117,6 +118,7 @@
   result =
       ReadImage(alternate_image.get(), arg_alternate_filename_, pixel_format,
                 arg_image_read_.depth, arg_image_read_.ignore_profile,
+                arg_image_read_.ignore_exif, arg_image_read_.ignore_xmp,
                 arg_image_read_.ignore_alpha, /*ignore_gain_map=*/true,
                 arg_jobs_.jobs.value());
   if (result != AVIF_RESULT_OK) {
diff --git a/apps/avifgainmaputil/convert_command.cc b/apps/avifgainmaputil/convert_command.cc
index 54e4744..b6ba6b3 100644
--- a/apps/avifgainmaputil/convert_command.cc
+++ b/apps/avifgainmaputil/convert_command.cc
@@ -62,6 +62,7 @@
   avifResult result =
       ReadImage(image.get(), arg_input_filename_.value(), pixel_format,
                 arg_image_read_.depth, arg_image_read_.ignore_profile,
+                arg_image_read_.ignore_exif, arg_image_read_.ignore_xmp,
                 arg_image_read_.ignore_alpha, /*ignore_gain_map=*/false,
                 arg_jobs_.jobs.value());
   if (result != AVIF_RESULT_OK) {
diff --git a/apps/avifgainmaputil/imageio.cc b/apps/avifgainmaputil/imageio.cc
index c1a0725..900f30c 100644
--- a/apps/avifgainmaputil/imageio.cc
+++ b/apps/avifgainmaputil/imageio.cc
@@ -191,8 +191,8 @@
 
 avifResult ReadImage(avifImage* image, const std::string& input_filename,
                      avifPixelFormat requested_format, uint32_t requested_depth,
-                     bool ignore_profile, bool ignore_alpha,
-                     bool ignore_gain_map, int jobs) {
+                     bool ignore_profile, bool ignore_exif, bool ignore_xmp,
+                     bool ignore_alpha, bool ignore_gain_map, int jobs) {
   avifAppFileFormat input_format = avifGuessFileFormat(input_filename.c_str());
   if (input_format == AVIF_APP_FILE_FORMAT_UNKNOWN) {
     std::cerr << "Cannot determine input format: " << input_filename;
@@ -210,6 +210,8 @@
       decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
     }
     decoder->ignoreICC = ignore_profile;
+    decoder->ignoreExif = ignore_exif;
+    decoder->ignoreXMP = ignore_xmp;
     avifResult result = ReadAvif(decoder.get(), input_filename);
     if (result != AVIF_RESULT_OK) {
       return result;
@@ -236,9 +238,9 @@
     const avifAppFileFormat file_format = avifReadImage(
         input_filename.c_str(), AVIF_APP_FILE_FORMAT_UNKNOWN /* guess format */,
         requested_format, static_cast<int>(requested_depth),
-        AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, ignore_profile,
-        /*ignoreExif=*/false, /*ignoreXMP=*/false, ignore_alpha,
-        ignore_gain_map, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image,
+        AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, ignore_profile, ignore_exif,
+        ignore_xmp, ignore_alpha, ignore_gain_map,
+        AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image,
         /*outDepth=*/nullptr,
         /*sourceTiming=*/nullptr, /*frameIter=*/nullptr);
     if (file_format == AVIF_APP_FILE_FORMAT_UNKNOWN) {
diff --git a/apps/avifgainmaputil/imageio.h b/apps/avifgainmaputil/imageio.h
index 48d0275..74dcacc 100644
--- a/apps/avifgainmaputil/imageio.h
+++ b/apps/avifgainmaputil/imageio.h
@@ -19,8 +19,8 @@
 // Reads an image in any of the supported formats. Ignores any gain map.
 avifResult ReadImage(avifImage* image, const std::string& input_filename,
                      avifPixelFormat requested_format, uint32_t requested_depth,
-                     bool ignore_profile, bool ignore_alpha,
-                     bool ignore_gain_map, int jobs);
+                     bool ignore_profile, bool ignore_exif, bool ignore_xmp,
+                     bool ignore_alpha, bool ignore_gain_map, int jobs);
 
 // Writes an image in avif format given a pre-configured encoder.
 avifResult WriteAvif(const avifImage* image, avifEncoder* encoder,
diff --git a/apps/avifgainmaputil/program_command.h b/apps/avifgainmaputil/program_command.h
index 5c18b0b..c60fb96 100644
--- a/apps/avifgainmaputil/program_command.h
+++ b/apps/avifgainmaputil/program_command.h
@@ -134,6 +134,8 @@
   argparse::ArgValue<int> pixel_format;
   argparse::ArgValue<bool> ignore_profile;
   argparse::ArgValue<bool> ignore_alpha;
+  argparse::ArgValue<bool> ignore_exif;
+  argparse::ArgValue<bool> ignore_xmp;
 
   void Init(argparse::ArgumentParser& argparse) {
     argparse
@@ -154,6 +156,18 @@
             "(no-op if absent)")
         .action(argparse::Action::STORE_TRUE)
         .default_value("false");
+    argparse.add_argument(ignore_exif, "--ignore-exif")
+        .help(
+            "If the input file contains embedded Exif metadata, ignore it "
+            "(no-op if absent)")
+        .action(argparse::Action::STORE_TRUE)
+        .default_value("false");
+    argparse.add_argument(ignore_xmp, "--ignore-xmp")
+        .help(
+            "If the input file contains embedded XMP metadata, ignore it "
+            "(no-op if absent)")
+        .action(argparse::Action::STORE_TRUE)
+        .default_value("false");
   }
 };
 
diff --git a/apps/avifgainmaputil/swapbase_command.cc b/apps/avifgainmaputil/swapbase_command.cc
index fdabf8c..67a9aa4 100644
--- a/apps/avifgainmaputil/swapbase_command.cc
+++ b/apps/avifgainmaputil/swapbase_command.cc
@@ -169,6 +169,8 @@
   }
   decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
   decoder->ignoreICC = arg_image_read_.ignore_profile;
+  decoder->ignoreExif = arg_image_read_.ignore_exif;
+  decoder->ignoreXMP = arg_image_read_.ignore_xmp;
   avifResult result = ReadAvif(decoder.get(), arg_input_filename_);
   if (result != AVIF_RESULT_OK) {
     return result;
diff --git a/apps/avifgainmaputil/tonemap_command.cc b/apps/avifgainmaputil/tonemap_command.cc
index b307fd6..05825e9 100644
--- a/apps/avifgainmaputil/tonemap_command.cc
+++ b/apps/avifgainmaputil/tonemap_command.cc
@@ -71,6 +71,8 @@
   }
   decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
   decoder->ignoreICC = arg_image_read_.ignore_profile;
+  decoder->ignoreExif = arg_image_read_.ignore_exif;
+  decoder->ignoreXMP = arg_image_read_.ignore_xmp;
   avifResult result = ReadAvif(decoder.get(), arg_input_filename_);
   if (result != AVIF_RESULT_OK) {
     return result;
diff --git a/tests/test_cmd_avifgainmaputil.sh b/tests/test_cmd_avifgainmaputil.sh
index b25061d..a5da841 100755
--- a/tests/test_cmd_avifgainmaputil.sh
+++ b/tests/test_cmd_avifgainmaputil.sh
@@ -59,11 +59,25 @@
   "${AVIFGAINMAPUTIL}" tonemap "${INPUT_AVIF_GAINMAP_SDR}" "${PNG_OUTPUT}" --headroom 0 --clli 400,500
   "${ARE_IMAGES_EQUAL}" "${PNG_OUTPUT}" "${INPUT_JPEG_GAINMAP_SDR}" 0 40 1
   # Check that metadata is copied over.
-  "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${AVIF_OUTPUT}" --headroom 0 > "${OUT_MSG}"
+  "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${AVIF_OUTPUT}.tonemapped.avif" --headroom 0 > "${OUT_MSG}"
   cat "${OUT_MSG}"
   grep "XMP Metadata   : Present" "${OUT_MSG}"
   grep "Exif Metadata  : Present" "${OUT_MSG}"
 
+  # Check that metadata can be ignored (tonemap command).
+  "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${AVIF_OUTPUT}.ignored.avif" --headroom 0 --ignore-xmp --ignore-exif > "${OUT_MSG}"
+  cat "${OUT_MSG}"
+  grep "XMP Metadata   : Absent" "${OUT_MSG}"
+  grep "Exif Metadata  : Absent" "${OUT_MSG}"
+
+  # Check that metadata can be ignored (combine command).
+  "${AVIFGAINMAPUTIL}" combine "${INPUT_AVIF_GAINMAP_HDR}" "${INPUT_AVIF_GAINMAP_SDR}" "${AVIF_OUTPUT}.ignored-combine.avif" \
+      -q 90 --qgain-map 90 --ignore-xmp --ignore-exif
+  "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}.ignored-combine.avif" "${AVIF_OUTPUT}.ignored-combine.tonemapped.avif" --headroom 0 > "${OUT_MSG}"
+  cat "${OUT_MSG}"
+  grep "XMP Metadata   : Absent" "${OUT_MSG}"
+  grep "Exif Metadata  : Absent" "${OUT_MSG}"
+
   # 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