Introduce avifDiagnostics, which allows for a detailed, freeform error string upon decode error
diff --git a/src/read.c b/src/read.c
index 632c97e..f94a9d1 100644
--- a/src/read.c
+++ b/src/read.c
@@ -4,6 +4,7 @@
 #include "avif/internal.h"
 
 #include <assert.h>
+#include <inttypes.h>
 #include <limits.h>
 #include <string.h>
 
@@ -350,7 +351,8 @@
 static avifBool avifCodecDecodeInputGetSamples(avifCodecDecodeInput * decodeInput,
                                                avifSampleTable * sampleTable,
                                                const uint32_t imageCountLimit,
-                                               const uint64_t sizeHint)
+                                               const uint64_t sizeHint,
+                                               avifDiagnostics * diag)
 {
     if (imageCountLimit) {
         // Verify that the we're not about to exceed the frame count limit.
@@ -361,11 +363,13 @@
             uint32_t sampleCount = avifGetSampleCountOfChunk(&sampleTable->sampleToChunks, chunkIndex);
             if (sampleCount == 0) {
                 // chunks with 0 samples are invalid
+                avifDiagnosticsPrintf(diag, "Sample table contains a chunk with 0 samples");
                 return AVIF_FALSE;
             }
 
             if (sampleCount > imageCountLeft) {
                 // This file exceeds the imageCountLimit, bail out
+                avifDiagnosticsPrintf(diag, "Exceeded avifDecoder's imageCountLimit");
                 return AVIF_FALSE;
             }
             imageCountLeft -= sampleCount;
@@ -380,6 +384,7 @@
         uint32_t sampleCount = avifGetSampleCountOfChunk(&sampleTable->sampleToChunks, chunkIndex);
         if (sampleCount == 0) {
             // chunks with 0 samples are invalid
+            avifDiagnosticsPrintf(diag, "Sample table contains a chunk with 0 samples");
             return AVIF_FALSE;
         }
 
@@ -389,6 +394,7 @@
             if (sampleSize == 0) {
                 if (sampleSizeIndex >= sampleTable->sampleSizes.count) {
                     // We've run out of samples to sum
+                    avifDiagnosticsPrintf(diag, "Truncated sample table");
                     return AVIF_FALSE;
                 }
                 avifSampleTableSampleSize * sampleSizePtr = &sampleTable->sampleSizes.sampleSize[sampleSizeIndex];
@@ -401,9 +407,14 @@
             sample->sync = AVIF_FALSE; // to potentially be set to true following the outer loop
 
             if (sampleSize > UINT64_MAX - sampleOffset) {
+                avifDiagnosticsPrintf(diag,
+                                      "Sample table contains an offset/size pair which overflows: [%" PRIu64 " / %" PRIu64 "]",
+                                      sampleOffset,
+                                      sampleSize);
                 return AVIF_FALSE;
             }
             if (sizeHint && ((sampleOffset + sampleSize) > sizeHint)) {
+                avifDiagnosticsPrintf(diag, "Exceeded avifIO's sizeHint, possibly truncated data");
                 return AVIF_FALSE;
             }
 
@@ -441,11 +452,12 @@
 // been seen yet, when parsing a parent box. If the "seen" bit is already set for a given box when
 // it is encountered during parse, an error is thrown. Which bit corresponds to which box is
 // dictated entirely by the calling function.
-static avifBool uniqueBoxSeen(uint32_t * uniqueBoxFlags, uint32_t whichFlag)
+static avifBool uniqueBoxSeen(uint32_t * uniqueBoxFlags, uint32_t whichFlag, const char * parentBoxType, const char * boxType, avifDiagnostics * diagnostics)
 {
     const uint32_t flag = 1 << whichFlag;
     if (*uniqueBoxFlags & flag) {
         // This box has already been seen. Error!
+        avifDiagnosticsPrintf(diagnostics, "Box type '%s' contains a duplicate unique box of type '%s'", parentBoxType, boxType);
         return AVIF_FALSE;
     }
 
@@ -568,6 +580,7 @@
     avifImageGrid colorGrid;
     avifImageGrid alphaGrid;
     avifDecoderSource source;
+    avifDiagnostics * diag;                    // Shallow copy; owned by avifDecoder
     const avifSampleTable * sourceSampleTable; // NULL unless (source == AVIF_DECODER_SOURCE_TRACKS), owned by an avifTrack
     avifBool cicpSet;                          // True if avifDecoder's image has had its CICP set correctly yet.
                                                // This allows nclx colr boxes to override AV1 CICP, as specified in the MIAF
@@ -869,6 +882,7 @@
             if (item->hasUnsupportedEssentialProperty) {
                 // An essential property isn't supported by libavif; can't
                 // decode a grid image if any tile in the grid isn't supported.
+                avifDiagnosticsPrintf(data->diag, "Grid image contains tile with an unsupported property marked as essential");
                 return AVIF_FALSE;
             }
 
@@ -877,6 +891,12 @@
     }
 
     if (tilesRequested != tilesAvailable) {
+        avifDiagnosticsPrintf(data->diag,
+                              "Grid image of dimensions %u/%u requires %u tiles, and only %u were found",
+                              grid->columns,
+                              grid->rows,
+                              tilesRequested,
+                              tilesAvailable);
         return AVIF_FALSE;
     }
 
@@ -903,6 +923,7 @@
                 // the top-level color/alpha item during avifDecoderReset().
                 const avifProperty * srcProp = avifPropertyArrayFind(&item->properties, "av1C");
                 if (!srcProp) {
+                    avifDiagnosticsPrintf(data->diag, "Grid image's first tile is missing an av1C property");
                     return AVIF_FALSE;
                 }
                 avifProperty * dstProp = (avifProperty *)avifArrayPushPtr(&gridItem->properties);
@@ -921,6 +942,7 @@
                                              avifBool alpha)
 {
     if (tileCount == 0) {
+        avifDiagnosticsPrintf(data->diag, "Cannot fill grid image, no tiles");
         return AVIF_FALSE;
     }
 
@@ -938,6 +960,7 @@
             (tile->image->transferCharacteristics != firstTile->image->transferCharacteristics) ||
             (tile->image->matrixCoefficients != firstTile->image->matrixCoefficients) ||
             (tile->image->alphaRange != firstTile->image->alphaRange)) {
+            avifDiagnosticsPrintf(data->diag, "Grid image contains mismatched tiles");
             return AVIF_FALSE;
         }
     }
@@ -948,29 +971,45 @@
     //   The tiled input images shall completely "cover" the reconstructed image grid canvas, ...
     if (((firstTile->image->width * grid->columns) < grid->outputWidth) ||
         ((firstTile->image->height * grid->rows) < grid->outputHeight)) {
+        avifDiagnosticsPrintf(data->diag,
+                              "Grid image tiles do not completely cover the image (HEIF (ISO/IEC 23008-12:2017), Section 6.6.2.3.1)");
         return AVIF_FALSE;
     }
     // Tiles in the rightmost column and bottommost row must overlap the reconstructed image grid canvas. See MIAF (ISO/IEC 23000-22:2019), Section 7.3.11.4.2, Figure 2.
     if (((firstTile->image->width * (grid->columns - 1)) >= grid->outputWidth) ||
         ((firstTile->image->height * (grid->rows - 1)) >= grid->outputHeight)) {
+        avifDiagnosticsPrintf(data->diag,
+                              "Grid image tiles in the rightmost column and bottommost row do not overlap the reconstructed image grid canvas. See MIAF (ISO/IEC 23000-22:2019), Section 7.3.11.4.2, Figure 2");
         return AVIF_FALSE;
     }
     // Check the restrictions in MIAF (ISO/IEC 23000-22:2019), Section 7.3.11.4.2.
     //
     // The tile_width shall be greater than or equal to 64, and the tile_height shall be greater than or equal to 64.
     if ((firstTile->image->width < 64) || (firstTile->image->height < 64)) {
+        avifDiagnosticsPrintf(data->diag,
+                              "Grid image tiles are smaller than 64x64 (%u/%u). See MIAF (ISO/IEC 23000-22:2019), Section 7.3.11.4.2",
+                              firstTile->image->width,
+                              firstTile->image->height);
         return AVIF_FALSE;
     }
     if (!alpha) {
         if ((firstTile->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV422) || (firstTile->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420)) {
             // The horizontal tile offsets and widths, and the output width, shall be even numbers.
             if (((firstTile->image->width & 1) != 0) || ((grid->outputWidth & 1) != 0)) {
+                avifDiagnosticsPrintf(data->diag,
+                                      "Grid image horizontal tile offsets and widths [%u], and the output width [%u], shall be even numbers.",
+                                      firstTile->image->width,
+                                      grid->outputWidth);
                 return AVIF_FALSE;
             }
         }
         if (firstTile->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) {
             // The vertical tile offsets and heights, and the output height, shall be even numbers.
             if (((firstTile->image->height & 1) != 0) || ((grid->outputHeight & 1) != 0)) {
+                avifDiagnosticsPrintf(data->diag,
+                                      "Grid image vertical tile offsets and heights [%u], and the output height [%u], shall be even numbers.",
+                                      firstTile->image->height,
+                                      grid->outputHeight);
                 return AVIF_FALSE;
             }
         }
@@ -982,6 +1021,7 @@
         (dstImage->depth != firstTile->image->depth) || (!alpha && (dstImage->yuvFormat != firstTile->image->yuvFormat))) {
         if (alpha) {
             // Alpha doesn't match size, just bail out
+            avifDiagnosticsPrintf(data->diag, "Alpha plane dimensions do not match color plane dimensions");
             return AVIF_FALSE;
         }
 
@@ -1136,7 +1176,7 @@
 // ---------------------------------------------------------------------------
 // BMFF Parsing
 
-static avifBool avifParseHandlerBox(const uint8_t * raw, size_t rawLen)
+static avifBool avifParseHandlerBox(const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1145,12 +1185,14 @@
     uint32_t predefined;
     CHECK(avifROStreamReadU32(&s, &predefined)); // unsigned int(32) pre_defined = 0;
     if (predefined != 0) {
+        avifDiagnosticsPrintf(diag, "Box type 'hdlr' contains a pre_defined value that is nonzero");
         return AVIF_FALSE;
     }
 
     uint8_t handlerType[4];
     CHECK(avifROStreamRead(&s, handlerType, 4)); // unsigned int(32) handler_type;
     if (memcmp(handlerType, "pict", 4) != 0) {
+        avifDiagnosticsPrintf(diag, "Box type 'hdlr' handler_type is not 'pict'");
         return AVIF_FALSE;
     }
 
@@ -1164,13 +1206,14 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemLocationBox(avifMeta * meta, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemLocationBox(avifMeta * meta, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
     uint8_t version;
     CHECK(avifROStreamReadVersionAndFlags(&s, &version, NULL));
     if (version > 2) {
+        avifDiagnosticsPrintf(diag, "Box type 'iloc' has an unsupported version [%u]", version);
         return AVIF_FALSE;
     }
 
@@ -1187,6 +1230,7 @@
         indexSize = baseOffsetSizeAndIndexSize & 0xf; // unsigned int(4) index_size;
         if (indexSize != 0) {
             // extent_index unsupported
+            avifDiagnosticsPrintf(diag, "Box type 'iloc' has an unsupported extent_index");
             return AVIF_FALSE;
         }
     }
@@ -1217,6 +1261,7 @@
             constructionMethod = constructionMethod & 0xf;
             if ((constructionMethod != 0 /* file */) && (constructionMethod != 1 /* idat */)) {
                 // construction method item(2) unsupported
+                avifDiagnosticsPrintf(diag, "Box type 'iloc' has an unsupported construction method [%u]", constructionMethod);
                 return AVIF_FALSE;
             }
             if (constructionMethod == 1) {
@@ -1226,10 +1271,12 @@
 
         avifDecoderItem * item = avifMetaFindItem(meta, itemID);
         if (!item) {
+            avifDiagnosticsPrintf(diag, "Box type 'iloc' has an invalid item ID [%u]", itemID);
             return AVIF_FALSE;
         }
         if (item->extents.count > 0) {
             // This item has already been given extents via this iloc box. This is invalid.
+            avifDiagnosticsPrintf(diag, "Item ID [%u] contains duplicate sets of extents", itemID);
             return AVIF_FALSE;
         }
         item->idatID = idatID;
@@ -1253,15 +1300,26 @@
 
             avifExtent * extent = (avifExtent *)avifArrayPushPtr(&item->extents);
             if (extentOffset > UINT64_MAX - baseOffset) {
+                avifDiagnosticsPrintf(diag,
+                                      "Item ID [%u] contains an extent offset which overflows: [base: %" PRIu64 " offset:%" PRIu64 "]",
+                                      itemID,
+                                      baseOffset,
+                                      extentOffset);
                 return AVIF_FALSE;
             }
             uint64_t offset = baseOffset + extentOffset;
             extent->offset = offset;
             if (extentLength > SIZE_MAX) {
+                avifDiagnosticsPrintf(diag, "Item ID [%u] contains an extent length which overflows: [%" PRIu64 "]", itemID, extentLength);
                 return AVIF_FALSE;
             }
             extent->size = (size_t)extentLength;
             if (extent->size > SIZE_MAX - item->size) {
+                avifDiagnosticsPrintf(diag,
+                                      "Item ID [%u] contains an extent length which overflows the item size: [%" PRIu64 ", %" PRIu64 "]",
+                                      itemID,
+                                      extent->size,
+                                      item->size);
                 return AVIF_FALSE;
             }
             item->size += extent->size;
@@ -1270,7 +1328,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1296,12 +1354,14 @@
     } else {
         if (fieldLength != 32) {
             // This should be impossible
+            avifDiagnosticsPrintf(diag, "Grid box contains illegal field length: [%u]", fieldLength);
             return AVIF_FALSE;
         }
         CHECK(avifROStreamReadU32(&s, &grid->outputWidth));  // unsigned int(FieldLength) output_width;
         CHECK(avifROStreamReadU32(&s, &grid->outputHeight)); // unsigned int(FieldLength) output_height;
     }
     if ((grid->outputWidth == 0) || (grid->outputHeight == 0) || (grid->outputWidth > (AVIF_MAX_IMAGE_SIZE / grid->outputHeight))) {
+        avifDiagnosticsPrintf(diag, "Grid box contains illegal dimensions: [%u x %u]", grid->outputWidth, grid->outputHeight);
         return AVIF_FALSE;
     }
     return avifROStreamRemainingBytes(&s) == 0;
@@ -1355,7 +1415,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseAV1CodecConfigurationBox(const uint8_t * raw, size_t rawLen, avifCodecConfigurationBox * av1C)
+static avifBool avifParseAV1CodecConfigurationBox(const uint8_t * raw, size_t rawLen, avifCodecConfigurationBox * av1C, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1368,6 +1428,7 @@
 
     if (markerAndVersion != 0x81) {
         // Marker and version must both == 1
+        avifDiagnosticsPrintf(diag, "av1C contains illegal marker and version pair: [%u]", markerAndVersion);
         return AVIF_FALSE;
     }
 
@@ -1383,9 +1444,9 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseAV1CodecConfigurationBoxProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseAV1CodecConfigurationBoxProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
-    return avifParseAV1CodecConfigurationBox(raw, rawLen, &prop->u.av1C);
+    return avifParseAV1CodecConfigurationBox(raw, rawLen, &prop->u.av1C, diag);
 }
 
 static avifBool avifParsePixelAspectRatioBoxProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen)
@@ -1414,7 +1475,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseImageRotationProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseImageRotationProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1422,12 +1483,13 @@
     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
+        avifDiagnosticsPrintf(diag, "Box type 'irot' contains nonzero reserved bits [%u]", irot->angle);
         return AVIF_FALSE;
     }
     return AVIF_TRUE;
 }
 
-static avifBool avifParseImageMirrorProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseImageMirrorProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1435,12 +1497,13 @@
     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
+        avifDiagnosticsPrintf(diag, "Box type 'imir' contains nonzero reserved bits [%u]", imir->axis);
         return AVIF_FALSE;
     }
     return AVIF_TRUE;
 }
 
-static avifBool avifParsePixelInformationProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen)
+static avifBool avifParsePixelInformationProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
     CHECK(avifROStreamReadAndEnforceVersion(&s, 0));
@@ -1448,6 +1511,7 @@
     avifPixelInformationProperty * pixi = &prop->u.pixi;
     CHECK(avifROStreamRead(&s, &pixi->planeCount, 1)); // unsigned int (8) num_channels;
     if (pixi->planeCount > MAX_PIXI_PLANE_DEPTHS) {
+        avifDiagnosticsPrintf(diag, "Box type 'pixi' contains unsupported plane count [%u]", pixi->planeCount);
         return AVIF_FALSE;
     }
     for (uint8_t i = 0; i < pixi->planeCount; ++i) {
@@ -1456,7 +1520,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemPropertyContainerBox(avifPropertyArray * properties, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemPropertyContainerBox(avifPropertyArray * properties, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1474,17 +1538,17 @@
         } else if (!memcmp(header.type, "colr", 4)) {
             CHECK(avifParseColourInformationBox(prop, avifROStreamCurrent(&s), header.size));
         } else if (!memcmp(header.type, "av1C", 4)) {
-            CHECK(avifParseAV1CodecConfigurationBoxProperty(prop, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParseAV1CodecConfigurationBoxProperty(prop, avifROStreamCurrent(&s), header.size, diag));
         } else if (!memcmp(header.type, "pasp", 4)) {
             CHECK(avifParsePixelAspectRatioBoxProperty(prop, avifROStreamCurrent(&s), header.size));
         } else if (!memcmp(header.type, "clap", 4)) {
             CHECK(avifParseCleanApertureBoxProperty(prop, avifROStreamCurrent(&s), header.size));
         } else if (!memcmp(header.type, "irot", 4)) {
-            CHECK(avifParseImageRotationProperty(prop, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParseImageRotationProperty(prop, avifROStreamCurrent(&s), header.size, diag));
         } else if (!memcmp(header.type, "imir", 4)) {
-            CHECK(avifParseImageMirrorProperty(prop, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParseImageMirrorProperty(prop, avifROStreamCurrent(&s), header.size, diag));
         } else if (!memcmp(header.type, "pixi", 4)) {
-            CHECK(avifParsePixelInformationProperty(prop, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParsePixelInformationProperty(prop, avifROStreamCurrent(&s), header.size, diag));
         }
 
         CHECK(avifROStreamSkip(&s, header.size));
@@ -1492,7 +1556,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemPropertyAssociation(avifMeta * meta, const uint8_t * raw, size_t rawLen, uint32_t * outVersionAndFlags)
+static avifBool avifParseItemPropertyAssociation(avifMeta * meta, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag, uint32_t * outVersionAndFlags)
 {
     // NOTE: If this function ever adds support for versions other than [0,1] or flags other than
     //       [0,1], please increase the value of MAX_IPMA_VERSION_AND_FLAGS_SEEN accordingly.
@@ -1521,15 +1585,18 @@
             CHECK(avifROStreamReadU32(&s, &itemID));
         }
         if (itemID <= prevItemID) {
+            avifDiagnosticsPrintf(diag, "Box type 'ipma' item IDs are not ordered by increasing ID");
             return AVIF_FALSE;
         }
         prevItemID = itemID;
 
         avifDecoderItem * item = avifMetaFindItem(meta, itemID);
         if (!item) {
+            avifDiagnosticsPrintf(diag, "Box type 'ipma' has an invalid item ID [%u]", itemID);
             return AVIF_FALSE;
         }
         if (item->ipmaSeen) {
+            avifDiagnosticsPrintf(diag, "Duplicate box type 'ipma' for item ID [%u]", itemID);
             return AVIF_FALSE;
         }
         item->ipmaSeen = AVIF_TRUE;
@@ -1557,6 +1624,11 @@
             --propertyIndex; // 1-indexed
 
             if (propertyIndex >= meta->properties.count) {
+                avifDiagnosticsPrintf(diag,
+                                      "Box type 'ipma' for item ID [%u] contains an illegal property index [%u] (out of [%u] properties)",
+                                      itemID,
+                                      propertyIndex,
+                                      meta->properties.count);
                 return AVIF_FALSE;
             }
 
@@ -1588,10 +1660,11 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParsePrimaryItemBox(avifMeta * meta, const uint8_t * raw, size_t rawLen)
+static avifBool avifParsePrimaryItemBox(avifMeta * meta, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     if (meta->primaryItemID > 0) {
         // Illegal to have multiple pitm boxes, bail out
+        avifDiagnosticsPrintf(diag, "Multiple boxes of unique box type 'pitm' found");
         return AVIF_FALSE;
     }
 
@@ -1610,11 +1683,12 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemDataBox(avifMeta * meta, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemDataBox(avifMeta * meta, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     // Check to see if we've already seen an idat box for this meta box. If so, bail out
     for (uint32_t i = 0; i < meta->idats.count; ++i) {
         if (meta->idats.idat[i].id == meta->idatID) {
+            avifDiagnosticsPrintf(diag, "Meta box contains multiple idat boxes");
             return AVIF_FALSE;
         }
     }
@@ -1626,7 +1700,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemPropertiesBox(avifMeta * meta, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemPropertiesBox(avifMeta * meta, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1637,7 +1711,7 @@
     }
 
     // Read all item properties inside of ItemPropertyContainerBox
-    CHECK(avifParseItemPropertyContainerBox(&meta->properties, avifROStreamCurrent(&s), ipcoHeader.size));
+    CHECK(avifParseItemPropertyContainerBox(&meta->properties, avifROStreamCurrent(&s), ipcoHeader.size, diag));
     CHECK(avifROStreamSkip(&s, ipcoHeader.size));
 
     uint32_t versionAndFlagsSeen[MAX_IPMA_VERSION_AND_FLAGS_SEEN];
@@ -1650,22 +1724,25 @@
 
         if (!memcmp(ipmaHeader.type, "ipma", 4)) {
             uint32_t versionAndFlags;
-            CHECK(avifParseItemPropertyAssociation(meta, avifROStreamCurrent(&s), ipmaHeader.size, &versionAndFlags));
+            CHECK(avifParseItemPropertyAssociation(meta, avifROStreamCurrent(&s), ipmaHeader.size, diag, &versionAndFlags));
             for (uint32_t i = 0; i < versionAndFlagsSeenCount; ++i) {
                 if (versionAndFlagsSeen[i] == versionAndFlags) {
                     // HEIF (ISO 23008-12:2017) 9.3.1 - There shall be at most one
                     // ItemPropertyAssociation box with a given pair of values of version and
                     // flags.
+                    avifDiagnosticsPrintf(diag, "Multiple box type 'ipma' with a given pair of values of version and flags. See HEIF (ISO 23008-12:2017) 9.3.1");
                     return AVIF_FALSE;
                 }
             }
             if (versionAndFlagsSeenCount == MAX_IPMA_VERSION_AND_FLAGS_SEEN) {
+                avifDiagnosticsPrintf(diag, "Exceeded possible count of unique ipma version and flags tuples");
                 return AVIF_FALSE;
             }
             versionAndFlagsSeen[versionAndFlagsSeenCount] = versionAndFlags;
             ++versionAndFlagsSeenCount;
         } else {
             // These must all be type ipma
+            avifDiagnosticsPrintf(diag, "Box type 'iprp' contains a box that isn't type 'ipma'");
             return AVIF_FALSE;
         }
 
@@ -1674,7 +1751,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemInfoEntry(avifMeta * meta, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemInfoEntry(avifMeta * meta, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1697,6 +1774,7 @@
 
     avifDecoderItem * item = avifMetaFindItem(meta, itemID);
     if (!item) {
+        avifDiagnosticsPrintf(diag, "Box type 'infe' has an invalid item ID [%u]", itemID);
         return AVIF_FALSE;
     }
 
@@ -1705,7 +1783,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemInfoBox(avifMeta * meta, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemInfoBox(avifMeta * meta, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1727,7 +1805,7 @@
         CHECK(avifROStreamReadBoxHeader(&s, &infeHeader));
 
         if (!memcmp(infeHeader.type, "infe", 4)) {
-            CHECK(avifParseItemInfoEntry(meta, avifROStreamCurrent(&s), infeHeader.size));
+            CHECK(avifParseItemInfoEntry(meta, avifROStreamCurrent(&s), infeHeader.size, diag));
         } else {
             // These must all be type ipma
             return AVIF_FALSE;
@@ -1739,7 +1817,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseItemReferenceBox(avifMeta * meta, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseItemReferenceBox(avifMeta * meta, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1782,6 +1860,7 @@
             if (fromID && toID) {
                 avifDecoderItem * item = avifMetaFindItem(meta, fromID);
                 if (!item) {
+                    avifDiagnosticsPrintf(diag, "Box type 'iref' has an invalid item ID [%u]", fromID);
                     return AVIF_FALSE;
                 }
 
@@ -1795,6 +1874,7 @@
                     // derived images refer in the opposite direction
                     avifDecoderItem * dimg = avifMetaFindItem(meta, toID);
                     if (!dimg) {
+                        avifDiagnosticsPrintf(diag, "Box type 'iref' has an invalid item ID dimg ref [%u]", toID);
                         return AVIF_FALSE;
                     }
 
@@ -1809,7 +1889,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseMetaBox(avifMeta * meta, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseMetaBox(avifMeta * meta, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1825,31 +1905,31 @@
 
         if (firstBox) {
             if (!memcmp(header.type, "hdlr", 4)) {
-                CHECK(uniqueBoxSeen(&uniqueBoxFlags, 0));
-                CHECK(avifParseHandlerBox(avifROStreamCurrent(&s), header.size));
+                CHECK(uniqueBoxSeen(&uniqueBoxFlags, 0, "meta", "hdlr", diag));
+                CHECK(avifParseHandlerBox(avifROStreamCurrent(&s), header.size, diag));
                 firstBox = AVIF_FALSE;
             } else {
                 // hdlr must be the first box!
                 return AVIF_FALSE;
             }
         } else if (!memcmp(header.type, "iloc", 4)) {
-            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 1));
-            CHECK(avifParseItemLocationBox(meta, avifROStreamCurrent(&s), header.size));
+            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 1, "meta", "iloc", diag));
+            CHECK(avifParseItemLocationBox(meta, avifROStreamCurrent(&s), header.size, diag));
         } else if (!memcmp(header.type, "pitm", 4)) {
-            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 2));
-            CHECK(avifParsePrimaryItemBox(meta, avifROStreamCurrent(&s), header.size));
+            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 2, "meta", "pitm", diag));
+            CHECK(avifParsePrimaryItemBox(meta, avifROStreamCurrent(&s), header.size, diag));
         } else if (!memcmp(header.type, "idat", 4)) {
-            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 3));
-            CHECK(avifParseItemDataBox(meta, avifROStreamCurrent(&s), header.size));
+            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 3, "meta", "idat", diag));
+            CHECK(avifParseItemDataBox(meta, avifROStreamCurrent(&s), header.size, diag));
         } else if (!memcmp(header.type, "iprp", 4)) {
-            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 4));
-            CHECK(avifParseItemPropertiesBox(meta, avifROStreamCurrent(&s), header.size));
+            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 4, "meta", "iprp", diag));
+            CHECK(avifParseItemPropertiesBox(meta, avifROStreamCurrent(&s), header.size, diag));
         } else if (!memcmp(header.type, "iinf", 4)) {
-            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 5));
-            CHECK(avifParseItemInfoBox(meta, avifROStreamCurrent(&s), header.size));
+            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 5, "meta", "iinf", diag));
+            CHECK(avifParseItemInfoBox(meta, avifROStreamCurrent(&s), header.size, diag));
         } else if (!memcmp(header.type, "iref", 4)) {
-            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 6));
-            CHECK(avifParseItemReferenceBox(meta, avifROStreamCurrent(&s), header.size));
+            CHECK(uniqueBoxSeen(&uniqueBoxFlags, 6, "meta", "iref", diag));
+            CHECK(avifParseItemReferenceBox(meta, avifROStreamCurrent(&s), header.size, diag));
         }
 
         CHECK(avifROStreamSkip(&s, header.size));
@@ -1861,7 +1941,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseTrackHeaderBox(avifTrack * track, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseTrackHeaderBox(avifTrack * track, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1884,6 +1964,7 @@
         CHECK(avifROStreamReadU32(&s, &ignored32)); // unsigned int(32) duration;
     } else {
         // Unsupported version
+        avifDiagnosticsPrintf(diag, "Box type 'tkhd' has an unsupported version [%u]", version);
         return AVIF_FALSE;
     }
 
@@ -1909,7 +1990,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseMediaHeaderBox(avifTrack * track, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseMediaHeaderBox(avifTrack * track, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1932,6 +2013,7 @@
         track->mediaDuration = (uint64_t)mediaDuration32;
     } else {
         // Unsupported version
+        avifDiagnosticsPrintf(diag, "Box type 'mdhd' has an unsupported version [%u]", version);
         return AVIF_FALSE;
     }
 
@@ -1963,7 +2045,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseSampleToChunkBox(avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseSampleToChunkBox(avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -1980,10 +2062,12 @@
         // The first_chunk fields should start with 1 and be strictly increasing.
         if (i == 0) {
             if (sampleToChunk->firstChunk != 1) {
+                avifDiagnosticsPrintf(diag, "Box type 'stsc' does not begin with chunk 1 [%u]", sampleToChunk->firstChunk);
                 return AVIF_FALSE;
             }
         } else {
             if (sampleToChunk->firstChunk <= prevFirstChunk) {
+                avifDiagnosticsPrintf(diag, "Box type 'stsc' chunks are not strictly increasing");
                 return AVIF_FALSE;
             }
         }
@@ -2048,7 +2132,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseSampleDescriptionBox(avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseSampleDescriptionBox(avifSampleTable * sampleTable, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -2068,7 +2152,8 @@
         if (!memcmp(description->format, "av01", 4) && (remainingBytes > VISUALSAMPLEENTRY_SIZE)) {
             CHECK(avifParseItemPropertyContainerBox(&description->properties,
                                                     avifROStreamCurrent(&s) + VISUALSAMPLEENTRY_SIZE,
-                                                    remainingBytes - VISUALSAMPLEENTRY_SIZE));
+                                                    remainingBytes - VISUALSAMPLEENTRY_SIZE,
+                                                    diag));
         }
 
         CHECK(avifROStreamSkip(&s, sampleEntryHeader.size));
@@ -2076,7 +2161,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseSampleTableBox(avifTrack * track, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseSampleTableBox(avifTrack * track, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     if (track->sampleTable) {
         // A TrackBox may only have one SampleTable
@@ -2095,7 +2180,7 @@
         } else if (!memcmp(header.type, "co64", 4)) {
             CHECK(avifParseChunkOffsetBox(track->sampleTable, AVIF_TRUE, avifROStreamCurrent(&s), header.size));
         } else if (!memcmp(header.type, "stsc", 4)) {
-            CHECK(avifParseSampleToChunkBox(track->sampleTable, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParseSampleToChunkBox(track->sampleTable, avifROStreamCurrent(&s), header.size, diag));
         } else if (!memcmp(header.type, "stsz", 4)) {
             CHECK(avifParseSampleSizeBox(track->sampleTable, avifROStreamCurrent(&s), header.size));
         } else if (!memcmp(header.type, "stss", 4)) {
@@ -2103,7 +2188,7 @@
         } else if (!memcmp(header.type, "stts", 4)) {
             CHECK(avifParseTimeToSampleBox(track->sampleTable, avifROStreamCurrent(&s), header.size));
         } else if (!memcmp(header.type, "stsd", 4)) {
-            CHECK(avifParseSampleDescriptionBox(track->sampleTable, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParseSampleDescriptionBox(track->sampleTable, avifROStreamCurrent(&s), header.size, diag));
         }
 
         CHECK(avifROStreamSkip(&s, header.size));
@@ -2111,7 +2196,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseMediaInformationBox(avifTrack * track, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseMediaInformationBox(avifTrack * track, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -2120,7 +2205,7 @@
         CHECK(avifROStreamReadBoxHeader(&s, &header));
 
         if (!memcmp(header.type, "stbl", 4)) {
-            CHECK(avifParseSampleTableBox(track, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParseSampleTableBox(track, avifROStreamCurrent(&s), header.size, diag));
         }
 
         CHECK(avifROStreamSkip(&s, header.size));
@@ -2128,7 +2213,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseMediaBox(avifTrack * track, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseMediaBox(avifTrack * track, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -2137,9 +2222,9 @@
         CHECK(avifROStreamReadBoxHeader(&s, &header));
 
         if (!memcmp(header.type, "mdhd", 4)) {
-            CHECK(avifParseMediaHeaderBox(track, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParseMediaHeaderBox(track, avifROStreamCurrent(&s), header.size, diag));
         } else if (!memcmp(header.type, "minf", 4)) {
-            CHECK(avifParseMediaInformationBox(track, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParseMediaInformationBox(track, avifROStreamCurrent(&s), header.size, diag));
         }
 
         CHECK(avifROStreamSkip(&s, header.size));
@@ -2183,11 +2268,11 @@
         CHECK(avifROStreamReadBoxHeader(&s, &header));
 
         if (!memcmp(header.type, "tkhd", 4)) {
-            CHECK(avifParseTrackHeaderBox(track, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParseTrackHeaderBox(track, avifROStreamCurrent(&s), header.size, data->diag));
         } else if (!memcmp(header.type, "meta", 4)) {
-            CHECK(avifParseMetaBox(track->meta, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParseMetaBox(track->meta, avifROStreamCurrent(&s), header.size, data->diag));
         } else if (!memcmp(header.type, "mdia", 4)) {
-            CHECK(avifParseMediaBox(track, avifROStreamCurrent(&s), header.size));
+            CHECK(avifParseMediaBox(track, avifROStreamCurrent(&s), header.size, data->diag));
         } else if (!memcmp(header.type, "tref", 4)) {
             CHECK(avifTrackReferenceBox(track, avifROStreamCurrent(&s), header.size));
         }
@@ -2214,7 +2299,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseFileTypeBox(avifFileType * ftyp, const uint8_t * raw, size_t rawLen)
+static avifBool avifParseFileTypeBox(avifFileType * ftyp, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen);
 
@@ -2223,6 +2308,7 @@
 
     size_t compatibleBrandsBytes = avifROStreamRemainingBytes(&s);
     if ((compatibleBrandsBytes % 4) != 0) {
+        avifDiagnosticsPrintf(diag, "Box type 'ftyp' contains a compatible brands section that isn't divisible by 4 [%u]", compatibleBrandsBytes);
         return AVIF_FALSE;
     }
     ftyp->compatibleBrands = avifROStreamCurrent(&s);
@@ -2293,7 +2379,7 @@
         if (!memcmp(header.type, "ftyp", 4)) {
             CHECKERR(!ftypSeen, AVIF_RESULT_BMFF_PARSE_FAILED);
             avifFileType ftyp;
-            CHECKERR(avifParseFileTypeBox(&ftyp, boxContents.data, boxContents.size), AVIF_RESULT_BMFF_PARSE_FAILED);
+            CHECKERR(avifParseFileTypeBox(&ftyp, boxContents.data, boxContents.size, data->diag), AVIF_RESULT_BMFF_PARSE_FAILED);
             if (!avifFileTypeIsCompatible(&ftyp)) {
                 return AVIF_RESULT_INVALID_FTYP;
             }
@@ -2302,7 +2388,7 @@
             needsMoov = avifFileTypeHasBrand(&ftyp, "avis");
         } else if (!memcmp(header.type, "meta", 4)) {
             CHECKERR(!metaSeen, AVIF_RESULT_BMFF_PARSE_FAILED);
-            CHECKERR(avifParseMetaBox(data->meta, boxContents.data, boxContents.size), AVIF_RESULT_BMFF_PARSE_FAILED);
+            CHECKERR(avifParseMetaBox(data->meta, boxContents.data, boxContents.size, data->diag), AVIF_RESULT_BMFF_PARSE_FAILED);
             metaSeen = AVIF_TRUE;
         } else if (!memcmp(header.type, "moov", 4)) {
             CHECKERR(!moovSeen, AVIF_RESULT_BMFF_PARSE_FAILED);
@@ -2360,7 +2446,7 @@
 
     avifFileType ftyp;
     memset(&ftyp, 0, sizeof(avifFileType));
-    avifBool parsed = avifParseFileTypeBox(&ftyp, avifROStreamCurrent(&s), header.size);
+    avifBool parsed = avifParseFileTypeBox(&ftyp, avifROStreamCurrent(&s), header.size, NULL);
     if (!parsed) {
         return AVIF_FALSE;
     }
@@ -2375,6 +2461,7 @@
     memset(decoder, 0, sizeof(avifDecoder));
     decoder->maxThreads = 1;
     decoder->imageCountLimit = AVIF_DEFAULT_IMAGE_COUNT_LIMIT;
+    avifDiagnosticsClearError(&decoder->diag);
     return decoder;
 }
 
@@ -2389,6 +2476,7 @@
         avifImageDestroy(decoder->image);
         decoder->image = NULL;
     }
+    avifDiagnosticsClearError(&decoder->diag);
 }
 
 void avifDecoderDestroy(avifDecoder * decoder)
@@ -2556,6 +2644,8 @@
 
 avifResult avifDecoderParse(avifDecoder * decoder)
 {
+    avifDiagnosticsClearError(&decoder->diag);
+
     if (!decoder->io || !decoder->io->read) {
         return AVIF_RESULT_IO_NOT_SET;
     }
@@ -2567,6 +2657,7 @@
     // Parse BMFF boxes
 
     decoder->data = avifDecoderDataCreate();
+    decoder->data->diag = &decoder->diag;
 
     avifResult parseResult = avifParse(decoder);
     if (parseResult != AVIF_RESULT_OK) {
@@ -2600,6 +2691,8 @@
 
 avifResult avifDecoderReset(avifDecoder * decoder)
 {
+    avifDiagnosticsClearError(&decoder->diag);
+
     avifDecoderData * data = decoder->data;
     if (!data) {
         // Nothing to reset.
@@ -2705,14 +2798,18 @@
         }
 
         avifTile * colorTile = avifDecoderDataCreateTile(data);
-        if (!avifCodecDecodeInputGetSamples(colorTile->input, colorTrack->sampleTable, decoder->imageCountLimit, decoder->io->sizeHint)) {
+        if (!avifCodecDecodeInputGetSamples(colorTile->input, colorTrack->sampleTable, decoder->imageCountLimit, decoder->io->sizeHint, data->diag)) {
             return AVIF_RESULT_BMFF_PARSE_FAILED;
         }
         data->colorTileCount = 1;
 
         if (alphaTrack) {
             avifTile * alphaTile = avifDecoderDataCreateTile(data);
-            if (!avifCodecDecodeInputGetSamples(alphaTile->input, alphaTrack->sampleTable, decoder->imageCountLimit, decoder->io->sizeHint)) {
+            if (!avifCodecDecodeInputGetSamples(alphaTile->input,
+                                                alphaTrack->sampleTable,
+                                                decoder->imageCountLimit,
+                                                decoder->io->sizeHint,
+                                                data->diag)) {
                 return AVIF_RESULT_BMFF_PARSE_FAILED;
             }
             alphaTile->input->alpha = AVIF_TRUE;
@@ -2779,7 +2876,7 @@
                 if (readResult != AVIF_RESULT_OK) {
                     return readResult;
                 }
-                if (!avifParseImageGridBox(&data->colorGrid, readData.data, readData.size)) {
+                if (!avifParseImageGridBox(&data->colorGrid, readData.data, readData.size, data->diag)) {
                     return AVIF_RESULT_INVALID_IMAGE_GRID;
                 }
             }
@@ -2818,7 +2915,7 @@
                     if (readResult != AVIF_RESULT_OK) {
                         return readResult;
                     }
-                    if (!avifParseImageGridBox(&data->alphaGrid, readData.data, readData.size)) {
+                    if (!avifParseImageGridBox(&data->alphaGrid, readData.data, readData.size, data->diag)) {
                         return AVIF_RESULT_INVALID_IMAGE_GRID;
                     }
                 }
@@ -3036,6 +3133,8 @@
 
 avifResult avifDecoderNextImage(avifDecoder * decoder)
 {
+    avifDiagnosticsClearError(&decoder->diag);
+
     if (!decoder->data) {
         // Nothing has been parsed yet
         return AVIF_RESULT_NO_CONTENT;
@@ -3193,6 +3292,8 @@
 
 avifResult avifDecoderNthImage(avifDecoder * decoder, uint32_t frameIndex)
 {
+    avifDiagnosticsClearError(&decoder->diag);
+
     if (frameIndex > INT_MAX) {
         // Impossible index
         return AVIF_RESULT_NO_IMAGES_REMAINING;
@@ -3279,6 +3380,7 @@
 
 avifResult avifDecoderReadMemory(avifDecoder * decoder, avifImage * image, const uint8_t * data, size_t size)
 {
+    avifDiagnosticsClearError(&decoder->diag);
     avifResult result = avifDecoderSetIOMemory(decoder, data, size);
     if (result != AVIF_RESULT_OK) {
         return result;
@@ -3288,6 +3390,7 @@
 
 avifResult avifDecoderReadFile(avifDecoder * decoder, avifImage * image, const char * filename)
 {
+    avifDiagnosticsClearError(&decoder->diag);
     avifResult result = avifDecoderSetIOFile(decoder, filename);
     if (result != AVIF_RESULT_OK) {
         return result;