Refactor write.c to use a similar Data/Item design as read.c

* Rename read.c's avifData -> avifDecoderData and all associated funcs (disambiguity)
* Rename read.c's struct avifItem -> struct avifDecoderItem (disambiguity)
* Rename read.c's avifDataNewTile() -> avifDataCreateTile() (consistency)
diff --git a/src/read.c b/src/read.c
index d1605b5..3c9abfe 100644
--- a/src/read.c
+++ b/src/read.c
@@ -74,8 +74,8 @@
 // ---------------------------------------------------------------------------
 // Top-level structures
 
-// one "item" worth (all iref, iloc, iprp, etc refer to one of these)
-typedef struct avifItem
+// one "item" worth for decoding (all iref, iloc, iprp, etc refer to one of these)
+typedef struct avifDecoderItem
 {
     uint32_t id;
     uint8_t type[4];
@@ -103,10 +103,10 @@
     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}
     uint32_t dimgForID;      // if non-zero, this item is a derived image for Item #{dimgForID}
-} avifItem;
-AVIF_ARRAY_DECLARE(avifItemArray, avifItem, item);
+} avifDecoderItem;
+AVIF_ARRAY_DECLARE(avifDecoderItemArray, avifDecoderItem, item);
 
-// Temporary storage for ipco contents until they can be associated and memcpy'd to an avifItem
+// Temporary storage for ipco contents until they can be associated and memcpy'd to an avifDecoderItem
 typedef struct avifProperty
 {
     uint8_t type[4];
@@ -122,12 +122,12 @@
 AVIF_ARRAY_DECLARE(avifPropertyArray, avifProperty, prop);
 
 // idat storage
-typedef struct avifItemData
+typedef struct avifDecoderItemData
 {
     uint32_t id;
     avifROData data;
-} avifItemData;
-AVIF_ARRAY_DECLARE(avifItemDataArray, avifItemData, idat);
+} avifDecoderItemData;
+AVIF_ARRAY_DECLARE(avifDecoderItemDataArray, avifDecoderItemData, idat);
 
 // grid storage
 typedef struct avifImageGrid
@@ -355,7 +355,7 @@
 }
 
 // ---------------------------------------------------------------------------
-// avifData
+// avifDecoderData
 
 typedef struct avifTile
 {
@@ -365,12 +365,12 @@
 } avifTile;
 AVIF_ARRAY_DECLARE(avifTileArray, avifTile, tile);
 
-typedef struct avifData
+typedef struct avifDecoderData
 {
     avifFileType ftyp;
-    avifItemArray items;
+    avifDecoderItemArray items;
     avifPropertyArray properties;
-    avifItemDataArray idats;
+    avifDecoderItemDataArray idats;
     avifTrackArray tracks;
     avifROData rawInput;
     avifTileArray tiles;
@@ -382,21 +382,21 @@
     avifSampleTable * sourceSampleTable; // NULL unless (source == AVIF_DECODER_SOURCE_TRACKS), owned by an avifTrack
     uint32_t primaryItemID;
     uint32_t metaBoxID; // Ever-incrementing ID for tracking which 'meta' box contains an idat, and which idat an iloc might refer to
-} avifData;
+} avifDecoderData;
 
-static avifData * avifDataCreate()
+static avifDecoderData * avifDecoderDataCreate()
 {
-    avifData * data = (avifData *)avifAlloc(sizeof(avifData));
-    memset(data, 0, sizeof(avifData));
-    avifArrayCreate(&data->items, sizeof(avifItem), 8);
+    avifDecoderData * data = (avifDecoderData *)avifAlloc(sizeof(avifDecoderData));
+    memset(data, 0, sizeof(avifDecoderData));
+    avifArrayCreate(&data->items, sizeof(avifDecoderItem), 8);
     avifArrayCreate(&data->properties, sizeof(avifProperty), 16);
-    avifArrayCreate(&data->idats, sizeof(avifItemData), 1);
+    avifArrayCreate(&data->idats, sizeof(avifDecoderItemData), 1);
     avifArrayCreate(&data->tracks, sizeof(avifTrack), 2);
     avifArrayCreate(&data->tiles, sizeof(avifTile), 8);
     return data;
 }
 
-static void avifDataResetCodec(avifData * data)
+static void avifDecoderDataResetCodec(avifDecoderData * data)
 {
     for (unsigned int i = 0; i < data->tiles.count; ++i) {
         avifTile * tile = &data->tiles.tile[i];
@@ -410,7 +410,7 @@
     }
 }
 
-static avifTile * avifDataNewTile(avifData * data)
+static avifTile * avifDecoderDataCreateTile(avifDecoderData * data)
 {
     avifTile * tile = (avifTile *)avifArrayPushPtr(&data->tiles);
     tile->image = avifImageCreateEmpty();
@@ -418,7 +418,7 @@
     return tile;
 }
 
-static void avifDataClearTiles(avifData * data)
+static void avifDecoderDataClearTiles(avifDecoderData * data)
 {
     for (unsigned int i = 0; i < data->tiles.count; ++i) {
         avifTile * tile = &data->tiles.tile[i];
@@ -440,7 +440,7 @@
     data->alphaTileCount = 0;
 }
 
-static void avifDataDestroy(avifData * data)
+static void avifDecoderDataDestroy(avifDecoderData * data)
 {
     avifArrayDestroy(&data->items);
     avifArrayDestroy(&data->properties);
@@ -451,12 +451,12 @@
         }
     }
     avifArrayDestroy(&data->tracks);
-    avifDataClearTiles(data);
+    avifDecoderDataClearTiles(data);
     avifArrayDestroy(&data->tiles);
     avifFree(data);
 }
 
-static avifItem * avifDataFindItem(avifData * data, uint32_t itemID)
+static avifDecoderItem * avifDecoderDataFindItem(avifDecoderData * data, uint32_t itemID)
 {
     if (itemID == 0) {
         return NULL;
@@ -468,12 +468,12 @@
         }
     }
 
-    avifItem * item = (avifItem *)avifArrayPushPtr(&data->items);
+    avifDecoderItem * item = (avifDecoderItem *)avifArrayPushPtr(&data->items);
     item->id = itemID;
     return item;
 }
 
-static const uint8_t * avifDataCalcItemPtr(avifData * data, avifItem * item)
+static const uint8_t * avifDecoderDataCalcItemPtr(avifDecoderData * data, avifDecoderItem * item)
 {
     avifROData * offsetBuffer = NULL;
     if (item->idatID == 0) {
@@ -507,14 +507,14 @@
     return offsetBuffer->data + item->offset;
 }
 
-static avifBool avifDataGenerateImageGridTiles(avifData * data, avifImageGrid * grid, avifItem * gridItem, avifBool alpha)
+static avifBool avifDecoderDataGenerateImageGridTiles(avifDecoderData * data, avifImageGrid * grid, avifDecoderItem * gridItem, avifBool alpha)
 {
     unsigned int tilesRequested = (unsigned int)grid->rows * (unsigned int)grid->columns;
 
     // Count number of dimg for this item, bail out if it doesn't match perfectly
     unsigned int tilesAvailable = 0;
     for (uint32_t i = 0; i < data->items.count; ++i) {
-        avifItem * item = &data->items.item[i];
+        avifDecoderItem * item = &data->items.item[i];
         if (item->dimgForID == gridItem->id) {
             if (memcmp(item->type, "av01", 4)) {
                 continue;
@@ -529,15 +529,15 @@
     }
 
     for (uint32_t i = 0; i < data->items.count; ++i) {
-        avifItem * item = &data->items.item[i];
+        avifDecoderItem * item = &data->items.item[i];
         if (item->dimgForID == gridItem->id) {
             if (memcmp(item->type, "av01", 4)) {
                 continue;
             }
 
-            avifTile * tile = avifDataNewTile(data);
+            avifTile * tile = avifDecoderDataCreateTile(data);
             avifSample * sample = (avifSample *)avifArrayPushPtr(&tile->input->samples);
-            sample->data.data = avifDataCalcItemPtr(data, item);
+            sample->data.data = avifDecoderDataCalcItemPtr(data, item);
             sample->data.size = item->size;
             sample->sync = AVIF_TRUE;
             tile->input->alpha = alpha;
@@ -546,12 +546,12 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifDataFillImageGrid(avifData * data,
-                                      avifImageGrid * grid,
-                                      avifImage * dstImage,
-                                      unsigned int firstTileIndex,
-                                      unsigned int tileCount,
-                                      avifBool alpha)
+static avifBool avifDecoderDataFillImageGrid(avifDecoderData * data,
+                                             avifImageGrid * grid,
+                                             avifImage * dstImage,
+                                             unsigned int firstTileIndex,
+                                             unsigned int tileCount,
+                                             avifBool alpha)
 {
     if (tileCount == 0) {
         return AVIF_FALSE;
@@ -690,7 +690,7 @@
     VARNAME##_roData.size = SIZE;        \
     avifROStreamStart(&VARNAME, &VARNAME##_roData)
 
-static avifBool avifParseItemLocationBox(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemLocationBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -768,7 +768,7 @@
             uint64_t extentLength; // unsigned int(offset_size*8) extent_length;
             CHECK(avifROStreamReadUX8(&s, &extentLength, lengthSize));
 
-            avifItem * item = avifDataFindItem(data, itemID);
+            avifDecoderItem * item = avifDecoderDataFindItem(data, itemID);
             if (!item) {
                 return AVIF_FALSE;
             }
@@ -817,7 +817,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseImageSpatialExtentsProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+static avifBool avifParseImageSpatialExtentsProperty(avifDecoderData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
 {
     BEGIN_STREAM(s, raw, rawLen);
     CHECK(avifROStreamReadAndEnforceVersion(&s, 0));
@@ -827,7 +827,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseAuxiliaryTypeProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+static avifBool avifParseAuxiliaryTypeProperty(avifDecoderData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
 {
     BEGIN_STREAM(s, raw, rawLen);
     CHECK(avifROStreamReadAndEnforceVersion(&s, 0));
@@ -836,7 +836,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseColourInformationBox(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+static avifBool avifParseColourInformationBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -892,12 +892,12 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseAV1CodecConfigurationBoxProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+static avifBool avifParseAV1CodecConfigurationBoxProperty(avifDecoderData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
 {
     return avifParseAV1CodecConfigurationBox(raw, rawLen, &data->properties.prop[propertyIndex].av1C);
 }
 
-static avifBool avifParsePixelAspectRatioBoxProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+static avifBool avifParsePixelAspectRatioBoxProperty(avifDecoderData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -907,7 +907,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseCleanApertureBoxProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+static avifBool avifParseCleanApertureBoxProperty(avifDecoderData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -923,7 +923,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseImageRotationProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+static avifBool avifParseImageRotationProperty(avifDecoderData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -936,7 +936,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseImageMirrorProperty(avifData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
+static avifBool avifParseImageMirrorProperty(avifDecoderData * data, const uint8_t * raw, size_t rawLen, int propertyIndex)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -949,7 +949,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemPropertyContainerBox(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemPropertyContainerBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -989,7 +989,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemPropertyAssociation(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemPropertyAssociation(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1035,7 +1035,7 @@
                 return AVIF_FALSE;
             }
 
-            avifItem * item = avifDataFindItem(data, itemID);
+            avifDecoderItem * item = avifDecoderDataFindItem(data, itemID);
             if (!item) {
                 return AVIF_FALSE;
             }
@@ -1073,7 +1073,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParsePrimaryItemBox(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParsePrimaryItemBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     if (data->primaryItemID > 0) {
         // Illegal to have multiple pitm boxes, bail out
@@ -1095,7 +1095,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemDataBox(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemDataBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     uint32_t idatID = data->metaBoxID;
 
@@ -1107,14 +1107,14 @@
     }
 
     int index = avifArrayPushIndex(&data->idats);
-    avifItemData * idat = &data->idats.idat[index];
+    avifDecoderItemData * idat = &data->idats.idat[index];
     idat->id = idatID;
     idat->data.data = raw;
     idat->data.size = rawLen;
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemPropertiesBox(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemPropertiesBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1145,7 +1145,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemInfoEntry(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemInfoEntry(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1166,7 +1166,7 @@
         memset(&contentType, 0, sizeof(contentType));
     }
 
-    avifItem * item = avifDataFindItem(data, itemID);
+    avifDecoderItem * item = avifDecoderDataFindItem(data, itemID);
     if (!item) {
         return AVIF_FALSE;
     }
@@ -1176,7 +1176,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemInfoBox(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemInfoBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1210,7 +1210,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemReferenceBox(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemReferenceBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1251,7 +1251,7 @@
 
             // Read this reference as "{fromID} is a {irefType} for {toID}"
             if (fromID && toID) {
-                avifItem * item = avifDataFindItem(data, fromID);
+                avifDecoderItem * item = avifDecoderDataFindItem(data, fromID);
                 if (!item) {
                     return AVIF_FALSE;
                 }
@@ -1267,7 +1267,7 @@
                 }
                 if (!memcmp(irefHeader.type, "dimg", 4)) {
                     // derived images refer in the opposite direction
-                    avifItem * dimg = avifDataFindItem(data, toID);
+                    avifDecoderItem * dimg = avifDecoderDataFindItem(data, toID);
                     if (!dimg) {
                         return AVIF_FALSE;
                     }
@@ -1281,7 +1281,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseMetaBox(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseMetaBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1312,7 +1312,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseTrackHeaderBox(avifData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseTrackHeaderBox(avifDecoderData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
     (void)data;
@@ -1362,7 +1362,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseMediaHeaderBox(avifData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseMediaHeaderBox(avifDecoderData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
     (void)data;
@@ -1394,7 +1394,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseChunkOffsetBox(avifData * data, avifSampleTable * sampleTable, avifBool largeOffsets, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseChunkOffsetBox(avifDecoderData * data, avifSampleTable * sampleTable, avifBool largeOffsets, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
     (void)data;
@@ -1419,7 +1419,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseSampleToChunkBox(avifData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseSampleToChunkBox(avifDecoderData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
     (void)data;
@@ -1437,7 +1437,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseSampleSizeBox(avifData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseSampleSizeBox(avifDecoderData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
     (void)data;
@@ -1459,7 +1459,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseSyncSampleBox(avifData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseSyncSampleBox(avifDecoderData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
     (void)data;
@@ -1478,7 +1478,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseTimeToSampleBox(avifData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseTimeToSampleBox(avifDecoderData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
     (void)data;
@@ -1496,7 +1496,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseSampleDescriptionBox(avifData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseSampleDescriptionBox(avifDecoderData * data, avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
     (void)data;
@@ -1533,7 +1533,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseSampleTableBox(avifData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseSampleTableBox(avifDecoderData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
 {
     if (track->sampleTable) {
         // A TrackBox may only have one SampleTable
@@ -1568,7 +1568,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseMediaInformationBox(avifData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseMediaInformationBox(avifDecoderData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1585,7 +1585,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseMediaBox(avifData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseMediaBox(avifDecoderData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1604,7 +1604,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifTrackReferenceBox(avifData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
+static avifBool avifTrackReferenceBox(avifDecoderData * data, avifTrack * track, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
     (void)data;
@@ -1625,7 +1625,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseTrackBox(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseTrackBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1648,7 +1648,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseMoovBox(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseMoovBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1686,7 +1686,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParse(avifData * data, const uint8_t * raw, size_t rawLen)
+static avifBool avifParse(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1769,7 +1769,7 @@
 static void avifDecoderCleanup(avifDecoder * decoder)
 {
     if (decoder->data) {
-        avifDataDestroy(decoder->data);
+        avifDecoderDataDestroy(decoder->data);
         decoder->data = NULL;
     }
 
@@ -1799,7 +1799,7 @@
     // -----------------------------------------------------------------------
     // Parse BMFF boxes
 
-    decoder->data = avifDataCreate();
+    decoder->data = avifDecoderDataCreate();
 
     // Shallow copy, on purpose
     memcpy(&decoder->data->rawInput, rawInput, sizeof(avifROData));
@@ -1815,8 +1815,8 @@
 
     // Sanity check items
     for (uint32_t itemIndex = 0; itemIndex < decoder->data->items.count; ++itemIndex) {
-        avifItem * item = &decoder->data->items.item[itemIndex];
-        const uint8_t * p = avifDataCalcItemPtr(decoder->data, item);
+        avifDecoderItem * item = &decoder->data->items.item[itemIndex];
+        const uint8_t * p = avifDecoderDataCalcItemPtr(decoder->data, item);
         if (p == NULL) {
             return AVIF_RESULT_BMFF_PARSE_FAILED;
         }
@@ -1850,7 +1850,7 @@
 
 static avifResult avifDecoderFlush(avifDecoder * decoder)
 {
-    avifDataResetCodec(decoder->data);
+    avifDecoderDataResetCodec(decoder->data);
 
     for (unsigned int i = 0; i < decoder->data->tiles.count; ++i) {
         avifTile * tile = &decoder->data->tiles.tile[i];
@@ -1867,7 +1867,7 @@
 
 avifResult avifDecoderReset(avifDecoder * decoder)
 {
-    avifData * data = decoder->data;
+    avifDecoderData * data = decoder->data;
     if (!data) {
         // Nothing to reset.
         return AVIF_RESULT_OK;
@@ -1875,7 +1875,7 @@
 
     memset(&data->colorGrid, 0, sizeof(data->colorGrid));
     memset(&data->alphaGrid, 0, sizeof(data->alphaGrid));
-    avifDataClearTiles(data);
+    avifDecoderDataClearTiles(data);
 
     // Prepare / cleanup decoded image state
     if (!decoder->image) {
@@ -1949,7 +1949,7 @@
             alphaTrack = &decoder->data->tracks.track[alphaTrackIndex];
         }
 
-        avifTile * colorTile = avifDataNewTile(decoder->data);
+        avifTile * colorTile = avifDecoderDataCreateTile(decoder->data);
         if (!avifCodecDecodeInputGetSamples(colorTile->input, colorTrack->sampleTable, &decoder->data->rawInput)) {
             return AVIF_RESULT_BMFF_PARSE_FAILED;
         }
@@ -1957,7 +1957,7 @@
 
         avifTile * alphaTile = NULL;
         if (alphaTrack) {
-            alphaTile = avifDataNewTile(decoder->data);
+            alphaTile = avifDecoderDataCreateTile(decoder->data);
             if (!avifCodecDecodeInputGetSamples(alphaTile->input, alphaTrack->sampleTable, &decoder->data->rawInput)) {
                 return AVIF_RESULT_BMFF_PARSE_FAILED;
             }
@@ -1990,12 +1990,12 @@
         avifROData alphaOBU = AVIF_DATA_EMPTY;
         avifROData exifData = AVIF_DATA_EMPTY;
         avifROData xmpData = AVIF_DATA_EMPTY;
-        avifItem * colorOBUItem = NULL;
-        avifItem * alphaOBUItem = NULL;
+        avifDecoderItem * colorOBUItem = NULL;
+        avifDecoderItem * alphaOBUItem = NULL;
 
         // Find the colorOBU (primary) item
         for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) {
-            avifItem * item = &data->items.item[itemIndex];
+            avifDecoderItem * item = &data->items.item[itemIndex];
             if (!item->id || !item->size) {
                 break;
             }
@@ -2014,7 +2014,7 @@
             }
 
             if (isGrid) {
-                const uint8_t * itemPtr = avifDataCalcItemPtr(data, item);
+                const uint8_t * itemPtr = avifDecoderDataCalcItemPtr(data, item);
                 if (itemPtr == NULL) {
                     return AVIF_RESULT_BMFF_PARSE_FAILED;
                 }
@@ -2022,7 +2022,7 @@
                     return AVIF_RESULT_INVALID_IMAGE_GRID;
                 }
             } else {
-                colorOBU.data = avifDataCalcItemPtr(data, item);
+                colorOBU.data = avifDecoderDataCalcItemPtr(data, item);
                 colorOBU.size = item->size;
             }
 
@@ -2036,7 +2036,7 @@
 
         // Find the alphaOBU item, if any
         for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) {
-            avifItem * item = &data->items.item[itemIndex];
+            avifDecoderItem * item = &data->items.item[itemIndex];
             if (!item->id || !item->size) {
                 break;
             }
@@ -2052,7 +2052,7 @@
 
             if (isAlphaURN(item->auxC.auxType) && (item->auxForID == colorOBUItem->id)) {
                 if (isGrid) {
-                    const uint8_t * itemPtr = avifDataCalcItemPtr(data, item);
+                    const uint8_t * itemPtr = avifDecoderDataCalcItemPtr(data, item);
                     if (itemPtr == NULL) {
                         return AVIF_RESULT_BMFF_PARSE_FAILED;
                     }
@@ -2060,7 +2060,7 @@
                         return AVIF_RESULT_INVALID_IMAGE_GRID;
                     }
                 } else {
-                    alphaOBU.data = avifDataCalcItemPtr(data, item);
+                    alphaOBU.data = avifDecoderDataCalcItemPtr(data, item);
                     alphaOBU.size = item->size;
                 }
 
@@ -2071,7 +2071,7 @@
 
         // Find Exif and/or XMP metadata, if any
         for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) {
-            avifItem * item = &data->items.item[itemIndex];
+            avifDecoderItem * item = &data->items.item[itemIndex];
             if (!item->id || !item->size) {
                 break;
             }
@@ -2083,7 +2083,7 @@
 
             if (!memcmp(item->type, "Exif", 4)) {
                 // Advance past Annex A.2.1's header
-                const uint8_t * boxPtr = avifDataCalcItemPtr(data, item);
+                const uint8_t * boxPtr = avifDecoderDataCalcItemPtr(data, item);
                 BEGIN_STREAM(exifBoxStream, boxPtr, item->size);
                 uint32_t exifTiffHeaderOffset;
                 CHECK(avifROStreamReadU32(&exifBoxStream, &exifTiffHeaderOffset)); // unsigned int(32) exif_tiff_header_offset;
@@ -2093,13 +2093,13 @@
             }
 
             if (!memcmp(item->type, "mime", 4) && !memcmp(item->contentType.contentType, xmpContentType, xmpContentTypeSize)) {
-                xmpData.data = avifDataCalcItemPtr(data, item);
+                xmpData.data = avifDecoderDataCalcItemPtr(data, item);
                 xmpData.size = item->size;
             }
         }
 
         if ((data->colorGrid.rows > 0) && (data->colorGrid.columns > 0)) {
-            if (!avifDataGenerateImageGridTiles(data, &data->colorGrid, colorOBUItem, AVIF_FALSE)) {
+            if (!avifDecoderDataGenerateImageGridTiles(data, &data->colorGrid, colorOBUItem, AVIF_FALSE)) {
                 return AVIF_RESULT_INVALID_IMAGE_GRID;
             }
             data->colorTileCount = data->tiles.count;
@@ -2108,7 +2108,7 @@
                 return AVIF_RESULT_NO_AV1_ITEMS_FOUND;
             }
 
-            avifTile * colorTile = avifDataNewTile(decoder->data);
+            avifTile * colorTile = avifDecoderDataCreateTile(decoder->data);
             avifSample * colorSample = (avifSample *)avifArrayPushPtr(&colorTile->input->samples);
             memcpy(&colorSample->data, &colorOBU, sizeof(avifROData));
             colorSample->sync = AVIF_TRUE;
@@ -2116,14 +2116,14 @@
         }
 
         if ((data->alphaGrid.rows > 0) && (data->alphaGrid.columns > 0)) {
-            if (!avifDataGenerateImageGridTiles(data, &data->alphaGrid, alphaOBUItem, AVIF_FALSE)) {
+            if (!avifDecoderDataGenerateImageGridTiles(data, &data->alphaGrid, alphaOBUItem, AVIF_FALSE)) {
                 return AVIF_RESULT_INVALID_IMAGE_GRID;
             }
             data->alphaTileCount = data->tiles.count - data->colorTileCount;
         } else {
             avifTile * alphaTile = NULL;
             if (alphaOBU.size > 0) {
-                alphaTile = avifDataNewTile(decoder->data);
+                alphaTile = avifDecoderDataCreateTile(decoder->data);
 
                 avifSample * alphaSample = (avifSample *)avifArrayPushPtr(&alphaTile->input->samples);
                 memcpy(&alphaSample->data, &alphaOBU, sizeof(avifROData));
@@ -2222,7 +2222,8 @@
     }
 
     if ((decoder->data->colorGrid.rows > 0) || (decoder->data->colorGrid.columns > 0)) {
-        if (!avifDataFillImageGrid(decoder->data, &decoder->data->colorGrid, decoder->image, 0, decoder->data->colorTileCount, AVIF_FALSE)) {
+        if (!avifDecoderDataFillImageGrid(
+                decoder->data, &decoder->data->colorGrid, decoder->image, 0, decoder->data->colorTileCount, AVIF_FALSE)) {
             return AVIF_RESULT_INVALID_IMAGE_GRID;
         }
     } else {
@@ -2251,7 +2252,7 @@
     }
 
     if ((decoder->data->alphaGrid.rows > 0) || (decoder->data->alphaGrid.columns > 0)) {
-        if (!avifDataFillImageGrid(
+        if (!avifDecoderDataFillImageGrid(
                 decoder->data, &decoder->data->alphaGrid, decoder->image, decoder->data->colorTileCount, decoder->data->alphaTileCount, AVIF_TRUE)) {
             return AVIF_RESULT_INVALID_IMAGE_GRID;
         }
diff --git a/src/write.c b/src/write.c
index c5a7d33..d50436d 100644
--- a/src/write.c
+++ b/src/write.c
@@ -27,6 +27,76 @@
 static void fillConfigBox(avifCodec * codec, avifImage * image, avifBool alpha);
 static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg);
 
+// ---------------------------------------------------------------------------
+// avifEncoderItem
+
+// one "item" worth for encoder
+typedef struct avifEncoderItem
+{
+    uint16_t id;
+    uint8_t type[4];
+    avifImage * image;  // avifImage* to use when encoding or populating ipma for this item (unowned)
+    avifCodec * codec;  // only present on type==av01
+    avifRWData content; // OBU data on av01, metadata payload for Exif/XMP
+    avifBool alpha;
+
+    const char * infeName;
+    size_t infeNameSize;
+    const char * infeContentType;
+    size_t infeContentTypeSize;
+    size_t infeOffsetOffset; // Stream offset where infe offset was written, so it can be properly set after mdat is written
+
+    uint16_t irefToID; // if non-zero, make an iref from this id -> irefToID
+    const char * irefType;
+
+    struct ipmaArray ipma;
+} avifEncoderItem;
+AVIF_ARRAY_DECLARE(avifEncoderItemArray, avifEncoderItem, item);
+
+// ---------------------------------------------------------------------------
+// avifEncoderData
+
+typedef struct avifEncoderData
+{
+    avifEncoderItemArray items;
+    uint16_t lastItemID;
+    uint16_t primaryItemID;
+} avifEncoderData;
+
+static avifEncoderData * avifEncoderDataCreate()
+{
+    avifEncoderData * data = (avifEncoderData *)avifAlloc(sizeof(avifEncoderData));
+    memset(data, 0, sizeof(avifEncoderData));
+    avifArrayCreate(&data->items, sizeof(avifEncoderItem), 8);
+    return data;
+}
+
+static avifEncoderItem * avifEncoderDataCreateItem(avifEncoderData * data, const char * type, const char * infeName, size_t infeNameSize)
+{
+    avifEncoderItem * item = (avifEncoderItem *)avifArrayPushPtr(&data->items);
+    ++data->lastItemID;
+    item->id = data->lastItemID;
+    memcpy(item->type, type, sizeof(item->type));
+    item->infeName = infeName;
+    item->infeNameSize = infeNameSize;
+    return item;
+}
+
+static void avifEncoderDataDestroy(avifEncoderData * data)
+{
+    for (uint32_t i = 0; i < data->items.count; ++i) {
+        avifEncoderItem * item = &data->items.item[i];
+        if (item->codec) {
+            avifCodecDestroy(item->codec);
+        }
+        avifRWDataFree(&item->content);
+    }
+    avifArrayDestroy(&data->items);
+    avifFree(data);
+}
+
+// ---------------------------------------------------------------------------
+
 avifEncoder * avifEncoderCreate(void)
 {
     avifEncoder * encoder = (avifEncoder *)avifAlloc(sizeof(avifEncoder));
@@ -39,11 +109,13 @@
     encoder->tileRowsLog2 = 0;
     encoder->tileColsLog2 = 0;
     encoder->speed = AVIF_SPEED_DEFAULT;
+    encoder->data = avifEncoderDataCreate();
     return encoder;
 }
 
 void avifEncoderDestroy(avifEncoder * encoder)
 {
+    avifEncoderDataDestroy(encoder->data);
     avifFree(encoder);
 }
 
@@ -54,59 +126,86 @@
     }
 
     avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
-    avifRWData colorOBU = AVIF_DATA_EMPTY;
-    avifRWData alphaOBU = AVIF_DATA_EMPTY;
-    avifCodec * codec[AVIF_CODEC_PLANES_COUNT];
 
-    codec[AVIF_CODEC_PLANES_COLOR] = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
-    if (!codec[AVIF_CODEC_PLANES_COLOR]) {
+    avifEncoderItem * colorItem = avifEncoderDataCreateItem(encoder->data, "av01", "Color", 6);
+    colorItem->image = image;
+    colorItem->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
+    if (!colorItem->codec) {
         // Just bail out early, we're not surviving this function without an encoder compiled in
         return AVIF_RESULT_NO_CODEC_AVAILABLE;
     }
+    encoder->data->primaryItemID = colorItem->id;
 
     avifBool imageIsOpaque = avifImageIsOpaque(image);
-    if (imageIsOpaque) {
-        codec[AVIF_CODEC_PLANES_ALPHA] = NULL;
-    } else {
-        codec[AVIF_CODEC_PLANES_ALPHA] = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
-        if (!codec[AVIF_CODEC_PLANES_ALPHA]) {
+    if (!imageIsOpaque) {
+        avifEncoderItem * alphaItem = avifEncoderDataCreateItem(encoder->data, "av01", "Alpha", 6);
+        alphaItem->image = image;
+        alphaItem->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
+        if (!alphaItem->codec) {
             return AVIF_RESULT_NO_CODEC_AVAILABLE;
         }
+        alphaItem->alpha = AVIF_TRUE;
+        alphaItem->irefToID = encoder->data->primaryItemID;
+        alphaItem->irefType = "auxl";
     }
 
     // -----------------------------------------------------------------------
-    // Validate Exif payload (if any) and find TIFF header offset
+    // Create metadata items (Exif, XMP)
 
-    uint32_t exifTiffHeaderOffset = 0;
     if (image->exif.size > 0) {
-        if (image->exif.size < 4) {
-            // Can't even fit the TIFF header, something is wrong
-            return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
-        }
-
-        const uint8_t tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
-        const uint8_t tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
-        for (; exifTiffHeaderOffset < (image->exif.size - 4); ++exifTiffHeaderOffset) {
-            if (!memcmp(&image->exif.data[exifTiffHeaderOffset], tiffHeaderBE, sizeof(tiffHeaderBE))) {
-                break;
+        // Validate Exif payload (if any) and find TIFF header offset
+        uint32_t exifTiffHeaderOffset = 0;
+        if (image->exif.size > 0) {
+            if (image->exif.size < 4) {
+                // Can't even fit the TIFF header, something is wrong
+                return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
             }
-            if (!memcmp(&image->exif.data[exifTiffHeaderOffset], tiffHeaderLE, sizeof(tiffHeaderLE))) {
-                break;
+
+            const uint8_t tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
+            const uint8_t tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
+            for (; exifTiffHeaderOffset < (image->exif.size - 4); ++exifTiffHeaderOffset) {
+                if (!memcmp(&image->exif.data[exifTiffHeaderOffset], tiffHeaderBE, sizeof(tiffHeaderBE))) {
+                    break;
+                }
+                if (!memcmp(&image->exif.data[exifTiffHeaderOffset], tiffHeaderLE, sizeof(tiffHeaderLE))) {
+                    break;
+                }
+            }
+
+            if (exifTiffHeaderOffset >= image->exif.size - 4) {
+                // Couldn't find the TIFF header
+                return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
             }
         }
 
-        if (exifTiffHeaderOffset >= image->exif.size - 4) {
-            // Couldn't find the TIFF header
-            return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
-        }
+        avifEncoderItem * exifItem = avifEncoderDataCreateItem(encoder->data, "Exif", "Exif", 5);
+        exifItem->irefToID = encoder->data->primaryItemID;
+        exifItem->irefType = "cdsc";
+
+        avifRWDataRealloc(&exifItem->content, sizeof(uint32_t) + image->exif.size);
+        exifTiffHeaderOffset = avifHTONL(exifTiffHeaderOffset);
+        memcpy(exifItem->content.data, &exifTiffHeaderOffset, sizeof(uint32_t));
+        memcpy(exifItem->content.data + sizeof(uint32_t), image->exif.data, image->exif.size);
+    }
+
+    if (image->xmp.size > 0) {
+        avifEncoderItem * xmpItem = avifEncoderDataCreateItem(encoder->data, "mime", "XMP", 4);
+        xmpItem->irefToID = encoder->data->primaryItemID;
+        xmpItem->irefType = "cdsc";
+
+        xmpItem->infeContentType = xmpContentType;
+        xmpItem->infeContentTypeSize = xmpContentTypeSize;
+        avifRWDataSet(&xmpItem->content, image->xmp.data, image->xmp.size);
     }
 
     // -----------------------------------------------------------------------
     // Pre-fill config boxes based on image (codec can query/update later)
 
-    fillConfigBox(codec[AVIF_CODEC_PLANES_COLOR], image, AVIF_FALSE);
-    if (codec[AVIF_CODEC_PLANES_ALPHA]) {
-        fillConfigBox(codec[AVIF_CODEC_PLANES_ALPHA], image, AVIF_TRUE);
+    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+        if (item->codec && item->image) {
+            fillConfigBox(item->codec, item->image, item->alpha);
+        }
     }
 
     // -----------------------------------------------------------------------
@@ -131,23 +230,23 @@
     // -----------------------------------------------------------------------
     // Encode AV1 OBUs
 
-    if (!codec[AVIF_CODEC_PLANES_COLOR]->encodeImage(codec[AVIF_CODEC_PLANES_COLOR], image, encoder, &colorOBU, AVIF_FALSE)) {
-        result = AVIF_RESULT_ENCODE_COLOR_FAILED;
-        goto writeCleanup;
-    }
+    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+        if (item->codec && item->image) {
+            if (!item->codec->encodeImage(item->codec, item->image, encoder, &item->content, item->alpha)) {
+                result = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
+                goto writeCleanup;
+            }
 
-    if (!imageIsOpaque) {
-        if (!codec[AVIF_CODEC_PLANES_ALPHA]->encodeImage(codec[AVIF_CODEC_PLANES_ALPHA], image, encoder, &alphaOBU, AVIF_TRUE)) {
-            result = AVIF_RESULT_ENCODE_ALPHA_FAILED;
-            goto writeCleanup;
+            // TODO: rethink this if/when image grid encoding support is added
+            if (item->alpha) {
+                encoder->ioStats.alphaOBUSize = item->content.size;
+            } else {
+                encoder->ioStats.colorOBUSize = item->content.size;
+            }
         }
     }
 
-    // TODO: consider collapsing all items into local structs for iteration / code sharing
-    avifBool hasAlpha = (alphaOBU.size > 0);
-    avifBool hasExif = (image->exif.size > 0);
-    avifBool hasXMP = (image->xmp.size > 0);
-
     // -----------------------------------------------------------------------
     // Write ftyp
 
@@ -184,94 +283,31 @@
     // -----------------------------------------------------------------------
     // Write pitm
 
-    avifRWStreamWriteBox(&s, "pitm", 0, sizeof(uint16_t));
-    avifRWStreamWriteU16(&s, 1); //  unsigned int(16) item_ID;
-
-    // -----------------------------------------------------------------------
-    // Calculate item IDs and counts
-
-    uint16_t itemCount = 1;
-    uint16_t colorItemID = 1;
-    uint16_t nextItemID = 2;
-    uint16_t alphaItemID = 0;
-    uint16_t exifItemID = 0;
-    uint16_t xmpItemID = 0;
-    if (hasAlpha) {
-        ++itemCount;
-        alphaItemID = nextItemID;
-        ++nextItemID;
-    }
-    if (hasExif) {
-        ++itemCount;
-        exifItemID = nextItemID;
-        ++nextItemID;
-    }
-    if (hasXMP) {
-        ++itemCount;
-        xmpItemID = nextItemID;
-        ++nextItemID;
+    if (encoder->data->primaryItemID != 0) {
+        avifRWStreamWriteBox(&s, "pitm", 0, sizeof(uint16_t));
+        avifRWStreamWriteU16(&s, encoder->data->primaryItemID); //  unsigned int(16) item_ID;
     }
 
     // -----------------------------------------------------------------------
     // Write iloc
 
-    // Remember where we want to store the offsets to the mdat OBU offsets to adjust them later.
-    // These are named as such because they are remembering the stream offset where we will write an offset later.
-    size_t colorOBUOffsetOffset = 0;
-    size_t alphaOBUOffsetOffset = 0;
-    size_t exifOffsetOffset = 0;
-    size_t xmpOffsetOffset = 0;
-
     avifBoxMarker iloc = avifRWStreamWriteBox(&s, "iloc", 0, 0);
 
-    // iloc header
-    uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size;
-                                                           // unsigned int(4) length_size;
-    avifRWStreamWrite(&s, &offsetSizeAndLengthSize, 1);    //
-    avifRWStreamWriteZeros(&s, 1);                         // unsigned int(4) base_offset_size;
-                                                           // unsigned int(4) reserved;
-    avifRWStreamWriteU16(&s, itemCount);                   // unsigned int(16) item_count;
+    uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0);          // unsigned int(4) offset_size;
+                                                                    // unsigned int(4) length_size;
+    avifRWStreamWrite(&s, &offsetSizeAndLengthSize, 1);             //
+    avifRWStreamWriteZeros(&s, 1);                                  // unsigned int(4) base_offset_size;
+                                                                    // unsigned int(4) reserved;
+    avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count); // unsigned int(16) item_count;
 
-    // Item ID #1 (Color OBU)
-    avifRWStreamWriteU16(&s, colorItemID);             // unsigned int(16) item_ID;
-    avifRWStreamWriteU16(&s, 0);                       // unsigned int(16) data_reference_index;
-    avifRWStreamWriteU16(&s, 1);                       // unsigned int(16) extent_count;
-    colorOBUOffsetOffset = avifRWStreamOffset(&s);     //
-    avifRWStreamWriteU32(&s, 0 /* set later */);       // unsigned int(offset_size*8) extent_offset;
-    avifRWStreamWriteU32(&s, (uint32_t)colorOBU.size); // unsigned int(length_size*8) extent_length;
-
-    if (hasAlpha) {
-        avifRWStreamWriteU16(&s, alphaItemID);             // unsigned int(16) item_ID;
-        avifRWStreamWriteU16(&s, 0);                       // unsigned int(16) data_reference_index;
-        avifRWStreamWriteU16(&s, 1);                       // unsigned int(16) extent_count;
-        alphaOBUOffsetOffset = avifRWStreamOffset(&s);     //
-        avifRWStreamWriteU32(&s, 0 /* set later */);       // unsigned int(offset_size*8) extent_offset;
-        avifRWStreamWriteU32(&s, (uint32_t)alphaOBU.size); // unsigned int(length_size*8) extent_length;
-    }
-
-    if (hasExif) {
-        // From ISO/IEC 23008-12:2017
-        // :: aligned(8) class ExifDataBlock() {
-        // ::     unsigned int(32) exif_tiff_header_offset;
-        // ::     unsigned int(8) exif_payload[];
-        // :: }
-        uint32_t exifDataBlockSize = (uint32_t)(sizeof(uint32_t) + image->exif.size);
-
-        avifRWStreamWriteU16(&s, exifItemID);        // unsigned int(16) item_ID;
-        avifRWStreamWriteU16(&s, 0);                 // unsigned int(16) data_reference_index;
-        avifRWStreamWriteU16(&s, 1);                 // unsigned int(16) extent_count;
-        exifOffsetOffset = avifRWStreamOffset(&s);   //
-        avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset;
-        avifRWStreamWriteU32(&s, exifDataBlockSize); // unsigned int(length_size*8) extent_length;
-    }
-
-    if (hasXMP) {
-        avifRWStreamWriteU16(&s, xmpItemID);                 // unsigned int(16) item_ID;
-        avifRWStreamWriteU16(&s, 0);                         // unsigned int(16) data_reference_index;
-        avifRWStreamWriteU16(&s, 1);                         // unsigned int(16) extent_count;
-        xmpOffsetOffset = avifRWStreamOffset(&s);            //
-        avifRWStreamWriteU32(&s, 0 /* set later */);         // unsigned int(offset_size*8) extent_offset;
-        avifRWStreamWriteU32(&s, (uint32_t)image->xmp.size); // unsigned int(length_size*8) extent_length;
+    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+        avifRWStreamWriteU16(&s, item->id);                     // unsigned int(16) item_ID;
+        avifRWStreamWriteU16(&s, 0);                            // unsigned int(16) data_reference_index;
+        avifRWStreamWriteU16(&s, 1);                            // unsigned int(16) extent_count;
+        item->infeOffsetOffset = avifRWStreamOffset(&s);        //
+        avifRWStreamWriteU32(&s, 0 /* set later */);            // unsigned int(offset_size*8) extent_offset;
+        avifRWStreamWriteU32(&s, (uint32_t)item->content.size); // unsigned int(length_size*8) extent_length;
     }
 
     avifRWStreamFinishBox(&s, iloc);
@@ -280,216 +316,168 @@
     // Write iinf
 
     avifBoxMarker iinf = avifRWStreamWriteBox(&s, "iinf", 0, 0);
-    avifRWStreamWriteU16(&s, itemCount); //  unsigned int(16) entry_count;
+    avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count); //  unsigned int(16) entry_count;
 
-    avifBoxMarker infeImage = avifRWStreamWriteBox(&s, "infe", 2, 0);
-    avifRWStreamWriteU16(&s, colorItemID);  // unsigned int(16) item_ID;
-    avifRWStreamWriteU16(&s, 0);            // unsigned int(16) item_protection_index;
-    avifRWStreamWriteChars(&s, "av01", 4);  // unsigned int(32) item_type;
-    avifRWStreamWriteChars(&s, "Color", 6); // string item_name; (writing null terminator)
-    avifRWStreamFinishBox(&s, infeImage);
-    if (hasAlpha) {
-        avifBoxMarker infeAlpha = avifRWStreamWriteBox(&s, "infe", 2, 0);
-        avifRWStreamWriteU16(&s, alphaItemID);  // unsigned int(16) item_ID;
-        avifRWStreamWriteU16(&s, 0);            // unsigned int(16) item_protection_index;
-        avifRWStreamWriteChars(&s, "av01", 4);  // unsigned int(32) item_type;
-        avifRWStreamWriteChars(&s, "Alpha", 6); // string item_name; (writing null terminator)
-        avifRWStreamFinishBox(&s, infeAlpha);
-    }
-    if (hasExif) {
-        avifBoxMarker infeExif = avifRWStreamWriteBox(&s, "infe", 2, 0);
-        avifRWStreamWriteU16(&s, exifItemID);  // unsigned int(16) item_ID;
-        avifRWStreamWriteU16(&s, 0);           // unsigned int(16) item_protection_index;
-        avifRWStreamWriteChars(&s, "Exif", 4); // unsigned int(32) item_type;
-        avifRWStreamWriteChars(&s, "Exif", 5); // string item_name; (writing null terminator)
-        avifRWStreamFinishBox(&s, infeExif);
-    }
-    if (hasXMP) {
-        avifBoxMarker infeXMP = avifRWStreamWriteBox(&s, "infe", 2, 0);
-        avifRWStreamWriteU16(&s, xmpItemID);                            // unsigned int(16) item_ID;
+    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+
+        avifBoxMarker infe = avifRWStreamWriteBox(&s, "infe", 2, 0);
+        avifRWStreamWriteU16(&s, item->id);                             // unsigned int(16) item_ID;
         avifRWStreamWriteU16(&s, 0);                                    // unsigned int(16) item_protection_index;
-        avifRWStreamWriteChars(&s, "mime", 4);                          // unsigned int(32) item_type;
-        avifRWStreamWriteChars(&s, "XMP", 4);                           // string item_name; (writing null terminator)
-        avifRWStreamWriteChars(&s, xmpContentType, xmpContentTypeSize); // string content_type; (writing null terminator)
-        avifRWStreamFinishBox(&s, infeXMP);
+        avifRWStreamWrite(&s, item->type, 4);                           // unsigned int(32) item_type;
+        avifRWStreamWriteChars(&s, item->infeName, item->infeNameSize); // string item_name; (writing null terminator)
+        if (item->infeContentType && item->infeContentTypeSize) {       // string content_type; (writing null terminator)
+            avifRWStreamWriteChars(&s, item->infeContentType, item->infeContentTypeSize);
+        }
+        avifRWStreamFinishBox(&s, infe);
     }
+
     avifRWStreamFinishBox(&s, iinf);
 
     // -----------------------------------------------------------------------
-    // Write iref (auxl) for alpha, if any
+    // Write iref boxes
 
-    if (hasAlpha) {
-        avifBoxMarker iref = avifRWStreamWriteBox(&s, "iref", 0, 0);
-        avifBoxMarker auxl = avifRWStreamWriteBox(&s, "auxl", -1, 0);
-        avifRWStreamWriteU16(&s, alphaItemID); // unsigned int(16) from_item_ID;
-        avifRWStreamWriteU16(&s, 1);           // unsigned int(16) reference_count;
-        avifRWStreamWriteU16(&s, colorItemID); // unsigned int(16) to_item_ID;
-        avifRWStreamFinishBox(&s, auxl);
-        avifRWStreamFinishBox(&s, iref);
+    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+        if (item->irefToID != 0) {
+            avifBoxMarker iref = avifRWStreamWriteBox(&s, "iref", 0, 0);
+            avifBoxMarker refType = avifRWStreamWriteBox(&s, item->irefType, -1, 0);
+            avifRWStreamWriteU16(&s, item->id);       // unsigned int(16) from_item_ID;
+            avifRWStreamWriteU16(&s, 1);              // unsigned int(16) reference_count;
+            avifRWStreamWriteU16(&s, item->irefToID); // unsigned int(16) to_item_ID;
+            avifRWStreamFinishBox(&s, refType);
+            avifRWStreamFinishBox(&s, iref);
+        }
     }
 
     // -----------------------------------------------------------------------
-    // Write iref (cdsc) for Exif, if any
-
-    if (hasExif) {
-        avifBoxMarker iref = avifRWStreamWriteBox(&s, "iref", 0, 0);
-        avifBoxMarker cdsc = avifRWStreamWriteBox(&s, "cdsc", -1, 0);
-        avifRWStreamWriteU16(&s, exifItemID);  // unsigned int(16) from_item_ID;
-        avifRWStreamWriteU16(&s, 1);           // unsigned int(16) reference_count;
-        avifRWStreamWriteU16(&s, colorItemID); // unsigned int(16) to_item_ID;
-        avifRWStreamFinishBox(&s, cdsc);
-        avifRWStreamFinishBox(&s, iref);
-    }
-
-    // -----------------------------------------------------------------------
-    // Write iref (cdsc) for XMP, if any
-
-    if (hasXMP) {
-        avifBoxMarker iref = avifRWStreamWriteBox(&s, "iref", 0, 0);
-        avifBoxMarker cdsc = avifRWStreamWriteBox(&s, "cdsc", -1, 0);
-        avifRWStreamWriteU16(&s, xmpItemID);   // unsigned int(16) from_item_ID;
-        avifRWStreamWriteU16(&s, 1);           // unsigned int(16) reference_count;
-        avifRWStreamWriteU16(&s, colorItemID); // unsigned int(16) to_item_ID;
-        avifRWStreamFinishBox(&s, cdsc);
-        avifRWStreamFinishBox(&s, iref);
-    }
-
-    // -----------------------------------------------------------------------
-    // Write iprp->ipco->ispe
+    // Write iprp -> ipco/ipma
 
     avifBoxMarker iprp = avifRWStreamWriteBox(&s, "iprp", -1, 0);
-    {
-        uint8_t ipcoIndex = 0;
-        struct ipmaArray ipmaColor;
-        memset(&ipmaColor, 0, sizeof(ipmaColor));
-        struct ipmaArray ipmaAlpha;
-        memset(&ipmaAlpha, 0, sizeof(ipmaAlpha));
 
-        avifBoxMarker ipco = avifRWStreamWriteBox(&s, "ipco", -1, 0);
-        {
-            avifBoxMarker ispe = avifRWStreamWriteBox(&s, "ispe", 0, 0);
-            avifRWStreamWriteU32(&s, image->width);  // unsigned int(32) image_width;
-            avifRWStreamWriteU32(&s, image->height); // unsigned int(32) image_height;
-            avifRWStreamFinishBox(&s, ispe);
-            ++ipcoIndex;
-            ipmaPush(&ipmaColor, ipcoIndex); // ipma is 1-indexed, doing this afterwards is correct
-            ipmaPush(&ipmaAlpha, ipcoIndex); // Alpha shares the ispe prop
+    uint8_t itemPropertyIndex = 0;
+    avifBoxMarker ipco = avifRWStreamWriteBox(&s, "ipco", -1, 0);
+    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+        memset(&item->ipma, 0, sizeof(item->ipma));
+        if (!item->image || !item->codec) {
+            // No ipma to write for this item
+            continue;
+        }
 
-            if (image->profileFormat == AVIF_PROFILE_FORMAT_NCLX) {
+        // Properties all av01 items need
+
+        avifBoxMarker ispe = avifRWStreamWriteBox(&s, "ispe", 0, 0);
+        avifRWStreamWriteU32(&s, item->image->width);  // unsigned int(32) image_width;
+        avifRWStreamWriteU32(&s, item->image->height); // unsigned int(32) image_height;
+        avifRWStreamFinishBox(&s, ispe);
+        ipmaPush(&item->ipma, ++itemPropertyIndex); // ipma is 1-indexed, doing this afterwards is correct
+
+        uint8_t channelCount = item->alpha ? 1 : 3; // TODO: write the correct value here when adding monochrome support
+        avifBoxMarker pixi = avifRWStreamWriteBox(&s, "pixi", 0, 0);
+        avifRWStreamWriteU8(&s, channelCount); // unsigned int (8) num_channels;
+        for (uint8_t chan = 0; chan < channelCount; ++chan) {
+            avifRWStreamWriteU8(&s, (uint8_t)item->image->depth); // unsigned int (8) bits_per_channel;
+        }
+        avifRWStreamFinishBox(&s, pixi);
+        ipmaPush(&item->ipma, ++itemPropertyIndex);
+
+        writeConfigBox(&s, &item->codec->configBox);
+        ipmaPush(&item->ipma, ++itemPropertyIndex);
+
+        if (item->alpha) {
+            // Alpha specific properties
+
+            avifBoxMarker auxC = avifRWStreamWriteBox(&s, "auxC", 0, 0);
+            avifRWStreamWriteChars(&s, alphaURN, alphaURNSize); //  string aux_type;
+            avifRWStreamFinishBox(&s, auxC);
+            ipmaPush(&item->ipma, ++itemPropertyIndex);
+        } else {
+            // Color specific properties
+
+            if (item->image->profileFormat == AVIF_PROFILE_FORMAT_NCLX) {
                 avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", -1, 0);
-                avifRWStreamWriteChars(&s, "nclx", 4);                         // unsigned int(32) colour_type;
-                avifRWStreamWriteU16(&s, image->nclx.colourPrimaries);         // unsigned int(16) colour_primaries;
-                avifRWStreamWriteU16(&s, image->nclx.transferCharacteristics); // unsigned int(16) transfer_characteristics;
-                avifRWStreamWriteU16(&s, image->nclx.matrixCoefficients);      // unsigned int(16) matrix_coefficients;
-                avifRWStreamWriteU8(&s, image->nclx.fullRangeFlag & 0x80);     // unsigned int(1) full_range_flag;
-                                                                               // unsigned int(7) reserved = 0;
+                avifRWStreamWriteChars(&s, "nclx", 4);                               // unsigned int(32) colour_type;
+                avifRWStreamWriteU16(&s, item->image->nclx.colourPrimaries);         // unsigned int(16) colour_primaries;
+                avifRWStreamWriteU16(&s, item->image->nclx.transferCharacteristics); // unsigned int(16) transfer_characteristics;
+                avifRWStreamWriteU16(&s, item->image->nclx.matrixCoefficients);      // unsigned int(16) matrix_coefficients;
+                avifRWStreamWriteU8(&s, item->image->nclx.fullRangeFlag & 0x80);     // unsigned int(1) full_range_flag;
+                                                                                     // unsigned int(7) reserved = 0;
                 avifRWStreamFinishBox(&s, colr);
-                ++ipcoIndex;
-                ipmaPush(&ipmaColor, ipcoIndex);
-            } else if ((image->profileFormat == AVIF_PROFILE_FORMAT_ICC) && image->icc.data && (image->icc.size > 0)) {
+                ipmaPush(&item->ipma, ++itemPropertyIndex);
+            } else if ((item->image->profileFormat == AVIF_PROFILE_FORMAT_ICC) && item->image->icc.data && (item->image->icc.size > 0)) {
                 avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", -1, 0);
                 avifRWStreamWriteChars(&s, "prof", 4); // unsigned int(32) colour_type;
-                avifRWStreamWrite(&s, image->icc.data, image->icc.size);
+                avifRWStreamWrite(&s, item->image->icc.data, item->image->icc.size);
                 avifRWStreamFinishBox(&s, colr);
-                ++ipcoIndex;
-                ipmaPush(&ipmaColor, ipcoIndex);
+                ipmaPush(&item->ipma, ++itemPropertyIndex);
             }
 
-            avifBoxMarker pixiC = avifRWStreamWriteBox(&s, "pixi", 0, 0);
-            avifRWStreamWriteU8(&s, 3);                     // unsigned int (8) num_channels;
-            avifRWStreamWriteU8(&s, (uint8_t)image->depth); // unsigned int (8) bits_per_channel;
-            avifRWStreamWriteU8(&s, (uint8_t)image->depth); // unsigned int (8) bits_per_channel;
-            avifRWStreamWriteU8(&s, (uint8_t)image->depth); // unsigned int (8) bits_per_channel;
-            avifRWStreamFinishBox(&s, pixiC);
-            ++ipcoIndex;
-            ipmaPush(&ipmaColor, ipcoIndex);
-
-            writeConfigBox(&s, &codec[AVIF_CODEC_PLANES_COLOR]->configBox);
-            ++ipcoIndex;
-            ipmaPush(&ipmaColor, ipcoIndex);
-
             // Write (Optional) Transformations
-            if (image->transformFlags & AVIF_TRANSFORM_PASP) {
+            if (item->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;
+                avifRWStreamWriteU32(&s, item->image->pasp.hSpacing); // unsigned int(32) hSpacing;
+                avifRWStreamWriteU32(&s, item->image->pasp.vSpacing); // unsigned int(32) vSpacing;
                 avifRWStreamFinishBox(&s, pasp);
-                ++ipcoIndex;
-                ipmaPush(&ipmaColor, ipcoIndex);
+                ipmaPush(&item->ipma, ++itemPropertyIndex);
             }
-            if (image->transformFlags & AVIF_TRANSFORM_CLAP) {
+            if (item->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;
+                avifRWStreamWriteU32(&s, item->image->clap.widthN);    // unsigned int(32) cleanApertureWidthN;
+                avifRWStreamWriteU32(&s, item->image->clap.widthD);    // unsigned int(32) cleanApertureWidthD;
+                avifRWStreamWriteU32(&s, item->image->clap.heightN);   // unsigned int(32) cleanApertureHeightN;
+                avifRWStreamWriteU32(&s, item->image->clap.heightD);   // unsigned int(32) cleanApertureHeightD;
+                avifRWStreamWriteU32(&s, item->image->clap.horizOffN); // unsigned int(32) horizOffN;
+                avifRWStreamWriteU32(&s, item->image->clap.horizOffD); // unsigned int(32) horizOffD;
+                avifRWStreamWriteU32(&s, item->image->clap.vertOffN);  // unsigned int(32) vertOffN;
+                avifRWStreamWriteU32(&s, item->image->clap.vertOffD);  // unsigned int(32) vertOffD;
                 avifRWStreamFinishBox(&s, clap);
-                ++ipcoIndex;
-                ipmaPush(&ipmaColor, ipcoIndex);
+                ipmaPush(&item->ipma, ++itemPropertyIndex);
             }
-            if (image->transformFlags & AVIF_TRANSFORM_IROT) {
+            if (item->image->transformFlags & AVIF_TRANSFORM_IROT) {
                 avifBoxMarker irot = avifRWStreamWriteBox(&s, "irot", -1, 0);
-                uint8_t angle = image->irot.angle & 0x3;
+                uint8_t angle = item->image->irot.angle & 0x3;
                 avifRWStreamWrite(&s, &angle, 1); // unsigned int (6) reserved = 0; unsigned int (2) angle;
                 avifRWStreamFinishBox(&s, irot);
-                ++ipcoIndex;
-                ipmaPush(&ipmaColor, ipcoIndex);
+                ipmaPush(&item->ipma, ++itemPropertyIndex);
             }
-            if (image->transformFlags & AVIF_TRANSFORM_IMIR) {
+            if (item->image->transformFlags & AVIF_TRANSFORM_IMIR) {
                 avifBoxMarker imir = avifRWStreamWriteBox(&s, "imir", -1, 0);
-                uint8_t axis = image->imir.axis & 0x1;
+                uint8_t axis = item->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;
-                avifRWStreamWriteU8(&s, (uint8_t)image->depth); // unsigned int (8) bits_per_channel;
-                avifRWStreamFinishBox(&s, pixiA);
-                ++ipcoIndex;
-                ipmaPush(&ipmaAlpha, ipcoIndex);
-
-                writeConfigBox(&s, &codec[AVIF_CODEC_PLANES_ALPHA]->configBox);
-                ++ipcoIndex;
-                ipmaPush(&ipmaAlpha, ipcoIndex);
-
-                avifBoxMarker auxC = avifRWStreamWriteBox(&s, "auxC", 0, 0);
-                avifRWStreamWriteChars(&s, alphaURN, alphaURNSize); //  string aux_type;
-                avifRWStreamFinishBox(&s, auxC);
-                ++ipcoIndex;
-                ipmaPush(&ipmaAlpha, ipcoIndex);
+                ipmaPush(&item->ipma, ++itemPropertyIndex);
             }
         }
-        avifRWStreamFinishBox(&s, ipco);
-
-        avifBoxMarker ipma = avifRWStreamWriteBox(&s, "ipma", 0, 0);
-        {
-            int ipmaCount = hasAlpha ? 2 : 1;
-            avifRWStreamWriteU32(&s, ipmaCount); // unsigned int(32) entry_count;
-
-            avifRWStreamWriteU16(&s, 1);                            // unsigned int(16) item_ID;
-            avifRWStreamWriteU8(&s, ipmaColor.count);               // unsigned int(8) association_count;
-            for (int i = 0; i < ipmaColor.count; ++i) {             //
-                avifRWStreamWriteU8(&s, ipmaColor.associations[i]); // bit(1) essential; unsigned int(7) property_index;
-            }
-
-            if (hasAlpha) {
-                avifRWStreamWriteU16(&s, 2);                            // unsigned int(16) item_ID;
-                avifRWStreamWriteU8(&s, ipmaAlpha.count);               // unsigned int(8) association_count;
-                for (int i = 0; i < ipmaAlpha.count; ++i) {             //
-                    avifRWStreamWriteU8(&s, ipmaAlpha.associations[i]); // bit(1) essential; unsigned int(7) property_index;
-                }
-            }
-        }
-        avifRWStreamFinishBox(&s, ipma);
     }
+    avifRWStreamFinishBox(&s, ipco);
+
+    avifBoxMarker ipma = avifRWStreamWriteBox(&s, "ipma", 0, 0);
+    {
+        int ipmaCount = 0;
+        for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+            avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+            if (item->ipma.count > 0) {
+                ++ipmaCount;
+            }
+        }
+        avifRWStreamWriteU32(&s, ipmaCount); // unsigned int(32) entry_count;
+
+        for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+            avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+            if (item->ipma.count == 0) {
+                continue;
+            }
+
+            avifRWStreamWriteU16(&s, item->id);                      // unsigned int(16) item_ID;
+            avifRWStreamWriteU8(&s, item->ipma.count);               // unsigned int(8) association_count;
+            for (int i = 0; i < item->ipma.count; ++i) {             //
+                avifRWStreamWriteU8(&s, item->ipma.associations[i]); // bit(1) essential; unsigned int(7) property_index;
+            }
+        }
+    }
+    avifRWStreamFinishBox(&s, ipma);
+
     avifRWStreamFinishBox(&s, iprp);
 
     // -----------------------------------------------------------------------
@@ -501,65 +489,35 @@
     // Write mdat
 
     avifBoxMarker mdat = avifRWStreamWriteBox(&s, "mdat", -1, 0);
-    uint32_t colorOBUOffset = (uint32_t)s.offset;
-    avifRWStreamWrite(&s, colorOBU.data, colorOBU.size);
-    uint32_t alphaOBUOffset = (uint32_t)s.offset;
-    avifRWStreamWrite(&s, alphaOBU.data, alphaOBU.size);
-    uint32_t exifOffset = (uint32_t)s.offset;
-    if (image->exif.size > 0) {
-        avifRWStreamWriteU32(&s, (uint32_t)exifTiffHeaderOffset); // unsigned int(32) exif_tiff_header_offset; (Annex A.2.1)
-        avifRWStreamWrite(&s, image->exif.data, image->exif.size);
+    for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+        avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+        if (item->content.size == 0) {
+            continue;
+        }
+
+        uint32_t infeOffset = (uint32_t)s.offset;
+        avifRWStreamWrite(&s, item->content.data, item->content.size);
+
+        if (item->infeOffsetOffset != 0) {
+            size_t prevOffset = avifRWStreamOffset(&s);
+            avifRWStreamSetOffset(&s, item->infeOffsetOffset);
+            avifRWStreamWriteU32(&s, infeOffset);
+            avifRWStreamSetOffset(&s, prevOffset);
+        }
     }
-    uint32_t xmpOffset = (uint32_t)s.offset;
-    avifRWStreamWrite(&s, image->xmp.data, image->xmp.size);
     avifRWStreamFinishBox(&s, mdat);
 
     // -----------------------------------------------------------------------
     // Finish up stream
 
-    // Set offsets needed in meta box based on where we eventually wrote mdat
-    size_t prevOffset = avifRWStreamOffset(&s);
-    if (colorOBUOffsetOffset != 0) {
-        avifRWStreamSetOffset(&s, colorOBUOffsetOffset);
-        avifRWStreamWriteU32(&s, colorOBUOffset);
-    }
-    if (alphaOBUOffsetOffset != 0) {
-        avifRWStreamSetOffset(&s, alphaOBUOffsetOffset);
-        avifRWStreamWriteU32(&s, alphaOBUOffset);
-    }
-    if (exifOffsetOffset != 0) {
-        avifRWStreamSetOffset(&s, exifOffsetOffset);
-        avifRWStreamWriteU32(&s, exifOffset);
-    }
-    if (xmpOffsetOffset != 0) {
-        avifRWStreamSetOffset(&s, xmpOffsetOffset);
-        avifRWStreamWriteU32(&s, xmpOffset);
-    }
-    avifRWStreamSetOffset(&s, prevOffset);
-
-    // Close write stream
     avifRWStreamFinishWrite(&s);
 
     // -----------------------------------------------------------------------
-    // IO stats
-
-    encoder->ioStats.colorOBUSize = colorOBU.size;
-    encoder->ioStats.alphaOBUSize = alphaOBU.size;
-
-    // -----------------------------------------------------------------------
     // Set result and cleanup
 
     result = AVIF_RESULT_OK;
 
 writeCleanup:
-    if (codec[AVIF_CODEC_PLANES_COLOR]) {
-        avifCodecDestroy(codec[AVIF_CODEC_PLANES_COLOR]);
-    }
-    if (codec[AVIF_CODEC_PLANES_ALPHA]) {
-        avifCodecDestroy(codec[AVIF_CODEC_PLANES_ALPHA]);
-    }
-    avifRWDataFree(&colorOBU);
-    avifRWDataFree(&alphaOBU);
     return result;
 }