avifgainmaputil: add --ignore-alpha flag to discard alpha channel (#3273)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68f62f5..0ba9e94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
### Added since 1.4.2
* avifenc: add --ignore-alpha flag to discard alpha channel on encode
+* avifgainmaputil: add --ignore-alpha flag to discard alpha channel
### Changed since 1.4.2
diff --git a/apps/avifgainmaputil/combine_command.cc b/apps/avifgainmaputil/combine_command.cc
index ce59cb6..97925f9 100644
--- a/apps/avifgainmaputil/combine_command.cc
+++ b/apps/avifgainmaputil/combine_command.cc
@@ -96,10 +96,10 @@
arg_base_cicp_.value().transfer_characteristics;
base_image->matrixCoefficients = arg_base_cicp_.value().matrix_coefficients;
}
- avifResult result =
- ReadImage(base_image.get(), arg_base_filename_, pixel_format,
- arg_image_read_.depth, arg_image_read_.ignore_profile,
- /*ignore_gain_map=*/true, arg_jobs_.jobs.value());
+ 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,
+ /*ignore_gain_map=*/true, arg_jobs_.jobs.value());
if (result != AVIF_RESULT_OK) {
std::cout << "Failed to read base image: " << avifResultToString(result)
<< "\n";
@@ -117,7 +117,8 @@
result =
ReadImage(alternate_image.get(), arg_alternate_filename_, pixel_format,
arg_image_read_.depth, arg_image_read_.ignore_profile,
- /*ignore_gain_map=*/true, arg_jobs_.jobs.value());
+ arg_image_read_.ignore_alpha, /*ignore_gain_map=*/true,
+ arg_jobs_.jobs.value());
if (result != AVIF_RESULT_OK) {
std::cout << "Failed to read alternate image: "
<< avifResultToString(result) << "\n";
diff --git a/apps/avifgainmaputil/convert_command.cc b/apps/avifgainmaputil/convert_command.cc
index 1cbe74e..54e4744 100644
--- a/apps/avifgainmaputil/convert_command.cc
+++ b/apps/avifgainmaputil/convert_command.cc
@@ -62,7 +62,8 @@
avifResult result =
ReadImage(image.get(), arg_input_filename_.value(), pixel_format,
arg_image_read_.depth, arg_image_read_.ignore_profile,
- /*ignore_gain_map=*/false, arg_jobs_.jobs.value());
+ arg_image_read_.ignore_alpha, /*ignore_gain_map=*/false,
+ arg_jobs_.jobs.value());
if (result != AVIF_RESULT_OK) {
std::cout << "Failed to decode image: " << arg_input_filename_;
return result;
diff --git a/apps/avifgainmaputil/extractgainmap_command.cc b/apps/avifgainmaputil/extractgainmap_command.cc
index 6ec9e5e..5779ab8 100644
--- a/apps/avifgainmaputil/extractgainmap_command.cc
+++ b/apps/avifgainmaputil/extractgainmap_command.cc
@@ -26,7 +26,8 @@
decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_GAIN_MAP;
avifResult result =
- ReadAvif(decoder.get(), arg_input_filename_, /*ignore_profile=*/true);
+ ReadAvif(decoder.get(), arg_input_filename_, /*ignore_profile=*/true,
+ /*ignore_alpha=*/false);
if (result != AVIF_RESULT_OK) {
return result;
}
diff --git a/apps/avifgainmaputil/imageio.cc b/apps/avifgainmaputil/imageio.cc
index 05b55f6..7786301 100644
--- a/apps/avifgainmaputil/imageio.cc
+++ b/apps/avifgainmaputil/imageio.cc
@@ -190,7 +190,8 @@
avifResult ReadImage(avifImage* image, const std::string& input_filename,
avifPixelFormat requested_format, uint32_t requested_depth,
- bool ignore_profile, bool ignore_gain_map, int jobs) {
+ bool ignore_profile, 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;
@@ -204,7 +205,8 @@
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
}
decoder->maxThreads = jobs;
- avifResult result = ReadAvif(decoder.get(), input_filename, ignore_profile);
+ avifResult result =
+ ReadAvif(decoder.get(), input_filename, ignore_profile, ignore_alpha);
if (result != AVIF_RESULT_OK) {
return result;
}
@@ -238,7 +240,7 @@
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, /*ignoreAlpha=*/false,
+ /*ignoreExif=*/false, /*ignoreXMP=*/false, ignore_alpha,
ignore_gain_map, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image,
/*outDepth=*/nullptr,
/*sourceTiming=*/nullptr, /*frameIter=*/nullptr);
@@ -263,7 +265,7 @@
}
avifResult ReadAvif(avifDecoder* decoder, const std::string& input_filename,
- bool ignore_profile) {
+ bool ignore_profile, bool ignore_alpha) {
avifResult result = avifDecoderSetIOFile(decoder, input_filename.c_str());
if (result != AVIF_RESULT_OK) {
std::cerr << "Cannot open file for read: " << input_filename << "\n";
@@ -287,6 +289,9 @@
avifRWDataFree(&decoder->image->gainMap->altICC);
}
}
+ if (ignore_alpha && decoder->image->alphaPlane) {
+ avifImageFreePlanes(decoder->image, AVIF_PLANES_A);
+ }
return AVIF_RESULT_OK;
}
diff --git a/apps/avifgainmaputil/imageio.h b/apps/avifgainmaputil/imageio.h
index bc69c63..7b567b8 100644
--- a/apps/avifgainmaputil/imageio.h
+++ b/apps/avifgainmaputil/imageio.h
@@ -19,7 +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_gain_map, int jobs);
+ bool ignore_profile, bool ignore_alpha,
+ bool ignore_gain_map, int jobs);
// Reads an image in avif format given a pre-configured encoder.
avifResult WriteAvif(const avifImage* image, avifEncoder* encoder,
@@ -33,7 +34,7 @@
// Reads an image in avif format given a pre-configured decoder.
// The image can be accessed at decoder->image.
avifResult ReadAvif(avifDecoder* decoder, const std::string& input_filename,
- bool ignore_profile);
+ bool ignore_profile, bool ignore_alpha);
} // namespace avif
diff --git a/apps/avifgainmaputil/program_command.h b/apps/avifgainmaputil/program_command.h
index b579253..5c18b0b 100644
--- a/apps/avifgainmaputil/program_command.h
+++ b/apps/avifgainmaputil/program_command.h
@@ -133,6 +133,7 @@
argparse::ArgValue<int> depth;
argparse::ArgValue<int> pixel_format;
argparse::ArgValue<bool> ignore_profile;
+ argparse::ArgValue<bool> ignore_alpha;
void Init(argparse::ArgumentParser& argparse) {
argparse
@@ -147,6 +148,12 @@
"(no-op if absent)")
.action(argparse::Action::STORE_TRUE)
.default_value("false");
+ argparse.add_argument(ignore_alpha, "--ignore-alpha")
+ .help(
+ "If the input file contains an alpha channel, 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 cff3f77..e774fd8 100644
--- a/apps/avifgainmaputil/swapbase_command.cc
+++ b/apps/avifgainmaputil/swapbase_command.cc
@@ -56,6 +56,9 @@
avifRGBImage swapped_rgb;
RGBImageCleanup rgb_cleanup(&swapped_rgb);
avifRGBImageSetDefaults(&swapped_rgb, swapped);
+ if (image.alphaPlane == nullptr) {
+ swapped_rgb.format = AVIF_RGB_FORMAT_RGB;
+ }
avifContentLightLevelInformationBox clli = image.gainMap->altCLLI;
const bool compute_clli =
@@ -161,8 +164,9 @@
}
decoder->maxThreads = arg_jobs_.jobs.value();
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
- avifResult result = ReadAvif(decoder.get(), arg_input_filename_,
- arg_image_read_.ignore_profile);
+ avifResult result =
+ ReadAvif(decoder.get(), arg_input_filename_,
+ arg_image_read_.ignore_profile, arg_image_read_.ignore_alpha);
if (result != AVIF_RESULT_OK) {
return result;
}
diff --git a/apps/avifgainmaputil/tonemap_command.cc b/apps/avifgainmaputil/tonemap_command.cc
index fa501ea..270096a 100644
--- a/apps/avifgainmaputil/tonemap_command.cc
+++ b/apps/avifgainmaputil/tonemap_command.cc
@@ -66,8 +66,9 @@
}
decoder->maxThreads = arg_jobs_.jobs.value();
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
- avifResult result = ReadAvif(decoder.get(), arg_input_filename_,
- arg_image_read_.ignore_profile);
+ avifResult result =
+ ReadAvif(decoder.get(), arg_input_filename_,
+ arg_image_read_.ignore_profile, arg_image_read_.ignore_alpha);
if (result != AVIF_RESULT_OK) {
return result;
}
@@ -197,6 +198,9 @@
avifRGBImage tone_mapped_rgb;
RGBImageCleanup rgb_cleanup(&tone_mapped_rgb);
avifRGBImageSetDefaults(&tone_mapped_rgb, tone_mapped.get());
+ if (image->alphaPlane == nullptr) {
+ tone_mapped_rgb.format = AVIF_RGB_FORMAT_RGB;
+ }
avifDiagnostics diag;
result = avifImageApplyGainMap(
decoder->image, image->gainMap, arg_headroom_, cicp.color_primaries,
diff --git a/tests/test_cmd_avifgainmaputil.sh b/tests/test_cmd_avifgainmaputil.sh
index e108d8a..b25061d 100755
--- a/tests/test_cmd_avifgainmaputil.sh
+++ b/tests/test_cmd_avifgainmaputil.sh
@@ -118,6 +118,38 @@
LC_ALL=C sed 's/<rdf:li>1/<rdf:li>2/' "${INPUT_JPEG_GAINMAP_SDR}" > "${INPUT_JPEG_GAINMAP_SDR_MODIFIED}"
"${ARE_IMAGES_EQUAL}" "${INPUT_JPEG_GAINMAP_SDR}" "${INPUT_JPEG_GAINMAP_SDR_MODIFIED}" 0 0 0 && exit 1
"${ARE_IMAGES_EQUAL}" "${INPUT_JPEG_GAINMAP_SDR}" "${INPUT_JPEG_GAINMAP_SDR_MODIFIED}" 0 30 0 && exit 1
+
+ # Test --ignore-alpha.
+ echo "Testing --ignore-alpha"
+ # Combine PNG with alpha. Output should have alpha.
+ "${AVIFGAINMAPUTIL}" combine "${TESTDATA_DIR}/circle-trns-after-plte.png" "${TESTDATA_DIR}/circle-trns-after-plte.png" "${AVIF_OUTPUT}" \
+ -q 50 --ignore-profile > "${OUT_MSG}"
+ cat "${OUT_MSG}"
+ grep " Alpha : Not premultiplied" "${OUT_MSG}"
+ # Combine PNG with alpha and --ignore-alpha. Output should NOT have alpha.
+ "${AVIFGAINMAPUTIL}" combine "${TESTDATA_DIR}/circle-trns-after-plte.png" "${TESTDATA_DIR}/circle-trns-after-plte.png" "${AVIF_OUTPUT}" \
+ -q 50 --ignore-profile --ignore-alpha > "${OUT_MSG}"
+ cat "${OUT_MSG}"
+ grep " Alpha : Absent" "${OUT_MSG}"
+ # Re-create AVIF with alpha.
+ "${AVIFGAINMAPUTIL}" combine "${TESTDATA_DIR}/circle-trns-after-plte.png" "${TESTDATA_DIR}/circle-trns-after-plte.png" "${AVIF_OUTPUT}" \
+ -q 50 --ignore-profile > /dev/null
+ # Tonemap AVIF with alpha. Output should have alpha.
+ "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${AVIF_OUTPUT}.tonemapped.avif" --headroom 0 > "${OUT_MSG}"
+ cat "${OUT_MSG}"
+ grep " Alpha : Not premultiplied" "${OUT_MSG}"
+ # Tonemap AVIF with alpha and --ignore-alpha. Output should NOT have alpha.
+ "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${AVIF_OUTPUT}.tonemapped.avif" --headroom 0 --ignore-alpha > "${OUT_MSG}"
+ cat "${OUT_MSG}"
+ grep " Alpha : Absent" "${OUT_MSG}"
+ # Swapbase AVIF with alpha. Output should have alpha.
+ "${AVIFGAINMAPUTIL}" swapbase "${AVIF_OUTPUT}" "${AVIF_OUTPUT}.swapped.avif" --qcolor 90 --qgain-map 90 > "${OUT_MSG}"
+ cat "${OUT_MSG}"
+ grep " Alpha : Not premultiplied" "${OUT_MSG}"
+ # Swapbase AVIF with alpha and --ignore-alpha. Output should NOT have alpha.
+ "${AVIFGAINMAPUTIL}" swapbase "${AVIF_OUTPUT}" "${AVIF_OUTPUT}.swapped.avif" --qcolor 90 --qgain-map 90 --ignore-alpha > "${OUT_MSG}"
+ cat "${OUT_MSG}"
+ grep " Alpha : Absent" "${OUT_MSG}"
popd
exit 0