Skip to content

Commit

Permalink
Add --icc flag to avifdec (#2042)
Browse files Browse the repository at this point in the history
Same meaning as in avifenc.
Move readEntireFile() from avifenc to avifutil.

Add CHANGELOG.md entry.
  • Loading branch information
y-guyon authored Feb 26, 2024
1 parent e7ab3d4 commit cdb1c3d
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ The changes are relative to the previous release, unless the baseline is specifi
* 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
Expand Down
25 changes: 25 additions & 0 deletions apps/avifdec.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ static void syntax(void)
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);
Expand All @@ -64,6 +65,7 @@ int main(int argc, char * argv[])
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;
Expand Down Expand Up @@ -178,6 +180,10 @@ int main(int argc, char * argv[])
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")) {
Expand Down Expand Up @@ -355,11 +361,30 @@ int main(int argc, char * argv[])
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;
}
Expand Down
36 changes: 3 additions & 33 deletions apps/avifenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -653,36 +653,6 @@ static avifBool avifInputReadImage(avifInput * input,
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)
{
Expand Down Expand Up @@ -1773,21 +1743,21 @@ int main(int argc, char * argv[])
}
} 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;
}
Expand Down
30 changes: 30 additions & 0 deletions apps/shared/avifutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,36 @@ avifAppFileFormat avifReadImage(const char * filename,
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
Expand Down
3 changes: 3 additions & 0 deletions apps/shared/avifutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ avifAppFileFormat avifReadImage(const char * filename,
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);

Expand Down
17 changes: 16 additions & 1 deletion tests/test_cmd_metadata.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,24 @@ else
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
Expand All @@ -60,14 +65,24 @@ pushd ${TMP_DIR}
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
Expand Down

0 comments on commit cdb1c3d

Please sign in to comment.