Generalized ipco property deduplication
diff --git a/src/write.c b/src/write.c
index 6a2474a..eff6d2c 100644
--- a/src/write.c
+++ b/src/write.c
@@ -181,6 +181,79 @@
}
// ---------------------------------------------------------------------------
+// avifItemPropertyDedup - Provides ipco deduplication
+
+typedef struct avifItemProperty
+{
+ uint8_t index;
+ size_t offset;
+ size_t size;
+} avifItemProperty;
+AVIF_ARRAY_DECLARE(avifItemPropertyArray, avifItemProperty, property);
+
+typedef struct avifItemPropertyDedup
+{
+ avifItemPropertyArray properties;
+ avifRWStream s; // Temporary stream for each new property, checked against already-written boxes for deduplications
+ avifRWData buffer; // Temporary storage for 's'
+ uint8_t nextIndex; // 1-indexed, incremented every time another unique property is finished
+} avifItemPropertyDedup;
+
+static avifItemPropertyDedup * avifItemPropertyDedupCreate(void)
+{
+ avifItemPropertyDedup * dedup = (avifItemPropertyDedup *)avifAlloc(sizeof(avifItemPropertyDedup));
+ memset(dedup, 0, sizeof(avifItemPropertyDedup));
+ avifArrayCreate(&dedup->properties, sizeof(avifItemProperty), 8);
+ avifRWDataRealloc(&dedup->buffer, 2048); // This will resize automatically (if necessary)
+ return dedup;
+}
+
+static void avifItemPropertyDedupDestroy(avifItemPropertyDedup * dedup)
+{
+ avifArrayDestroy(&dedup->properties);
+ avifRWDataFree(&dedup->buffer);
+ avifFree(dedup);
+}
+
+// Resets the dedup's temporary write stream in preparation for a single item property's worth of writing
+static void avifItemPropertyDedupStart(avifItemPropertyDedup * dedup)
+{
+ avifRWStreamStart(&dedup->s, &dedup->buffer);
+}
+
+// This compares the newly written item property (in the dedup's temporary storage buffer) to
+// already-written properties (whose offsets/sizes in outputStream are recorded in the dedup). If a
+// match is found, the previous item's index is used. If this new property is unique, it is
+// assigned the next available property index, written to the output stream, and its offset/size in
+// the output stream is recorded in the dedup for future comparisons.
+//
+// This function always returns a valid 1-indexed property index for usage in a property association
+// (ipma) box later. If the most recent property was a duplicate of a previous property, the return
+// value will be the index of the original property, otherwise it will be the index of the newly
+// created property.
+static uint8_t avifItemPropertyDedupFinish(avifItemPropertyDedup * dedup, avifRWStream * outputStream)
+{
+ const size_t newPropertySize = avifRWStreamOffset(&dedup->s);
+
+ for (size_t i = 0; i < dedup->properties.count; ++i) {
+ avifItemProperty * property = &dedup->properties.property[i];
+ if ((property->size == newPropertySize) &&
+ !memcmp(&outputStream->raw->data[property->offset], dedup->buffer.data, newPropertySize)) {
+ // We've already written this exact property, reuse it
+ return property->index;
+ }
+ }
+
+ // Write a new property, and remember its location in the output stream for future deduplication
+ avifItemProperty * property = (avifItemProperty *)avifArrayPushPtr(&dedup->properties);
+ property->index = ++dedup->nextIndex; // preincrement so the first new index is 1 (as ipma is 1-indexed)
+ property->size = newPropertySize;
+ property->offset = avifRWStreamOffset(outputStream);
+ avifRWStreamWrite(outputStream, dedup->buffer.data, newPropertySize);
+ return property->index;
+}
+
+// ---------------------------------------------------------------------------
avifEncoder * avifEncoderCreate(void)
{
@@ -213,20 +286,48 @@
avifCodecSpecificOptionsSet(encoder->csOptions, key, value);
}
-static void avifEncoderWriteColorProperties(avifRWStream * s, const avifImage * imageMetadata, struct ipmaArray * ipma, uint8_t * itemPropertyIndex)
+// This function is used in two codepaths:
+// * writing color *item* properties
+// * writing color *track* properties
+//
+// Item properties must have property associations with them and can be deduplicated (by reusing
+// these associations), so this function leverages the ipma and dedup arguments to do this.
+//
+// Track properties, however, are implicitly associated by the track in which they are contained, so
+// there is no need to build a property association box (ipma), and no way to deduplicate/reuse a
+// property. In this case, the ipma and dedup properties should/will be set to NULL, and this
+// function will avoid using them.
+static void avifEncoderWriteColorProperties(avifRWStream * outputStream,
+ const avifImage * imageMetadata,
+ struct ipmaArray * ipma,
+ avifItemPropertyDedup * dedup)
{
+ avifRWStream * s = outputStream;
+ if (dedup) {
+ assert(ipma);
+
+ // Use the dedup's temporary stream for box writes
+ s = &dedup->s;
+ }
+
if (imageMetadata->icc.size > 0) {
+ if (dedup) {
+ avifItemPropertyDedupStart(dedup);
+ }
avifBoxMarker colr = avifRWStreamWriteBox(s, "colr", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteChars(s, "prof", 4); // unsigned int(32) colour_type;
avifRWStreamWrite(s, imageMetadata->icc.data, imageMetadata->icc.size);
avifRWStreamFinishBox(s, colr);
- if (ipma && itemPropertyIndex) {
- ipmaPush(ipma, ++(*itemPropertyIndex), AVIF_FALSE);
+ if (dedup) {
+ ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE);
}
}
// HEIF 6.5.5.1, from Amendment 3 allows multiple colr boxes: "at most one for a given value of colour type"
// Therefore, *always* writing an nclx box, even if an a prof box was already written above.
+ if (dedup) {
+ avifItemPropertyDedupStart(dedup);
+ }
avifBoxMarker colr = avifRWStreamWriteBox(s, "colr", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteChars(s, "nclx", 4); // unsigned int(32) colour_type;
avifRWStreamWriteU16(s, imageMetadata->colorPrimaries); // unsigned int(16) colour_primaries;
@@ -235,21 +336,27 @@
avifRWStreamWriteU8(s, (imageMetadata->yuvRange == AVIF_RANGE_FULL) ? 0x80 : 0); // unsigned int(1) full_range_flag;
// unsigned int(7) reserved = 0;
avifRWStreamFinishBox(s, colr);
- if (ipma && itemPropertyIndex) {
- ipmaPush(ipma, ++(*itemPropertyIndex), AVIF_FALSE);
+ if (dedup) {
+ ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE);
}
// Write (Optional) Transformations
if (imageMetadata->transformFlags & AVIF_TRANSFORM_PASP) {
+ if (dedup) {
+ avifItemPropertyDedupStart(dedup);
+ }
avifBoxMarker pasp = avifRWStreamWriteBox(s, "pasp", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteU32(s, imageMetadata->pasp.hSpacing); // unsigned int(32) hSpacing;
avifRWStreamWriteU32(s, imageMetadata->pasp.vSpacing); // unsigned int(32) vSpacing;
avifRWStreamFinishBox(s, pasp);
- if (ipma && itemPropertyIndex) {
- ipmaPush(ipma, ++(*itemPropertyIndex), AVIF_FALSE);
+ if (dedup) {
+ ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE);
}
}
if (imageMetadata->transformFlags & AVIF_TRANSFORM_CLAP) {
+ if (dedup) {
+ avifItemPropertyDedupStart(dedup);
+ }
avifBoxMarker clap = avifRWStreamWriteBox(s, "clap", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteU32(s, imageMetadata->clap.widthN); // unsigned int(32) cleanApertureWidthN;
avifRWStreamWriteU32(s, imageMetadata->clap.widthD); // unsigned int(32) cleanApertureWidthD;
@@ -260,26 +367,32 @@
avifRWStreamWriteU32(s, imageMetadata->clap.vertOffN); // unsigned int(32) vertOffN;
avifRWStreamWriteU32(s, imageMetadata->clap.vertOffD); // unsigned int(32) vertOffD;
avifRWStreamFinishBox(s, clap);
- if (ipma && itemPropertyIndex) {
- ipmaPush(ipma, ++(*itemPropertyIndex), AVIF_TRUE);
+ if (dedup) {
+ ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE);
}
}
if (imageMetadata->transformFlags & AVIF_TRANSFORM_IROT) {
+ if (dedup) {
+ avifItemPropertyDedupStart(dedup);
+ }
avifBoxMarker irot = avifRWStreamWriteBox(s, "irot", AVIF_BOX_SIZE_TBD);
uint8_t angle = imageMetadata->irot.angle & 0x3;
avifRWStreamWrite(s, &angle, 1); // unsigned int (6) reserved = 0; unsigned int (2) angle;
avifRWStreamFinishBox(s, irot);
- if (ipma && itemPropertyIndex) {
- ipmaPush(ipma, ++(*itemPropertyIndex), AVIF_TRUE);
+ if (dedup) {
+ ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE);
}
}
if (imageMetadata->transformFlags & AVIF_TRANSFORM_IMIR) {
+ if (dedup) {
+ avifItemPropertyDedupStart(dedup);
+ }
avifBoxMarker imir = avifRWStreamWriteBox(s, "imir", AVIF_BOX_SIZE_TBD);
uint8_t mode = imageMetadata->imir.mode & 0x1;
avifRWStreamWrite(s, &mode, 1); // unsigned int (7) reserved = 0; unsigned int (1) mode;
avifRWStreamFinishBox(s, imir);
- if (ipma && itemPropertyIndex) {
- ipmaPush(ipma, ++(*itemPropertyIndex), AVIF_TRUE);
+ if (dedup) {
+ ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE);
}
}
}
@@ -900,7 +1013,7 @@
avifBoxMarker iprp = avifRWStreamWriteBox(&s, "iprp", AVIF_BOX_SIZE_TBD);
- uint8_t itemPropertyIndex = 0;
+ avifItemPropertyDedup * dedup = avifItemPropertyDedupCreate();
avifBoxMarker ipco = avifRWStreamWriteBox(&s, "ipco", AVIF_BOX_SIZE_TBD);
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
@@ -940,40 +1053,46 @@
// Properties all av01 items need
- avifBoxMarker ispe = avifRWStreamWriteFullBox(&s, "ispe", AVIF_BOX_SIZE_TBD, 0, 0);
- avifRWStreamWriteU32(&s, imageWidth); // unsigned int(32) image_width;
- avifRWStreamWriteU32(&s, imageHeight); // unsigned int(32) image_height;
- avifRWStreamFinishBox(&s, ispe);
- ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE); // ipma is 1-indexed, doing this afterwards is correct
+ avifItemPropertyDedupStart(dedup);
+ avifBoxMarker ispe = avifRWStreamWriteFullBox(&dedup->s, "ispe", AVIF_BOX_SIZE_TBD, 0, 0);
+ avifRWStreamWriteU32(&dedup->s, imageWidth); // unsigned int(32) image_width;
+ avifRWStreamWriteU32(&dedup->s, imageHeight); // unsigned int(32) image_height;
+ avifRWStreamFinishBox(&dedup->s, ispe);
+ ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE);
+ avifItemPropertyDedupStart(dedup);
uint8_t channelCount = (item->alpha || (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) ? 1 : 3;
- avifBoxMarker pixi = avifRWStreamWriteFullBox(&s, "pixi", AVIF_BOX_SIZE_TBD, 0, 0);
- avifRWStreamWriteU8(&s, channelCount); // unsigned int (8) num_channels;
+ avifBoxMarker pixi = avifRWStreamWriteFullBox(&dedup->s, "pixi", AVIF_BOX_SIZE_TBD, 0, 0);
+ avifRWStreamWriteU8(&dedup->s, channelCount); // unsigned int (8) num_channels;
for (uint8_t chan = 0; chan < channelCount; ++chan) {
- avifRWStreamWriteU8(&s, (uint8_t)imageMetadata->depth); // unsigned int (8) bits_per_channel;
+ avifRWStreamWriteU8(&dedup->s, (uint8_t)imageMetadata->depth); // unsigned int (8) bits_per_channel;
}
- avifRWStreamFinishBox(&s, pixi);
- ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
+ avifRWStreamFinishBox(&dedup->s, pixi);
+ ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE);
if (item->codec) {
- writeConfigBox(&s, &item->av1C);
- ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_TRUE);
+ avifItemPropertyDedupStart(dedup);
+ writeConfigBox(&dedup->s, &item->av1C);
+ ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_TRUE);
}
if (item->alpha) {
// Alpha specific properties
- avifBoxMarker auxC = avifRWStreamWriteFullBox(&s, "auxC", AVIF_BOX_SIZE_TBD, 0, 0);
- avifRWStreamWriteChars(&s, alphaURN, alphaURNSize); // string aux_type;
- avifRWStreamFinishBox(&s, auxC);
- ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
+ avifItemPropertyDedupStart(dedup);
+ avifBoxMarker auxC = avifRWStreamWriteFullBox(&dedup->s, "auxC", AVIF_BOX_SIZE_TBD, 0, 0);
+ avifRWStreamWriteChars(&dedup->s, alphaURN, alphaURNSize); // string aux_type;
+ avifRWStreamFinishBox(&dedup->s, auxC);
+ ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE);
} else {
// Color specific properties
- avifEncoderWriteColorProperties(&s, imageMetadata, &item->ipma, &itemPropertyIndex);
+ avifEncoderWriteColorProperties(&s, imageMetadata, &item->ipma, dedup);
}
}
avifRWStreamFinishBox(&s, ipco);
+ avifItemPropertyDedupDestroy(dedup);
+ dedup = NULL;
avifBoxMarker ipma = avifRWStreamWriteFullBox(&s, "ipma", AVIF_BOX_SIZE_TBD, 0, 0);
{