Add the avifDecoder option 'imageDimensionLimit'

Add the avifDecoder option 'imageDimensionLimit', default to 32768. This
allows the decoder to reject extra large image widths or heights by
default. Note that our image size and dimension limits are essentially
the same as Chrome media's limits:
https://source.chromium.org/chromium/chromium/src/+/main:media/base/limits.h

  // Maximum possible dimension (width or height) for any video.
  constexpr int kMaxDimension = (1 << 15) - 1;  // 32767

  // Maximum possible canvas size (width multiplied by height) for any video.
  constexpr int kMaxCanvas = (1 << (14 * 2));  // 16384 x 16384

Add the --dimension-limit option to avifdec.

Add the internal utility function avifDimensionsTooLarge(), declared in
include/avif/internal.h and defined in src/avif.c.

Bug: b/242362974
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b07142d..0aa0674 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,9 +8,10 @@
 
 There are incompatible ABI changes in this release. The alphaRange member was
 removed from avifImage struct. The chromaDownsampling member was added to the
-avifRGBImage struct. avifImageCopy() and avifImageAllocatePlanes()
-signatures changed. It is necessary to recompile your code. Also check the
-return values of avifImageCopy() and avifImageAllocatePlanes().
+avifRGBImage struct. The imageDimensionLimit member was added to the avifDecoder
+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
@@ -30,6 +31,7 @@
 ### Added
 * Add avifChromaDownsampling enum
 * Add chromaDownsampling field to avifRGBImage struct
+* Add imageDimensionLimit field to avifDecoder struct
 
 ## [0.10.1] - 2022-04-11
 
diff --git a/apps/avifdec.c b/apps/avifdec.c
index 6081e92..98c341d 100644
--- a/apps/avifdec.c
+++ b/apps/avifdec.c
@@ -45,6 +45,8 @@
     printf("    --ignore-icc      : If the input file contains an embedded ICC profile, ignore it (no-op if absent)\n");
     printf("    --size-limit C    : Specifies the image size limit (in total pixels) that should be tolerated.\n");
     printf("                        Default: %u, set to a smaller value to further restrict.\n", AVIF_DEFAULT_IMAGE_SIZE_LIMIT);
+    printf("  --dimension-limit C : Specifies the image dimension limit (width or height) that should be tolerated.\n");
+    printf("                        Default: %u, set to 0 to ignore.\n", AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT);
     printf("\n");
     avifPrintVersions();
 }
@@ -66,6 +68,7 @@
     avifStrictFlags strictFlags = AVIF_STRICT_ENABLED;
     uint32_t frameIndex = 0;
     uint32_t imageSizeLimit = AVIF_DEFAULT_IMAGE_SIZE_LIMIT;
+    uint32_t imageDimensionLimit = AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT;
 
     if (argc < 2) {
         syntax();
@@ -165,6 +168,14 @@
                 return 1;
             }
             imageSizeLimit = (uint32_t)value;
+        } else if (!strcmp(arg, "--dimension-limit")) {
+            NEXTARG();
+            unsigned long value = strtoul(arg, NULL, 10);
+            if (value > UINT32_MAX) {
+                fprintf(stderr, "ERROR: invalid image dimension limit: %s\n", arg);
+                return 1;
+            }
+            imageDimensionLimit = (uint32_t)value;
         } else {
             // Positional argument
             if (!inputFilename) {
@@ -196,6 +207,7 @@
         decoder->maxThreads = jobs;
         decoder->codecChoice = codecChoice;
         decoder->imageSizeLimit = imageSizeLimit;
+        decoder->imageDimensionLimit = imageDimensionLimit;
         decoder->strictFlags = strictFlags;
         decoder->allowProgressive = allowProgressive;
         avifResult result = avifDecoderSetIOFile(decoder, inputFilename);
@@ -264,6 +276,7 @@
     decoder->maxThreads = jobs;
     decoder->codecChoice = codecChoice;
     decoder->imageSizeLimit = imageSizeLimit;
+    decoder->imageDimensionLimit = imageDimensionLimit;
     decoder->strictFlags = strictFlags;
     decoder->allowProgressive = allowProgressive;
 
diff --git a/include/avif/avif.h b/include/avif/avif.h
index ced9c3e..acc6671 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -68,10 +68,13 @@
 
 #define AVIF_DIAGNOSTICS_ERROR_BUFFER_SIZE 256
 
-// A reasonable default for maximum image size to avoid out-of-memory errors or integer overflow in
-// (32-bit) int or unsigned int arithmetic operations.
+// A reasonable default for maximum image size (in pixel count) to avoid out-of-memory errors or
+// integer overflow in (32-bit) int or unsigned int arithmetic operations.
 #define AVIF_DEFAULT_IMAGE_SIZE_LIMIT (16384 * 16384)
 
+// A reasonable default for maximum image dimension (width or height).
+#define AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT 32768
+
 // a 12 hour AVIF image sequence, running at 60 fps (a basic sanity check as this is quite ridiculous)
 #define AVIF_DEFAULT_IMAGE_COUNT_LIMIT (12 * 3600 * 60)
 
@@ -878,12 +881,17 @@
     avifBool ignoreExif;
     avifBool ignoreXMP;
 
-    // This represents the maximum size of a image (in pixel count) that libavif and the underlying
-    // AV1 decoder should attempt to decode. It defaults to AVIF_DEFAULT_IMAGE_SIZE_LIMIT, and can be
-    // set to a smaller value. The value 0 is reserved.
+    // This represents the maximum size of an image (in pixel count) that libavif and the underlying
+    // AV1 decoder should attempt to decode. It defaults to AVIF_DEFAULT_IMAGE_SIZE_LIMIT, and can
+    // be set to a smaller value. The value 0 is reserved.
     // Note: Only some underlying AV1 codecs support a configurable size limit (such as dav1d).
     uint32_t imageSizeLimit;
 
+    // This represents the maximum dimension of an image (width or height) that libavif should
+    // attempt to decode. It defaults to AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT. Set it to 0 to ignore
+    // the limit.
+    uint32_t imageDimensionLimit;
+
     // This provides an upper bound on how many images the decoder is willing to attempt to decode,
     // to provide a bit of protection from malicious or malformed AVIFs citing millions upon
     // millions of frames, only to be invalid later. The default is AVIF_DEFAULT_IMAGE_COUNT_LIMIT
diff --git a/include/avif/internal.h b/include/avif/internal.h
index c328c06..f17ee38 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -164,11 +164,18 @@
 avifResult avifRGBImagePremultiplyAlphaLibYUV(avifRGBImage * rgb);
 avifResult avifRGBImageUnpremultiplyAlphaLibYUV(avifRGBImage * rgb);
 
+avifBool avifDimensionsTooLarge(uint32_t width, uint32_t height, uint32_t imageSizeLimit, uint32_t imageDimensionLimit);
+
 // ---------------------------------------------------------------------------
 // Scaling
 
 // This scales the YUV/A planes in-place.
-avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, uint32_t imageSizeLimit, avifDiagnostics * diag);
+avifBool avifImageScale(avifImage * image,
+                        uint32_t dstWidth,
+                        uint32_t dstHeight,
+                        uint32_t imageSizeLimit,
+                        uint32_t imageDimensionLimit,
+                        avifDiagnostics * diag);
 
 // ---------------------------------------------------------------------------
 // Grid AVIF images
diff --git a/src/avif.c b/src/avif.c
index 6ced063..41e4748 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -407,6 +407,17 @@
     return (image->depth > 8);
 }
 
+avifBool avifDimensionsTooLarge(uint32_t width, uint32_t height, uint32_t imageSizeLimit, uint32_t imageDimensionLimit)
+{
+    if (width > (imageSizeLimit / height)) {
+        return AVIF_TRUE;
+    }
+    if ((imageDimensionLimit != 0) && ((width > imageDimensionLimit) || (height > imageDimensionLimit))) {
+        return AVIF_TRUE;
+    }
+    return AVIF_FALSE;
+}
+
 // avifCodecCreate*() functions are in their respective codec_*.c files
 
 void avifCodecDestroy(avifCodec * codec)
diff --git a/src/read.c b/src/read.c
index 64c3434..48e3451 100644
--- a/src/read.c
+++ b/src/read.c
@@ -1658,7 +1658,12 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit, avifDiagnostics * diag)
+static avifBool avifParseImageGridBox(avifImageGrid * grid,
+                                      const uint8_t * raw,
+                                      size_t rawLen,
+                                      uint32_t imageSizeLimit,
+                                      uint32_t imageDimensionLimit,
+                                      avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen, diag, "Box[grid]");
 
@@ -1695,7 +1700,7 @@
         avifDiagnosticsPrintf(diag, "Grid box contains illegal dimensions: [%u x %u]", grid->outputWidth, grid->outputHeight);
         return AVIF_FALSE;
     }
-    if (grid->outputWidth > (imageSizeLimit / grid->outputHeight)) {
+    if (avifDimensionsTooLarge(grid->outputWidth, grid->outputHeight, imageSizeLimit, imageDimensionLimit)) {
         avifDiagnosticsPrintf(diag, "Grid box dimensions are too large: [%u x %u]", grid->outputWidth, grid->outputHeight);
         return AVIF_FALSE;
     }
@@ -2410,7 +2415,12 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseTrackHeaderBox(avifTrack * track, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit, avifDiagnostics * diag)
+static avifBool avifParseTrackHeaderBox(avifTrack * track,
+                                        const uint8_t * raw,
+                                        size_t rawLen,
+                                        uint32_t imageSizeLimit,
+                                        uint32_t imageDimensionLimit,
+                                        avifDiagnostics * diag)
 {
     BEGIN_STREAM(s, raw, rawLen, diag, "Box[tkhd]");
 
@@ -2457,8 +2467,8 @@
         avifDiagnosticsPrintf(diag, "Track ID [%u] has an invalid size [%ux%u]", track->id, track->width, track->height);
         return AVIF_FALSE;
     }
-    if (track->width > (imageSizeLimit / track->height)) {
-        avifDiagnosticsPrintf(diag, "Track ID [%u] size is too large [%ux%u]", track->id, track->width, track->height);
+    if (avifDimensionsTooLarge(track->width, track->height, imageSizeLimit, imageDimensionLimit)) {
+        avifDiagnosticsPrintf(diag, "Track ID [%u] dimensions are too large [%ux%u]", track->id, track->width, track->height);
         return AVIF_FALSE;
     }
 
@@ -2739,7 +2749,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseTrackBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit)
+static avifBool avifParseTrackBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit, uint32_t imageDimensionLimit)
 {
     BEGIN_STREAM(s, raw, rawLen, data->diag, "Box[trak]");
 
@@ -2750,7 +2760,7 @@
         CHECK(avifROStreamReadBoxHeader(&s, &header));
 
         if (!memcmp(header.type, "tkhd", 4)) {
-            CHECK(avifParseTrackHeaderBox(track, avifROStreamCurrent(&s), header.size, imageSizeLimit, data->diag));
+            CHECK(avifParseTrackHeaderBox(track, avifROStreamCurrent(&s), header.size, imageSizeLimit, imageDimensionLimit, data->diag));
         } else if (!memcmp(header.type, "meta", 4)) {
             CHECK(avifParseMetaBox(track->meta, avifROStreamCurrent(&s), header.size, data->diag));
         } else if (!memcmp(header.type, "mdia", 4)) {
@@ -2764,7 +2774,7 @@
     return AVIF_TRUE;
 }
 
-static avifBool avifParseMovieBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit)
+static avifBool avifParseMovieBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit, uint32_t imageDimensionLimit)
 {
     BEGIN_STREAM(s, raw, rawLen, data->diag, "Box[moov]");
 
@@ -2773,7 +2783,7 @@
         CHECK(avifROStreamReadBoxHeader(&s, &header));
 
         if (!memcmp(header.type, "trak", 4)) {
-            CHECK(avifParseTrackBox(data, avifROStreamCurrent(&s), header.size, imageSizeLimit));
+            CHECK(avifParseTrackBox(data, avifROStreamCurrent(&s), header.size, imageSizeLimit, imageDimensionLimit));
         }
 
         CHECK(avifROStreamSkip(&s, header.size));
@@ -2875,7 +2885,8 @@
             metaSeen = AVIF_TRUE;
         } else if (!memcmp(header.type, "moov", 4)) {
             CHECKERR(!moovSeen, AVIF_RESULT_BMFF_PARSE_FAILED);
-            CHECKERR(avifParseMovieBox(data, boxContents.data, boxContents.size, decoder->imageSizeLimit), AVIF_RESULT_BMFF_PARSE_FAILED);
+            CHECKERR(avifParseMovieBox(data, boxContents.data, boxContents.size, decoder->imageSizeLimit, decoder->imageDimensionLimit),
+                     AVIF_RESULT_BMFF_PARSE_FAILED);
             moovSeen = AVIF_TRUE;
         }
 
@@ -2944,6 +2955,7 @@
     memset(decoder, 0, sizeof(avifDecoder));
     decoder->maxThreads = 1;
     decoder->imageSizeLimit = AVIF_DEFAULT_IMAGE_SIZE_LIMIT;
+    decoder->imageDimensionLimit = AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT;
     decoder->imageCountLimit = AVIF_DEFAULT_IMAGE_COUNT_LIMIT;
     decoder->strictFlags = AVIF_STRICT_ENABLED;
     return decoder;
@@ -3179,8 +3191,8 @@
                 avifDiagnosticsPrintf(data->diag, "Item ID [%u] has an invalid size [%ux%u]", item->id, item->width, item->height);
                 return AVIF_RESULT_BMFF_PARSE_FAILED;
             }
-            if (item->width > (decoder->imageSizeLimit / item->height)) {
-                avifDiagnosticsPrintf(data->diag, "Item ID [%u] size is too large [%ux%u]", item->id, item->width, item->height);
+            if (avifDimensionsTooLarge(item->width, item->height, decoder->imageSizeLimit, decoder->imageDimensionLimit)) {
+                avifDiagnosticsPrintf(data->diag, "Item ID [%u] dimensions are too large [%ux%u]", item->id, item->width, item->height);
                 return AVIF_RESULT_BMFF_PARSE_FAILED;
             }
         } else {
@@ -3428,7 +3440,12 @@
                 if (readResult != AVIF_RESULT_OK) {
                     return readResult;
                 }
-                if (!avifParseImageGridBox(&data->colorGrid, readData.data, readData.size, decoder->imageSizeLimit, data->diag)) {
+                if (!avifParseImageGridBox(&data->colorGrid,
+                                           readData.data,
+                                           readData.size,
+                                           decoder->imageSizeLimit,
+                                           decoder->imageDimensionLimit,
+                                           data->diag)) {
                     return AVIF_RESULT_INVALID_IMAGE_GRID;
                 }
             }
@@ -3468,7 +3485,12 @@
                     if (readResult != AVIF_RESULT_OK) {
                         return readResult;
                     }
-                    if (!avifParseImageGridBox(&data->alphaGrid, readData.data, readData.size, decoder->imageSizeLimit, data->diag)) {
+                    if (!avifParseImageGridBox(&data->alphaGrid,
+                                               readData.data,
+                                               readData.size,
+                                               decoder->imageSizeLimit,
+                                               decoder->imageDimensionLimit,
+                                               data->diag)) {
                         return AVIF_RESULT_INVALID_IMAGE_GRID;
                     }
                 }
@@ -3813,7 +3835,12 @@
 
         // Scale the decoded image so that it corresponds to this tile's output dimensions
         if ((tile->width != tile->image->width) || (tile->height != tile->image->height)) {
-            if (!avifImageScale(tile->image, tile->width, tile->height, decoder->imageSizeLimit, &decoder->diag)) {
+            if (!avifImageScale(tile->image,
+                                tile->width,
+                                tile->height,
+                                decoder->imageSizeLimit,
+                                decoder->imageDimensionLimit,
+                                &decoder->diag)) {
                 avifDiagnosticsPrintf(&decoder->diag, "avifImageScale() failed");
                 return tile->input->alpha ? AVIF_RESULT_DECODE_ALPHA_FAILED : AVIF_RESULT_DECODE_COLOR_FAILED;
             }
diff --git a/src/scale.c b/src/scale.c
index 82d24d4..2b96760 100644
--- a/src/scale.c
+++ b/src/scale.c
@@ -5,12 +5,18 @@
 
 #if !defined(AVIF_LIBYUV_ENABLED)
 
-avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, uint32_t imageSizeLimit, avifDiagnostics * diag)
+avifBool avifImageScale(avifImage * image,
+                        uint32_t dstWidth,
+                        uint32_t dstHeight,
+                        uint32_t imageSizeLimit,
+                        uint32_t imageDimensionLimit,
+                        avifDiagnostics * diag)
 {
     (void)image;
     (void)dstWidth;
     (void)dstHeight;
     (void)imageSizeLimit;
+    (void)imageDimensionLimit;
     avifDiagnosticsPrintf(diag, "avifImageScale() called, but is unimplemented without libyuv!");
     return AVIF_FALSE;
 }
@@ -36,7 +42,12 @@
 // This should be configurable and/or smarter. kFilterBox has the highest quality but is the slowest.
 #define AVIF_LIBYUV_FILTER_MODE kFilterBox
 
-avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, uint32_t imageSizeLimit, avifDiagnostics * diag)
+avifBool avifImageScale(avifImage * image,
+                        uint32_t dstWidth,
+                        uint32_t dstHeight,
+                        uint32_t imageSizeLimit,
+                        uint32_t imageDimensionLimit,
+                        avifDiagnostics * diag)
 {
     if ((image->width == dstWidth) && (image->height == dstHeight)) {
         // Nothing to do
@@ -47,7 +58,7 @@
         avifDiagnosticsPrintf(diag, "avifImageScale requested invalid dst dimensions [%ux%u]", dstWidth, dstHeight);
         return AVIF_FALSE;
     }
-    if (dstWidth > (imageSizeLimit / dstHeight)) {
+    if (avifDimensionsTooLarge(dstWidth, dstHeight, imageSizeLimit, imageDimensionLimit)) {
         avifDiagnosticsPrintf(diag, "avifImageScale requested dst dimensions that are too large [%ux%u]", dstWidth, dstHeight);
         return AVIF_FALSE;
     }