Make avifImageAllocatePlanes() return avifResult

To catch memory allocation failures and invalid parameters, it is
safer to return a status from avifImageAllocatePlanes() and from
avifImageCopy().
Update CHANGELOG.md.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e326f5f..7c0cb1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,12 +6,16 @@
 
 ## [Unreleased]
 
-There is an incompatible ABI change in this release. Members were removed from
-avifImage struct. It is necessary to recompile your code.
+There are incompatible ABI changes in this release. The alphaRange member was
+removed from avifImage struct. avifImageCopy() and avifImageAllocatePlanes()
+signatures changed. It is necessary to recompile your code. Also check the
+return values of avifImageCopy() and avifImageAllocatePlanes().
 
 ### Changed
 * Update aom.cmd: v3.4.0
 * Update svt.cmd/svt.sh: v1.1.0
+* avifImageCopy() and avifImageAllocatePlanes() now return avifResult instead of
+  void to report invalid parameters or memory allocation failures
 
 ### Removed
 * alphaRange field was removed from the avifImage struct. It it presumed that
diff --git a/apps/avifenc.c b/apps/avifenc.c
index 1a1a0ff..1e7c3d6 100644
--- a/apps/avifenc.c
+++ b/apps/avifenc.c
@@ -352,7 +352,11 @@
             avifImage * cellImage = avifImageCreateEmpty();
             gridCells[gridIndex] = cellImage;
 
-            avifImageCopy(cellImage, gridSplitImage, 0);
+            const avifResult copyResult = avifImageCopy(cellImage, gridSplitImage, 0);
+            if (copyResult != AVIF_RESULT_OK) {
+                fprintf(stderr, "ERROR: Image copy failed: %s\n", avifResultToString(copyResult));
+                return AVIF_FALSE;
+            }
             cellImage->width = cellWidth;
             cellImage->height = cellHeight;
 
diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c
index 4460ed8..0934c4f 100644
--- a/apps/shared/avifjpeg.c
+++ b/apps/shared/avifjpeg.c
@@ -40,7 +40,7 @@
 
 // An internal function used by avifJPEGReadCopy(), this is the shared libjpeg decompression code
 // for all paths avifJPEGReadCopy() takes.
-static void avifJPEGCopyPixels(avifImage * avif, struct jpeg_decompress_struct * cinfo)
+static avifBool avifJPEGCopyPixels(avifImage * avif, struct jpeg_decompress_struct * cinfo)
 {
     cinfo->raw_data_out = TRUE;
     jpeg_start_decompress(cinfo);
@@ -68,7 +68,9 @@
         readLines = AVIF_MAX(readLines, linesPerCall[i]);
     }
 
-    avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
+    if (avifImageAllocatePlanes(avif, AVIF_PLANES_YUV) != AVIF_RESULT_OK) {
+        return AVIF_FALSE;
+    }
 
     // destination avif channel for each jpeg channel
     avifChannelIndex targetChannel[3] = { AVIF_CHAN_Y, AVIF_CHAN_Y, AVIF_CHAN_Y };
@@ -102,6 +104,7 @@
             alreadyRead[i] += linesPerCall[i];
         }
     }
+    return AVIF_TRUE;
 }
 
 static avifBool avifJPEGHasCompatibleMatrixCoefficients(avifMatrixCoefficients matrixCoefficients)
@@ -148,9 +151,7 @@
                 }
                 if (avif->yuvFormat == jpegFormat) {
                     cinfo->out_color_space = JCS_YCbCr;
-                    avifJPEGCopyPixels(avif, cinfo);
-
-                    return AVIF_TRUE;
+                    return avifJPEGCopyPixels(avif, cinfo);
                 }
             }
 
@@ -158,9 +159,7 @@
             if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) && (cinfo->comp_info[0].h_samp_factor == cinfo->max_h_samp_factor &&
                                                                   cinfo->comp_info[0].v_samp_factor == cinfo->max_v_samp_factor)) {
                 cinfo->out_color_space = JCS_YCbCr;
-                avifJPEGCopyPixels(avif, cinfo);
-
-                return AVIF_TRUE;
+                return avifJPEGCopyPixels(avif, cinfo);
             }
         }
     } else if (cinfo->jpeg_color_space == JCS_GRAYSCALE) {
@@ -173,16 +172,16 @@
                 if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE)) {
                     avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
                     cinfo->out_color_space = JCS_GRAYSCALE;
-                    avifJPEGCopyPixels(avif, cinfo);
-
-                    return AVIF_TRUE;
+                    return avifJPEGCopyPixels(avif, cinfo);
                 }
 
                 // Grayscale->YUV: copy Y, fill UV with monochrome value.
                 if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV422) ||
                     (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV420)) {
                     cinfo->out_color_space = JCS_GRAYSCALE;
-                    avifJPEGCopyPixels(avif, cinfo);
+                    if (!avifJPEGCopyPixels(avif, cinfo)) {
+                        return AVIF_FALSE;
+                    }
 
                     avifPixelFormatInfo info;
                     avifGetPixelFormatInfo(avif->yuvFormat, &info);
@@ -199,7 +198,9 @@
                 ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE))) {
                 avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
                 cinfo->out_color_space = JCS_GRAYSCALE;
-                avifJPEGCopyPixels(avif, cinfo);
+                if (!avifJPEGCopyPixels(avif, cinfo)) {
+                    return AVIF_FALSE;
+                }
 
                 memcpy(avif->yuvPlanes[AVIF_CHAN_U], avif->yuvPlanes[AVIF_CHAN_Y], (size_t)avif->yuvRowBytes[AVIF_CHAN_U] * avif->height);
                 memcpy(avif->yuvPlanes[AVIF_CHAN_V], avif->yuvPlanes[AVIF_CHAN_Y], (size_t)avif->yuvRowBytes[AVIF_CHAN_V] * avif->height);
@@ -216,9 +217,7 @@
              cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1)) {
             avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
             cinfo->out_color_space = JCS_RGB;
-            avifJPEGCopyPixels(avif, cinfo);
-
-            return AVIF_TRUE;
+            return avifJPEGCopyPixels(avif, cinfo);
         }
     }
 
diff --git a/apps/shared/y4m.c b/apps/shared/y4m.c
index 2f4b8c4..e924600 100644
--- a/apps/shared/y4m.c
+++ b/apps/shared/y4m.c
@@ -348,16 +348,17 @@
         *sourceTiming = frame.sourceTiming;
     }
 
-    avifImageFreePlanes(avif, AVIF_PLANES_YUV | AVIF_PLANES_A);
+    avifImageFreePlanes(avif, AVIF_PLANES_ALL);
     avif->width = frame.width;
     avif->height = frame.height;
     avif->depth = frame.depth;
     avif->yuvFormat = frame.format;
     avif->yuvRange = frame.range;
     avif->yuvChromaSamplePosition = frame.chromaSamplePosition;
-    avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
-    if (frame.hasAlpha) {
-        avifImageAllocatePlanes(avif, AVIF_PLANES_A);
+    avifResult allocationResult = avifImageAllocatePlanes(avif, frame.hasAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV);
+    if (allocationResult != AVIF_RESULT_OK) {
+        fprintf(stderr, "Failed to allocate the planes: %s\n", avifResultToString(allocationResult));
+        goto cleanup;
     }
 
     avifPixelFormatInfo formatInfo;
diff --git a/examples/avif_example_encode.c b/examples/avif_example_encode.c
index e8812fc..054cbdf 100644
--- a/examples/avif_example_encode.c
+++ b/examples/avif_example_encode.c
@@ -41,7 +41,11 @@
         // If you have YUV(A) data you want to encode, use this path
         printf("Encoding raw YUVA data\n");
 
-        avifImageAllocatePlanes(image, AVIF_PLANES_YUV | AVIF_PLANES_A);
+        const avifResult allocateResult = avifImageAllocatePlanes(image, AVIF_PLANES_ALL);
+        if (allocateResult != AVIF_RESULT_OK) {
+            fprintf(stderr, "Failed to allocate the planes: %s\n", avifResultToString(allocateResult));
+            goto cleanup;
+        }
 
         // Fill your YUV(A) data here
         memset(image->yuvPlanes[AVIF_CHAN_Y], 255, image->yuvRowBytes[AVIF_CHAN_Y] * image->height);
diff --git a/include/avif/avif.h b/include/avif/avif.h
index b690bd7..459fa32 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -466,7 +466,7 @@
 
 AVIF_API avifImage * avifImageCreate(int width, int height, int depth, avifPixelFormat yuvFormat);
 AVIF_API avifImage * avifImageCreateEmpty(void); // helper for making an image to decode into
-AVIF_API void avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes); // deep copy
+AVIF_API avifResult avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes); // deep copy
 AVIF_API avifResult avifImageSetViewRect(avifImage * dstImage, const avifImage * srcImage, const avifCropRect * rect); // shallow copy, no metadata
 AVIF_API void avifImageDestroy(avifImage * image);
 
@@ -476,8 +476,8 @@
 AVIF_API void avifImageSetMetadataExif(avifImage * image, const uint8_t * exif, size_t exifSize);
 AVIF_API void avifImageSetMetadataXMP(avifImage * image, const uint8_t * xmp, size_t xmpSize);
 
-AVIF_API void avifImageAllocatePlanes(avifImage * image, avifPlanesFlags planes); // Ignores any pre-existing planes
-AVIF_API void avifImageFreePlanes(avifImage * image, avifPlanesFlags planes);     // Ignores already-freed planes
+AVIF_API avifResult avifImageAllocatePlanes(avifImage * image, avifPlanesFlags planes); // Ignores any pre-existing planes
+AVIF_API void avifImageFreePlanes(avifImage * image, avifPlanesFlags planes);           // Ignores already-freed planes
 AVIF_API void avifImageStealPlanes(avifImage * dstImage, avifImage * srcImage, avifPlanesFlags planes);
 
 // ---------------------------------------------------------------------------
diff --git a/src/avif.c b/src/avif.c
index 947b42c..1856111 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -3,6 +3,8 @@
 
 #include "avif/internal.h"
 
+#include <limits.h>
+#include <stdint.h>
 #include <string.h>
 
 #define STR_HELPER(x) #x
@@ -163,7 +165,7 @@
     dstImage->imir = srcImage->imir;
 }
 
-void avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes)
+avifResult avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes)
 {
     avifImageFreePlanes(dstImage, AVIF_PLANES_ALL);
     avifImageCopyNoAlloc(dstImage, srcImage);
@@ -174,7 +176,10 @@
     avifImageSetMetadataXMP(dstImage, srcImage->xmp.data, srcImage->xmp.size);
 
     if ((planes & AVIF_PLANES_YUV) && srcImage->yuvPlanes[AVIF_CHAN_Y]) {
-        avifImageAllocatePlanes(dstImage, AVIF_PLANES_YUV);
+        const avifResult allocationResult = avifImageAllocatePlanes(dstImage, AVIF_PLANES_YUV);
+        if (allocationResult != AVIF_RESULT_OK) {
+            return allocationResult;
+        }
 
         avifPixelFormatInfo formatInfo;
         avifGetPixelFormatInfo(srcImage->yuvFormat, &formatInfo);
@@ -200,13 +205,17 @@
     }
 
     if ((planes & AVIF_PLANES_A) && srcImage->alphaPlane) {
-        avifImageAllocatePlanes(dstImage, AVIF_PLANES_A);
+        const avifResult allocationResult = avifImageAllocatePlanes(dstImage, AVIF_PLANES_A);
+        if (allocationResult != AVIF_RESULT_OK) {
+            return allocationResult;
+        }
         for (uint32_t j = 0; j < dstImage->height; ++j) {
             uint8_t * srcAlphaRow = &srcImage->alphaPlane[j * srcImage->alphaRowBytes];
             uint8_t * dstAlphaRow = &dstImage->alphaPlane[j * dstImage->alphaRowBytes];
             memcpy(dstAlphaRow, srcAlphaRow, dstImage->alphaRowBytes);
         }
     }
+    return AVIF_RESULT_OK;
 }
 
 avifResult avifImageSetViewRect(avifImage * dstImage, const avifImage * srcImage, const avifCropRect * rect)
@@ -264,45 +273,70 @@
     avifRWDataSet(&image->xmp, xmp, xmpSize);
 }
 
-void avifImageAllocatePlanes(avifImage * image, avifPlanesFlags planes)
+avifResult avifImageAllocatePlanes(avifImage * image, avifPlanesFlags planes)
 {
-    int channelSize = avifImageUsesU16(image) ? 2 : 1;
-    int fullRowBytes = channelSize * image->width;
-    int fullSize = fullRowBytes * image->height;
+    if (image->width == 0 || image->height == 0) {
+        return AVIF_RESULT_INVALID_ARGUMENT;
+    }
+    const size_t channelSize = avifImageUsesU16(image) ? 2 : 1;
+    if (image->width > SIZE_MAX / channelSize) {
+        return AVIF_RESULT_INVALID_ARGUMENT;
+    }
+    const size_t fullRowBytes = channelSize * image->width;
+    if ((fullRowBytes > UINT32_MAX) || (image->height > SIZE_MAX / fullRowBytes)) {
+        return AVIF_RESULT_INVALID_ARGUMENT;
+    }
+    const size_t fullSize = fullRowBytes * image->height;
+
     if ((planes & AVIF_PLANES_YUV) && (image->yuvFormat != AVIF_PIXEL_FORMAT_NONE)) {
         avifPixelFormatInfo info;
         avifGetPixelFormatInfo(image->yuvFormat, &info);
 
-        int shiftedW = (image->width + info.chromaShiftX) >> info.chromaShiftX;
-        int shiftedH = (image->height + info.chromaShiftY) >> info.chromaShiftY;
+        // Intermediary computation as 64 bits in case width or height is exactly UINT32_MAX.
+        const uint32_t shiftedW = (uint32_t)(((uint64_t)image->width + info.chromaShiftX) >> info.chromaShiftX);
+        const uint32_t shiftedH = (uint32_t)(((uint64_t)image->height + info.chromaShiftY) >> info.chromaShiftY);
 
-        int uvRowBytes = channelSize * shiftedW;
-        int uvSize = uvRowBytes * shiftedH;
+        // These are less than or equal to fullRowBytes/fullSize. No need to check overflows.
+        const size_t uvRowBytes = channelSize * shiftedW;
+        const size_t uvSize = uvRowBytes * shiftedH;
 
+        image->imageOwnsYUVPlanes = AVIF_TRUE;
         if (!image->yuvPlanes[AVIF_CHAN_Y]) {
             image->yuvRowBytes[AVIF_CHAN_Y] = fullRowBytes;
             image->yuvPlanes[AVIF_CHAN_Y] = avifAlloc(fullSize);
+            if (!image->yuvPlanes[AVIF_CHAN_Y]) {
+                return AVIF_RESULT_OUT_OF_MEMORY;
+            }
         }
 
         if (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV400) {
             if (!image->yuvPlanes[AVIF_CHAN_U]) {
                 image->yuvRowBytes[AVIF_CHAN_U] = uvRowBytes;
                 image->yuvPlanes[AVIF_CHAN_U] = avifAlloc(uvSize);
+                if (!image->yuvPlanes[AVIF_CHAN_U]) {
+                    return AVIF_RESULT_OUT_OF_MEMORY;
+                }
             }
             if (!image->yuvPlanes[AVIF_CHAN_V]) {
                 image->yuvRowBytes[AVIF_CHAN_V] = uvRowBytes;
                 image->yuvPlanes[AVIF_CHAN_V] = avifAlloc(uvSize);
+                if (!image->yuvPlanes[AVIF_CHAN_V]) {
+                    return AVIF_RESULT_OUT_OF_MEMORY;
+                }
             }
         }
-        image->imageOwnsYUVPlanes = AVIF_TRUE;
     }
     if (planes & AVIF_PLANES_A) {
+        image->imageOwnsAlphaPlane = AVIF_TRUE;
         if (!image->alphaPlane) {
             image->alphaRowBytes = fullRowBytes;
             image->alphaPlane = avifAlloc(fullSize);
+            if (!image->alphaPlane) {
+                return AVIF_RESULT_OUT_OF_MEMORY;
+            }
         }
-        image->imageOwnsAlphaPlane = AVIF_TRUE;
     }
+    return AVIF_RESULT_OK;
 }
 
 void avifImageFreePlanes(avifImage * image, avifPlanesFlags planes)
diff --git a/src/read.c b/src/read.c
index bf5a7b5..64c3434 100644
--- a/src/read.c
+++ b/src/read.c
@@ -1363,7 +1363,10 @@
         }
     }
 
-    avifImageAllocatePlanes(dstImage, alpha ? AVIF_PLANES_A : AVIF_PLANES_YUV);
+    if (avifImageAllocatePlanes(dstImage, alpha ? AVIF_PLANES_A : AVIF_PLANES_YUV) != AVIF_RESULT_OK) {
+        avifDiagnosticsPrintf(data->diag, "Image allocation failure");
+        return AVIF_FALSE;
+    }
 
     avifPixelFormatInfo formatInfo;
     avifGetPixelFormatInfo(firstTile->image->yuvFormat, &formatInfo);
@@ -3744,7 +3747,10 @@
     // codec's internal frame buffers. Allocate memory for the conversion.
     image->alphaPlane = NULL;
     image->alphaRowBytes = 0;
-    avifImageAllocatePlanes(image, AVIF_PLANES_A);
+    const avifResult allocationResult = avifImageAllocatePlanes(image, AVIF_PLANES_A);
+    if (allocationResult != AVIF_RESULT_OK) {
+        return allocationResult;
+    }
 
     if (image->depth > 8) {
         for (uint32_t j = 0; j < image->height; ++j) {
@@ -4145,8 +4151,7 @@
     if (result != AVIF_RESULT_OK) {
         return result;
     }
-    avifImageCopy(image, decoder->image, AVIF_PLANES_ALL);
-    return AVIF_RESULT_OK;
+    return avifImageCopy(image, decoder->image, AVIF_PLANES_ALL);
 }
 
 avifResult avifDecoderReadMemory(avifDecoder * decoder, avifImage * image, const uint8_t * data, size_t size)
diff --git a/src/reformat.c b/src/reformat.c
index 7d7c13f..1e2f89a 100644
--- a/src/reformat.c
+++ b/src/reformat.c
@@ -193,10 +193,14 @@
         return AVIF_RESULT_NOT_IMPLEMENTED;
     }
 
+    const avifBool hasAlpha = avifRGBFormatHasAlpha(rgb->format) && !rgb->ignoreAlpha;
+    avifResult allocationResult = avifImageAllocatePlanes(image, hasAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV);
+    if (allocationResult != AVIF_RESULT_OK) {
+        return allocationResult;
+    }
+
     avifAlphaMultiplyMode alphaMode = AVIF_ALPHA_MULTIPLY_MODE_NO_OP;
-    avifImageAllocatePlanes(image, AVIF_PLANES_YUV);
-    if (avifRGBFormatHasAlpha(rgb->format) && !rgb->ignoreAlpha) {
-        avifImageAllocatePlanes(image, AVIF_PLANES_A);
+    if (hasAlpha) {
         if (!rgb->alphaPremultiplied && image->alphaPremultiplied) {
             alphaMode = AVIF_ALPHA_MULTIPLY_MODE_MULTIPLY;
         } else if (rgb->alphaPremultiplied && !image->alphaPremultiplied) {
diff --git a/src/scale.c b/src/scale.c
index 17251b4..82d24d4 100644
--- a/src/scale.c
+++ b/src/scale.c
@@ -89,7 +89,11 @@
     }
 
     if (srcYUVPlanes[0]) {
-        avifImageAllocatePlanes(image, AVIF_PLANES_YUV);
+        const avifResult allocationResult = avifImageAllocatePlanes(image, AVIF_PLANES_YUV);
+        if (allocationResult != AVIF_RESULT_OK) {
+            avifDiagnosticsPrintf(diag, "Allocation of YUV planes failed: %s", avifResultToString(allocationResult));
+            return AVIF_FALSE;
+        }
 
         avifPixelFormatInfo formatInfo;
         avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
@@ -132,7 +136,11 @@
     }
 
     if (srcAlphaPlane) {
-        avifImageAllocatePlanes(image, AVIF_PLANES_A);
+        const avifResult allocationResult = avifImageAllocatePlanes(image, AVIF_PLANES_A);
+        if (allocationResult != AVIF_RESULT_OK) {
+            avifDiagnosticsPrintf(diag, "Allocation of alpha plane failed: %s", avifResultToString(allocationResult));
+            return AVIF_FALSE;
+        }
 
         if (image->depth > 8) {
             uint16_t * const srcPlane = (uint16_t *)srcAlphaPlane;
diff --git a/src/write.c b/src/write.c
index ad2a956..2585678 100644
--- a/src/write.c
+++ b/src/write.c
@@ -683,7 +683,10 @@
 
     if (encoder->data->items.count == 0) {
         // Make a copy of the first image's metadata (sans pixels) for future writing/validation
-        avifImageCopy(encoder->data->imageMetadata, firstCell, 0);
+        const avifResult copyResult = avifImageCopy(encoder->data->imageMetadata, firstCell, 0);
+        if (copyResult != AVIF_RESULT_OK) {
+            return copyResult;
+        }
 
         // Prepare all AV1 items
 
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index eab84e6..869db35 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -68,6 +68,11 @@
         find_package(GTest REQUIRED)
     endif()
 
+    add_executable(avifallocationtest gtest/avifallocationtest.cc)
+    target_link_libraries(avifallocationtest aviftest_helpers ${GTEST_BOTH_LIBRARIES})
+    target_include_directories(avifallocationtest PRIVATE ${GTEST_INCLUDE_DIRS})
+    add_test(NAME avifallocationtest COMMAND avifallocationtest)
+
     add_executable(avifgridapitest gtest/avifgridapitest.cc)
     target_link_libraries(avifgridapitest aviftest_helpers ${GTEST_BOTH_LIBRARIES})
     target_include_directories(avifgridapitest PRIVATE ${GTEST_INCLUDE_DIRS})
diff --git a/tests/gtest/avifallocationtest.cc b/tests/gtest/avifallocationtest.cc
new file mode 100644
index 0000000..ecf3350
--- /dev/null
+++ b/tests/gtest/avifallocationtest.cc
@@ -0,0 +1,187 @@
+// Copyright 2022 Google LLC. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include <limits>
+#include <vector>
+
+#include "avif/avif.h"
+#include "aviftest_helpers.h"
+#include "gtest/gtest.h"
+
+using ::testing::Combine;
+using ::testing::Values;
+using ::testing::ValuesIn;
+
+namespace libavif {
+namespace {
+
+void TestAllocation(uint32_t width, uint32_t height, uint32_t depth,
+                    avifResult expected_result) {
+  // The format of the image and which planes are allocated should not matter.
+  // Test all combinations.
+  for (avifPixelFormat format :
+       {AVIF_PIXEL_FORMAT_NONE, AVIF_PIXEL_FORMAT_YUV444,
+        AVIF_PIXEL_FORMAT_YUV422, AVIF_PIXEL_FORMAT_YUV420,
+        AVIF_PIXEL_FORMAT_YUV400}) {
+    for (avifPlanesFlag planes :
+         {AVIF_PLANES_YUV, AVIF_PLANES_A, AVIF_PLANES_ALL}) {
+      testutil::AvifImagePtr image(avifImageCreateEmpty(), avifImageDestroy);
+      ASSERT_NE(image, nullptr);
+      image->width = width;
+      image->height = height;
+      image->depth = depth;
+      image->yuvFormat = format;
+      EXPECT_EQ(avifImageAllocatePlanes(image.get(), planes), expected_result);
+
+      // Make sure the actual plane pointers are consistent with the settings.
+      if (expected_result == AVIF_RESULT_OK &&
+          format != AVIF_PIXEL_FORMAT_NONE && (planes & AVIF_PLANES_YUV)) {
+        EXPECT_NE(image->yuvPlanes[AVIF_CHAN_Y], nullptr);
+      } else {
+        EXPECT_EQ(image->yuvPlanes[AVIF_CHAN_Y], nullptr);
+      }
+      if (expected_result == AVIF_RESULT_OK &&
+          format != AVIF_PIXEL_FORMAT_NONE &&
+          format != AVIF_PIXEL_FORMAT_YUV400 && (planes & AVIF_PLANES_YUV)) {
+        EXPECT_NE(image->yuvPlanes[AVIF_CHAN_U], nullptr);
+        EXPECT_NE(image->yuvPlanes[AVIF_CHAN_V], nullptr);
+      } else {
+        EXPECT_EQ(image->yuvPlanes[AVIF_CHAN_U], nullptr);
+        EXPECT_EQ(image->yuvPlanes[AVIF_CHAN_V], nullptr);
+      }
+      if (expected_result == AVIF_RESULT_OK && (planes & AVIF_PLANES_A)) {
+        EXPECT_NE(image->alphaPlane, nullptr);
+      } else {
+        EXPECT_EQ(image->alphaPlane, nullptr);
+      }
+    }
+  }
+}
+
+TEST(AllocationTest, MinimumValidDimensions) {
+  TestAllocation(1, 1, 8, AVIF_RESULT_OK);
+}
+
+TEST(AllocationTest, MaximumValidDimensions) {
+  // On 32-bit builds, malloc() will fail with fairly low sizes.
+  // Adapt the tests to take that into account.
+  constexpr bool kIsPlatform64b = sizeof(void*) > 4;
+  constexpr uint32_t kMaxAllocatableDimension =
+      kIsPlatform64b ? std::numeric_limits<typeof(avifImage::width)>::max()
+                     : 134217728;  // Up to 1 GB total for YUVA
+
+  // 8 bits
+  TestAllocation(kMaxAllocatableDimension, 1, 8, AVIF_RESULT_OK);
+  TestAllocation(1, kMaxAllocatableDimension, 8, AVIF_RESULT_OK);
+  // 12 bits (impacts the width because avifImage stride is stored as uint32_t)
+  TestAllocation(kMaxAllocatableDimension / 2, 1, 12, AVIF_RESULT_OK);
+  TestAllocation(1, kMaxAllocatableDimension, 12, AVIF_RESULT_OK);
+  // Some high number of bytes that malloc() accepts to allocate.
+  TestAllocation(1024 * 16, 1024 * 8, 12, AVIF_RESULT_OK);  // Up to 1 GB total
+}
+
+TEST(AllocationTest, MinimumInvalidDimensions) {
+  TestAllocation(std::numeric_limits<typeof(avifImage::width)>::max(), 1, 12,
+                 AVIF_RESULT_INVALID_ARGUMENT);
+}
+
+TEST(AllocationTest, MaximumInvalidDimensions) {
+  TestAllocation(std::numeric_limits<typeof(avifImage::width)>::max(),
+                 std::numeric_limits<typeof(avifImage::height)>::max(), 12,
+                 AVIF_RESULT_INVALID_ARGUMENT);
+}
+
+// This is valid in theory but malloc() should refuse to allocate so much and
+// avifAlloc() aborts on malloc() failure.
+TEST(DISABLED_AllocationTest, OutOfMemory) {
+  TestAllocation(std::numeric_limits<typeof(avifImage::width)>::max() / 2,
+                 std::numeric_limits<typeof(avifImage::height)>::max(), 12,
+                 AVIF_RESULT_OUT_OF_MEMORY);
+}
+
+void TestEncoding(uint32_t width, uint32_t height, uint32_t depth,
+                  avifResult expected_result) {
+  testutil::AvifImagePtr image(avifImageCreateEmpty(), avifImageDestroy);
+  ASSERT_NE(image, nullptr);
+  image->width = width;
+  image->height = height;
+  image->depth = depth;
+  image->yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
+
+  // This is a fairly high number of bytes that can safely be allocated in this
+  // test. The goal is to have something to give to libavif but libavif should
+  // return an error before attempting to read all of it, so it does not matter
+  // if there are fewer bytes than the provided image dimensions.
+  static constexpr uint64_t kMaxAlloc = 1073741824;
+  uint32_t row_bytes;
+  size_t num_allocated_bytes;
+  if ((uint64_t)image->width * image->height >
+      kMaxAlloc / (avifImageUsesU16(image.get()) ? 2 : 1)) {
+    row_bytes = 1024;  // Does not matter much.
+    num_allocated_bytes = kMaxAlloc;
+  } else {
+    row_bytes = image->width * (avifImageUsesU16(image.get()) ? 2 : 1);
+    num_allocated_bytes = row_bytes * image->height;
+  }
+
+  // Initialize pixels as 16b values to make sure values are valid for 10
+  // and 12-bit depths. The array will be cast to uint8_t for 8-bit depth.
+  std::vector<uint16_t> pixels(num_allocated_bytes / sizeof(uint16_t), 400);
+  uint8_t* bytes = reinterpret_cast<uint8_t*>(pixels.data());
+  // Avoid avifImageAllocatePlanes() to exercise the checks at encoding.
+  image->imageOwnsYUVPlanes = AVIF_FALSE;
+  image->imageOwnsAlphaPlane = AVIF_FALSE;
+  image->yuvRowBytes[AVIF_CHAN_Y] = row_bytes;
+  image->yuvPlanes[AVIF_CHAN_Y] = bytes;
+  image->yuvRowBytes[AVIF_CHAN_U] = row_bytes;
+  image->yuvPlanes[AVIF_CHAN_U] = bytes;
+  image->yuvRowBytes[AVIF_CHAN_V] = row_bytes;
+  image->yuvPlanes[AVIF_CHAN_V] = bytes;
+  image->alphaRowBytes = row_bytes;
+  image->alphaPlane = bytes;
+
+  // Try to encode.
+  testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
+  ASSERT_NE(encoder, nullptr);
+  encoder->speed = AVIF_SPEED_FASTEST;
+  testutil::AvifRwData encoded_avif;
+  ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded_avif),
+            expected_result);
+}
+
+TEST(EncodingTest, MinimumValidDimensions) {
+  TestAllocation(1, 1, 8, AVIF_RESULT_OK);
+}
+
+TEST(EncodingTest, MaximumValidDimensions) {
+  // 65536 is the maximum AV1 frame dimension allowed by the AV1 specification.
+  // See the section 5.5.1. General sequence header OBU syntax.
+  // Old versions of libaom are capped to 65535 (http://crbug.com/aomedia/3304).
+  TestEncoding(65535, 1, 12, AVIF_RESULT_OK);
+  TestEncoding(1, 65535, 12, AVIF_RESULT_OK);
+  // TestEncoding(65536, 65536, 12, AVIF_RESULT_OK);  // Too slow.
+}
+
+TEST(EncodingTest, MinimumInvalidDimensions) {
+  TestEncoding(0, 1, 8, AVIF_RESULT_NO_CONTENT);
+  TestEncoding(1, 0, 8, AVIF_RESULT_NO_CONTENT);
+  TestEncoding(1, 1, 0, AVIF_RESULT_UNSUPPORTED_DEPTH);
+  TestEncoding(65536 + 1, 1, 8, AVIF_RESULT_ENCODE_COLOR_FAILED);
+  TestEncoding(1, 65536 + 1, 8, AVIF_RESULT_ENCODE_COLOR_FAILED);
+  TestEncoding(65536 + 1, 65536 + 1, 8, AVIF_RESULT_ENCODE_COLOR_FAILED);
+}
+
+TEST(EncodingTest, MaximumInvalidDimensions) {
+  TestEncoding(std::numeric_limits<typeof(avifImage::width)>::max(), 1, 8,
+               AVIF_RESULT_ENCODE_COLOR_FAILED);
+  TestEncoding(1, std::numeric_limits<typeof(avifImage::height)>::max(), 8,
+               AVIF_RESULT_ENCODE_COLOR_FAILED);
+  TestEncoding(std::numeric_limits<typeof(avifImage::width)>::max(),
+               std::numeric_limits<typeof(avifImage::height)>::max(), 12,
+               AVIF_RESULT_ENCODE_COLOR_FAILED);
+  TestEncoding(1, 1, std::numeric_limits<typeof(avifImage::depth)>::max(),
+               AVIF_RESULT_UNSUPPORTED_DEPTH);
+}
+
+}  // namespace
+}  // namespace libavif
diff --git a/tests/gtest/avifgridapitest.cc b/tests/gtest/avifgridapitest.cc
index 2fc9232..f373240 100644
--- a/tests/gtest/avifgridapitest.cc
+++ b/tests/gtest/avifgridapitest.cc
@@ -41,6 +41,9 @@
     cell_images.emplace_back(testutil::CreateImage(
         horizontal.size, vertical.size, bit_depth, yuv_format,
         create_alpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV));
+    if (cell_images.back() == nullptr && !expected_success) {
+      return;  // Bad dimensions may have been already caught.
+    }
     ASSERT_NE(cell_images.back(), nullptr);
     testutil::FillImageGradient(cell_images.back().get());
   }
diff --git a/tests/gtest/aviftest_helpers.cc b/tests/gtest/aviftest_helpers.cc
index 6ca14dc..cdcafc4 100644
--- a/tests/gtest/aviftest_helpers.cc
+++ b/tests/gtest/aviftest_helpers.cc
@@ -57,7 +57,9 @@
     return {nullptr, nullptr};
   }
   image->yuvRange = yuv_range;
-  avifImageAllocatePlanes(image.get(), planes);
+  if (avifImageAllocatePlanes(image.get(), planes) != AVIF_RESULT_OK) {
+    return {nullptr, nullptr};
+  }
   return image;
 }