Read XMP from PNG and Exif,XMP from Jpeg
diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c
index fcaa4ce..4cb54ba 100644
--- a/apps/shared/avifjpeg.c
+++ b/apps/shared/avifjpeg.c
@@ -239,7 +239,14 @@
// longjmp. But GCC's -Wclobbered warning may have trouble figuring that out, so
// we preemptively declare it as volatile.
-avifBool avifJPEGRead(const char * inputFilename, avifImage * avif, avifPixelFormat requestedFormat, uint32_t requestedDepth, avifRGBToYUVFlags flags)
+avifBool avifJPEGRead(const char * inputFilename,
+ avifImage * avif,
+ avifPixelFormat requestedFormat,
+ uint32_t requestedDepth,
+ avifRGBToYUVFlags flags,
+ avifBool ignoreICC,
+ avifBool ignoreExif,
+ avifBool ignoreXMP)
{
volatile avifBool ret = AVIF_FALSE;
uint8_t * volatile iccData = NULL;
@@ -263,15 +270,22 @@
jpeg_create_decompress(&cinfo);
- setup_read_icc_profile(&cinfo);
+ if (!ignoreExif || !ignoreXMP) {
+ jpeg_save_markers(&cinfo, JPEG_APP0 + 1, /*length_limit=*/0xFFFF); // Exif/XMP
+ }
+ if (!ignoreICC) {
+ setup_read_icc_profile(&cinfo);
+ }
jpeg_stdio_src(&cinfo, f);
jpeg_read_header(&cinfo, TRUE);
- uint8_t * iccDataTmp;
- unsigned int iccDataLen;
- if (read_icc_profile(&cinfo, &iccDataTmp, &iccDataLen)) {
- iccData = iccDataTmp;
- avifImageSetProfileICC(avif, iccDataTmp, (size_t)iccDataLen);
+ if (!ignoreICC) {
+ uint8_t * iccDataTmp;
+ unsigned int iccDataLen;
+ if (read_icc_profile(&cinfo, &iccDataTmp, &iccDataLen)) {
+ iccData = iccDataTmp;
+ avifImageSetProfileICC(avif, iccDataTmp, (size_t)iccDataLen);
+ }
}
avif->yuvFormat = requestedFormat; // This may be AVIF_PIXEL_FORMAT_NONE, which is "auto" to avifJPEGReadCopy()
@@ -320,13 +334,48 @@
}
}
+ if (!ignoreExif) {
+ const avifROData tagExif = { (const uint8_t *)"Exif\0\0", 6 };
+ avifBool found = AVIF_FALSE;
+ for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != NULL; marker = marker->next) {
+ if ((marker->marker == (JPEG_APP0 + 1)) && (marker->data_length > tagExif.size) &&
+ !memcmp(marker->data, tagExif.data, tagExif.size)) {
+ if (found) {
+ // TODO(yguyon): Implement instead of outputting an error.
+ fprintf(stderr, "Exif extraction failed: unsupported Exif split into multiple chunks or invalid multiple Exif chunks\n");
+ goto cleanup;
+ }
+ avifImageSetMetadataExif(avif, marker->data + tagExif.size, marker->data_length - tagExif.size);
+ found = AVIF_TRUE;
+ }
+ }
+ }
+ if (!ignoreXMP) {
+ const avifROData tagStandardXmp = { (const uint8_t *)"http://ns.adobe.com/xap/1.0/\0", 29 };
+ const avifROData tagExtendedXmp = { (const uint8_t *)"http://ns.adobe.com/xmp/extension/\0", 35 };
+ avifBool found = AVIF_FALSE;
+ for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != NULL; marker = marker->next) {
+ if ((marker->marker == (JPEG_APP0 + 1)) && (marker->data_length > tagStandardXmp.size) &&
+ !memcmp(marker->data, tagStandardXmp.data, tagStandardXmp.size)) {
+ if (found) {
+ fprintf(stderr, "XMP extraction failed: invalid multiple XMP chunks\n");
+ goto cleanup;
+ }
+ avifImageSetMetadataXMP(avif, marker->data + tagStandardXmp.size, marker->data_length - tagStandardXmp.size);
+ found = AVIF_TRUE;
+ } else if ((marker->marker == (JPEG_APP0 + 1)) && (marker->data_length > tagExtendedXmp.size) &&
+ !memcmp(marker->data, tagExtendedXmp.data, tagExtendedXmp.size)) {
+ // TODO(yguyon): Implement instead of outputting an error.
+ fprintf(stderr, "XMP extraction failed: extended XMP is unsupported\n");
+ goto cleanup;
+ }
+ }
+ }
jpeg_finish_decompress(&cinfo);
ret = AVIF_TRUE;
cleanup:
jpeg_destroy_decompress(&cinfo);
- if (f) {
- fclose(f);
- }
+ fclose(f);
free(iccData);
avifRGBImageFreePixels(&rgb);
return ret;
diff --git a/apps/shared/avifjpeg.h b/apps/shared/avifjpeg.h
index 5e2b981..214bcca 100644
--- a/apps/shared/avifjpeg.h
+++ b/apps/shared/avifjpeg.h
@@ -10,7 +10,14 @@
extern "C" {
#endif
-avifBool avifJPEGRead(const char * inputFilename, avifImage * avif, avifPixelFormat requestedFormat, uint32_t requestedDepth, avifRGBToYUVFlags flags);
+avifBool avifJPEGRead(const char * inputFilename,
+ avifImage * avif,
+ avifPixelFormat requestedFormat,
+ uint32_t requestedDepth,
+ avifRGBToYUVFlags flags,
+ avifBool ignoreICC,
+ avifBool ignoreExif,
+ avifBool ignoreXMP);
avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int jpegQuality, avifYUVToRGBFlags conversionFlags);
#ifdef __cplusplus
diff --git a/apps/shared/avifpng.c b/apps/shared/avifpng.c
index 07882fe..0e9ed2c 100644
--- a/apps/shared/avifpng.c
+++ b/apps/shared/avifpng.c
@@ -26,31 +26,6 @@
// AVIF_FALSE is returned if fewer than numExpectedBytes hexadecimal pairs are converted.
static avifBool avifHexStringToBytes(const char * hexString, size_t hexStringLength, size_t numExpectedBytes, avifRWData * bytes)
{
- // Remove any leading new line. They are not part of the numExpectedBytes.
- while ((hexStringLength != 0) && (hexString[0] == '\n')) {
- ++hexString;
- --hexStringLength;
- }
- if (hexStringLength < (numExpectedBytes * 2)) {
- fprintf(stderr, "Exif extraction failed: " AVIF_FMT_ZU " missing characters\n", (numExpectedBytes * 2) - hexStringLength);
- return AVIF_FALSE;
- }
-
- // Preprocess the input hexString by removing a tag commonly added at encoding, if present.
- // HEIF specification ISO-23008 section A.2.1 allows including and excluding it from AVIF files.
- // The PNG 1.5 extension mentions the omission of this header for the modern standard eXIf chunk.
- const char tagExif00[] = "457869660000"; // "Exif\0\0" tag encoded as a hexadecimal string.
- const size_t tagExif00Len = 6 * 2;
- if ((numExpectedBytes >= (tagExif00Len / 2)) && !memcmp(hexString, tagExif00, tagExif00Len)) {
- hexString += tagExif00Len;
- hexStringLength -= tagExif00Len;
- numExpectedBytes -= (tagExif00Len / 2);
- }
- if (numExpectedBytes == 0) {
- fprintf(stderr, "Exif extraction failed: empty payload\n");
- return AVIF_FALSE;
- }
-
avifRWDataRealloc(bytes, numExpectedBytes);
size_t numBytes = 0;
for (size_t i = 0; (i + 1 < hexStringLength) && (numBytes < numExpectedBytes);) {
@@ -60,7 +35,7 @@
}
if (!isxdigit(hexString[i]) || !isxdigit(hexString[i + 1])) {
avifRWDataFree(bytes);
- fprintf(stderr, "Exif extraction failed: invalid character at " AVIF_FMT_ZU "\n", i);
+ fprintf(stderr, "Metadata extraction failed: invalid character at " AVIF_FMT_ZU "\n", i);
return AVIF_FALSE;
}
const char twoHexDigits[] = { hexString[i], hexString[i + 1], '\0' };
@@ -71,7 +46,7 @@
if (numBytes != numExpectedBytes) {
avifRWDataFree(bytes);
- fprintf(stderr, "Exif extraction failed: expected " AVIF_FMT_ZU " tokens but got " AVIF_FMT_ZU "\n", numExpectedBytes, numBytes);
+ fprintf(stderr, "Metadata extraction failed: expected " AVIF_FMT_ZU " tokens but got " AVIF_FMT_ZU "\n", numExpectedBytes, numBytes);
return AVIF_FALSE;
}
return AVIF_TRUE;
@@ -82,7 +57,7 @@
{
// ImageMagick formats 'raw profiles' as "\n<name>\n<length>(%8lu)\n<hex payload>\n".
if (!profile || (profileLength == 0) || (profile[0] != '\n')) {
- fprintf(stderr, "Exif extraction failed: truncated or malformed raw profile\n");
+ fprintf(stderr, "Metadata extraction failed: truncated or malformed raw profile\n");
return AVIF_FALSE;
}
@@ -90,7 +65,7 @@
for (size_t i = 1; i < profileLength; ++i) { // i starts at 1 because the first '\n' was already checked above.
if (profile[i] == '\0') {
// This should not happen as libpng provides this guarantee but extra safety does not hurt.
- fprintf(stderr, "Exif extraction failed: malformed raw profile, unexpected null character at " AVIF_FMT_ZU "\n", i);
+ fprintf(stderr, "Metadata extraction failed: malformed raw profile, unexpected null character at " AVIF_FMT_ZU "\n", i);
return AVIF_FALSE;
}
if (profile[i] == '\n') {
@@ -104,13 +79,13 @@
char * lengthEnd;
const long expectedLength = strtol(lengthStart, &lengthEnd, 10);
if (lengthEnd != &profile[i]) {
- fprintf(stderr, "Exif extraction failed: malformed raw profile, expected '\\n' but got '\\x%.2X'\n", *lengthEnd);
+ fprintf(stderr, "Metadata extraction failed: malformed raw profile, expected '\\n' but got '\\x%.2X'\n", *lengthEnd);
return AVIF_FALSE;
}
// No need to check for errno. Just make sure expectedLength is not LONG_MIN and not LONG_MAX.
if ((expectedLength <= 0) || (expectedLength == LONG_MAX) ||
((unsigned long)expectedLength > (hexPayloadMaxLength / 2))) {
- fprintf(stderr, "Exif extraction failed: invalid length %ld\n", expectedLength);
+ fprintf(stderr, "Metadata extraction failed: invalid length %ld\n", expectedLength);
return AVIF_FALSE;
}
// Note: The profile may be malformed by containing more data than the extracted expectedLength bytes.
@@ -119,46 +94,94 @@
}
}
}
- fprintf(stderr, "Exif extraction failed: malformed or truncated raw profile\n");
+ fprintf(stderr, "Metadata extraction failed: malformed or truncated raw profile\n");
return AVIF_FALSE;
}
-// Returns AVIF_TRUE if there was no Exif metadata located at info or if the Exif metadata located at info
-// was correctly parsed and imported to avif->exif. Returns AVIF_FALSE in case of error.
-static avifBool avifExtractExif(png_structp png, png_infop const info, avifImage * avif)
+static avifBool avifRemoveHeader(const avifROData * header, avifRWData * payload)
+{
+ if (payload->size > header->size && !memcmp(payload->data, header->data, header->size)) {
+ memmove(payload->data, payload->data + header->size, payload->size - header->size);
+ payload->size -= header->size;
+ return AVIF_TRUE;
+ }
+ return AVIF_FALSE;
+}
+
+// Extracts metadata to avif->exif and avif->xmp unless the corresponding *ignoreExif or *ignoreXMP is set to AVIF_TRUE.
+// *ignoreExif and *ignoreXMP may be set to AVIF_TRUE if the corresponding Exif or XMP metadata was extracted.
+// Returns AVIF_FALSE in case of a parsing error.
+static avifBool avifExtractExifAndXMP(png_structp png, png_infop info, avifBool * ignoreExif, avifBool * ignoreXMP, avifImage * avif)
{
#ifdef PNG_eXIf_SUPPORTED
- png_uint_32 exifSize = 0;
- png_bytep exif = NULL;
- if (png_get_eXIf_1(png, info, &exifSize, &exif) == PNG_INFO_eXIf) {
- if ((exifSize == 0) || !exif) {
- fprintf(stderr, "Exif extraction failed: empty eXIf chunk\n");
- return AVIF_FALSE;
+ if (!*ignoreExif) {
+ png_uint_32 exifSize = 0;
+ png_bytep exif = NULL;
+ if (png_get_eXIf_1(png, info, &exifSize, &exif) == PNG_INFO_eXIf) {
+ if ((exifSize == 0) || !exif) {
+ fprintf(stderr, "Exif extraction failed: empty eXIf chunk\n");
+ return AVIF_FALSE;
+ }
+ avifImageSetMetadataExif(avif, exif, exifSize);
+ *ignoreExif = AVIF_TRUE; // Ignore any other Exif chunk.
}
- avifImageSetMetadataExif(avif, exif, exifSize);
- return AVIF_TRUE;
}
#endif // PNG_eXIf_SUPPORTED
+ // HEIF specification ISO-23008 section A.2.1 allows including and excluding the Exif\0\0 header from AVIF files.
+ // The PNG 1.5 extension mentions the omission of this header for the modern standard eXIf chunk.
+ const avifROData exifApp1Header = { (const uint8_t *)"Exif\0\0", 6 };
+ const avifROData xmpApp1Header = { (const uint8_t *)"http://ns.adobe.com/xap/1.0/\0", 29 };
+
+ // tXMP could be retrieved using the png_get_unknown_chunks() API but tXMP is deprecated
+ // and there is no PNG file example with a tXMP chunk lying around, so it is not worth the hassle.
+
png_textp text = NULL;
const png_uint_32 numTextChunks = png_get_text(png, info, &text, NULL);
- for (png_uint_32 i = 0; i < numTextChunks; ++i, ++text) {
- if (!strcmp(text->key, "Raw profile type exif") || !strcmp(text->key, "Raw profile type APP1")) {
- png_size_t textLength;
- switch (text->compression) {
+ for (png_uint_32 i = 0; (!*ignoreExif || !*ignoreXMP) && (i < numTextChunks); ++i, ++text) {
+ png_size_t textLength = text->text_length;
#ifdef PNG_iTXt_SUPPORTED
- case PNG_ITXT_COMPRESSION_NONE:
- case PNG_ITXT_COMPRESSION_zTXt:
- textLength = text->itxt_length;
- break;
+ if ((text->compression == PNG_ITXT_COMPRESSION_NONE) || (text->compression == PNG_ITXT_COMPRESSION_zTXt)) {
+ textLength = text->itxt_length;
+ }
#endif
- case PNG_TEXT_COMPRESSION_NONE:
- case PNG_TEXT_COMPRESSION_zTXt:
- default:
- textLength = text->text_length;
- break;
+
+ if (!*ignoreExif && !strcmp(text->key, "Raw profile type exif")) {
+ if (!avifCopyRawProfile(text->text, textLength, &avif->exif)) {
+ return AVIF_FALSE;
}
- return avifCopyRawProfile(text->text, textLength, &avif->exif);
+ avifRemoveHeader(&exifApp1Header, &avif->exif); // Ignore the return value because the header is optional.
+ *ignoreExif = AVIF_TRUE; // Ignore any other Exif chunk.
+ } else if (!*ignoreXMP && !strcmp(text->key, "Raw profile type xmp")) {
+ if (!avifCopyRawProfile(text->text, textLength, &avif->xmp)) {
+ return AVIF_FALSE;
+ }
+ avifRemoveHeader(&xmpApp1Header, &avif->xmp); // Ignore the return value because the header is optional.
+ *ignoreXMP = AVIF_TRUE; // Ignore any other XMP chunk.
+ } else if (!strcmp(text->key, "Raw profile type APP1")) {
+ // This can be either Exif, XMP or something else.
+ avifRWData metadata = { NULL, 0 };
+ if (!avifCopyRawProfile(text->text, textLength, &metadata)) {
+ return AVIF_FALSE;
+ }
+ if (!*ignoreExif && avifRemoveHeader(&exifApp1Header, &metadata)) {
+ avifRWDataFree(&avif->exif);
+ avif->exif = metadata;
+ *ignoreExif = AVIF_TRUE; // Ignore any other Exif chunk.
+ } else if (!*ignoreXMP && avifRemoveHeader(&xmpApp1Header, &metadata)) {
+ avifRWDataFree(&avif->xmp);
+ avif->xmp = metadata;
+ *ignoreXMP = AVIF_TRUE; // Ignore any other XMP chunk.
+ } else {
+ avifRWDataFree(&metadata); // Discard chunk.
+ }
+ } else if (!*ignoreXMP && !strcmp(text->key, "XML:com.adobe.xmp")) {
+ if (textLength == 0) {
+ fprintf(stderr, "XMP extraction failed: empty XML:com.adobe.xmp payload\n");
+ return AVIF_FALSE;
+ }
+ avifImageSetMetadataXMP(avif, (const uint8_t *)text->text, textLength);
+ *ignoreXMP = AVIF_TRUE; // Ignore any other XMP chunk.
}
}
return AVIF_TRUE;
@@ -183,7 +206,9 @@
avifPixelFormat requestedFormat,
uint32_t requestedDepth,
avifRGBToYUVFlags flags,
+ avifBool ignoreICC,
avifBool ignoreExif,
+ avifBool ignoreXMP,
uint32_t * outPNGDepth)
{
volatile avifBool readResult = AVIF_FALSE;
@@ -232,14 +257,17 @@
png_set_sig_bytes(png, 8);
png_read_info(png, info);
- char * iccpProfileName = NULL;
- int iccpCompression = 0;
- unsigned char * iccpData = NULL;
- png_uint_32 iccpDataLen = 0;
- if (png_get_iCCP(png, info, &iccpProfileName, &iccpCompression, (png_iccp_datap *)&iccpData, &iccpDataLen) == PNG_INFO_iCCP) {
- avifImageSetProfileICC(avif, iccpData, iccpDataLen);
+ if (!ignoreICC) {
+ char * iccpProfileName = NULL;
+ int iccpCompression = 0;
+ unsigned char * iccpData = NULL;
+ png_uint_32 iccpDataLen = 0;
+ if (png_get_iCCP(png, info, &iccpProfileName, &iccpCompression, (png_iccp_datap *)&iccpData, &iccpDataLen) == PNG_INFO_iCCP) {
+ avifImageSetProfileICC(avif, iccpData, iccpDataLen);
+ }
+ // Note: There is no support for the rare "Raw profile type icc" or "Raw profile type icm" text chunks.
+ // TODO(yguyon): Also check if there is a cICp chunk (https://github.com/AOMediaCodec/libavif/pull/1065#discussion_r958534232)
}
- // TODO(yguyon): Also check if there is a cICp chunk (https://github.com/AOMediaCodec/libavif/pull/1065#discussion_r958534232)
int rawWidth = png_get_image_width(png, info);
int rawHeight = png_get_image_height(png, info);
@@ -313,25 +341,22 @@
goto cleanup;
}
- if (!ignoreExif) {
- // Read Exif metadata at the beginning of the file.
- if (!avifExtractExif(png, info, avif)) {
+ // Read Exif metadata at the beginning of the file.
+ if (!avifExtractExifAndXMP(png, info, &ignoreExif, &ignoreXMP, avif)) {
+ goto cleanup;
+ }
+ // Read Exif or XMP metadata at the end of the file if there was none at the beginning.
+ if (!ignoreExif || !ignoreXMP) {
+ infoEnd = png_create_info_struct(png);
+ if (!infoEnd) {
+ fprintf(stderr, "Cannot init libpng (infoEnd): %s\n", inputFilename);
goto cleanup;
}
- // Read Exif metadata at the end of the file if there was none at the beginning.
- if (!avif->exif.data) {
- infoEnd = png_create_info_struct(png);
- if (!infoEnd) {
- fprintf(stderr, "Cannot init libpng (infoEnd): %s\n", inputFilename);
- goto cleanup;
- }
- png_read_end(png, infoEnd);
- if (!avifExtractExif(png, infoEnd, avif)) {
- goto cleanup;
- }
+ png_read_end(png, infoEnd);
+ if (!avifExtractExifAndXMP(png, infoEnd, &ignoreExif, &ignoreXMP, avif)) {
+ goto cleanup;
}
}
- // TODO(yguyon): Extract XMP to avif->xmp, if any.
readResult = AVIF_TRUE;
cleanup:
diff --git a/apps/shared/avifpng.h b/apps/shared/avifpng.h
index c15d7a1..9597211 100644
--- a/apps/shared/avifpng.h
+++ b/apps/shared/avifpng.h
@@ -16,7 +16,9 @@
avifPixelFormat requestedFormat,
uint32_t requestedDepth,
avifRGBToYUVFlags flags,
+ avifBool ignoreICC,
avifBool ignoreExif,
+ avifBool ignoreXMP,
uint32_t * outPNGDepth);
avifBool avifPNGWrite(const char * outputFilename,
const avifImage * avif,
diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c
index 0752dbf..c0c4470 100644
--- a/apps/shared/avifutil.c
+++ b/apps/shared/avifutil.c
@@ -245,15 +245,14 @@
*outDepth = image->depth;
}
} else if (format == AVIF_APP_FILE_FORMAT_JPEG) {
- if (!avifJPEGRead(filename, image, requestedFormat, requestedDepth, flags)) {
+ if (!avifJPEGRead(filename, image, requestedFormat, requestedDepth, flags, ignoreICC, ignoreExif, ignoreXMP)) {
return AVIF_APP_FILE_FORMAT_UNKNOWN;
}
if (outDepth) {
*outDepth = 8;
}
} else if (format == AVIF_APP_FILE_FORMAT_PNG) {
- (void)ignoreICC, (void)ignoreXMP; // TODO(yguyon): Implement
- if (!avifPNGRead(filename, image, requestedFormat, requestedDepth, flags, ignoreExif, outDepth)) {
+ if (!avifPNGRead(filename, image, requestedFormat, requestedDepth, flags, ignoreICC, ignoreExif, ignoreXMP, outDepth)) {
return AVIF_APP_FILE_FORMAT_UNKNOWN;
}
} else {
diff --git a/tests/data/paris_exif_at_end.png b/tests/data/paris_exif_at_end.png
deleted file mode 100644
index 53bfc08..0000000
--- a/tests/data/paris_exif_at_end.png
+++ /dev/null
Binary files differ
diff --git a/tests/data/paris_exif_xmp_icc.jpg b/tests/data/paris_exif_xmp_icc.jpg
index 805f9df..4b40002 100644
--- a/tests/data/paris_exif_xmp_icc.jpg
+++ b/tests/data/paris_exif_xmp_icc.jpg
Binary files differ
diff --git a/tests/data/paris_exif_xmp_icc.png b/tests/data/paris_exif_xmp_icc.png
deleted file mode 100644
index df1f02e..0000000
--- a/tests/data/paris_exif_xmp_icc.png
+++ /dev/null
Binary files differ
diff --git a/tests/data/paris_icc_exif_xmp.png b/tests/data/paris_icc_exif_xmp.png
new file mode 100644
index 0000000..1bee17d
--- /dev/null
+++ b/tests/data/paris_icc_exif_xmp.png
Binary files differ
diff --git a/tests/data/paris_icc_exif_xmp_at_end.png b/tests/data/paris_icc_exif_xmp_at_end.png
new file mode 100644
index 0000000..7ad09e3
--- /dev/null
+++ b/tests/data/paris_icc_exif_xmp_at_end.png
Binary files differ
diff --git a/tests/gtest/avifmetadatatest.cc b/tests/gtest/avifmetadatatest.cc
index 690e83c..d70c983 100644
--- a/tests/gtest/avifmetadatatest.cc
+++ b/tests/gtest/avifmetadatatest.cc
@@ -155,40 +155,36 @@
INSTANTIATE_TEST_SUITE_P(
PngNone, MetadataTest,
- Combine(Values("paris_exif_xmp_icc.png"), // zTXt iCCP iTXt IDAT
+ Combine(Values("paris_icc_exif_xmp.png"), // iCCP zTXt zTXt IDAT
/*use_icc=*/Values(false), /*use_exif=*/Values(false),
- /*use_xmp=*/Values(false),
- // ignoreICC is not yet implemented.
- /*expected_icc=*/Values(true),
+ /*use_xmp=*/Values(false), /*expected_icc=*/Values(false),
/*expected_exif=*/Values(false), /*expected_xmp=*/Values(false)));
INSTANTIATE_TEST_SUITE_P(
PngAll, MetadataTest,
- Combine(Values("paris_exif_xmp_icc.png"), /*use_icc=*/Values(true),
- /*use_exif=*/Values(true), /*use_xmp=*/Values(true),
- /*expected_icc=*/Values(true), /*expected_exif=*/Values(true),
- // XMP extraction is not yet implemented.
- /*expected_xmp=*/Values(false)));
+ Combine(Values("paris_icc_exif_xmp.png"), // iCCP zTXt zTXt IDAT
+ /*use_icc=*/Values(true), /*use_exif=*/Values(true),
+ /*use_xmp=*/Values(true), /*expected_icc=*/Values(true),
+ /*expected_exif=*/Values(true), /*expected_xmp=*/Values(true)));
INSTANTIATE_TEST_SUITE_P(
PngExifAtEnd, MetadataTest,
- Combine(Values("paris_exif_at_end.png"), // iCCP IDAT eXIf
+ Combine(Values("paris_icc_exif_xmp_at_end.png"), // iCCP IDAT eXIf tEXt
/*use_icc=*/Values(true), /*use_exif=*/Values(true),
/*use_xmp=*/Values(true), /*expected_icc=*/Values(true),
- /*expected_exif=*/Values(true), /*expected_xmp=*/Values(false)));
+ /*expected_exif=*/Values(true), /*expected_xmp=*/Values(true)));
INSTANTIATE_TEST_SUITE_P(
Jpeg, MetadataTest,
- Combine(Values("paris_exif_xmp_icc.jpg"), /*use_icc=*/Values(true),
- /*use_exif=*/Values(true), /*use_xmp=*/Values(true),
- /*expected_icc=*/Values(true),
- // Exif and XMP are not yet implemented.
- /*expected_exif=*/Values(false), /*expected_xmp=*/Values(false)));
+ Combine(Values("paris_exif_xmp_icc.jpg"), // APP1-Exif, APP1-XMP, APP2-ICC
+ /*use_icc=*/Values(true), /*use_exif=*/Values(true),
+ /*use_xmp=*/Values(true), /*expected_icc=*/Values(true),
+ /*expected_exif=*/Values(true), /*expected_xmp=*/Values(true)));
// Verify all parsers lead exactly to the same metadata bytes.
TEST(MetadataTest, Compare) {
- constexpr const char* kFileNames[] = {"paris_exif_at_end.png",
+ constexpr const char* kFileNames[] = {"paris_icc_exif_xmp.png",
"paris_exif_xmp_icc.jpg",
- "paris_exif_xmp_icc.png"};
+ "paris_icc_exif_xmp_at_end.png"};
avifImage* images[sizeof(kFileNames) / sizeof(kFileNames[0])];
avifImage** image_it = images;
for (const char* file_name : kFileNames) {
@@ -205,13 +201,8 @@
}
for (avifImage* image : images) {
- if (image->exif.size != 0) { // Not implemented for JPEG.
- EXPECT_TRUE(
- testutil::AreByteSequencesEqual(image->exif, images[0]->exif));
- }
- if (image->xmp.size != 0) { // Not implemented.
- EXPECT_TRUE(testutil::AreByteSequencesEqual(image->xmp, images[0]->xmp));
- }
+ EXPECT_TRUE(testutil::AreByteSequencesEqual(image->exif, images[0]->exif));
+ EXPECT_TRUE(testutil::AreByteSequencesEqual(image->xmp, images[0]->xmp));
EXPECT_TRUE(testutil::AreByteSequencesEqual(image->icc, images[0]->icc));
}
}
diff --git a/tests/test_cmd.sh b/tests/test_cmd.sh
index fde09a2..7f065d0 100755
--- a/tests/test_cmd.sh
+++ b/tests/test_cmd.sh
@@ -59,8 +59,21 @@
# Metadata test.
echo "Testing metadata enc/dec"
-"${AVIFENC}" "${TESTDATA_DIR}/paris_exif_xmp_icc.png" -o "${ENCODED_FILE}"
-"${AVIFENC}" "${TESTDATA_DIR}/paris_exif_xmp_icc.png" -o "${ENCODED_FILE_NO_METADATA}" --ignore-exif
+# PNG.
+"${AVIFENC}" "${TESTDATA_DIR}/paris_icc_exif_xmp.png" -o "${ENCODED_FILE}"
+"${AVIFENC}" "${TESTDATA_DIR}/paris_icc_exif_xmp.png" -o "${ENCODED_FILE_NO_METADATA}" --ignore-icc
+cmp "${ENCODED_FILE}" "${ENCODED_FILE_NO_METADATA}" && exit 1
+"${AVIFENC}" "${TESTDATA_DIR}/paris_icc_exif_xmp.png" -o "${ENCODED_FILE_NO_METADATA}" --ignore-exif
+cmp "${ENCODED_FILE}" "${ENCODED_FILE_NO_METADATA}" && exit 1
+"${AVIFENC}" "${TESTDATA_DIR}/paris_icc_exif_xmp.png" -o "${ENCODED_FILE_NO_METADATA}" --ignore-xmp
+cmp "${ENCODED_FILE}" "${ENCODED_FILE_NO_METADATA}" && exit 1
+# JPEG.
+"${AVIFENC}" "${TESTDATA_DIR}/paris_exif_xmp_icc.jpg" -o "${ENCODED_FILE}"
+"${AVIFENC}" "${TESTDATA_DIR}/paris_exif_xmp_icc.jpg" -o "${ENCODED_FILE_NO_METADATA}" --ignore-icc
+cmp "${ENCODED_FILE}" "${ENCODED_FILE_NO_METADATA}" && exit 1
+"${AVIFENC}" "${TESTDATA_DIR}/paris_exif_xmp_icc.jpg" -o "${ENCODED_FILE_NO_METADATA}" --ignore-exif
+cmp "${ENCODED_FILE}" "${ENCODED_FILE_NO_METADATA}" && exit 1
+"${AVIFENC}" "${TESTDATA_DIR}/paris_exif_xmp_icc.jpg" -o "${ENCODED_FILE_NO_METADATA}" --ignore-xmp
cmp "${ENCODED_FILE}" "${ENCODED_FILE_NO_METADATA}" && exit 1
# Argument parsing test with filenames starting with a dash.