Do not allow keeping the ICC when converting from RGB to gray (#2669)
diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c
index 1b974dc..09bfb64 100644
--- a/apps/shared/avifjpeg.c
+++ b/apps/shared/avifjpeg.c
@@ -909,6 +909,12 @@
unsigned int iccDataLen;
if (read_icc_profile(&cinfo, &iccDataTmp, &iccDataLen)) {
iccData = iccDataTmp;
+ if (requestedFormat == AVIF_PIXEL_FORMAT_YUV400) {
+ fprintf(stderr,
+ "The image contains an RGB ICC profile which is incompatible with the requested output "
+ "format YUV400 (grayscale). Pass --ignore-icc to discard the ICC profile.\n");
+ goto cleanup;
+ }
if (avifImageSetProfileICC(avif, iccDataTmp, (size_t)iccDataLen) != AVIF_RESULT_OK) {
fprintf(stderr, "Setting ICC profile failed: %s (out of memory)\n", inputFilename);
goto cleanup;
diff --git a/apps/shared/avifpng.c b/apps/shared/avifpng.c
index b1685c8..9727ea9 100644
--- a/apps/shared/avifpng.c
+++ b/apps/shared/avifpng.c
@@ -365,6 +365,13 @@
// When the sRGB / iCCP chunk is present, applications that recognize it and are capable of color management
// must ignore the gAMA and cHRM chunks and use the sRGB / iCCP chunk instead.
if (png_get_iCCP(png, info, &iccpProfileName, &iccpCompression, &iccpData, &iccpDataLen) == PNG_INFO_iCCP) {
+ if (rawColorType != PNG_COLOR_TYPE_GRAY && rawColorType != PNG_COLOR_TYPE_GRAY_ALPHA &&
+ avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
+ fprintf(stderr,
+ "The image contains an RGB ICC profile which is incompatible with the requested output "
+ "format YUV400 (grayscale). Pass --ignore-icc to discard the ICC profile.\n");
+ goto cleanup;
+ }
if (avifImageSetProfileICC(avif, iccpData, iccpDataLen) != AVIF_RESULT_OK) {
fprintf(stderr, "Setting ICC profile failed: out of memory.\n");
goto cleanup;
diff --git a/tests/gtest/avifreadimagetest.cc b/tests/gtest/avifreadimagetest.cc
index 1126128..68e1285 100644
--- a/tests/gtest/avifreadimagetest.cc
+++ b/tests/gtest/avifreadimagetest.cc
@@ -289,6 +289,34 @@
0);
}
+// Verify the invalidity of keeping the ICC profile for a gray image read from
+// an RGB image.
+TEST(ICCTest, RGB2Gray) {
+ for (const auto& file_name :
+ {"paris_icc_exif_xmp.png", "paris_exif_xmp_icc.jpg"}) {
+ const std::string file_path = std::string(data_path) + file_name;
+ for (bool ignore_icc : {false, true}) {
+ ImagePtr image(avifImageCreateEmpty());
+ // Read the image.
+ const avifAppFileFormat file_format = avifReadImage(
+ file_path.c_str(),
+ /*requestedFormat=*/AVIF_PIXEL_FORMAT_YUV400,
+ /*requestedDepth=*/0,
+ /*chromaDownsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
+ /*ignoreColorProfile=*/ignore_icc, /*ignoreExif=*/false,
+ /*ignoreXMP=*/false, /*allowChangingCicp=*/true,
+ /*ignoreGainMap=*/true, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(),
+ /*outDepth=*/nullptr, /*sourceTiming=*/nullptr,
+ /*frameIter=*/nullptr);
+ if (ignore_icc) {
+ ASSERT_NE(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN);
+ } else {
+ ASSERT_EQ(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN);
+ }
+ }
+ }
+}
+
//------------------------------------------------------------------------------
// Memory management tests
diff --git a/tests/test_cmd_grid.sh b/tests/test_cmd_grid.sh
index 4418a09..f5e3f6e 100755
--- a/tests/test_cmd_grid.sh
+++ b/tests/test_cmd_grid.sh
@@ -44,7 +44,7 @@
"${AVIFDEC}" "${ENCODED_FILE_2x2}" "${DECODED_FILE_2x2}"
echo "Testing monochrome grid with odd width (403 px)"
- "${AVIFENC}" -s 8 "${INPUT_PNG}" --grid 2x2 --yuv 400 -o "${ENCODED_FILE_2x2}"
+ "${AVIFENC}" -s 8 "${INPUT_PNG}" --grid 2x2 --yuv 400 --ignore-icc -o "${ENCODED_FILE_2x2}"
"${AVIFDEC}" "${ENCODED_FILE_2x2}" "${DECODED_FILE_2x2}"
echo "Testing max grid"
diff --git a/tests/test_cmd_icc_profile.sh b/tests/test_cmd_icc_profile.sh
index 7e06acb..bb161ad 100755
--- a/tests/test_cmd_icc_profile.sh
+++ b/tests/test_cmd_icc_profile.sh
@@ -24,6 +24,8 @@
INPUT_GRAY_PNG="${TESTDATA_DIR}/kodim03_grayscale_gamma1.6.png"
REFERENCE_GRAY_PNG="${TESTDATA_DIR}/kodim03_grayscale_gamma1.6-reference.png"
SRGB_ICC="${TESTDATA_DIR}/sRGB2014.icc"
+INPUT_ICC_PNG="${TESTDATA_DIR}/paris_icc_exif_xmp.png"
+INPUT_ICC_JPG="${TESTDATA_DIR}/paris_exif_xmp_icc.jpg"
# Output file names.
ENCODED_FILE="avif_test_cmd_icc_profile_encoded.avif"
DECODED_FILE="avif_test_cmd_icc_profile_decoded.png"
@@ -44,6 +46,12 @@
"${AVIFDEC}" "${ENCODED_FILE}" "${DECODED_FILE}" | grep "Transfer Char.* 12$"
"${AVIFDEC}" "${ENCODED_FILE}" "${DECODED_FILE}" | grep "Matrix Coeffs.* 8$"
+ # RGB ICC cannot be kept when asking for gray.
+ "${AVIFENC}" -s 10 "${INPUT_ICC_JPG}" --yuv 400 -o "${ENCODED_FILE}" && exit 1
+ "${AVIFENC}" -s 10 "${INPUT_ICC_PNG}" --yuv 400 -o "${ENCODED_FILE}" && exit 1
+ "${AVIFENC}" -s 10 "${INPUT_ICC_JPG}" --yuv 400 --ignore-icc -o "${ENCODED_FILE}"
+ "${AVIFENC}" -s 10 "${INPUT_ICC_PNG}" --yuv 400 --ignore-icc -o "${ENCODED_FILE}"
+
# We use third-party tool (ImageMagick) to independently check
# our generated ICC profile is valid and correct.
if command -v magick &> /dev/null