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;