Skip to content

Commit 218c3d2

Browse files
committedMar 7, 2025
Fix reading gray/color ICC profile from color/gray image in apps
1 parent f8e40a5 commit 218c3d2

File tree

4 files changed

+99
-31
lines changed

4 files changed

+99
-31
lines changed
 

‎CHANGELOG.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ The changes are relative to the previous release, unless the baseline is specifi
1919
* Fix local libargparse dependency patch step on macOS 10.15 and earlier.
2020
* Patch local libyuv dependency for compatibility with gcc 10.
2121
* Use stricter C99 syntax to avoid related compilation issues.
22-
* Reject the conversion in avifenc of non-monochrome input to monochrome when an
23-
ICC profile is present and not explicitly discarded.
22+
* Reject the conversion in avifenc from non-monochrome/monochrome to
23+
monochrome/non-monochrome when an ICC profile is present and not explicitly
24+
discarded.
2425

2526
## [1.2.0] - 2025-02-25
2627

‎apps/shared/avifjpeg.c

+11-3
Original file line numberDiff line numberDiff line change
@@ -909,12 +909,19 @@ static avifBool avifJPEGReadInternal(FILE * f,
909909
unsigned int iccDataLen;
910910
if (read_icc_profile(&cinfo, &iccDataTmp, &iccDataLen)) {
911911
iccData = iccDataTmp;
912-
if (requestedFormat == AVIF_PIXEL_FORMAT_YUV400) {
912+
const avifBool is_gray = (cinfo.jpeg_color_space == JCS_GRAYSCALE);
913+
if (!is_gray && (requestedFormat == AVIF_PIXEL_FORMAT_YUV400)) {
913914
fprintf(stderr,
914915
"The image contains a color ICC profile which is incompatible with the requested output "
915916
"format YUV400 (grayscale). Pass --ignore-icc to discard the ICC profile.\n");
916917
goto cleanup;
917918
}
919+
if (is_gray && requestedFormat != AVIF_PIXEL_FORMAT_YUV400) {
920+
fprintf(stderr,
921+
"The image contains a gray ICC profile which is incompatible with the requested output "
922+
"format YUV (color). Pass --ignore-icc to discard the ICC profile.\n");
923+
goto cleanup;
924+
}
918925
if (avifImageSetProfileICC(avif, iccDataTmp, (size_t)iccDataLen) != AVIF_RESULT_OK) {
919926
fprintf(stderr, "Setting ICC profile failed: %s (out of memory)\n", inputFilename);
920927
goto cleanup;
@@ -1283,8 +1290,9 @@ avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int
12831290
jpeg_stdio_dest(&cinfo, f);
12841291
cinfo.image_width = avif->width;
12851292
cinfo.image_height = avif->height;
1286-
cinfo.input_components = 3;
1287-
cinfo.in_color_space = JCS_RGB;
1293+
const avifBool is_gray = avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400;
1294+
cinfo.input_components = is_gray ? 1 : 3;
1295+
cinfo.in_color_space = is_gray ? JCS_GRAYSCALE : JCS_RGB;
12881296
jpeg_set_defaults(&cinfo);
12891297
jpeg_set_quality(&cinfo, jpegQuality, TRUE);
12901298
jpeg_start_compress(&cinfo, TRUE);

‎apps/shared/avifpng.c

+10-4
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,8 @@ avifBool avifPNGRead(const char * inputFilename,
298298
png_set_tRNS_to_alpha(png);
299299
}
300300

301-
if ((rawColorType == PNG_COLOR_TYPE_GRAY) || (rawColorType == PNG_COLOR_TYPE_GRAY_ALPHA)) {
301+
const avifBool raw_color_type_is_gray = (rawColorType == PNG_COLOR_TYPE_GRAY) || (rawColorType == PNG_COLOR_TYPE_GRAY_ALPHA);
302+
if (raw_color_type_is_gray) {
302303
png_set_gray_to_rgb(png);
303304
}
304305

@@ -322,7 +323,7 @@ avifBool avifPNGRead(const char * inputFilename,
322323
goto cleanup;
323324
}
324325
if (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
325-
if ((rawColorType == PNG_COLOR_TYPE_GRAY) || (rawColorType == PNG_COLOR_TYPE_GRAY_ALPHA)) {
326+
if (raw_color_type_is_gray) {
326327
avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
327328
} else if (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY ||
328329
avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RE) {
@@ -365,8 +366,13 @@ avifBool avifPNGRead(const char * inputFilename,
365366
// When the sRGB / iCCP chunk is present, applications that recognize it and are capable of color management
366367
// must ignore the gAMA and cHRM chunks and use the sRGB / iCCP chunk instead.
367368
if (png_get_iCCP(png, info, &iccpProfileName, &iccpCompression, &iccpData, &iccpDataLen) == PNG_INFO_iCCP) {
368-
if (rawColorType != PNG_COLOR_TYPE_GRAY && rawColorType != PNG_COLOR_TYPE_GRAY_ALPHA &&
369-
avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
369+
if (!raw_color_type_is_gray && avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
370+
fprintf(stderr,
371+
"The image contains a color ICC profile which is incompatible with the requested output "
372+
"format YUV400 (grayscale). Pass --ignore-icc to discard the ICC profile.\n");
373+
goto cleanup;
374+
}
375+
if (raw_color_type_is_gray && avif->yuvFormat != AVIF_PIXEL_FORMAT_YUV400) {
370376
fprintf(stderr,
371377
"The image contains a color ICC profile which is incompatible with the requested output "
372378
"format YUV400 (grayscale). Pass --ignore-icc to discard the ICC profile.\n");

‎tests/gtest/avifreadimagetest.cc

+75-22
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <string>
55

66
#include "avif/avif.h"
7+
#include "avifjpeg.h"
8+
#include "avifpng.h"
79
#include "aviftest_helpers.h"
810
#include "avifutil.h"
911
#include "gtest/gtest.h"
@@ -289,29 +291,80 @@ TEST(ICCTest, GeneratedICCHash) {
289291
0);
290292
}
291293

292-
// Verify the invalidity of keeping the ICC profile for a gray image read from
293-
// an RGB image.
294-
TEST(ICCTest, RGB2Gray) {
295-
for (const auto& file_name :
296-
{"paris_icc_exif_xmp.png", "paris_exif_xmp_icc.jpg"}) {
297-
const std::string file_path = std::string(data_path) + file_name;
298-
for (bool ignore_icc : {false, true}) {
299-
ImagePtr image(avifImageCreateEmpty());
300-
// Read the image.
301-
const avifAppFileFormat file_format = avifReadImage(
302-
file_path.c_str(),
303-
/*requestedFormat=*/AVIF_PIXEL_FORMAT_YUV400,
304-
/*requestedDepth=*/0,
305-
/*chromaDownsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
306-
/*ignoreColorProfile=*/ignore_icc, /*ignoreExif=*/false,
307-
/*ignoreXMP=*/false, /*allowChangingCicp=*/true,
308-
/*ignoreGainMap=*/true, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(),
309-
/*outDepth=*/nullptr, /*sourceTiming=*/nullptr,
310-
/*frameIter=*/nullptr);
311-
if (ignore_icc) {
312-
ASSERT_NE(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN);
294+
// Simpler function to read an image.
295+
static avifAppFileFormat avifReadImageForRGB2Gray2RGB(const std::string& path,
296+
avifPixelFormat format,
297+
bool ignore_icc,
298+
ImagePtr& image) {
299+
return avifReadImage(
300+
path.c_str(), format, /*requestedDepth=*/0,
301+
/*chromaDownsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
302+
/*ignoreColorProfile=*/ignore_icc, /*ignoreExif=*/false,
303+
/*ignoreXMP=*/false, /*allowChangingCicp=*/true,
304+
/*ignoreGainMap=*/true, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(),
305+
/*outDepth=*/nullptr, /*sourceTiming=*/nullptr,
306+
/*frameIter=*/nullptr);
307+
}
308+
309+
// Verify the invalidity of keeping the ICC profile for a gray/color image read
310+
// from a color/gray image.
311+
TEST(ICCTest, RGB2Gray2RGB) {
312+
constexpr char file_name[] = "paris_icc_exif_xmp.png";
313+
const std::string file_path = std::string(data_path) + file_name;
314+
315+
for (auto format : {AVIF_PIXEL_FORMAT_YUV400, AVIF_PIXEL_FORMAT_YUV444}) {
316+
// Read the ground truth image in the appropriate format.
317+
ImagePtr image(avifImageCreateEmpty());
318+
ASSERT_NE(image, nullptr);
319+
ASSERT_NE(avifReadImageForRGB2Gray2RGB(file_path, format,
320+
/*ignore_icc=*/true, image),
321+
AVIF_APP_FILE_FORMAT_UNKNOWN);
322+
323+
// Add an ICC profile.
324+
float primariesCoords[8];
325+
avifColorPrimariesGetValues(AVIF_COLOR_PRIMARIES_BT709, primariesCoords);
326+
327+
testutil::AvifRwData icc;
328+
if (format == AVIF_PIXEL_FORMAT_YUV400) {
329+
EXPECT_EQ(avifGenerateGrayICC(&icc, 2.2f, primariesCoords), AVIF_TRUE);
330+
} else {
331+
EXPECT_EQ(avifGenerateRGBICC(&icc, 2.2f, primariesCoords), AVIF_TRUE);
332+
}
333+
ASSERT_EQ(avifImageSetProfileICC(image.get(), icc.data, icc.size),
334+
AVIF_RESULT_OK);
335+
336+
for (const std::string ext : {"png", "jpg"}) {
337+
// Write the image with the appropriate codec.
338+
const std::string new_path =
339+
testing::TempDir() + "tmp_RGB2Gray2RGB." + ext;
340+
if (ext == "png") {
341+
ASSERT_EQ(
342+
avifPNGWrite(new_path.c_str(), image.get(), /*requestedDepth=*/0,
343+
AVIF_CHROMA_UPSAMPLING_BEST_QUALITY,
344+
/*compressionLevel=*/0),
345+
AVIF_TRUE);
313346
} else {
314-
ASSERT_EQ(file_format, AVIF_APP_FILE_FORMAT_UNKNOWN);
347+
ASSERT_EQ(
348+
avifJPEGWrite(new_path.c_str(), image.get(), /*jpegQuality=*/75,
349+
AVIF_CHROMA_UPSAMPLING_BEST_QUALITY),
350+
AVIF_TRUE);
351+
}
352+
353+
for (bool ignore_icc : {false, true}) {
354+
for (auto new_format :
355+
{AVIF_PIXEL_FORMAT_YUV400, AVIF_PIXEL_FORMAT_YUV444}) {
356+
ImagePtr new_image(avifImageCreateEmpty());
357+
ASSERT_NE(new_image, nullptr);
358+
const avifAppFileFormat new_file_format =
359+
avifReadImageForRGB2Gray2RGB(new_path, new_format, ignore_icc,
360+
new_image);
361+
if (format == new_format || ignore_icc) {
362+
ASSERT_NE(new_file_format, AVIF_APP_FILE_FORMAT_UNKNOWN);
363+
} else {
364+
// When formats are different, the ICC cannot be kept.
365+
ASSERT_EQ(new_file_format, AVIF_APP_FILE_FORMAT_UNKNOWN);
366+
}
367+
}
315368
}
316369
}
317370
}

0 commit comments

Comments
 (0)