Skip to content

Commit

Permalink
Fix reading gray/color ICC profile from color/gray image in apps
Browse files Browse the repository at this point in the history
  • Loading branch information
vrabaud committed Mar 7, 2025
1 parent f8e40a5 commit a66e0c7
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 31 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ The changes are relative to the previous release, unless the baseline is specifi
* Fix local libargparse dependency patch step on macOS 10.15 and earlier.
* Patch local libyuv dependency for compatibility with gcc 10.
* Use stricter C99 syntax to avoid related compilation issues.
* Reject the conversion in avifenc of non-monochrome input to monochrome when an
ICC profile is present and not explicitly discarded.
* Reject the conversion in avifenc from non-monochrome/monochrome to
monochrome/non-monochrome when an ICC profile is present and not explicitly
discarded.

## [1.2.0] - 2025-02-25

Expand Down
14 changes: 11 additions & 3 deletions apps/shared/avifjpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -909,12 +909,19 @@ static avifBool avifJPEGReadInternal(FILE * f,
unsigned int iccDataLen;
if (read_icc_profile(&cinfo, &iccDataTmp, &iccDataLen)) {
iccData = iccDataTmp;
if (requestedFormat == AVIF_PIXEL_FORMAT_YUV400) {
const avifBool is_gray = (cinfo.jpeg_color_space == JCS_GRAYSCALE);
if (!is_gray && (requestedFormat == AVIF_PIXEL_FORMAT_YUV400)) {
fprintf(stderr,
"The image contains a color ICC profile which is incompatible with the requested output "
"format YUV400 (grayscale). Pass --ignore-icc to discard the ICC profile.\n");
goto cleanup;
}
if (is_gray && requestedFormat != AVIF_PIXEL_FORMAT_YUV400) {
fprintf(stderr,
"The image contains a gray ICC profile which is incompatible with the requested output "
"format YUV (color). 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;
Expand Down Expand Up @@ -1283,8 +1290,9 @@ avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int
jpeg_stdio_dest(&cinfo, f);
cinfo.image_width = avif->width;
cinfo.image_height = avif->height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
const avifBool is_gray = avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400;
cinfo.input_components = is_gray ? 1 : 3;
cinfo.in_color_space = is_gray ? JCS_GRAYSCALE : JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, jpegQuality, TRUE);
jpeg_start_compress(&cinfo, TRUE);
Expand Down
14 changes: 10 additions & 4 deletions apps/shared/avifpng.c
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,8 @@ avifBool avifPNGRead(const char * inputFilename,
png_set_tRNS_to_alpha(png);
}

if ((rawColorType == PNG_COLOR_TYPE_GRAY) || (rawColorType == PNG_COLOR_TYPE_GRAY_ALPHA)) {
const avifBool raw_color_type_is_gray = (rawColorType == PNG_COLOR_TYPE_GRAY) || (rawColorType == PNG_COLOR_TYPE_GRAY_ALPHA);
if (raw_color_type_is_gray) {
png_set_gray_to_rgb(png);
}

Expand All @@ -322,7 +323,7 @@ avifBool avifPNGRead(const char * inputFilename,
goto cleanup;
}
if (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
if ((rawColorType == PNG_COLOR_TYPE_GRAY) || (rawColorType == PNG_COLOR_TYPE_GRAY_ALPHA)) {
if (raw_color_type_is_gray) {
avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
} else if (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY ||
avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RE) {
Expand Down Expand Up @@ -365,8 +366,13 @@ avifBool avifPNGRead(const char * inputFilename,
// 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) {
if (!raw_color_type_is_gray && avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
fprintf(stderr,
"The image contains a color ICC profile which is incompatible with the requested output "
"format YUV400 (grayscale). Pass --ignore-icc to discard the ICC profile.\n");
goto cleanup;
}
if (raw_color_type_is_gray && avif->yuvFormat != AVIF_PIXEL_FORMAT_YUV400) {
fprintf(stderr,
"The image contains a color ICC profile which is incompatible with the requested output "
"format YUV400 (grayscale). Pass --ignore-icc to discard the ICC profile.\n");
Expand Down
97 changes: 75 additions & 22 deletions tests/gtest/avifreadimagetest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <string>

#include "avif/avif.h"
#include "avifjpeg.h"
#include "avifpng.h"
#include "aviftest_helpers.h"
#include "avifutil.h"
#include "gtest/gtest.h"
Expand Down Expand Up @@ -289,29 +291,80 @@ TEST(ICCTest, GeneratedICCHash) {
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);
// Simpler function to read an image.
static avifAppFileFormat avifReadImageForRGB2Gray2RGB(const std::string& path,
avifPixelFormat format,
bool ignore_icc,
ImagePtr& image) {
return avifReadImage(
path.c_str(), format, /*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);
}

// Verify the invalidity of keeping the ICC profile for a gray/color image read
// from a color/gray image.
TEST(ICCTest, RGB2Gray2RGB) {
constexpr char file_name[] = "paris_icc_exif_xmp.png";
const std::string file_path = std::string(data_path) + file_name;

for (auto format : {AVIF_PIXEL_FORMAT_YUV400, AVIF_PIXEL_FORMAT_YUV444}) {
// Read the ground truth image in the appropriate format.
ImagePtr image(avifImageCreateEmpty());
ASSERT_NE(image, nullptr);
ASSERT_NE(avifReadImageForRGB2Gray2RGB(file_path, format,
/*ignore_icc=*/true, image),
AVIF_APP_FILE_FORMAT_UNKNOWN);

// Add an ICC profile.
float primariesCoords[8];
avifColorPrimariesGetValues(AVIF_COLOR_PRIMARIES_BT709, primariesCoords);

testutil::AvifRwData icc;
if (format == AVIF_PIXEL_FORMAT_YUV400) {
EXPECT_EQ(avifGenerateGrayICC(&icc, 2.2f, primariesCoords), AVIF_TRUE);
} else {
EXPECT_EQ(avifGenerateRGBICC(&icc, 2.2f, primariesCoords), AVIF_TRUE);
}
ASSERT_EQ(avifImageSetProfileICC(image.get(), icc.data, icc.size),
AVIF_RESULT_OK);

for (const std::string ext : {"png", "jpg"}) {
// Write the image with the appropriate codec.
const std::string tmp_path =
testing::TempDir() + "tmp_RGB2Gray2RGB." + ext;
if (ext == "png") {
ASSERT_EQ(
avifPNGWrite(tmp_path.c_str(), image.get(), /*requestedDepth=*/0,
AVIF_CHROMA_UPSAMPLING_BEST_QUALITY,
/*compressionLevel=*/0),
AVIF_TRUE);
} else {
ASSERT_EQ(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN);
ASSERT_EQ(
avifJPEGWrite(tmp_path.c_str(), image.get(), /*jpegQuality=*/75,
AVIF_CHROMA_UPSAMPLING_BEST_QUALITY),
AVIF_TRUE);
}

for (bool ignore_icc : {false, true}) {
for (auto new_format :
{AVIF_PIXEL_FORMAT_YUV400, AVIF_PIXEL_FORMAT_YUV444}) {
ImagePtr new_image(avifImageCreateEmpty());
ASSERT_NE(new_image, nullptr);
const avifAppFileFormat new_file_format =
avifReadImageForRGB2Gray2RGB(new_path, new_format, ignore_icc,
new_image);
if (format == new_format || ignore_icc) {
ASSERT_NE(new_file_format, AVIF_APP_FILE_FORMAT_UNKNOWN);
} else {
// When formats are different, the ICC cannot be kpet.
ASSERT_EQ(new_file_format, AVIF_APP_FILE_FORMAT_UNKNOWN);
}
}
}
}
}
Expand Down

0 comments on commit a66e0c7

Please sign in to comment.