Support pasp, clap, irot, imir metadata for encode/decode
Fixes: #41
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f013e5d..0ab20d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@
- Update default rav1e version to v0.3.1
- `avifRGBImage` structure and associated routines (BREAKING CHANGE)
- avifImage alphaRange support
+- Support pasp, clap, irot, imir metadata for encode/decode
### Changed
- Large RGB conversion refactor (BREAKING CHANGE), see README for new examples
diff --git a/apps/avifenc.c b/apps/avifenc.c
index fe428bd..9c4fa46 100644
--- a/apps/avifenc.c
+++ b/apps/avifenc.c
@@ -21,31 +21,35 @@
{
printf("Syntax: avifenc [options] input.y4m output.avif\n");
printf("Options:\n");
- printf(" -h,--help : Show syntax help\n");
- printf(" -j,--jobs J : Number of jobs (worker threads, default: 1)\n");
- printf(" -n,--nclx P/T/M/R : Set nclx colr box values (4 raw numbers)\n");
- printf(" P = enum avifNclxColourPrimaries\n");
- printf(" T = enum avifNclxTransferCharacteristics\n");
- printf(" M = enum avifNclxMatrixCoefficients\n");
- printf(" R = avifNclxRangeFlag (any nonzero value becomes AVIF_NCLX_FULL_RANGE)\n");
- printf(" --min Q : Set min quantizer for color (%d-%d, where %d is lossless)\n",
+ printf(" -h,--help : Show syntax help\n");
+ printf(" -j,--jobs J : Number of jobs (worker threads, default: 1)\n");
+ printf(" -n,--nclx P/T/M/R : Set nclx colr box values (4 raw numbers)\n");
+ printf(" P = enum avifNclxColourPrimaries\n");
+ printf(" T = enum avifNclxTransferCharacteristics\n");
+ printf(" M = enum avifNclxMatrixCoefficients\n");
+ printf(" R = avifNclxRangeFlag (any nonzero value becomes AVIF_NCLX_FULL_RANGE)\n");
+ printf(" --min Q : Set min quantizer for color (%d-%d, where %d is lossless)\n",
AVIF_QUANTIZER_BEST_QUALITY,
AVIF_QUANTIZER_WORST_QUALITY,
AVIF_QUANTIZER_LOSSLESS);
- printf(" --max Q : Set max quantizer for color (%d-%d, where %d is lossless)\n",
+ printf(" --max Q : Set max quantizer for color (%d-%d, where %d is lossless)\n",
AVIF_QUANTIZER_BEST_QUALITY,
AVIF_QUANTIZER_WORST_QUALITY,
AVIF_QUANTIZER_LOSSLESS);
- printf(" --minalpha Q : Set min quantizer for alpha (%d-%d, where %d is lossless)\n",
+ printf(" --minalpha Q : Set min quantizer for alpha (%d-%d, where %d is lossless)\n",
AVIF_QUANTIZER_BEST_QUALITY,
AVIF_QUANTIZER_WORST_QUALITY,
AVIF_QUANTIZER_LOSSLESS);
- printf(" --maxalpha Q : Set max quantizer for alpha (%d-%d, where %d is lossless)\n",
+ printf(" --maxalpha Q : Set max quantizer for alpha (%d-%d, where %d is lossless)\n",
AVIF_QUANTIZER_BEST_QUALITY,
AVIF_QUANTIZER_WORST_QUALITY,
AVIF_QUANTIZER_LOSSLESS);
- printf(" -s,--speed S : Encoder speed (%d-%d, slowest to fastest)\n", AVIF_SPEED_SLOWEST, AVIF_SPEED_FASTEST);
- printf(" -c,--codec C : AV1 codec to use (choose from versions list below)\n");
+ printf(" -s,--speed S : Encoder speed (%d-%d, slowest to fastest)\n", AVIF_SPEED_SLOWEST, AVIF_SPEED_FASTEST);
+ printf(" -c,--codec C : AV1 codec to use (choose from versions list below)\n");
+ printf(" --pasp H,V : Add pasp property (aspect ratio). H=horizontal spacing, V=vertical spacing\n");
+ printf(" --clap WN,WD,HN,HD,HON,HOD,VON,VOD: Add clap property (clean aperture). Width, Height, HOffset, VOffset (in num/denom pairs)\n");
+ printf(" --irot ANGLE : Add irot property (rotation). [0-3], makes (90 * ANGLE) degree rotation anti-clockwise\n");
+ printf(" --imir AXIS : Add imir property (mirroring). 0=vertical, 1=horizontal\n");
printf("\n");
avifPrintVersions();
return 0;
@@ -98,6 +102,27 @@
return AVIF_FALSE;
}
+// Returns the count of uint32_t (up to 8)
+static int parseU32List(uint32_t output[8], const char * arg)
+{
+ char buffer[128];
+ strncpy(buffer, arg, 127);
+ buffer[127] = 0;
+
+ int index = 0;
+ char * token = strtok(buffer, ",");
+ while (token != NULL) {
+ output[index] = (uint32_t)atoi(token);
+ ++index;
+ if (index >= 8) {
+ break;
+ }
+
+ token = strtok(NULL, ",");
+ }
+ return index;
+}
+
int main(int argc, char * argv[])
{
const char * inputFilename = NULL;
@@ -113,6 +138,12 @@
int minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
int maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
int speed = AVIF_SPEED_DEFAULT;
+ int paspCount = 0;
+ uint32_t paspValues[8]; // only the first two are used
+ int clapCount = 0;
+ uint32_t clapValues[8];
+ uint8_t irotAngle = 0xff; // sentinel value indicating "unused"
+ uint8_t imirAxis = 0xff; // sentinel value indicating "unused"
avifCodecChoice codecChoice = AVIF_CODEC_CHOICE_AUTO;
avifBool nclxSet = AVIF_FALSE;
avifEncoder * encoder = NULL;
@@ -196,6 +227,34 @@
return 1;
}
}
+ } else if (!strcmp(arg, "--pasp")) {
+ NEXTARG();
+ paspCount = parseU32List(paspValues, arg);
+ if (paspCount != 2) {
+ fprintf(stderr, "ERROR: Invalid pasp values: %s\n", arg);
+ return 1;
+ }
+ } else if (!strcmp(arg, "--clap")) {
+ NEXTARG();
+ clapCount = parseU32List(clapValues, arg);
+ if (clapCount != 8) {
+ fprintf(stderr, "ERROR: Invalid clap values: %s\n", arg);
+ return 1;
+ }
+ } else if (!strcmp(arg, "--irot")) {
+ NEXTARG();
+ irotAngle = (uint8_t)atoi(arg);
+ if (irotAngle > 3) {
+ fprintf(stderr, "ERROR: Invalid irot angle: %s\n", arg);
+ return 1;
+ }
+ } else if (!strcmp(arg, "--imir")) {
+ NEXTARG();
+ imirAxis = (uint8_t)atoi(arg);
+ if (imirAxis > 1) {
+ fprintf(stderr, "ERROR: Invalid imir axis: %s\n", arg);
+ return 1;
+ }
} else {
// Positional argument
if (!inputFilename) {
@@ -230,6 +289,31 @@
memcpy(&avif->nclx, &nclx, sizeof(nclx));
}
+ if (paspCount == 2) {
+ avif->transformFlags |= AVIF_TRANSFORM_PASP;
+ avif->pasp.hSpacing = paspValues[0];
+ avif->pasp.vSpacing = paspValues[1];
+ }
+ if (clapCount == 8) {
+ avif->transformFlags |= AVIF_TRANSFORM_CLAP;
+ avif->clap.widthN = clapValues[0];
+ avif->clap.widthD = clapValues[1];
+ avif->clap.heightN = clapValues[2];
+ avif->clap.heightD = clapValues[3];
+ avif->clap.horizOffN = clapValues[4];
+ avif->clap.horizOffD = clapValues[5];
+ avif->clap.vertOffN = clapValues[6];
+ avif->clap.vertOffD = clapValues[7];
+ }
+ if (irotAngle != 0xff) {
+ avif->transformFlags |= AVIF_TRANSFORM_IROT;
+ avif->irot.angle = irotAngle;
+ }
+ if (imirAxis != 0xff) {
+ avif->transformFlags |= AVIF_TRANSFORM_IMIR;
+ avif->imir.axis = imirAxis;
+ }
+
printf("AVIF to be written:\n");
avifImageDump(avif);
diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c
index 9df8b96..3aa3cd2 100644
--- a/apps/shared/avifutil.c
+++ b/apps/shared/avifutil.c
@@ -4,28 +4,56 @@
#include "avifutil.h"
#include <stdio.h>
+#include <string.h>
void avifImageDump(avifImage * avif)
{
- printf(" * Resolution : %dx%d\n", avif->width, avif->height);
- printf(" * Bit Depth : %d\n", avif->depth);
- printf(" * Format : %s\n", avifPixelFormatToString(avif->yuvFormat));
- printf(" * Alpha : %s\n", (avif->alphaPlane && (avif->alphaRowBytes > 0)) ? "Present" : "Absent");
+ printf(" * Resolution : %dx%d\n", avif->width, avif->height);
+ printf(" * Bit Depth : %d\n", avif->depth);
+ printf(" * Format : %s\n", avifPixelFormatToString(avif->yuvFormat));
+ printf(" * Alpha : %s\n", (avif->alphaPlane && (avif->alphaRowBytes > 0)) ? "Present" : "Absent");
switch (avif->profileFormat) {
case AVIF_PROFILE_FORMAT_NONE:
- printf(" * Color Profile: None\n");
+ printf(" * Color Profile : None\n");
break;
case AVIF_PROFILE_FORMAT_ICC:
- printf(" * Color Profile: ICC (%zu bytes)\n", avif->icc.size);
+ printf(" * Color Profile : ICC (%zu bytes)\n", avif->icc.size);
break;
case AVIF_PROFILE_FORMAT_NCLX:
- printf(" * Color Profile: nclx - P:%d / T:%d / M:%d / R:%s\n",
+ printf(" * Color Profile : nclx - P:%d / T:%d / M:%d / R:%s\n",
avif->nclx.colourPrimaries,
avif->nclx.transferCharacteristics,
avif->nclx.matrixCoefficients,
- avif->nclx.fullRangeFlag ? "full" : "limited");
+ avif->nclx.fullRangeFlag ? "Full" : "Limited");
break;
}
+
+ if (avif->transformFlags == AVIF_TRANSFORM_NONE) {
+ printf(" * Transformations: None\n");
+ } else {
+ printf(" * Transformations:\n");
+
+ if (avif->transformFlags & AVIF_TRANSFORM_PASP) {
+ printf(" * pasp (Aspect Ratio) : %d/%d\n", (int)avif->pasp.hSpacing, (int)avif->pasp.vSpacing);
+ }
+ if (avif->transformFlags & AVIF_TRANSFORM_CLAP) {
+ printf(" * clap (Clean Aperture): W: %d/%d, H: %d/%d, hOff: %d/%d, vOff: %d/%d\n",
+ (int)avif->clap.widthN,
+ (int)avif->clap.widthD,
+ (int)avif->clap.heightN,
+ (int)avif->clap.heightD,
+ (int)avif->clap.horizOffN,
+ (int)avif->clap.horizOffD,
+ (int)avif->clap.vertOffN,
+ (int)avif->clap.vertOffD);
+ }
+ if (avif->transformFlags & AVIF_TRANSFORM_IROT) {
+ printf(" * irot (Rotation) : %u\n", avif->irot.angle);
+ }
+ if (avif->transformFlags & AVIF_TRANSFORM_IMIR) {
+ printf(" * imir (Mirror) : %u (%s)\n", avif->imir.axis, (avif->imir.axis == 0) ? "Vertical" : "Horizontal");
+ }
+ }
printf("\n");
}
diff --git a/include/avif/avif.h b/include/avif/avif.h
index fb05654..e2e4cbb 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -274,6 +274,65 @@
} avifProfileFormat;
// ---------------------------------------------------------------------------
+// Optional transformation structs
+
+typedef enum avifTransformationFlags
+{
+ AVIF_TRANSFORM_NONE = 0,
+
+ AVIF_TRANSFORM_PASP = (1 << 0),
+ AVIF_TRANSFORM_CLAP = (1 << 1),
+ AVIF_TRANSFORM_IROT = (1 << 2),
+ AVIF_TRANSFORM_IMIR = (1 << 3)
+} avifTransformationFlags;
+
+typedef struct avifPixelAspectRatioBox
+{
+ // 'pasp' from ISO/IEC 14496-12:2015 12.1.4.3
+
+ // define the relative width and height of a pixel
+ uint32_t hSpacing;
+ uint32_t vSpacing;
+} avifPixelAspectRatioBox;
+
+typedef struct avifCleanApertureBox
+{
+ // 'clap' from ISO/IEC 14496-12:2015 12.1.4.3
+
+ // a fractional number which defines the exact clean aperture width, in counted pixels, of the video image
+ uint32_t widthN;
+ uint32_t widthD;
+
+ // a fractional number which defines the exact clean aperture height, in counted pixels, of the video image
+ uint32_t heightN;
+ uint32_t heightD;
+
+ // a fractional number which defines the horizontal offset of clean aperture centre minus (width‐1)/2. Typically 0.
+ uint32_t horizOffN;
+ uint32_t horizOffD;
+
+ // a fractional number which defines the vertical offset of clean aperture centre minus (height‐1)/2. Typically 0.
+ uint32_t vertOffN;
+ uint32_t vertOffD;
+} avifCleanApertureBox;
+
+typedef struct avifImageRotation
+{
+ // 'irot' from ISO/IEC 23008-12:2017 6.5.10
+
+ // angle * 90 specifies the angle (in anti-clockwise direction) in units of degrees.
+ uint8_t angle; // legal values: [0-3]
+} avifImageRotation;
+
+typedef struct avifImageMirror
+{
+ // 'imir' from ISO/IEC 23008-12:2017 6.5.12
+
+ // axis specifies a vertical (axis = 0) or horizontal (axis = 1) axis for the mirroring operation.
+ uint8_t axis; // legal values: [0, 1]
+} avifImageMirror;
+
+// ---------------------------------------------------------------------------
// avifImage
typedef struct avifImage
@@ -299,6 +358,20 @@
avifRWData icc;
avifNclxColorProfile nclx;
+ // Transformations - These metadata values are encoded/decoded when transformFlags are set
+ // appropriately, but do not impact/adjust the actual pixel buffers used (images won't be
+ // pre-cropped or mirrored upon decode). Basic explanations from the standards are offered in
+ // comments above, but for detailed explanations, please refer to the HEIF standard (ISO/IEC
+ // 23008-12:2017) and the BMFF standard (ISO/IEC 14496-12:2015).
+ //
+ // To encode any of these boxes, set the values in the associated box, then enable the flag in
+ // transformFlags. On decode, only honor the values in boxes with the associated transform flag set.
+ uint32_t transformFlags;
+ avifPixelAspectRatioBox pasp;
+ avifCleanApertureBox clap;
+ avifImageRotation irot;
+ avifImageMirror imir;
+
// Metadata - set with avifImageSetMetadata*() before write, check .size>0 for existence after read
avifRWData exif;
avifRWData xmp;
diff --git a/src/avif.c b/src/avif.c
index 9894461..70a748c 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -132,6 +132,12 @@
dstImage->yuvRange = srcImage->yuvRange;
dstImage->alphaRange = srcImage->alphaRange;
+ dstImage->transformFlags = srcImage->transformFlags;
+ memcpy(&dstImage->pasp, &srcImage->pasp, sizeof(dstImage->pasp));
+ memcpy(&dstImage->clap, &srcImage->clap, sizeof(dstImage->clap));
+ memcpy(&dstImage->irot, &srcImage->irot, sizeof(dstImage->irot));
+ memcpy(&dstImage->imir, &srcImage->imir, sizeof(dstImage->pasp));
+
if (srcImage->profileFormat == AVIF_PROFILE_FORMAT_ICC) {
avifImageSetProfileICC(dstImage, srcImage->icc.data, srcImage->icc.size);
} else if (srcImage->profileFormat == AVIF_PROFILE_FORMAT_NCLX) {
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);
}
diff --git a/src/write.c b/src/write.c
index 8f2fbac..5ca31df 100644
--- a/src/write.c
+++ b/src/write.c
@@ -403,6 +403,46 @@
++ipcoIndex;
ipmaPush(&ipmaColor, ipcoIndex);
+ // Write (Optional) Transformations
+ if (image->transformFlags & AVIF_TRANSFORM_PASP) {
+ avifBoxMarker pasp = avifRWStreamWriteBox(&s, "pasp", -1, 0);
+ avifRWStreamWriteU32(&s, image->pasp.hSpacing); // unsigned int(32) hSpacing;
+ avifRWStreamWriteU32(&s, image->pasp.vSpacing); // unsigned int(32) vSpacing;
+ avifRWStreamFinishBox(&s, pasp);
+ ++ipcoIndex;
+ ipmaPush(&ipmaColor, ipcoIndex);
+ }
+ if (image->transformFlags & AVIF_TRANSFORM_CLAP) {
+ avifBoxMarker clap = avifRWStreamWriteBox(&s, "clap", -1, 0);
+ avifRWStreamWriteU32(&s, image->clap.widthN); // unsigned int(32) cleanApertureWidthN;
+ avifRWStreamWriteU32(&s, image->clap.widthD); // unsigned int(32) cleanApertureWidthD;
+ avifRWStreamWriteU32(&s, image->clap.heightN); // unsigned int(32) cleanApertureHeightN;
+ avifRWStreamWriteU32(&s, image->clap.heightD); // unsigned int(32) cleanApertureHeightD;
+ avifRWStreamWriteU32(&s, image->clap.horizOffN); // unsigned int(32) horizOffN;
+ avifRWStreamWriteU32(&s, image->clap.horizOffD); // unsigned int(32) horizOffD;
+ avifRWStreamWriteU32(&s, image->clap.vertOffN); // unsigned int(32) vertOffN;
+ avifRWStreamWriteU32(&s, image->clap.vertOffD); // unsigned int(32) vertOffD;
+ avifRWStreamFinishBox(&s, clap);
+ ++ipcoIndex;
+ ipmaPush(&ipmaColor, ipcoIndex);
+ }
+ if (image->transformFlags & AVIF_TRANSFORM_IROT) {
+ avifBoxMarker irot = avifRWStreamWriteBox(&s, "irot", -1, 0);
+ uint8_t angle = image->irot.angle & 0x3;
+ avifRWStreamWrite(&s, &angle, 1); // unsigned int (6) reserved = 0; unsigned int (2) angle;
+ avifRWStreamFinishBox(&s, irot);
+ ++ipcoIndex;
+ ipmaPush(&ipmaColor, ipcoIndex);
+ }
+ if (image->transformFlags & AVIF_TRANSFORM_IMIR) {
+ avifBoxMarker imir = avifRWStreamWriteBox(&s, "imir", -1, 0);
+ uint8_t axis = image->imir.axis & 0x1;
+ avifRWStreamWrite(&s, &axis, 1); // unsigned int (7) reserved = 0; unsigned int (1) axis;
+ avifRWStreamFinishBox(&s, imir);
+ ++ipcoIndex;
+ ipmaPush(&ipmaColor, ipcoIndex);
+ }
+
if (hasAlpha) {
avifBoxMarker pixiA = avifRWStreamWriteBox(&s, "pixi", 0, 0);
avifRWStreamWriteU8(&s, 1); // unsigned int (8) num_channels;