Support pasp, clap, irot, imir metadata for encode/decode

Fixes: #41
diff --git a/src/read.c b/src/read.c
index 5088e8d..8a0d3e4 100644
--- a/src/read.c
+++ b/src/read.c
@@ -91,6 +91,14 @@
     avifColourInformationBox colr;
     avifBool av1CPresent;
     avifCodecConfigurationBox av1C;
+    avifBool paspPresent;
+    avifPixelAspectRatioBox pasp;
+    avifBool clapPresent;
+    avifCleanApertureBox clap;
+    avifBool irotPresent;
+    avifImageRotation irot;
+    avifBool imirPresent;
+    avifImageMirror imir;
     uint32_t thumbnailForID; // if non-zero, this item is a thumbnail for Item #{thumbnailForID}
     uint32_t auxForID;       // if non-zero, this item is an auxC plane for Item #{auxForID}
     uint32_t descForID;      // if non-zero, this item is a content description for Item #{descForID}
@@ -106,6 +114,10 @@
     avifAuxiliaryType auxC;
     avifColourInformationBox colr;
     avifCodecConfigurationBox av1C;
+    avifPixelAspectRatioBox pasp;
+    avifCleanApertureBox clap;
+    avifImageRotation irot;
+    avifImageMirror imir;
 } avifProperty;
 AVIF_ARRAY_DECLARE(avifPropertyArray, avifProperty, prop);
 
@@ -890,6 +902,58 @@
     return avifParseAV1CodecConfigurationBox(raw, rawLen, &data->properties.prop[propertyIndex].av1C);
 }
 
+static avifBool avifParsePixelAspectRatioBoxProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+{
+    BEGIN_STREAM(s, raw, rawLen);
+
+    avifPixelAspectRatioBox * pasp = &data->properties.prop[propertyIndex].pasp;
+    CHECK(avifROStreamReadU32(&s, &pasp->hSpacing)); // unsigned int(32) hSpacing;
+    CHECK(avifROStreamReadU32(&s, &pasp->vSpacing)); // unsigned int(32) vSpacing;
+    return AVIF_TRUE;
+}
+
+static avifBool avifParseCleanApertureBoxProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+{
+    BEGIN_STREAM(s, raw, rawLen);
+
+    avifCleanApertureBox * clap = &data->properties.prop[propertyIndex].clap;
+    CHECK(avifROStreamReadU32(&s, &clap->widthN));    // unsigned int(32) cleanApertureWidthN;
+    CHECK(avifROStreamReadU32(&s, &clap->widthD));    // unsigned int(32) cleanApertureWidthD;
+    CHECK(avifROStreamReadU32(&s, &clap->heightN));   // unsigned int(32) cleanApertureHeightN;
+    CHECK(avifROStreamReadU32(&s, &clap->heightD));   // unsigned int(32) cleanApertureHeightD;
+    CHECK(avifROStreamReadU32(&s, &clap->horizOffN)); // unsigned int(32) horizOffN;
+    CHECK(avifROStreamReadU32(&s, &clap->horizOffD)); // unsigned int(32) horizOffD;
+    CHECK(avifROStreamReadU32(&s, &clap->vertOffN));  // unsigned int(32) vertOffN;
+    CHECK(avifROStreamReadU32(&s, &clap->vertOffD));  // unsigned int(32) vertOffD;
+    return AVIF_TRUE;
+}
+
+static avifBool avifParseImageRotationProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+{
+    BEGIN_STREAM(s, raw, rawLen);
+
+    avifImageRotation * irot = &data->properties.prop[propertyIndex].irot;
+    CHECK(avifROStreamRead(&s, &irot->angle, 1)); // unsigned int (6) reserved = 0; unsigned int (2) angle;
+    if ((irot->angle & 0xfc) != 0) {
+        // reserved bits must be 0
+        return AVIF_FALSE;
+    }
+    return AVIF_TRUE;
+}
+
+static avifBool avifParseImageMirrorProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+{
+    BEGIN_STREAM(s, raw, rawLen);
+
+    avifImageMirror * imir = &data->properties.prop[propertyIndex].imir;
+    CHECK(avifROStreamRead(&s, &imir->axis, 1)); // unsigned int (7) reserved = 0; unsigned int (1) axis;
+    if ((imir->axis & 0xfe) != 0) {
+        // reserved bits must be 0
+        return AVIF_FALSE;
+    }
+    return AVIF_TRUE;
+}
+
 static avifBool avifParseItemPropertyContainerBox(avifData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
@@ -912,6 +976,18 @@
         if (!memcmp(header.type, "av1C", 4)) {
             CHECK(avifParseAV1CodecConfigurationBoxProperty(data, avifROStreamCurrent(&s), header.size, propertyIndex));
         }
+        if (!memcmp(header.type, "pasp", 4)) {
+            CHECK(avifParsePixelAspectRatioBoxProperty(data, avifROStreamCurrent(&s), header.size, propertyIndex));
+        }
+        if (!memcmp(header.type, "clap", 4)) {
+            CHECK(avifParseCleanApertureBoxProperty(data, avifROStreamCurrent(&s), header.size, propertyIndex));
+        }
+        if (!memcmp(header.type, "irot", 4)) {
+            CHECK(avifParseImageRotationProperty(data, avifROStreamCurrent(&s), header.size, propertyIndex));
+        }
+        if (!memcmp(header.type, "imir", 4)) {
+            CHECK(avifParseImageMirrorProperty(data, avifROStreamCurrent(&s), header.size, propertyIndex));
+        }
 
         CHECK(avifROStreamSkip(&s, header.size));
     }
@@ -983,6 +1059,18 @@
             } else if (!memcmp(prop->type, "av1C", 4)) {
                 item->av1CPresent = AVIF_TRUE;
                 memcpy(&item->av1C, &prop->av1C, sizeof(avifCodecConfigurationBox));
+            } else if (!memcmp(prop->type, "pasp", 4)) {
+                item->paspPresent = AVIF_TRUE;
+                memcpy(&item->pasp, &prop->pasp, sizeof(avifPixelAspectRatioBox));
+            } else if (!memcmp(prop->type, "clap", 4)) {
+                item->clapPresent = AVIF_TRUE;
+                memcpy(&item->clap, &prop->clap, sizeof(avifCleanApertureBox));
+            } else if (!memcmp(prop->type, "irot", 4)) {
+                item->irotPresent = AVIF_TRUE;
+                memcpy(&item->irot, &prop->irot, sizeof(avifImageRotation));
+            } else if (!memcmp(prop->type, "imir", 4)) {
+                item->imirPresent = AVIF_TRUE;
+                memcpy(&item->imir, &prop->imir, sizeof(avifImageMirror));
             }
         }
     }
@@ -1793,9 +1881,12 @@
     memset(&data->colorGrid, 0, sizeof(data->colorGrid));
     memset(&data->alphaGrid, 0, sizeof(data->alphaGrid));
     avifDataClearTiles(data);
+
+    // Prepare / cleanup decoded image state
     if (!decoder->image) {
         decoder->image = avifImageCreateEmpty();
     }
+    decoder->image->transformFlags = AVIF_TRANSFORM_NONE;
 
     memset(&decoder->ioStats, 0, sizeof(decoder->ioStats));
 
@@ -2055,6 +2146,24 @@
             }
         }
 
+        // Transformations
+        if (colorOBUItem->paspPresent) {
+            decoder->image->transformFlags |= AVIF_TRANSFORM_PASP;
+            memcpy(&decoder->image->pasp, &colorOBUItem->pasp, sizeof(avifPixelAspectRatioBox));
+        }
+        if (colorOBUItem->clapPresent) {
+            decoder->image->transformFlags |= AVIF_TRANSFORM_CLAP;
+            memcpy(&decoder->image->clap, &colorOBUItem->clap, sizeof(avifCleanApertureBox));
+        }
+        if (colorOBUItem->irotPresent) {
+            decoder->image->transformFlags |= AVIF_TRANSFORM_IROT;
+            memcpy(&decoder->image->irot, &colorOBUItem->irot, sizeof(avifImageRotation));
+        }
+        if (colorOBUItem->imirPresent) {
+            decoder->image->transformFlags |= AVIF_TRANSFORM_IMIR;
+            memcpy(&decoder->image->imir, &colorOBUItem->imir, sizeof(avifImageMirror));
+        }
+
         if (exifData.data && exifData.size) {
             avifImageSetMetadataExif(decoder->image, exifData.data, exifData.size);
         }