Replace some assertions by AVIF_ASSERT_OR_RETURN() (#1958)

Add CHANGELOG.md entry
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f31161d..71cc78a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -45,6 +45,7 @@
 * Add avif_cxx.h as a C++ header with basic functionality.
 * Add enum aliases AVIF_COLOR_PRIMARIES_SRGB, AVIF_COLOR_PRIMARIES_BT2100,
   AVIF_COLOR_PRIMARIES_DCI_P3, AVIF_TRANSFER_CHARACTERISTICS_PQ.
+* Add avifResult enum entry AVIF_RESULT_INTERNAL_ERROR.
 
 ### Changed
 * Update aom.cmd: v3.8.1
diff --git a/include/avif/avif.h b/include/avif/avif.h
index ef63353..e9cb1de 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -186,10 +186,11 @@
     AVIF_RESULT_OUT_OF_MEMORY = 26,
     AVIF_RESULT_CANNOT_CHANGE_SETTING = 27, // a setting that can't change is changed during encoding
     AVIF_RESULT_INCOMPATIBLE_IMAGE = 28,    // the image is incompatible with already encoded images
+    AVIF_RESULT_INTERNAL_ERROR = 29,        // some invariants have not been satisfied (likely a bug in libavif)
 #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
-    AVIF_RESULT_ENCODE_GAIN_MAP_FAILED = 29,
-    AVIF_RESULT_DECODE_GAIN_MAP_FAILED = 30,
-    AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE = 31,
+    AVIF_RESULT_ENCODE_GAIN_MAP_FAILED = 30,
+    AVIF_RESULT_DECODE_GAIN_MAP_FAILED = 31,
+    AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE = 32,
 #endif
 
     // Kept for backward compatibility; please use the symbols above instead.
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 7e0396b..3cf3aa0 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -59,6 +59,13 @@
         }                                 \
     } while (0)
 
+// AVIF_ASSERT_OR_RETURN() can be used instead of assert() for extra security in release builds.
+#ifdef NDEBUG
+#define AVIF_ASSERT_OR_RETURN(A) AVIF_CHECKERR((A), AVIF_RESULT_INTERNAL_ERROR)
+#else
+#define AVIF_ASSERT_OR_RETURN(A) assert(A)
+#endif
+
 // ---------------------------------------------------------------------------
 // URNs and Content-Types
 
diff --git a/src/avif.c b/src/avif.c
index 5d6609c..264d004 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -103,6 +103,7 @@
         case AVIF_RESULT_OUT_OF_MEMORY:                 return "Out of memory";
         case AVIF_RESULT_CANNOT_CHANGE_SETTING:         return "Cannot change some setting during encoding";
         case AVIF_RESULT_INCOMPATIBLE_IMAGE:            return "The image is incompatible with already encoded images";
+        case AVIF_RESULT_INTERNAL_ERROR:                return "Internal error";
 #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
         case AVIF_RESULT_ENCODE_GAIN_MAP_FAILED:        return "Encoding of gain map planes failed";
         case AVIF_RESULT_DECODE_GAIN_MAP_FAILED:        return "Decoding of gain map planes failed";
diff --git a/src/read.c b/src/read.c
index 43e8687..7e24828 100644
--- a/src/read.c
+++ b/src/read.c
@@ -566,7 +566,7 @@
             }
         }
         if (remainingSize > 0) {
-            assert(layerCount == 3);
+            AVIF_ASSERT_OR_RETURN(layerCount == 3);
             ++layerCount;
             layerSizes[3] = remainingSize;
         }
@@ -611,7 +611,7 @@
         sample->itemID = item->id;
         sample->offset = 0;
         sample->size = sampleSize;
-        assert(lselProp->u.lsel.layerID < AVIF_MAX_AV1_LAYER_COUNT);
+        AVIF_ASSERT_OR_RETURN(lselProp->u.lsel.layerID < AVIF_MAX_AV1_LAYER_COUNT);
         sample->spatialID = (uint8_t)lselProp->u.lsel.layerID;
         sample->sync = AVIF_TRUE;
     } else if (allowProgressive && item->progressive) {
@@ -790,7 +790,7 @@
 static avifResult avifMetaFindOrCreateItem(avifMeta * meta, uint32_t itemID, avifDecoderItem ** item, avifDiagnostics * diag)
 {
     *item = NULL;
-    assert(itemID != 0);
+    AVIF_ASSERT_OR_RETURN(itemID != 0);
 
     for (uint32_t i = 0; i < meta->items.count; ++i) {
         if (meta->items.item[i]->id == itemID) {
@@ -1055,7 +1055,7 @@
     size_t remainingBytes = sample->size; // This may be smaller than item->size if the item is progressive
 
     // Assert that the for loop below will execute at least one iteration.
-    assert(item->extents.count != 0);
+    AVIF_ASSERT_OR_RETURN(item->extents.count != 0);
     uint64_t minOffset = UINT64_MAX;
     uint64_t maxOffset = 0;
     for (uint32_t extentIter = 0; extentIter < item->extents.count; ++extentIter) {
@@ -1380,8 +1380,8 @@
             memcpy(&item->mergedExtents, &offsetBuffer, sizeof(avifRWData));
             item->mergedExtents.size = bytesToRead;
         } else {
-            assert(item->ownsMergedExtents);
-            assert(front);
+            AVIF_ASSERT_OR_RETURN(item->ownsMergedExtents);
+            AVIF_ASSERT_OR_RETURN(front);
             memcpy(front, offsetBuffer.data, bytesToRead);
             front += bytesToRead;
         }
@@ -1526,7 +1526,7 @@
     avifBool alpha = (tile->input->itemCategory == AVIF_ITEM_ALPHA);
     if (alpha) {
         // An alpha tile does not contain any YUV pixels.
-        assert(tile->image->yuvFormat == AVIF_PIXEL_FORMAT_NONE);
+        AVIF_ASSERT_OR_RETURN(tile->image->yuvFormat == AVIF_PIXEL_FORMAT_NONE);
     }
     if (!avifAreGridDimensionsValid(tile->image->yuvFormat, grid->outputWidth, grid->outputHeight, tile->image->width, tile->image->height, data->diag)) {
         return AVIF_RESULT_INVALID_IMAGE_GRID;
@@ -1573,11 +1573,11 @@
 
 // After verifying that the relevant properties of the tile match those of the first tile, copies over the pixels from the tile
 // into dstImage.
-static avifBool avifDecoderDataCopyTileToImage(avifDecoderData * data,
-                                               const avifTileInfo * info,
-                                               avifImage * dstImage,
-                                               const avifTile * tile,
-                                               unsigned int tileIndex)
+static avifResult avifDecoderDataCopyTileToImage(avifDecoderData * data,
+                                                 const avifTileInfo * info,
+                                                 avifImage * dstImage,
+                                                 const avifTile * tile,
+                                                 unsigned int tileIndex)
 {
     const avifImageGrid * grid = &info->grid;
     const avifTile * firstTile = &data->tiles.tile[info->firstTileIndex];
@@ -1589,7 +1589,7 @@
             (tile->image->transferCharacteristics != firstTile->image->transferCharacteristics) ||
             (tile->image->matrixCoefficients != firstTile->image->matrixCoefficients)) {
             avifDiagnosticsPrintf(data->diag, "Grid image contains mismatched tiles");
-            return AVIF_FALSE;
+            return AVIF_RESULT_INVALID_IMAGE_GRID;
         }
     }
 
@@ -1612,18 +1612,15 @@
     avifImage * dst = dstImage;
 #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
     if (tile->input->itemCategory == AVIF_ITEM_GAIN_MAP) {
-        assert(dst->gainMap && dst->gainMap->image);
+        AVIF_ASSERT_OR_RETURN(dst->gainMap && dst->gainMap->image);
         dst = dst->gainMap->image;
     }
 #endif
-    if ((avifImageSetViewRect(&dstView, dst, &dstViewRect) != AVIF_RESULT_OK) ||
-        (avifImageSetViewRect(&srcView, tile->image, &srcViewRect) != AVIF_RESULT_OK)) {
-        assert(AVIF_FALSE);
-        return AVIF_FALSE;
-    }
+    AVIF_ASSERT_OR_RETURN(avifImageSetViewRect(&dstView, dst, &dstViewRect) == AVIF_RESULT_OK &&
+                          avifImageSetViewRect(&srcView, tile->image, &srcViewRect) == AVIF_RESULT_OK);
     avifImageCopySamples(&dstView, &srcView, (tile->input->itemCategory == AVIF_ITEM_ALPHA) ? AVIF_PLANES_A : AVIF_PLANES_YUV);
 
-    return AVIF_TRUE;
+    return AVIF_RESULT_OK;
 }
 
 // If colorId == 0 (a sentinel value as item IDs must be nonzero), accept any found EXIF/XMP metadata. Passing in 0
@@ -1927,7 +1924,7 @@
     uint8_t flags;
     AVIF_CHECK(avifROStreamRead(&s, &flags, 1)); // unsigned int(8) flags;
     uint8_t channelCount = (flags & 1) * 2 + 1;
-    assert(channelCount == 1 || channelCount == 3);
+    AVIF_ASSERT_OR_RETURN(channelCount == 1 || channelCount == 3);
     metadata->useBaseColorSpace = (flags & 2) != 0;
     metadata->backwardDirection = (flags & 4) != 0;
     const avifBool useCommonDenominator = (flags & 8) != 0;
@@ -2025,13 +2022,13 @@
         } else {
             // item was generated for convenience and is not part of the bitstream.
             // grid information should already be set.
-            assert(grid->rows > 0 && grid->columns > 0);
+            AVIF_ASSERT_OR_RETURN(grid->rows > 0 && grid->columns > 0);
         }
         *codecType = avifDecoderItemGetGridCodecType(item);
         AVIF_CHECKERR(*codecType != AVIF_CODEC_TYPE_UNKNOWN, AVIF_RESULT_INVALID_IMAGE_GRID);
     } else {
         *codecType = avifGetCodecType(item->type);
-        assert(*codecType != AVIF_CODEC_TYPE_UNKNOWN);
+        AVIF_ASSERT_OR_RETURN(*codecType != AVIF_CODEC_TYPE_UNKNOWN);
     }
     return AVIF_RESULT_OK;
 }
@@ -2356,7 +2353,7 @@
     if (meta->fromMini) {
         // The CondensedImageBox will always create 8 item properties, so to refer to the
         // first property in the ItemPropertyContainerBox of its extendedMeta field, use index 9.
-        assert(meta->properties.count >= 8);
+        AVIF_ASSERT_OR_RETURN(meta->properties.count >= 8);
     }
 #endif // AVIF_ENABLE_EXPERIMENTAL_AVIR
 
@@ -2643,7 +2640,7 @@
         AVIF_CHECKERR(avifROStreamReadU16(&s, &tmp), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) item_ID;
         itemID = tmp;
     } else {
-        assert(version == 3);
+        AVIF_ASSERT_OR_RETURN(version == 3);
         AVIF_CHECKERR(avifROStreamReadU32(&s, &itemID), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) item_ID;
     }
     AVIF_CHECKRES(avifCheckItemID("infe", itemID, diag));
@@ -3314,7 +3311,7 @@
             //
             // Since libavif uses repetitionCount (which is 0-based), we subtract the value by 1 to derive the number of
             // repetitions.
-            assert(track->segmentDuration != 0);
+            AVIF_ASSERT_OR_RETURN(track->segmentDuration != 0);
             // We specifically check for trackDuration == 0 here and not when it is actually read in order to accept files which
             // inadvertently has a trackDuration of 0 without any edit lists.
             if (track->trackDuration == 0) {
@@ -3724,7 +3721,7 @@
     // The ExtendedMetaBox may reuse items and properties created above so it must be parsed last.
 
     if (hasExtendedMeta) {
-        assert(avifROStreamHasBytesLeft(&s, extendedMetaSize));
+        AVIF_ASSERT_OR_RETURN(avifROStreamHasBytesLeft(&s, extendedMetaSize));
         AVIF_CHECKRES(avifParseExtendedMeta(meta, rawOffset + avifROStreamOffset(&s), avifROStreamCurrent(&s), extendedMetaSize, diag));
     }
     return AVIF_RESULT_OK;
@@ -3792,7 +3789,7 @@
         avifBoxHeader header;
         AVIF_CHECKERR(avifROStreamReadBoxHeaderPartial(&headerStream, &header), AVIF_RESULT_BMFF_PARSE_FAILED);
         parseOffset += headerStream.offset;
-        assert((decoder->io->sizeHint == 0) || (parseOffset <= decoder->io->sizeHint));
+        AVIF_ASSERT_OR_RETURN(decoder->io->sizeHint == 0 || parseOffset <= decoder->io->sizeHint);
 
         // Try to get the remainder of the box, if necessary
         uint64_t boxOffset = 0;
@@ -4413,7 +4410,7 @@
             }
         }
     }
-    assert(alphaItemCount == colorItemCount);
+    AVIF_ASSERT_OR_RETURN(alphaItemCount == colorItemCount);
     // Figure out the last used itemID.
     avifResult result;
     const uint32_t lastID = meta->items.item[meta->items.count - 1]->id;
@@ -4514,7 +4511,7 @@
                     continue;
                 }
                 if (otherItem->dimgIdx < 2) {
-                    assert(dimgItemIDs[otherItem->dimgIdx] == 0);
+                    AVIF_ASSERT_OR_RETURN(dimgItemIDs[otherItem->dimgIdx] == 0);
                     dimgItemIDs[otherItem->dimgIdx] = otherItem->id;
                 }
                 numDimgItemIDs++;
@@ -4563,7 +4560,7 @@
         return AVIF_RESULT_OK;
     }
 
-    assert(gainMapItemID != 0);
+    AVIF_ASSERT_OR_RETURN(gainMapItemID != 0);
     avifDecoderItem * gainMapItemTmp;
     AVIF_CHECKRES(avifMetaFindOrCreateItem(data->meta, gainMapItemID, &gainMapItemTmp, data->diag));
     if (avifDecoderItemShouldBeSkipped(gainMapItemTmp)) {
@@ -4645,7 +4642,7 @@
         AVIF_CHECKERR(item->size != 0, AVIF_RESULT_MISSING_IMAGE_ITEM);
 
         const avifCodecType codecType = avifGetCodecType(item->type);
-        assert(codecType != AVIF_CODEC_TYPE_UNKNOWN);
+        AVIF_ASSERT_OR_RETURN(codecType != AVIF_CODEC_TYPE_UNKNOWN);
         avifTile * tile =
             avifDecoderDataCreateTile(decoder->data, codecType, item->width, item->height, avifDecoderItemOperatingPoint(item));
         AVIF_CHECKERR(tile, AVIF_RESULT_OUT_OF_MEMORY);
@@ -4963,7 +4960,7 @@
 #endif
             if (c == AVIF_ITEM_ALPHA && !mainItems[c]->width && !mainItems[c]->height) {
                 // NON-STANDARD: Alpha subimage does not have an ispe property; adopt width/height from color item
-                assert(!(decoder->strictFlags & AVIF_STRICT_ALPHA_ISPE_REQUIRED));
+                AVIF_ASSERT_OR_RETURN(!(decoder->strictFlags & AVIF_STRICT_ALPHA_ISPE_REQUIRED));
                 mainItems[c]->width = mainItems[AVIF_ITEM_COLOR]->width;
                 mainItems[c]->height = mainItems[AVIF_ITEM_COLOR]->height;
             }
@@ -4998,7 +4995,7 @@
 
 #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
         if (mainItems[AVIF_ITEM_GAIN_MAP]) {
-            assert(decoder->image->gainMap && decoder->image->gainMap->image);
+            AVIF_ASSERT_OR_RETURN(decoder->image->gainMap && decoder->image->gainMap->image);
             decoder->image->gainMap->image->width = mainItems[AVIF_ITEM_GAIN_MAP]->width;
             decoder->image->gainMap->image->height = mainItems[AVIF_ITEM_GAIN_MAP]->height;
             decoder->gainMapPresent = AVIF_TRUE;
@@ -5188,7 +5185,7 @@
 
         const avifDecodeSample * sample = &tile->input->samples.sample[nextImageIndex];
         if (sample->data.size < sample->size) {
-            assert(decoder->allowIncremental);
+            AVIF_ASSERT_OR_RETURN(decoder->allowIncremental);
             // Data is missing but there is no error yet. Output available pixel rows.
             return AVIF_RESULT_OK;
         }
@@ -5249,25 +5246,23 @@
                 avifImage * dstImage = decoder->image;
 #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
                 if (tile->input->itemCategory == AVIF_ITEM_GAIN_MAP) {
-                    assert(dstImage->gainMap && dstImage->gainMap->image);
+                    AVIF_ASSERT_OR_RETURN(dstImage->gainMap && dstImage->gainMap->image);
                     dstImage = dstImage->gainMap->image;
                 }
 #endif
                 AVIF_CHECKRES(avifDecoderDataAllocateGridImagePlanes(decoder->data, info, dstImage));
             }
-            if (!avifDecoderDataCopyTileToImage(decoder->data, info, decoder->image, tile, tileIndex)) {
-                return AVIF_RESULT_INVALID_IMAGE_GRID;
-            }
+            AVIF_CHECKRES(avifDecoderDataCopyTileToImage(decoder->data, info, decoder->image, tile, tileIndex));
         } else {
             // Non-grid path. Just steal the planes from the only "tile".
-            assert(info->tileCount == 1);
-            assert(tileIndex == 0);
+            AVIF_ASSERT_OR_RETURN(info->tileCount == 1);
+            AVIF_ASSERT_OR_RETURN(tileIndex == 0);
             avifImage * src = tile->image;
 
             switch (tile->input->itemCategory) {
 #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
                 case AVIF_ITEM_GAIN_MAP:
-                    assert(decoder->image->gainMap && decoder->image->gainMap->image);
+                    AVIF_ASSERT_OR_RETURN(decoder->image->gainMap && decoder->image->gainMap->image);
                     decoder->image->gainMap->image->width = src->width;
                     decoder->image->gainMap->image->height = src->height;
                     decoder->image->gainMap->image->depth = src->depth;
@@ -5294,7 +5289,7 @@
                 avifImageStealPlanes(decoder->image, src, AVIF_PLANES_A);
 #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
             } else if (tile->input->itemCategory == AVIF_ITEM_GAIN_MAP) {
-                assert(decoder->image->gainMap && decoder->image->gainMap->image);
+                AVIF_ASSERT_OR_RETURN(decoder->image->gainMap && decoder->image->gainMap->image);
                 avifImageStealPlanes(decoder->image->gainMap->image, src, AVIF_PLANES_YUV);
 #endif
             } else { // AVIF_ITEM_COLOR
@@ -5336,8 +5331,8 @@
         }
     }
 
-    assert(decoder->data->tiles.count == (decoder->data->tileInfos[AVIF_ITEM_CATEGORY_COUNT - 1].firstTileIndex +
-                                          decoder->data->tileInfos[AVIF_ITEM_CATEGORY_COUNT - 1].tileCount));
+    AVIF_ASSERT_OR_RETURN(decoder->data->tiles.count == (decoder->data->tileInfos[AVIF_ITEM_CATEGORY_COUNT - 1].firstTileIndex +
+                                                         decoder->data->tileInfos[AVIF_ITEM_CATEGORY_COUNT - 1].tileCount));
 
     const uint32_t nextImageIndex = (uint32_t)(decoder->imageIndex + 1);
 
@@ -5366,22 +5361,22 @@
     }
 
     if (!avifDecoderDataFrameFullyDecoded(decoder->data)) {
-        assert(decoder->allowIncremental);
+        AVIF_ASSERT_OR_RETURN(decoder->allowIncremental);
         // The image is not completely decoded. There should be no error unrelated to missing bytes,
         // and at least some missing bytes.
         avifResult firstNonOkResult = AVIF_RESULT_OK;
         for (int c = 0; c < AVIF_ITEM_CATEGORY_COUNT; ++c) {
-            assert((prepareTileResult[c] == AVIF_RESULT_OK) || (prepareTileResult[c] == AVIF_RESULT_WAITING_ON_IO));
+            AVIF_ASSERT_OR_RETURN(prepareTileResult[c] == AVIF_RESULT_OK || prepareTileResult[c] == AVIF_RESULT_WAITING_ON_IO);
             if (firstNonOkResult == AVIF_RESULT_OK) {
                 firstNonOkResult = prepareTileResult[c];
             }
         }
-        assert(firstNonOkResult != AVIF_RESULT_OK);
+        AVIF_ASSERT_OR_RETURN(firstNonOkResult != AVIF_RESULT_OK);
         // Return the "not enough bytes" status now instead of moving on to the next frame.
         return AVIF_RESULT_WAITING_ON_IO;
     }
     for (int c = 0; c < AVIF_ITEM_CATEGORY_COUNT; ++c) {
-        assert(prepareTileResult[c] == AVIF_RESULT_OK);
+        AVIF_ASSERT_OR_RETURN(prepareTileResult[c] == AVIF_RESULT_OK);
     }
 
     // Only advance decoder->imageIndex once the image is completely decoded, so that
@@ -5558,8 +5553,9 @@
                     const uint32_t scaledGainMapRowCount =
                         (uint32_t)floorf((float)gainMapRowCount / gainMap->height * decoder->image->height);
                     // Make sure it matches the formula described in the comment of avifDecoderDecodedRowCount() in avif.h.
-                    assert((uint32_t)lround((double)scaledGainMapRowCount / decoder->image->height *
-                                            decoder->image->gainMap->image->height) <= gainMapRowCount);
+                    AVIF_CHECKERR((uint32_t)lround((double)scaledGainMapRowCount / decoder->image->height *
+                                                   decoder->image->gainMap->image->height) <= gainMapRowCount,
+                                  0);
                     gainMapRowCount = scaledGainMapRowCount;
                 }
                 minRowCount = AVIF_MIN(minRowCount, gainMapRowCount);
diff --git a/src/write.c b/src/write.c
index 5881a64..9cbe1f8 100644
--- a/src/write.c
+++ b/src/write.c
@@ -425,7 +425,7 @@
             !memcmp(&outputStream->raw->data[property->offset], dedup->buffer.data, newPropertySize)) {
             // We've already written this exact property, reuse it
             propertyIndex = property->index;
-            assert(propertyIndex != 0);
+            AVIF_ASSERT_OR_RETURN(propertyIndex != 0);
             break;
         }
     }
@@ -639,7 +639,7 @@
     // of an already stored property.
     avifRWStream * dedupStream = outputStream;
     if (dedup) {
-        assert(ipma);
+        AVIF_ASSERT_OR_RETURN(ipma);
 
         // Use the dedup's temporary stream for box writes.
         dedupStream = &dedup->s;
@@ -821,7 +821,7 @@
             continue;
         }
 
-        assert(!item->hiddenImage);
+        AVIF_ASSERT_OR_RETURN(!item->hiddenImage);
         avifBoxMarker infe;
         AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "infe", AVIF_BOX_SIZE_TBD, 2, 0, &infe));
         AVIF_CHECKRES(avifRWStreamWriteU16(s, item->id));                             // unsigned int(16) item_ID;
@@ -1022,36 +1022,23 @@
 }
 
 // Same as avifImageCopy() but pads the dstImage with border pixel values to reach dstWidth and dstHeight.
-// Returns NULL if a memory allocation failed.
-static avifImage * avifImageCopyAndPad(const avifImage * srcImage, uint32_t dstWidth, uint32_t dstHeight)
+static avifResult avifImageCopyAndPad(avifImage * const dstImage, const avifImage * srcImage, uint32_t dstWidth, uint32_t dstHeight)
 {
-    avifImage * dstImage = avifImageCreateEmpty();
-    if (!dstImage) {
-        return NULL;
-    }
-    // Copy all fields but do not allocate.
-    if (avifImageCopy(dstImage, srcImage, (avifPlanesFlag)0) != AVIF_RESULT_OK) {
-        avifImageDestroy(dstImage);
-        return NULL;
-    }
-    assert(dstWidth >= srcImage->width);
-    assert(dstHeight >= srcImage->height);
+    AVIF_ASSERT_OR_RETURN(dstImage);
+    AVIF_ASSERT_OR_RETURN(!dstImage->width && !dstImage->height); // dstImage is not set yet.
+    AVIF_ASSERT_OR_RETURN(dstWidth >= srcImage->width);
+    AVIF_ASSERT_OR_RETURN(dstHeight >= srcImage->height);
+
+    // Copy all fields but do not allocate the planes.
+    AVIF_CHECKRES(avifImageCopy(dstImage, srcImage, (avifPlanesFlag)0));
     dstImage->width = dstWidth;
     dstImage->height = dstHeight;
 
     if (srcImage->yuvPlanes[AVIF_CHAN_Y]) {
-        const avifResult allocationResult = avifImageAllocatePlanes(dstImage, AVIF_PLANES_YUV);
-        if (allocationResult != AVIF_RESULT_OK) {
-            avifImageDestroy(dstImage);
-            return NULL;
-        }
+        AVIF_CHECKRES(avifImageAllocatePlanes(dstImage, AVIF_PLANES_YUV));
     }
     if (srcImage->alphaPlane) {
-        const avifResult allocationResult = avifImageAllocatePlanes(dstImage, AVIF_PLANES_A);
-        if (allocationResult != AVIF_RESULT_OK) {
-            avifImageDestroy(dstImage);
-            return NULL;
-        }
+        AVIF_CHECKRES(avifImageAllocatePlanes(dstImage, AVIF_PLANES_A));
     }
     const avifBool usesU16 = avifImageUsesU16(srcImage);
     for (int plane = AVIF_CHAN_Y; plane <= AVIF_CHAN_A; ++plane) {
@@ -1091,7 +1078,7 @@
             dstRow += dstRowBytes;
         }
     }
-    return dstImage;
+    return AVIF_RESULT_OK;
 }
 
 static int avifQualityToQuantizer(int quality, int minQuantizer, int maxQuantizer)
@@ -1242,9 +1229,9 @@
     const avifImage * bottomRightCell = cellImages[cellCount - 1];
 #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
     if (validateGainMap) {
-        assert(firstCell->gainMap && firstCell->gainMap->image);
+        AVIF_ASSERT_OR_RETURN(firstCell->gainMap && firstCell->gainMap->image);
         firstCell = firstCell->gainMap->image;
-        assert(bottomRightCell->gainMap && bottomRightCell->gainMap->image);
+        AVIF_ASSERT_OR_RETURN(bottomRightCell->gainMap && bottomRightCell->gainMap->image);
         bottomRightCell = bottomRightCell->gainMap->image;
     }
 #endif
@@ -1256,7 +1243,7 @@
         const avifImage * cellImage = cellImages[cellIndex];
 #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
         if (validateGainMap) {
-            assert(cellImage->gainMap && cellImage->gainMap->image);
+            AVIF_ASSERT_OR_RETURN(cellImage->gainMap && cellImage->gainMap->image);
             cellImage = cellImage->gainMap->image;
         }
 #endif
@@ -1530,12 +1517,12 @@
             uint16_t alphaItemID;
             AVIF_CHECKRES(avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_ALPHA, &alphaItemID));
             avifEncoderItem * alphaItem = avifEncoderDataFindItemByID(encoder->data, alphaItemID);
-            assert(alphaItem);
+            AVIF_ASSERT_OR_RETURN(alphaItem);
             alphaItem->irefType = "auxl";
             alphaItem->irefToID = colorItemID;
             if (encoder->data->imageMetadata->alphaPremultiplied) {
                 avifEncoderItem * colorItem = avifEncoderDataFindItemByID(encoder->data, colorItemID);
-                assert(colorItem);
+                AVIF_ASSERT_OR_RETURN(colorItem);
                 colorItem->irefType = "prem";
                 colorItem->irefToID = alphaItemID;
             }
@@ -1573,17 +1560,17 @@
             AVIF_CHECKRES(
                 avifEncoderAddImageItems(encoder, gridCols, gridRows, gainMapGridWidth, gainMapGridHeight, AVIF_ITEM_GAIN_MAP, &gainMapItemID));
             avifEncoderItem * gainMapItem = avifEncoderDataFindItemByID(encoder->data, gainMapItemID);
-            assert(gainMapItem);
+            AVIF_ASSERT_OR_RETURN(gainMapItem);
             gainMapItem->hiddenImage = AVIF_TRUE;
 
             // Set the color item and gain map item's dimgFromID value to point to the tone mapped item.
             // The color item shall be first, and the gain map second. avifEncoderFinish() writes the
             // dimg item references in item id order, so as long as colorItemID < gainMapItemID, the order
             // will be correct.
-            assert(colorItemID < gainMapItemID);
+            AVIF_ASSERT_OR_RETURN(colorItemID < gainMapItemID);
             avifEncoderItem * colorItem = avifEncoderDataFindItemByID(encoder->data, colorItemID);
-            assert(colorItem);
-            assert(colorItem->dimgFromID == 0); // Our internal API only allows one dimg value per item.
+            AVIF_ASSERT_OR_RETURN(colorItem);
+            AVIF_ASSERT_OR_RETURN(colorItem->dimgFromID == 0); // Our internal API only allows one dimg value per item.
             colorItem->dimgFromID = toneMappedItemID;
             gainMapItem->dimgFromID = toneMappedItemID;
         }
@@ -1643,11 +1630,9 @@
         //     }
         // Check whether it is safe to cast width and height to uint16_t. The maximum width and
         // height of an AV1 frame are 65536, which just exceeds uint16_t.
-        assert(encoder->data->items.count > 0);
+        AVIF_ASSERT_OR_RETURN(encoder->data->items.count > 0);
         const avifImage * imageMetadata = encoder->data->imageMetadata;
-        if ((imageMetadata->width > 65535) || (imageMetadata->height > 65535)) {
-            return AVIF_RESULT_INVALID_ARGUMENT;
-        }
+        AVIF_CHECKERR(imageMetadata->width <= 65535 && imageMetadata->height <= 65535, AVIF_RESULT_INVALID_ARGUMENT);
     }
 
     // -----------------------------------------------------------------------
@@ -1660,17 +1645,20 @@
             const avifImage * firstCellImage = firstCell;
 #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
             if (item->itemCategory == AVIF_ITEM_GAIN_MAP) {
-                assert(cellImage->gainMap && cellImage->gainMap->image);
+                AVIF_ASSERT_OR_RETURN(cellImage->gainMap && cellImage->gainMap->image);
                 cellImage = cellImage->gainMap->image;
-                assert(firstCell->gainMap && firstCell->gainMap->image);
+                AVIF_ASSERT_OR_RETURN(firstCell->gainMap && firstCell->gainMap->image);
                 firstCellImage = firstCell->gainMap->image;
             }
 #endif
             avifImage * paddedCellImage = NULL;
             if ((cellImage->width != firstCellImage->width) || (cellImage->height != firstCellImage->height)) {
-                paddedCellImage = avifImageCopyAndPad(cellImage, firstCellImage->width, firstCellImage->height);
-                if (!paddedCellImage) {
-                    return AVIF_RESULT_OUT_OF_MEMORY;
+                paddedCellImage = avifImageCreateEmpty();
+                AVIF_CHECKERR(paddedCellImage, AVIF_RESULT_OUT_OF_MEMORY);
+                const avifResult result = avifImageCopyAndPad(paddedCellImage, cellImage, firstCellImage->width, firstCellImage->height);
+                if (result != AVIF_RESULT_OK) {
+                    avifImageDestroy(paddedCellImage);
+                    return result;
                 }
                 cellImage = paddedCellImage;
             }
@@ -1804,7 +1792,7 @@
             if ((encoder->extraLayerCount > 0) && (item->encodeOutput->samples.count > 0)) {
                 // Interleave - Pick out AV1 items and interleave them later.
                 // We always interleave all AV1 items for layered images.
-                assert(item->encodeOutput->samples.count == item->mdatFixups.count);
+                AVIF_ASSERT_OR_RETURN(item->encodeOutput->samples.count == item->mdatFixups.count);
 
                 avifEncoderItemReference * ref = (item->itemCategory == AVIF_ITEM_ALPHA) ? avifArrayPush(layeredAlphaItems)
                                                                                          : avifArrayPush(layeredColorItems);
@@ -1905,7 +1893,7 @@
             ++layerIndex;
         } while (hasMoreSample);
 
-        assert(layerIndex <= AVIF_MAX_AV1_LAYER_COUNT);
+        AVIF_ASSERT_OR_RETURN(layerIndex <= AVIF_MAX_AV1_LAYER_COUNT);
     }
     avifRWStreamFinishBox(s, mdat);
     return AVIF_RESULT_OK;
@@ -1964,7 +1952,7 @@
                                                    ((imageMetadata->transformFlags & AVIF_TRANSFORM_IROT) ? 1 : 0) +
                                                    ((imageMetadata->transformFlags & AVIF_TRANSFORM_IMIR) ? 1 : 0);
             const uint8_t ipmaCount = numNonEssentialProperties + numEssentialProperties;
-            assert(ipmaCount >= 1);
+            AVIF_ASSERT_OR_RETURN(ipmaCount >= 1);
 
             // Only add properties to the primary item.
             AVIF_CHECKRES(avifRWStreamWriteU32(stream, 1)); // unsigned int(32) entry_count;
@@ -2074,15 +2062,15 @@
         avifEncoderItem * item = &encoder->data->items.item[itemIndex];
         if (item->id == encoder->data->primaryItemID) {
             colorItem = item;
-            assert(colorItem->encodeOutput->samples.count == 1);
+            AVIF_ASSERT_OR_RETURN(colorItem->encodeOutput->samples.count == 1);
         } else if (item->itemCategory == AVIF_ITEM_ALPHA && item->irefToID == encoder->data->primaryItemID) {
-            assert(!alphaItem);
+            AVIF_ASSERT_OR_RETURN(!alphaItem);
             alphaItem = item;
-            assert(alphaItem->encodeOutput->samples.count == 1);
+            AVIF_ASSERT_OR_RETURN(alphaItem->encodeOutput->samples.count == 1);
         }
     }
 
-    assert(colorItem);
+    AVIF_ASSERT_OR_RETURN(colorItem);
     const avifRWData * colorData = &colorItem->encodeOutput->samples.sample[0].data;
     const avifRWData * alphaData = alphaItem ? &alphaItem->encodeOutput->samples.sample[0].data : NULL;
 
@@ -2146,7 +2134,7 @@
                                                    // unsigned int(32) infe_type;
                                                    // unsigned int(32) codec_config_type;
     AVIF_CHECKRES(avifRWStreamWriteVarInt(s, 4));  // varint main_item_codec_config_size;
-    assert(colorData->size >= 1);
+    AVIF_ASSERT_OR_RETURN(colorData->size >= 1);
     AVIF_CHECKRES(avifRWStreamWriteVarInt(s, (uint32_t)colorData->size - 1)); // varint main_item_data_size_minus_one;
 
     AVIF_CHECKRES(avifRWStreamWriteBits(s, alphaItem ? 1 : 0, 1)); // unsigned int(1) has_alpha;
@@ -2253,7 +2241,7 @@
         if (isToneMappedImage) {
             itemMetadata = altImageMetadata;
         } else if (item->itemCategory == AVIF_ITEM_GAIN_MAP) {
-            assert(itemMetadata->gainMap && itemMetadata->gainMap->image);
+            AVIF_ASSERT_OR_RETURN(itemMetadata->gainMap && itemMetadata->gainMap->image);
             itemMetadata = itemMetadata->gainMap->image;
         }
 #else
@@ -2761,7 +2749,7 @@
             durationInTimescales = AVIF_INDEFINITE_DURATION64;
         } else {
             uint64_t loopCount = encoder->repetitionCount + 1;
-            assert(framesDurationInTimescales != 0);
+            AVIF_ASSERT_OR_RETURN(framesDurationInTimescales != 0);
             if (loopCount > UINT64_MAX / framesDurationInTimescales) {
                 // The multiplication will overflow uint64_t.
                 return AVIF_RESULT_INVALID_ARGUMENT;