Add avifImageExtractExifOrientationToIrotImir()

Move avifGetExifTiffHeaderOffset() from write.c to exif.c.
Add tests in avifmetadatatest.
Add avifenc change to CHANGELOG.md.
diff --git a/src/exif.c b/src/exif.c
new file mode 100644
index 0000000..fc73f8c
--- /dev/null
+++ b/src/exif.c
@@ -0,0 +1,119 @@
+// Copyright 2022 Google LLC. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "avif/internal.h"
+
+#include <stdint.h>
+#include <string.h>
+
+avifResult avifGetExifTiffHeaderOffset(const avifRWData * exif, uint32_t * offset)
+{
+    const uint8_t tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
+    const uint8_t tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
+    for (*offset = 0; *offset + 4 < exif->size; ++*offset) {
+        if (!memcmp(&exif->data[*offset], tiffHeaderBE, 4) || !memcmp(&exif->data[*offset], tiffHeaderLE, 4)) {
+            return AVIF_RESULT_OK;
+        }
+    }
+    // Couldn't find the TIFF header
+    return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
+}
+
+avifResult avifImageExtractExifOrientationToIrotImir(avifImage * image)
+{
+    const avifTransformFlags otherFlags = image->transformFlags & ~(AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR);
+    uint32_t offset;
+    const avifResult result = avifGetExifTiffHeaderOffset(&image->exif, &offset);
+    if (result != AVIF_RESULT_OK) {
+        // Couldn't find the TIFF header
+        return result;
+    }
+
+    avifROData raw = { image->exif.data + offset, image->exif.size - offset };
+    const avifBool littleEndian = (raw.data[0] == 'I');
+    avifROStream stream;
+    avifROStreamStart(&stream, &raw, NULL, NULL);
+
+    // TIFF Header
+    uint32_t offsetTo0thIfd;
+    if (!avifROStreamSkip(&stream, 4) || // Skip tiffHeaderBE or tiffHeaderLE.
+        !avifROStreamReadU32Endianness(&stream, &offsetTo0thIfd, littleEndian)) {
+        return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
+    }
+
+    avifROStreamSetOffset(&stream, offsetTo0thIfd);
+    uint16_t fieldCount;
+    if (!avifROStreamReadU16Endianness(&stream, &fieldCount, littleEndian)) {
+        return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
+    }
+    for (uint16_t field = 0; field < fieldCount; ++field) { // for each field interoperability array
+        uint16_t tag;
+        uint16_t type;
+        uint32_t count;
+        uint16_t firstHalfOfValueOffset;
+        if (!avifROStreamReadU16Endianness(&stream, &tag, littleEndian) || !avifROStreamReadU16Endianness(&stream, &type, littleEndian) ||
+            !avifROStreamReadU32Endianness(&stream, &count, littleEndian) ||
+            !avifROStreamReadU16Endianness(&stream, &firstHalfOfValueOffset, littleEndian) || !avifROStreamSkip(&stream, 2)) {
+            return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
+        }
+        // Orientation attribute according to JEITA CP-3451C section 4.6.4 (TIFF Rev. 6.0 Attribute Information):
+        const uint16_t shortType = 0x03;
+        if (tag == 0x0112 && type == shortType && count == 0x01) {
+            // Mapping from Exif orientation as defined in JEITA CP-3451C section 4.6.4.A Orientation
+            // to irot and imir boxes as defined in HEIF ISO/IEC 28002-12:2021 sections 6.5.10 and 6.5.12.
+            switch (firstHalfOfValueOffset) {
+                case 1: // The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
+                    image->transformFlags = otherFlags;
+                    image->irot.angle = 0; // ignored
+                    image->imir.mode = 0;  // ignored
+                    return AVIF_RESULT_OK;
+                case 2: // The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
+                    image->transformFlags = otherFlags | AVIF_TRANSFORM_IMIR;
+                    image->irot.angle = 0; // ignored
+                    image->imir.mode = 1;
+                    return AVIF_RESULT_OK;
+                case 3: // The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
+                    image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT;
+                    image->irot.angle = 2;
+                    image->imir.mode = 0; // ignored
+                    return AVIF_RESULT_OK;
+                case 4: // The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
+                    image->transformFlags = otherFlags | AVIF_TRANSFORM_IMIR;
+                    image->irot.angle = 0; // ignored
+                    image->imir.mode = 0;
+                    return AVIF_RESULT_OK;
+                case 5: // The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
+                    image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR;
+                    image->irot.angle = 1; // applied before imir according to MIAF spec ISO/IEC 28002-12:2021 - section 7.3.6.7
+                    image->imir.mode = 0;
+                    return AVIF_RESULT_OK;
+                case 6: // The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
+                    image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT;
+                    image->irot.angle = 3;
+                    image->imir.mode = 0; // ignored
+                    return AVIF_RESULT_OK;
+                case 7: // The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
+                    image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR;
+                    image->irot.angle = 3; // applied before imir according to MIAF spec ISO/IEC 28002-12:2021 - section 7.3.6.7
+                    image->imir.mode = 0;
+                    return AVIF_RESULT_OK;
+                case 8: // The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
+                    image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT;
+                    image->irot.angle = 1;
+                    image->imir.mode = 0; // ignored
+                    return AVIF_RESULT_OK;
+                default: // reserved
+                    break;
+            }
+        }
+    }
+    // Orientation is in the 0th IFD, so no need to parse the following ones.
+
+    // The orientation tag is not mandatory (only recommended) according to JEITA CP-3451C section 4.6.8.A.
+    // The default value is 1 if the orientation tag is missing, meaning:
+    //   The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
+    image->transformFlags = otherFlags;
+    image->irot.angle = 0; // ignored
+    image->imir.mode = 0;  // ignored
+    return AVIF_RESULT_OK;
+}
diff --git a/src/stream.c b/src/stream.c
index a0cf021..a5935b4 100644
--- a/src/stream.c
+++ b/src/stream.c
@@ -108,6 +108,13 @@
     return AVIF_TRUE;
 }
 
+avifBool avifROStreamReadU16Endianness(avifROStream * stream, uint16_t * v, avifBool littleEndian)
+{
+    AVIF_CHECK(avifROStreamRead(stream, (uint8_t *)v, sizeof(uint16_t)));
+    *v = littleEndian ? avifCTOHS(*v) : avifNTOHS(*v);
+    return AVIF_TRUE;
+}
+
 avifBool avifROStreamReadU32(avifROStream * stream, uint32_t * v)
 {
     AVIF_CHECK(avifROStreamRead(stream, (uint8_t *)v, sizeof(uint32_t)));
@@ -115,6 +122,13 @@
     return AVIF_TRUE;
 }
 
+avifBool avifROStreamReadU32Endianness(avifROStream * stream, uint32_t * v, avifBool littleEndian)
+{
+    AVIF_CHECK(avifROStreamRead(stream, (uint8_t *)v, sizeof(uint32_t)));
+    *v = littleEndian ? avifCTOHL(*v) : avifNTOHL(*v);
+    return AVIF_TRUE;
+}
+
 avifBool avifROStreamReadU64(avifROStream * stream, uint64_t * v)
 {
     AVIF_CHECK(avifROStreamRead(stream, (uint8_t *)v, sizeof(uint64_t)));
diff --git a/src/utils.c b/src/utils.c
index 2e5cb7c..697cb0e 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -29,6 +29,12 @@
     return (uint16_t)((data[1] << 0) | (data[0] << 8));
 }
 
+uint16_t avifCTOHS(uint16_t s)
+{
+    const uint8_t * data = (const uint8_t *)&s;
+    return (uint16_t)((data[0] << 0) | (data[1] << 8));
+}
+
 uint32_t avifHTONL(uint32_t l)
 {
     uint32_t result;
@@ -46,6 +52,12 @@
     return ((uint32_t)data[3] << 0) | ((uint32_t)data[2] << 8) | ((uint32_t)data[1] << 16) | ((uint32_t)data[0] << 24);
 }
 
+uint32_t avifCTOHL(uint32_t l)
+{
+    const uint8_t * data = (const uint8_t *)&l;
+    return ((uint32_t)data[0] << 0) | ((uint32_t)data[1] << 8) | ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24);
+}
+
 uint64_t avifHTON64(uint64_t l)
 {
     uint64_t result;
diff --git a/src/write.c b/src/write.c
index 12438ac..8f0bb30 100644
--- a/src/write.c
+++ b/src/write.c
@@ -690,27 +690,11 @@
 
 static avifResult avifEncoderDataCreateExifItem(avifEncoderData * data, const avifRWData * exif)
 {
-    // Validate Exif payload (if any) and find TIFF header offset
-    uint32_t exifTiffHeaderOffset = 0;
-    if (exif->size < 4) {
-        // Can't even fit the TIFF header, something is wrong
-        return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
-    }
-
-    const uint8_t tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
-    const uint8_t tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
-    for (; exifTiffHeaderOffset < (exif->size - 4); ++exifTiffHeaderOffset) {
-        if (!memcmp(&exif->data[exifTiffHeaderOffset], tiffHeaderBE, sizeof(tiffHeaderBE))) {
-            break;
-        }
-        if (!memcmp(&exif->data[exifTiffHeaderOffset], tiffHeaderLE, sizeof(tiffHeaderLE))) {
-            break;
-        }
-    }
-
-    if (exifTiffHeaderOffset >= exif->size - 4) {
+    uint32_t exifTiffHeaderOffset;
+    const avifResult result = avifGetExifTiffHeaderOffset(exif, &exifTiffHeaderOffset);
+    if (result != AVIF_RESULT_OK) {
         // Couldn't find the TIFF header
-        return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
+        return result;
     }
 
     avifEncoderItem * exifItem = avifEncoderDataCreateItem(data, "Exif", "Exif", 5, 0);