Support pasp, clap, irot, imir metadata for encode/decode
Fixes: #41
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;