Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The changes are relative to the previous release, unless the baseline is specifi

## [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
Expand Down
12 changes: 12 additions & 0 deletions apps/avifenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ typedef struct
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
Expand Down Expand Up @@ -256,6 +257,7 @@ static void syntaxLong(void)
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",
Expand Down Expand Up @@ -527,6 +529,7 @@ static avifBool avifInputReadImage(avifInput * input,
avifBool ignoreExif,
avifBool ignoreXMP,
avifBool allowChangingCicp,
avifBool ignoreAlpha,
avifBool ignoreGainMap,
avifImage * image,
const avifInputFileSettings ** settings,
Expand Down Expand Up @@ -622,6 +625,7 @@ static avifBool avifInputReadImage(avifInput * input,
ignoreColorProfile,
ignoreExif,
ignoreXMP,
ignoreAlpha,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vincent: When I wrote this PR, I didn't realize that avifImageFreePlanes() is a public function. Since adding the ignoreAlpha parameter to avifReadImage() makes it necessary to update its callers, an alternative solution is to leave avifReadImage() unchanged and call avifImageFreePlanes(dstImage, AVIF_PLANES_A) if ignoreAlpha is true. This will make the PR much smaller at the cost of allocating and copying the alpha plane from the input image unnecessarily. Would you prefer this alternative solution?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's ok, it's all internal. We should probably merge those ignores flags into a struct anyway.

ignoreGainMap,
UINT32_MAX,
dstImage,
Expand Down Expand Up @@ -677,6 +681,7 @@ static avifBool avifInputReadImage(avifInput * input,
ignoreExif,
ignoreXMP,
allowChangingCicp,
ignoreAlpha,
ignoreGainMap,
image,
settings,
Expand Down Expand Up @@ -938,6 +943,7 @@ static avifBool avifEncodeRestOfImageSequence(avifEncoder * encoder,
/*ignoreExif=*/AVIF_TRUE,
/*ignoreXMP=*/AVIF_TRUE,
/*allowChangingCicp=*/AVIF_FALSE,
settings->ignoreAlpha,
/*ignoreGainMap=*/AVIF_TRUE,
nextImage,
&nextSettings,
Expand Down Expand Up @@ -1044,6 +1050,7 @@ static avifBool avifEncodeRestOfLayeredImage(avifEncoder * encoder,
/*ignoreExif=*/AVIF_TRUE,
/*ignoreXMP=*/AVIF_TRUE,
!settings->cicpExplicitlySet,
settings->ignoreAlpha,
/*ignoreGainMap=*/AVIF_TRUE,
nextImage,
&nextSettings,
Expand Down Expand Up @@ -1443,6 +1450,7 @@ int main(int argc, char * argv[])
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;
Expand Down Expand Up @@ -1929,6 +1937,8 @@ int main(int argc, char * argv[])
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;
Expand Down Expand Up @@ -2343,6 +2353,7 @@ int main(int argc, char * argv[])
settings.ignoreExif,
settings.ignoreXMP,
/*allowChangingCicp=*/!settings.cicpExplicitlySet,
settings.ignoreAlpha,
ignoreGainMap,
image,
/*settings=*/NULL, // Must use the setting for first input
Expand Down Expand Up @@ -2588,6 +2599,7 @@ int main(int argc, char * argv[])
/*ignoreExif=*/AVIF_TRUE,
/*ignoreXMP=*/AVIF_TRUE,
/*allowChangingCicp=*/AVIF_FALSE,
settings.ignoreAlpha,
settings.ignoreGainMap,
cellImage,
/*settings=*/NULL,
Expand Down
4 changes: 2 additions & 2 deletions apps/avifgainmaputil/imageio.cc
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ avifResult ReadImage(avifImage* image, const std::string& input_filename,
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) {
Expand Down
4 changes: 4 additions & 0 deletions apps/shared/avifpng.c
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ static avifBool avifPNGReadImpl(FILE * f,
avifBool ignoreColorProfile,
avifBool ignoreExif,
avifBool ignoreXMP,
avifBool ignoreAlpha,
uint32_t imageSizeLimit,
uint32_t * outPNGDepth)
{
Expand Down Expand Up @@ -524,6 +525,7 @@ static avifBool avifPNGReadImpl(FILE * f,
} 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;
Expand Down Expand Up @@ -584,6 +586,7 @@ avifBool avifPNGRead(const char * inputFilename,
avifBool ignoreColorProfile,
avifBool ignoreExif,
avifBool ignoreXMP,
avifBool ignoreAlpha,
uint32_t imageSizeLimit,
uint32_t * outPNGDepth)
{
Expand All @@ -608,6 +611,7 @@ avifBool avifPNGRead(const char * inputFilename,
ignoreColorProfile,
ignoreExif,
ignoreXMP,
ignoreAlpha,
imageSizeLimit,
outPNGDepth);

Expand Down
1 change: 1 addition & 0 deletions apps/shared/avifpng.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ avifBool avifPNGRead(const char * inputFilename,
avifBool ignoreColorProfile,
avifBool ignoreExif,
avifBool ignoreXMP,
avifBool ignoreAlpha,
uint32_t imageSizeLimit,
uint32_t * outPNGDepth);
avifBool avifPNGWrite(const char * outputFilename,
Expand Down
5 changes: 3 additions & 2 deletions apps/shared/avifutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ avifAppFileFormat avifReadImage(const char * filename,
avifBool ignoreColorProfile,
avifBool ignoreExif,
avifBool ignoreXMP,
avifBool ignoreAlpha,
avifBool ignoreGainMap,
uint32_t imageSizeLimit,
avifImage * image,
Expand All @@ -329,7 +330,7 @@ avifAppFileFormat avifReadImage(const char * filename,
}

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) {
Expand All @@ -344,7 +345,7 @@ avifAppFileFormat avifReadImage(const char * filename,
*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) {
Expand Down
3 changes: 3 additions & 0 deletions apps/shared/avifutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ struct y4mFrameIterator;
// 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.
Expand All @@ -108,6 +110,7 @@ avifAppFileFormat avifReadImage(const char * filename,
avifBool ignoreColorProfile,
avifBool ignoreExif,
avifBool ignoreXMP,
avifBool ignoreAlpha,
avifBool ignoreGainMap,
uint32_t imageSizeLimit,
avifImage * image,
Expand Down
5 changes: 5 additions & 0 deletions apps/shared/y4m.c
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ static avifBool y4mClampSamples(avifImage * avif)
} while (0)

avifBool y4mRead(const char * inputFilename,
avifBool ignoreAlpha,
uint32_t imageSizeLimit,
avifImage * avif,
avifAppSourceTiming * sourceTiming,
Expand Down Expand Up @@ -436,6 +437,10 @@ avifBool y4mRead(const char * inputFilename,
}
}

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)) {
Expand Down
1 change: 1 addition & 0 deletions apps/shared/y4m.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ extern "C" {
struct y4mFrameIterator;

avifBool y4mRead(const char * inputFilename,
avifBool ignoreAlpha,
uint32_t imageSizeLimit,
avifImage * avif,
avifAppSourceTiming * sourceTiming,
Expand Down
3 changes: 3 additions & 0 deletions doc/avifenc.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ Input format can be either JPEG, PNG or YUV4MPEG2 (Y4M).
**\--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).

Expand Down
3 changes: 2 additions & 1 deletion tests/gtest/are_images_equal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ int main(int argc, char** argv) {
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) {
Expand Down
4 changes: 2 additions & 2 deletions tests/gtest/avif_fuzztest_read_image.cc
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ void ReadImageFile(const std::string& arbitrary_bytes,
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) {
Expand Down
5 changes: 3 additions & 2 deletions tests/gtest/aviflosslesstest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ TEST(LosslessTest, YCGCO_RO) {
/*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);
Expand All @@ -51,7 +52,7 @@ void ReadImageSimple(const std::string& file_path, avifPixelFormat pixel_format,
/*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 &&
Expand Down Expand Up @@ -79,7 +80,7 @@ void GetIsGray(const std::string& path, bool& is_gray) {
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));
Expand Down
1 change: 1 addition & 0 deletions tests/gtest/avifpng16bittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ ImagePtr ReadImageLosslessBitDepth(const std::string& path,
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,
Expand Down
4 changes: 2 additions & 2 deletions tests/gtest/avifreadimagetest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ static avifAppFileFormat avifReadImageForRGB2Gray2RGB(const std::string& path,
/*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);
Expand Down Expand Up @@ -450,7 +450,7 @@ TEST(ImageSizeLimitTest, AllFormats) {
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);
Expand Down
4 changes: 2 additions & 2 deletions tests/gtest/aviftest_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -650,8 +650,8 @@ ImagePtr ReadImage(const char* folder_path, const char* file_name,
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;
Expand Down
10 changes: 6 additions & 4 deletions tests/gtest/avify4mtest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ TEST_P(Y4mTest, EncodeDecode) {

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));
Expand Down Expand Up @@ -88,8 +89,9 @@ TEST_P(Y4mTest, OutOfRange) {
// 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
Expand Down
8 changes: 8 additions & 0 deletions tests/test_cmd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ pushd ${TMP_DIR}
"${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
Loading