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