Rename AVIF_MAX_IMAGE_SIZE to AVIF_DEFAULT_MAX_IMAGE_SIZE, set defaults in avifDecoder, promote some indexing math vars to uint32_t, update some comments
diff --git a/apps/avifdec.c b/apps/avifdec.c
index b4aafbd..497534a 100644
--- a/apps/avifdec.c
+++ b/apps/avifdec.c
@@ -36,8 +36,8 @@
printf(" -u,--upsampling U : Chroma upsampling (for 420/422). automatic (default), fastest, best, nearest, or bilinear\n");
printf(" -i,--info : Decode all frames and display all image information instead of saving to disk\n");
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 the AV1 codec should tolerate.\n");
- printf(" Default: %u, set to 0 to disable. Supported codecs: dav1d.\n", AVIF_MAX_IMAGE_SIZE);
+ printf(" --size-limit C : Specifies the image size limit (in total pixels) that should be tolerated.\n");
+ printf(" Default: %u, set to 0 to disable.\n", AVIF_DEFAULT_MAX_IMAGE_SIZE);
printf("\n");
avifPrintVersions();
}
@@ -93,7 +93,7 @@
avifBool infoOnly = AVIF_FALSE;
avifChromaUpsampling chromaUpsampling = AVIF_CHROMA_UPSAMPLING_AUTOMATIC;
avifBool ignoreICC = AVIF_FALSE;
- uint32_t imageSizeLimit = AVIF_MAX_IMAGE_SIZE;
+ uint32_t imageSizeLimit = AVIF_DEFAULT_MAX_IMAGE_SIZE;
if (argc < 2) {
syntax();
diff --git a/include/avif/avif.h b/include/avif/avif.h
index e8e452a..9e01cdc 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -76,9 +76,9 @@
#define AVIF_SPEED_SLOWEST 0
#define AVIF_SPEED_FASTEST 10
-// A maximum image size to avoid out-of-memory errors or integer overflow in
+// 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.
-#define AVIF_MAX_IMAGE_SIZE (16384 * 16384)
+#define AVIF_DEFAULT_MAX_IMAGE_SIZE (16384 * 16384)
enum avifPlanesFlags
{
@@ -144,7 +144,8 @@
AVIF_RESULT_IO_ERROR,
AVIF_RESULT_WAITING_ON_IO, // similar to EAGAIN/EWOULDBLOCK, this means the avifIO doesn't have necessary data available yet
AVIF_RESULT_INVALID_ARGUMENT, // an argument passed into this function is invalid
- AVIF_RESULT_NOT_IMPLEMENTED // a requested code path is not (yet) implemented
+ AVIF_RESULT_NOT_IMPLEMENTED, // a requested code path is not (yet) implemented
+ AVIF_RESULT_IMAGE_TOO_LARGE // The image exceeds the configured imageSizeLimit
} avifResult;
AVIF_API const char * avifResultToString(avifResult result);
@@ -705,9 +706,10 @@
avifBool ignoreExif;
avifBool ignoreXMP;
- // This represents the maximum size of a image (in pixel count) that the underlying AV1 decoder
- // should attempt to decode. It defaults to AVIF_MAX_IMAGE_SIZE, and can be set to 0 to disable
- // the limit. Currently supported codecs: dav1d.
+ // 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_MAX_IMAGE_SIZE, and can be
+ // set to 0 to disable the limit.
+ // Note: Only some underlying AV1 codecs support a configurable size limit (such as dav1d).
uint32_t imageSizeLimit;
// stats from the most recent read, possibly 0s if reading an image sequence
diff --git a/src/avif.c b/src/avif.c
index 18e6734..085e526 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -93,6 +93,7 @@
case AVIF_RESULT_WAITING_ON_IO: return "Waiting on IO";
case AVIF_RESULT_INVALID_ARGUMENT: return "Invalid argument";
case AVIF_RESULT_NOT_IMPLEMENTED: return "Not implemented";
+ case AVIF_RESULT_IMAGE_TOO_LARGE: return "Image too large";
case AVIF_RESULT_UNKNOWN_ERROR:
default:
break;
diff --git a/src/read.c b/src/read.c
index 44ee044..a826a80 100644
--- a/src/read.c
+++ b/src/read.c
@@ -1182,14 +1182,14 @@
return AVIF_TRUE;
}
-static avifBool avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw, size_t rawLen)
+static avifResult avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit)
{
BEGIN_STREAM(s, raw, rawLen);
uint8_t version, flags;
CHECK(avifROStreamRead(&s, &version, 1)); // unsigned int(8) version = 0;
if (version != 0) {
- return AVIF_FALSE;
+ return AVIF_RESULT_INVALID_IMAGE_GRID;
}
uint8_t rowsMinusOne, columnsMinusOne;
CHECK(avifROStreamRead(&s, &flags, 1)); // unsigned int(8) flags;
@@ -1208,15 +1208,18 @@
} else {
if (fieldLength != 32) {
// This should be impossible
- return AVIF_FALSE;
+ return AVIF_RESULT_INVALID_IMAGE_GRID;
}
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))) {
- return AVIF_FALSE;
+ if ((grid->outputWidth == 0) || (grid->outputHeight == 0) || (avifROStreamRemainingBytes(&s) != 0)) {
+ return AVIF_RESULT_INVALID_IMAGE_GRID;
}
- return avifROStreamRemainingBytes(&s) == 0;
+ if (imageSizeLimit && (grid->outputWidth > (imageSizeLimit / grid->outputHeight))) {
+ return AVIF_RESULT_IMAGE_TOO_LARGE;
+ }
+ return AVIF_RESULT_OK;
}
static avifBool avifParseImageSpatialExtentsProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen)
@@ -2233,6 +2236,7 @@
avifDecoder * decoder = (avifDecoder *)avifAlloc(sizeof(avifDecoder));
memset(decoder, 0, sizeof(avifDecoder));
decoder->maxThreads = 1;
+ decoder->imageSizeLimit = AVIF_DEFAULT_MAX_IMAGE_SIZE;
return decoder;
}
@@ -2632,8 +2636,9 @@
if (readResult != AVIF_RESULT_OK) {
return readResult;
}
- if (!avifParseImageGridBox(&data->colorGrid, readData.data, readData.size)) {
- return AVIF_RESULT_INVALID_IMAGE_GRID;
+ avifResult parseResult = avifParseImageGridBox(&data->colorGrid, readData.data, readData.size, decoder->imageSizeLimit);
+ if (parseResult != AVIF_RESULT_OK) {
+ return parseResult;
}
}
@@ -2671,8 +2676,10 @@
if (readResult != AVIF_RESULT_OK) {
return readResult;
}
- if (!avifParseImageGridBox(&data->alphaGrid, readData.data, readData.size)) {
- return AVIF_RESULT_INVALID_IMAGE_GRID;
+ avifResult parseResult =
+ avifParseImageGridBox(&data->alphaGrid, readData.data, readData.size, decoder->imageSizeLimit);
+ if (parseResult != AVIF_RESULT_OK) {
+ return parseResult;
}
}
@@ -2747,6 +2754,12 @@
if (ispeProp) {
decoder->image->width = ispeProp->u.ispe.width;
decoder->image->height = ispeProp->u.ispe.height;
+ if (!decoder->image->width || !decoder->image->height) {
+ return AVIF_RESULT_BMFF_PARSE_FAILED;
+ }
+ if (decoder->imageSizeLimit && (decoder->image->width > (decoder->imageSizeLimit / decoder->image->height))) {
+ return AVIF_RESULT_IMAGE_TOO_LARGE;
+ }
} else {
decoder->image->width = 0;
decoder->image->height = 0;
diff --git a/src/reformat.c b/src/reformat.c
index be035c4..f1dfbdb 100644
--- a/src/reformat.c
+++ b/src/reformat.c
@@ -180,7 +180,7 @@
uint32_t * yuvRowBytes = image->yuvRowBytes;
for (uint32_t outerJ = 0; outerJ < image->height; outerJ += 2) {
for (uint32_t outerI = 0; outerI < image->width; outerI += 2) {
- int blockW = 2, blockH = 2;
+ uint32_t blockW = 2, blockH = 2;
if ((outerI + 1) >= image->width) {
blockW = 1;
}
@@ -189,10 +189,10 @@
}
// Convert an entire 2x2 block to YUV, and populate any fully sampled channels as we go
- for (int bJ = 0; bJ < blockH; ++bJ) {
- for (int bI = 0; bI < blockW; ++bI) {
- int i = outerI + bI;
- int j = outerJ + bJ;
+ for (uint32_t bJ = 0; bJ < blockH; ++bJ) {
+ for (uint32_t bI = 0; bI < blockW; ++bI) {
+ uint32_t i = outerI + bI;
+ uint32_t j = outerJ + bJ;
// Unpack RGB into normalized float
if (state.rgbChannelBytes > 1) {
@@ -297,8 +297,8 @@
float sumU = 0.0f;
float sumV = 0.0f;
- for (int bJ = 0; bJ < blockH; ++bJ) {
- for (int bI = 0; bI < blockW; ++bI) {
+ for (uint32_t bJ = 0; bJ < blockH; ++bJ) {
+ for (uint32_t bI = 0; bI < blockW; ++bI) {
sumU += yuvBlock[bI][bJ].u;
sumV += yuvBlock[bI][bJ].v;
}
@@ -307,10 +307,10 @@
float avgU = sumU / totalSamples;
float avgV = sumV / totalSamples;
- const int chromaShiftX = 1;
- const int chromaShiftY = 1;
- int uvI = outerI >> chromaShiftX;
- int uvJ = outerJ >> chromaShiftY;
+ const uint32_t chromaShiftX = 1;
+ const uint32_t chromaShiftY = 1;
+ uint32_t uvI = outerI >> chromaShiftX;
+ uint32_t uvJ = outerJ >> chromaShiftY;
if (state.yuvChannelBytes > 1) {
uint16_t * pU = (uint16_t *)&yuvPlanes[AVIF_CHAN_U][(uvI * 2) + (uvJ * yuvRowBytes[AVIF_CHAN_U])];
*pU = (uint16_t)avifReformatStateUVToUNorm(&state, avgU);
@@ -323,10 +323,10 @@
} else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV422) {
// YUV422, average 2 samples (1x2), twice
- for (int bJ = 0; bJ < blockH; ++bJ) {
+ for (uint32_t bJ = 0; bJ < blockH; ++bJ) {
float sumU = 0.0f;
float sumV = 0.0f;
- for (int bI = 0; bI < blockW; ++bI) {
+ for (uint32_t bI = 0; bI < blockW; ++bI) {
sumU += yuvBlock[bI][bJ].u;
sumV += yuvBlock[bI][bJ].v;
}
@@ -334,9 +334,9 @@
float avgU = sumU / totalSamples;
float avgV = sumV / totalSamples;
- const int chromaShiftX = 1;
- int uvI = outerI >> chromaShiftX;
- int uvJ = outerJ + bJ;
+ const uint32_t chromaShiftX = 1;
+ uint32_t uvI = outerI >> chromaShiftX;
+ uint32_t uvJ = outerJ + bJ;
if (state.yuvChannelBytes > 1) {
uint16_t * pU = (uint16_t *)&yuvPlanes[AVIF_CHAN_U][(uvI * 2) + (uvJ * yuvRowBytes[AVIF_CHAN_U])];
*pU = (uint16_t)avifReformatStateUVToUNorm(&state, avgU);
@@ -488,7 +488,7 @@
uint16_t unormU[2][2], unormV[2][2];
// How many bytes to add to a uint8_t pointer index to get to the adjacent (lesser) sample in a given direction
- int uAdjCol, vAdjCol, uAdjRow, vAdjRow;
+ uint32_t uAdjCol, vAdjCol, uAdjRow, vAdjRow;
if ((i == 0) || ((i == (image->width - 1)) && ((i % 2) != 0))) {
uAdjCol = 0;
vAdjCol = 0;
@@ -538,8 +538,8 @@
unormV[1][1] = *((const uint16_t *)&vPlane[(uvJ * vRowBytes) + (uvI * yuvChannelBytes) + vAdjCol + vAdjRow]);
// clamp incoming data to protect against bad LUT lookups
- for (int bJ = 0; bJ < 2; ++bJ) {
- for (int bI = 0; bI < 2; ++bI) {
+ for (uint32_t bJ = 0; bJ < 2; ++bJ) {
+ for (uint32_t bI = 0; bI < 2; ++bI) {
unormU[bI][bJ] = AVIF_MIN(unormU[bI][bJ], yuvMaxChannel);
unormV[bI][bJ] = AVIF_MIN(unormV[bI][bJ], yuvMaxChannel);
}