avifenc: add --ignore-alpha flag to discard alpha

Add --ignore-alpha flag to avifenc to discard alpha channel on encode.

Have avifInputReadImage() propagate the ignoreAlpha parameter further
down the call chain all the way to the leaf functions.

Co-authored-by: enyetsuen <175347006+enyetsuen@users.noreply.github.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4fa0dda..8707067 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,10 @@
 
 ## [Unreleased]
 
+### Added since 1.4.2
+
+* avifenc: add --ignore-alpha flag to discard alpha channel on encode
+
 ## [1.4.2] - 2026-05-26
 
 ### Added since 1.4.1
diff --git a/apps/avifenc.c b/apps/avifenc.c
index 4d6106e..e169839 100644
--- a/apps/avifenc.c
+++ b/apps/avifenc.c
@@ -59,6 +59,7 @@
     avifBool ignoreExif;
     avifBool ignoreXMP;
     avifBool ignoreColorProfile;
+    avifBool ignoreAlpha;
 
     // These settings are only relevant when compiled with AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION.
     avifBool qualityGainMapIsConstrained; // true if qualityGainMap explicitly set by the user
@@ -256,6 +257,7 @@
     printf("    --ignore-exif                     : If the input file contains embedded Exif metadata, ignore it (no-op if absent)\n");
     printf("    --ignore-xmp                      : If the input file contains embedded XMP metadata, ignore it (no-op if absent)\n");
     printf("    --ignore-profile,--ignore-icc     : If the input file contains an embedded color profile, ignore it (no-op if absent)\n");
+    printf("    --ignore-alpha                    : If the input file contains an alpha channel, ignore it (no-op if absent)\n");
 #if defined(AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION)
     printf("    --ignore-gain-map                 : If the input file contains an embedded gain map, ignore it (no-op if absent)\n");
     printf("    --qgain-map Q                     : Quality for the gain map in %d..%d where %d is lossless\n",
@@ -527,6 +529,7 @@
                                    avifBool ignoreExif,
                                    avifBool ignoreXMP,
                                    avifBool allowChangingCicp,
+                                   avifBool ignoreAlpha,
                                    avifBool ignoreGainMap,
                                    avifImage * image,
                                    const avifInputFileSettings ** settings,
@@ -622,6 +625,7 @@
                                                               ignoreColorProfile,
                                                               ignoreExif,
                                                               ignoreXMP,
+                                                              ignoreAlpha,
                                                               ignoreGainMap,
                                                               UINT32_MAX,
                                                               dstImage,
@@ -677,6 +681,7 @@
                                   ignoreExif,
                                   ignoreXMP,
                                   allowChangingCicp,
+                                  ignoreAlpha,
                                   ignoreGainMap,
                                   image,
                                   settings,
@@ -938,6 +943,7 @@
                                 /*ignoreExif=*/AVIF_TRUE,
                                 /*ignoreXMP=*/AVIF_TRUE,
                                 /*allowChangingCicp=*/AVIF_FALSE,
+                                settings->ignoreAlpha,
                                 /*ignoreGainMap=*/AVIF_TRUE,
                                 nextImage,
                                 &nextSettings,
@@ -1044,6 +1050,7 @@
                                     /*ignoreExif=*/AVIF_TRUE,
                                     /*ignoreXMP=*/AVIF_TRUE,
                                     !settings->cicpExplicitlySet,
+                                    settings->ignoreAlpha,
                                     /*ignoreGainMap=*/AVIF_TRUE,
                                     nextImage,
                                     &nextSettings,
@@ -1443,6 +1450,7 @@
     settings.ignoreExif = AVIF_FALSE;
     settings.ignoreXMP = AVIF_FALSE;
     settings.ignoreColorProfile = AVIF_FALSE;
+    settings.ignoreAlpha = AVIF_FALSE;
     settings.ignoreGainMap = AVIF_FALSE;
     settings.cicpExplicitlySet = AVIF_FALSE;
     settings.inputFormat = AVIF_APP_FILE_FORMAT_UNKNOWN;
@@ -1929,6 +1937,8 @@
             settings.ignoreXMP = AVIF_TRUE;
         } else if (!strcmp(arg, "--ignore-profile") || !strcmp(arg, "--ignore-icc")) {
             settings.ignoreColorProfile = AVIF_TRUE;
+        } else if (!strcmp(arg, "--ignore-alpha")) {
+            settings.ignoreAlpha = AVIF_TRUE;
 #if defined(AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION)
         } else if (!strcmp(arg, "--ignore-gain-map")) {
             settings.ignoreGainMap = AVIF_TRUE;
@@ -2343,6 +2353,7 @@
                             settings.ignoreExif,
                             settings.ignoreXMP,
                             /*allowChangingCicp=*/!settings.cicpExplicitlySet,
+                            settings.ignoreAlpha,
                             ignoreGainMap,
                             image,
                             /*settings=*/NULL, // Must use the setting for first input
@@ -2588,6 +2599,7 @@
                                     /*ignoreExif=*/AVIF_TRUE,
                                     /*ignoreXMP=*/AVIF_TRUE,
                                     /*allowChangingCicp=*/AVIF_FALSE,
+                                    settings.ignoreAlpha,
                                     settings.ignoreGainMap,
                                     cellImage,
                                     /*settings=*/NULL,
diff --git a/apps/avifgainmaputil/imageio.cc b/apps/avifgainmaputil/imageio.cc
index 2d1b1e8..c127d06 100644
--- a/apps/avifgainmaputil/imageio.cc
+++ b/apps/avifgainmaputil/imageio.cc
@@ -238,8 +238,8 @@
         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_gain_map,
-        AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image,
+        /*ignoreExif=*/false, /*ignoreXMP=*/false, /*ignoreAlpha=*/false,
+        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/shared/avifpng.c b/apps/shared/avifpng.c
index 6f35a02..794e8ef 100644
--- a/apps/shared/avifpng.c
+++ b/apps/shared/avifpng.c
@@ -277,6 +277,7 @@
                                 avifBool ignoreColorProfile,
                                 avifBool ignoreExif,
                                 avifBool ignoreXMP,
+                                avifBool ignoreAlpha,
                                 uint32_t imageSizeLimit,
                                 uint32_t * outPNGDepth)
 {
@@ -524,6 +525,7 @@
     } else if (numChannels == 3) {
         rgb.format = AVIF_RGB_FORMAT_RGB;
     }
+    rgb.ignoreAlpha = ignoreAlpha;
     if (avifRGBImageAllocatePixels(&rgb) != AVIF_RESULT_OK) {
         fprintf(stderr, "Conversion to YUV failed: %s (out of memory)\n", inputFilename);
         goto cleanup;
@@ -584,6 +586,7 @@
                      avifBool ignoreColorProfile,
                      avifBool ignoreExif,
                      avifBool ignoreXMP,
+                     avifBool ignoreAlpha,
                      uint32_t imageSizeLimit,
                      uint32_t * outPNGDepth)
 {
@@ -608,6 +611,7 @@
                                          ignoreColorProfile,
                                          ignoreExif,
                                          ignoreXMP,
+                                         ignoreAlpha,
                                          imageSizeLimit,
                                          outPNGDepth);
 
diff --git a/apps/shared/avifpng.h b/apps/shared/avifpng.h
index 5292a27..b50d2b7 100644
--- a/apps/shared/avifpng.h
+++ b/apps/shared/avifpng.h
@@ -19,6 +19,7 @@
                      avifBool ignoreColorProfile,
                      avifBool ignoreExif,
                      avifBool ignoreXMP,
+                     avifBool ignoreAlpha,
                      uint32_t imageSizeLimit,
                      uint32_t * outPNGDepth);
 avifBool avifPNGWrite(const char * outputFilename,
diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c
index 8578b29..9cc6ead 100644
--- a/apps/shared/avifutil.c
+++ b/apps/shared/avifutil.c
@@ -317,6 +317,7 @@
                                 avifBool ignoreColorProfile,
                                 avifBool ignoreExif,
                                 avifBool ignoreXMP,
+                                avifBool ignoreAlpha,
                                 avifBool ignoreGainMap,
                                 uint32_t imageSizeLimit,
                                 avifImage * image,
@@ -329,7 +330,7 @@
     }
 
     if (inputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
-        if (!y4mRead(filename, imageSizeLimit, image, sourceTiming, frameIter)) {
+        if (!y4mRead(filename, ignoreAlpha, imageSizeLimit, image, sourceTiming, frameIter)) {
             return AVIF_APP_FILE_FORMAT_UNKNOWN;
         }
         if (outDepth) {
@@ -344,7 +345,7 @@
             *outDepth = 8;
         }
     } else if (inputFormat == AVIF_APP_FILE_FORMAT_PNG) {
-        if (!avifPNGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, imageSizeLimit, outDepth)) {
+        if (!avifPNGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, ignoreAlpha, imageSizeLimit, outDepth)) {
             return AVIF_APP_FILE_FORMAT_UNKNOWN;
         }
     } else if (inputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
diff --git a/apps/shared/avifutil.h b/apps/shared/avifutil.h
index 6596cfc..192bbc7 100644
--- a/apps/shared/avifutil.h
+++ b/apps/shared/avifutil.h
@@ -95,6 +95,8 @@
 // In case of a y4m file, sourceTiming and frameIter can be set.
 // Returns the format of the file, or AVIF_APP_FILE_FORMAT_UNKNOWN in case of
 // error.
+// 'ignoreAlpha' is only relevant for y4m or png files that have an alpha
+// channel. Otherwise it has no effect.
 // 'ignoreGainMap' is only relevant for jpeg files that have a gain map
 // and only if AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION is ON
 // (requires libxml2). Otherwise it has no effect.
@@ -108,6 +110,7 @@
                                 avifBool ignoreColorProfile,
                                 avifBool ignoreExif,
                                 avifBool ignoreXMP,
+                                avifBool ignoreAlpha,
                                 avifBool ignoreGainMap,
                                 uint32_t imageSizeLimit,
                                 avifImage * image,
diff --git a/apps/shared/y4m.c b/apps/shared/y4m.c
index 2049b86..9a604ed 100644
--- a/apps/shared/y4m.c
+++ b/apps/shared/y4m.c
@@ -254,6 +254,7 @@
     } while (0)
 
 avifBool y4mRead(const char * inputFilename,
+                 avifBool ignoreAlpha,
                  uint32_t imageSizeLimit,
                  avifImage * avif,
                  avifAppSourceTiming * sourceTiming,
@@ -436,6 +437,10 @@
         }
     }
 
+    if (frame.hasAlpha && ignoreAlpha) {
+        avifImageFreePlanes(avif, AVIF_PLANES_A);
+    }
+
     // libavif API does not guarantee the absence of undefined behavior if samples exceed the specified avif->depth.
     // Avoid that by making sure input values are within the correct range.
     if (y4mClampSamples(avif)) {
diff --git a/apps/shared/y4m.h b/apps/shared/y4m.h
index a4d837a..bf9d645 100644
--- a/apps/shared/y4m.h
+++ b/apps/shared/y4m.h
@@ -19,6 +19,7 @@
 struct y4mFrameIterator;
 
 avifBool y4mRead(const char * inputFilename,
+                 avifBool ignoreAlpha,
                  uint32_t imageSizeLimit,
                  avifImage * avif,
                  avifAppSourceTiming * sourceTiming,
diff --git a/doc/avifenc.1.md b/doc/avifenc.1.md
index b502c18..b1adf17 100644
--- a/doc/avifenc.1.md
+++ b/doc/avifenc.1.md
@@ -140,6 +140,9 @@
 **\--ignore-profile**, **\--ignore-icc**
 :   If the input file contains an embedded color profile, ignore it (no-op if absent).
 
+**\--ignore-alpha**
+:   If the input file contains an alpha channel, ignore it (no-op if absent).
+
 **\--ignore-gain-map**
 :   If the input file contains an embedded gain map, ignore it (no-op if absent).
 
diff --git a/tests/gtest/are_images_equal.cc b/tests/gtest/are_images_equal.cc
index f6805d5..66be844 100644
--- a/tests/gtest/are_images_equal.cc
+++ b/tests/gtest/are_images_equal.cc
@@ -95,7 +95,8 @@
             requestedFormat, kRequestedDepth,
             AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
             /*ignoreColorProfile==*/AVIF_FALSE, /*ignoreExif=*/AVIF_FALSE,
-            /*ignoreXMP=*/AVIF_FALSE, ignore_gain_map,
+            /*ignoreXMP=*/AVIF_FALSE, /*ignoreAlpha=*/AVIF_FALSE,
+            ignore_gain_map,
             /*imageSizeLimit=*/std::numeric_limits<uint32_t>::max(),
             decoded[i].get(), &depth[i], nullptr,
             nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) {
diff --git a/tests/gtest/avif_fuzztest_read_image.cc b/tests/gtest/avif_fuzztest_read_image.cc
index f04cd18..432e08b 100644
--- a/tests/gtest/avif_fuzztest_read_image.cc
+++ b/tests/gtest/avif_fuzztest_read_image.cc
@@ -80,8 +80,8 @@
   const avifAppFileFormat file_format = avifReadImage(
       file_path.c_str(), AVIF_APP_FILE_FORMAT_UNKNOWN /* guess format */,
       requested_format, requested_depth, chroma_downsampling,
-      ignore_color_profile, ignore_exif, ignore_xmp, ignore_gain_map,
-      imageSizeLimit, avif_image.get(), &out_depth, &timing,
+      ignore_color_profile, ignore_exif, ignore_xmp, /*ignoreAlpha=*/false,
+      ignore_gain_map, imageSizeLimit, avif_image.get(), &out_depth, &timing,
       /*frameIter=*/nullptr);
 
   if (file_format != AVIF_APP_FILE_FORMAT_UNKNOWN) {
diff --git a/tests/gtest/aviflosslesstest.cc b/tests/gtest/aviflosslesstest.cc
index c74788d..1a1b8a6 100644
--- a/tests/gtest/aviflosslesstest.cc
+++ b/tests/gtest/aviflosslesstest.cc
@@ -28,6 +28,7 @@
       /*requestedDepth=*/0,
       /*chromaDownsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
       /*ignoreColorProfile=*/false, /*ignoreExif=*/false, /*ignoreXMP=*/false,
+      /*ignoreAlpha=*/false,
       /*ignoreGainMap=*/true, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(),
       /*outDepth=*/nullptr,
       /*sourceTiming=*/nullptr, /*frameIter=*/nullptr);
@@ -51,7 +52,7 @@
       /*requestedFormat=*/pixel_format, /*requestedDepth=*/0,
       /*chromaDownsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
       /*ignoreColorProfile=*/ignore_icc, /*ignoreExif=*/false,
-      /*ignoreXMP=*/false, /*ignoreGainMap=*/true,
+      /*ignoreXMP=*/false, /*ignoreAlpha=*/false, /*ignoreGainMap=*/true,
       AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(), /*outDepth=*/nullptr,
       /*sourceTiming=*/nullptr, /*frameIter=*/nullptr);
   if (matrix_coefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY &&
@@ -79,7 +80,7 @@
           AVIF_PIXEL_FORMAT_NONE, /*requestedDepth=*/0,
           /*chromaDownsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
           /*ignoreColorProfile=*/true, /*ignoreExif=*/true,
-          /*ignoreXMP=*/true,
+          /*ignoreXMP=*/true, /*ignoreAlpha=*/false,
           /*ignoreGainMap=*/true, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(),
           /*outDepth=*/nullptr, /*sourceTiming=*/nullptr,
           /*frameIter=*/nullptr));
diff --git a/tests/gtest/avifpng16bittest.cc b/tests/gtest/avifpng16bittest.cc
index 5472033..52a1c8a 100644
--- a/tests/gtest/avifpng16bittest.cc
+++ b/tests/gtest/avifpng16bittest.cc
@@ -38,6 +38,7 @@
                     AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
                     /*ignoreColorProfile=*/true,
                     /*ignoreExif=*/true, /*ignoreXMP=*/true,
+                    /*ignoreAlpha=*/false,
                     /*ignoreGainMap=*/true, AVIF_DEFAULT_IMAGE_SIZE_LIMIT,
                     image.get(), &output_depth,
                     /*sourceTiming=*/nullptr,
diff --git a/tests/gtest/avifreadimagetest.cc b/tests/gtest/avifreadimagetest.cc
index c1423fe..689741b 100644
--- a/tests/gtest/avifreadimagetest.cc
+++ b/tests/gtest/avifreadimagetest.cc
@@ -281,7 +281,7 @@
       /*requestedDepth=*/0,
       /*chromaDownsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
       /*ignoreColorProfile=*/ignore_icc, /*ignoreExif=*/false,
-      /*ignoreXMP=*/false,
+      /*ignoreXMP=*/false, /*ignoreAlpha=*/false,
       /*ignoreGainMap=*/true, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(),
       /*outDepth=*/nullptr, /*sourceTiming=*/nullptr,
       /*frameIter=*/nullptr);
@@ -450,7 +450,7 @@
           filepath.c_str(), AVIF_APP_FILE_FORMAT_UNKNOWN /* guess format */,
           AVIF_PIXEL_FORMAT_NONE, /*requestedDepth=*/0,
           AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, /*ignoreColorProfile=*/true,
-          /*ignoreExif=*/true, /*ignoreXMP=*/true,
+          /*ignoreExif=*/true, /*ignoreXMP=*/true, /*ignoreAlpha=*/false,
           /*ignoreGainMap=*/true, image_size_limit, image.get(),
           /*outDepth=*/nullptr, /*sourceTiming=*/nullptr,
           /*frameIter=*/nullptr);
diff --git a/tests/gtest/aviftest_helpers.cc b/tests/gtest/aviftest_helpers.cc
index 2ed0adc..40730d1 100644
--- a/tests/gtest/aviftest_helpers.cc
+++ b/tests/gtest/aviftest_helpers.cc
@@ -650,8 +650,8 @@
       avifReadImage((std::string(folder_path) + file_name).c_str(),
                     AVIF_APP_FILE_FORMAT_UNKNOWN /* guess format */,
                     requested_format, requested_depth, chroma_downsampling,
-                    ignore_icc, ignore_exif, ignore_xmp, ignore_gain_map,
-                    AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(),
+                    ignore_icc, ignore_exif, ignore_xmp, /*ignoreAlpha=*/false,
+                    ignore_gain_map, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(),
                     /*outDepth=*/nullptr, /*sourceTiming=*/nullptr,
                     /*frameIter=*/nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) {
     return nullptr;
diff --git a/tests/gtest/avify4mtest.cc b/tests/gtest/avify4mtest.cc
index bb9ceec..1ff2760 100644
--- a/tests/gtest/avify4mtest.cc
+++ b/tests/gtest/avify4mtest.cc
@@ -50,8 +50,9 @@
 
   ImagePtr decoded(avifImageCreateEmpty());
   ASSERT_NE(decoded, nullptr);
-  ASSERT_TRUE(y4mRead(file_path.str().c_str(), AVIF_DEFAULT_IMAGE_SIZE_LIMIT,
-                      decoded.get(), /*sourceTiming=*/nullptr,
+  ASSERT_TRUE(y4mRead(file_path.str().c_str(), /*ignoreAlpha=*/false,
+                      AVIF_DEFAULT_IMAGE_SIZE_LIMIT, decoded.get(),
+                      /*sourceTiming=*/nullptr,
                       /*iter=*/nullptr));
 
   EXPECT_TRUE(testutil::AreImagesEqual(*image, *decoded));
@@ -88,8 +89,9 @@
   // that tag along, it is ignored by the compression algorithm.
   ImagePtr decoded(avifImageCreateEmpty());
   ASSERT_NE(decoded, nullptr);
-  ASSERT_TRUE(y4mRead(file_path.str().c_str(), AVIF_DEFAULT_IMAGE_SIZE_LIMIT,
-                      decoded.get(), /*sourceTiming=*/nullptr,
+  ASSERT_TRUE(y4mRead(file_path.str().c_str(), /*ignoreAlpha=*/false,
+                      AVIF_DEFAULT_IMAGE_SIZE_LIMIT, decoded.get(),
+                      /*sourceTiming=*/nullptr,
                       /*iter=*/nullptr));
 
   // Pass it through the libavif API to make sure reading a bad y4m does not
diff --git a/tests/test_cmd.sh b/tests/test_cmd.sh
index b9260dd..0ba0e3a 100755
--- a/tests/test_cmd.sh
+++ b/tests/test_cmd.sh
@@ -143,6 +143,14 @@
   "${AVIFENC}" --autotiling --tilerowslog2 1 "${INPUT_Y4M}" "${ENCODED_FILE}" && exit 1
   "${AVIFENC}" --autotiling --tilecolslog2 2 "${INPUT_Y4M}" "${ENCODED_FILE}" && exit 1
   "${AVIFENC}" --autotiling --tilerowslog2 1 --tilecolslog2 2 "${INPUT_Y4M}" "${ENCODED_FILE}" && exit 1
+
+  # Test --ignore-alpha.
+  echo "Testing --ignore-alpha"
+  "${AVIFENC}" -s 10 "${INPUT_PNG}" "${ENCODED_FILE}" > "${OUT_MSG}"
+  grep " Alpha          : Not premultiplied" "${OUT_MSG}"
+  "${AVIFENC}" -s 10 --ignore-alpha "${INPUT_PNG}" "${ENCODED_FILE}" > "${OUT_MSG}"
+  grep " Alpha          : Absent" "${OUT_MSG}"
+  grep " Alpha total size: 0 bytes" "${OUT_MSG}"
 popd
 
 exit 0