Remove Exif orientation after saving it to irot/imir when reading JPEGs.
Currently we read the Exif Orientation tag and translate it to irot/imir,
but also leave the Exif as is.
However, MIAF says that AVIF files should not contain Exif transformations.
Fixes https://github.com/AOMediaCodec/libavif/issues/3078.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1fdcd9d..f123062 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@
* Add --sato flag to avifdec to enable Sample Transforms support at decoding.
* Add --grid option to avifgainmaputil.
* Apply clean aperture crop, rotation and mirror when decoding to PNG or JPEG.
+ Remove orientation information from Exif if present.
### Changed since 1.3.0
@@ -61,6 +62,8 @@
libaom v3.13.0 or later.
* Converting an image containing a gain map using avifenc with the --grid flag
now also splits the gain map into a grid.
+* In avifenc, set Exif orientation to 1 (no transformation) when converting
+ JPEGs to Avif.
### Removed since 1.3.0
diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c
index 5491186..dc23cea 100644
--- a/apps/shared/avifjpeg.c
+++ b/apps/shared/avifjpeg.c
@@ -1394,15 +1394,22 @@
goto cleanup;
}
- // Exif orientation, if any, is imported to avif->irot/imir and kept in avif->exif.
- // libheif has the same behavior, see
- // https://github.com/strukturag/libheif/blob/ea78603d8e47096606813d221725621306789ff2/examples/heif_enc.cc#L403
+ // Exif orientation, if any, is imported to avif->irot/imir, and the Exif data is saved to avif->exif.
if (avifImageSetMetadataExif(avif,
marker->data + AVIF_JPEG_EXIF_HEADER_LENGTH,
marker->data_length - AVIF_JPEG_EXIF_HEADER_LENGTH) != AVIF_RESULT_OK) {
fprintf(stderr, "Setting Exif metadata failed: %s (out of memory)\n", inputFilename);
goto cleanup;
}
+ // Set the Exif orientation to 1 (no transformation).
+ // ISO/IEC 23000-22:2024 (MIAF), Section 7.3.10.1:
+ // There should be no image transformations expressed by Exif (rotation,
+ // mirroring, etc.) indicated in the Exif metadata, in files encoded according
+ // to this document.
+ // Do not check for errors, it's a "should" so ok to do on a best-effort basis.
+ // Moreover it should only fail if the Exif is marlformed or there is no orientation
+ // tag to begin with.
+ (void)avifSetExifOrientation(&avif->exif, 1);
found = AVIF_TRUE;
}
}
diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c
index 5476b5e..39a4b29 100644
--- a/apps/shared/avifutil.c
+++ b/apps/shared/avifutil.c
@@ -622,7 +622,7 @@
}
}
if (gridSplitImage->exif.size > 0) {
- const avifResult result = avifImageSetMetadataExif(firstCell, gridSplitImage->exif.data, gridSplitImage->exif.size);
+ const avifResult result = avifRWDataSet(&firstCell->exif, gridSplitImage->exif.data, gridSplitImage->exif.size);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "ERROR: Failed to set Exif metadata on grid cell: %s\n", avifResultToString(result));
return AVIF_FALSE;
diff --git a/tests/gtest/avifmetadatatest.cc b/tests/gtest/avifmetadatatest.cc
index c7f60d1..e54ed7a 100644
--- a/tests/gtest/avifmetadatatest.cc
+++ b/tests/gtest/avifmetadatatest.cc
@@ -246,11 +246,21 @@
testutil::ReadImage(data_path, "paris_exif_orientation_5.jpg");
ASSERT_NE(image, nullptr);
// The Exif metadata contains orientation information: 5.
+ // When reading the JPEG file, the Exif orientation is set to 1.
EXPECT_GT(image->exif.size, 0u);
EXPECT_EQ(image->transformFlags & (AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR),
avifTransformFlags{AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR});
EXPECT_EQ(image->irot.angle, 1u);
EXPECT_EQ(image->imir.axis, 0u);
+ testutil::AvifRwData originalReadExif;
+ ASSERT_EQ(
+ avifRWDataSet(&originalReadExif, image->exif.data, image->exif.size),
+ AVIF_RESULT_OK);
+ // For testing purposes, set back the orientation.
+ ASSERT_EQ(avifSetExifOrientation(&image->exif, 5), AVIF_RESULT_OK);
+ // The two Exifs are different, showing that the Orientation was indeed
+ // modified when reading.
+ EXPECT_FALSE(testutil::AreByteSequencesEqual(originalReadExif, image->exif));
const testutil::AvifRwData encoded =
testutil::Encode(image.get(), AVIF_SPEED_FASTEST);
@@ -313,6 +323,15 @@
EXPECT_GT(image->exif.size, 0u);
image->transformFlags = AVIF_TRANSFORM_IMIR;
image->imir.axis = 1;
+ testutil::AvifRwData originalReadExif;
+ ASSERT_EQ(
+ avifRWDataSet(&originalReadExif, image->exif.data, image->exif.size),
+ AVIF_RESULT_OK);
+ // For testing purposes, set back the orientation.
+ ASSERT_EQ(avifSetExifOrientation(&image->exif, 5), AVIF_RESULT_OK);
+ // The two Exifs are different, showing that the Orientation was indeed
+ // modified when reading.
+ EXPECT_FALSE(testutil::AreByteSequencesEqual(originalReadExif, image->exif));
const testutil::AvifRwData encoded =
testutil::Encode(image.get(), AVIF_SPEED_FASTEST);