Add --icc flag to avifdec (#2042)
Same meaning as in avifenc.
Move readEntireFile() from avifenc to avifutil.
Add CHANGELOG.md entry.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa146dc..16c0c4c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -50,6 +50,7 @@
* Add avifResult enum entry AVIF_RESULT_INTERNAL_ERROR.
* Require libyuv by default (but it can still be disabled with
-DAVIF_LIBYUV=OFF).
+* Add avifdec --icc flag to override the output color profile.
### Changed since 1.0.0
* Update aom.cmd: v3.8.1
diff --git a/apps/avifdec.c b/apps/avifdec.c
index 705db3c..1c1788b 100644
--- a/apps/avifdec.c
+++ b/apps/avifdec.c
@@ -43,6 +43,7 @@
printf(" avifdec will use --index to choose which layer to decode (in progressive order).\n");
printf(" --no-strict : Disable strict decoding, which disables strict validation checks and errors\n");
printf(" -i,--info : Decode all frames and display all image information instead of saving to disk\n");
+ printf(" --icc FILENAME : Provide an ICC profile payload (implies --ignore-icc)\n");
printf(" --ignore-icc : If the input file contains an embedded ICC profile, ignore it (no-op if absent)\n");
printf(" --size-limit C : Specifies the image size limit (in total pixels) that should be tolerated.\n");
printf(" Default: %u, set to a smaller value to further restrict.\n", AVIF_DEFAULT_IMAGE_SIZE_LIMIT);
@@ -64,6 +65,7 @@
avifCodecChoice codecChoice = AVIF_CODEC_CHOICE_AUTO;
avifBool infoOnly = AVIF_FALSE;
avifChromaUpsampling chromaUpsampling = AVIF_CHROMA_UPSAMPLING_AUTOMATIC;
+ const char * iccOverrideFilename = NULL;
avifBool ignoreICC = AVIF_FALSE;
avifBool rawColor = AVIF_FALSE;
avifBool allowProgressive = AVIF_FALSE;
@@ -178,6 +180,10 @@
strictFlags = AVIF_STRICT_DISABLED;
} else if (!strcmp(arg, "-i") || !strcmp(arg, "--info")) {
infoOnly = AVIF_TRUE;
+ } else if (!strcmp(arg, "--icc")) {
+ NEXTARG();
+ iccOverrideFilename = arg;
+ ignoreICC = AVIF_TRUE;
} else if (!strcmp(arg, "--ignore-icc")) {
ignoreICC = AVIF_TRUE;
} else if (!strcmp(arg, "--size-limit")) {
@@ -355,11 +361,30 @@
assert(result == AVIF_RESULT_OK);
}
+ if (iccOverrideFilename) {
+ avifRWData iccOverride = AVIF_DATA_EMPTY;
+ if (!avifReadEntireFile(iccOverrideFilename, &iccOverride)) {
+ fprintf(stderr, "ERROR: Unable to read ICC: %s\n", iccOverrideFilename);
+ avifRWDataFree(&iccOverride);
+ goto cleanup;
+ }
+ printf("[--icc] Setting ICC profile: %s\n", iccOverrideFilename);
+ result = avifImageSetProfileICC(decoder->image, iccOverride.data, iccOverride.size);
+ avifRWDataFree(&iccOverride);
+ if (result != AVIF_RESULT_OK) {
+ fprintf(stderr, "ERROR: Failed to set ICC: %s\n", avifResultToString(result));
+ goto cleanup;
+ }
+ }
+
avifAppFileFormat outputFormat = avifGuessFileFormat(outputFilename);
if (outputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
fprintf(stderr, "Cannot determine output file extension: %s\n", outputFilename);
goto cleanup;
} else if (outputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
+ if (decoder->image->icc.size || decoder->image->exif.size || decoder->image->xmp.size) {
+ printf("Warning: metadata dropped when saving to y4m.\n");
+ }
if (!y4mWrite(outputFilename, decoder->image)) {
goto cleanup;
}
diff --git a/apps/avifenc.c b/apps/avifenc.c
index 980711d..7abbf4b 100644
--- a/apps/avifenc.c
+++ b/apps/avifenc.c
@@ -653,36 +653,6 @@
return AVIF_TRUE;
}
-static avifBool readEntireFile(const char * filename, avifRWData * raw)
-{
- FILE * f = fopen(filename, "rb");
- if (!f) {
- return AVIF_FALSE;
- }
-
- fseek(f, 0, SEEK_END);
- long pos = ftell(f);
- if (pos <= 0) {
- fclose(f);
- return AVIF_FALSE;
- }
- size_t fileSize = (size_t)pos;
- fseek(f, 0, SEEK_SET);
-
- if (avifRWDataRealloc(raw, fileSize) != AVIF_RESULT_OK) {
- fclose(f);
- return AVIF_FALSE;
- }
- size_t bytesRead = fread(raw->data, 1, fileSize, f);
- fclose(f);
-
- if (bytesRead != fileSize) {
- avifRWDataFree(raw);
- return AVIF_FALSE;
- }
- return AVIF_TRUE;
-}
-
// Returns NULL if a memory allocation failed. The return value should be freed with free().
static char * avifStrdup(const char * str)
{
@@ -1773,21 +1743,21 @@
}
} else if (!strcmp(arg, "--exif")) {
NEXTARG();
- if (!readEntireFile(arg, &exifOverride)) {
+ if (!avifReadEntireFile(arg, &exifOverride)) {
fprintf(stderr, "ERROR: Unable to read Exif metadata: %s\n", arg);
goto cleanup;
}
settings.ignoreExif = AVIF_TRUE;
} else if (!strcmp(arg, "--xmp")) {
NEXTARG();
- if (!readEntireFile(arg, &xmpOverride)) {
+ if (!avifReadEntireFile(arg, &xmpOverride)) {
fprintf(stderr, "ERROR: Unable to read XMP metadata: %s\n", arg);
goto cleanup;
}
settings.ignoreXMP = AVIF_TRUE;
} else if (!strcmp(arg, "--icc")) {
NEXTARG();
- if (!readEntireFile(arg, &iccOverride)) {
+ if (!avifReadEntireFile(arg, &iccOverride)) {
fprintf(stderr, "ERROR: Unable to read ICC profile: %s\n", arg);
goto cleanup;
}
diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c
index 20f54ea..3af0cdf 100644
--- a/apps/shared/avifutil.c
+++ b/apps/shared/avifutil.c
@@ -348,6 +348,36 @@
return format;
}
+avifBool avifReadEntireFile(const char * filename, avifRWData * raw)
+{
+ FILE * f = fopen(filename, "rb");
+ if (!f) {
+ return AVIF_FALSE;
+ }
+
+ fseek(f, 0, SEEK_END);
+ long pos = ftell(f);
+ if (pos <= 0) {
+ fclose(f);
+ return AVIF_FALSE;
+ }
+ size_t fileSize = (size_t)pos;
+ fseek(f, 0, SEEK_SET);
+
+ if (avifRWDataRealloc(raw, fileSize) != AVIF_RESULT_OK) {
+ fclose(f);
+ return AVIF_FALSE;
+ }
+ size_t bytesRead = fread(raw->data, 1, fileSize, f);
+ fclose(f);
+
+ if (bytesRead != fileSize) {
+ avifRWDataFree(raw);
+ return AVIF_FALSE;
+ }
+ return AVIF_TRUE;
+}
+
void avifImageFixXMP(avifImage * image)
{
// Zero bytes are forbidden in UTF-8 XML: https://en.wikipedia.org/wiki/Valid_characters_in_XML
diff --git a/apps/shared/avifutil.h b/apps/shared/avifutil.h
index 55edd52..1870097 100644
--- a/apps/shared/avifutil.h
+++ b/apps/shared/avifutil.h
@@ -84,6 +84,9 @@
avifAppSourceTiming * sourceTiming,
struct y4mFrameIterator ** frameIter);
+// Copies all the bytes from the file at filename to a newly allocated memory chunk.
+avifBool avifReadEntireFile(const char * filename, avifRWData * raw);
+
// Removes a single trailing null character from the image->xmp, if there is exactly one.
void avifImageFixXMP(avifImage * image);
diff --git a/tests/test_cmd_metadata.sh b/tests/test_cmd_metadata.sh
index 5e8ebd9..cfbd9fc 100755
--- a/tests/test_cmd_metadata.sh
+++ b/tests/test_cmd_metadata.sh
@@ -38,19 +38,24 @@
fi
AVIFENC="${BINARY_DIR}/avifenc"
+AVIFDEC="${BINARY_DIR}/avifdec"
# Input file paths.
INPUT_PNG="${TESTDATA_DIR}/paris_icc_exif_xmp.png"
INPUT_JPG="${TESTDATA_DIR}/paris_exif_xmp_icc.jpg"
+INPUT_ICC="${TESTDATA_DIR}/sRGB2014.icc"
# Output file names.
ENCODED_FILE="avif_test_cmd_metadata_encoded.avif"
ENCODED_FILE_NO_METADATA="avif_test_cmd_metadata_encoded_no_metadata.avif"
ENCODED_FILE_MORE_METADATA="avif_test_cmd_metadata_encoded_more_metadata.avif"
+DECODED_FILE="avif_test_cmd_metadata_decoded.png"
+DECODED_FILE_CHANGED_ICC="avif_test_cmd_metadata_decoded_changed_icc.png"
# Cleanup
cleanup() {
pushd ${TMP_DIR}
- rm -- "${ENCODED_FILE}" "${ENCODED_FILE_NO_METADATA}" "${ENCODED_FILE_MORE_METADATA}"
+ rm -- "${ENCODED_FILE}" "${ENCODED_FILE_NO_METADATA}" "${ENCODED_FILE_MORE_METADATA}" \
+ "${DECODED_FILE}" "${DECODED_FILE_CHANGED_ICC}"
popd
}
trap cleanup EXIT
@@ -60,14 +65,24 @@
echo "Testing metadata enc"
for INPUT in "${INPUT_PNG}" "${INPUT_JPG}"; do
"${AVIFENC}" "${INPUT}" -o "${ENCODED_FILE}"
+
# Ignoring a metadata chunk should produce a different output file.
+
"${AVIFENC}" "${INPUT}" -o "${ENCODED_FILE_NO_METADATA}" --ignore-icc
cmp "${ENCODED_FILE}" "${ENCODED_FILE_NO_METADATA}" && exit 1
"${AVIFENC}" "${INPUT}" -o "${ENCODED_FILE_NO_METADATA}" --ignore-exif
cmp "${ENCODED_FILE}" "${ENCODED_FILE_NO_METADATA}" && exit 1
"${AVIFENC}" "${INPUT}" -o "${ENCODED_FILE_NO_METADATA}" --ignore-xmp
cmp "${ENCODED_FILE}" "${ENCODED_FILE_NO_METADATA}" && exit 1
+
+ "${AVIFDEC}" "${ENCODED_FILE}" "${DECODED_FILE}"
+ "${AVIFDEC}" "${ENCODED_FILE}" --ignore-icc "${DECODED_FILE_CHANGED_ICC}"
+ cmp "${DECODED_FILE}" "${DECODED_FILE_CHANGED_ICC}" && exit 1
+ "${AVIFDEC}" "${ENCODED_FILE}" --icc "${INPUT_ICC}" "${DECODED_FILE_CHANGED_ICC}"
+ cmp "${DECODED_FILE}" "${DECODED_FILE_CHANGED_ICC}" && exit 1
+
# As should adding metadata.
+
"${AVIFENC}" "${INPUT}" -o "${ENCODED_FILE_MORE_METADATA}" --clli 1000,50
cmp "${ENCODED_FILE}" "${ENCODED_FILE_MORE_METADATA}" && exit 1
done