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