Move checks to avifAreGridDimensionsValid() (#834)
It was possible to encode a 65x65 4:2:0 image with avifenc that could
not be decoded with avifdec. Calling avifAreGridDimensionsValid() from
both encoding and decoding pipelines removes this asymmetric behavior.
Add tests/avifgridapitest.c.
diff --git a/src/avif.c b/src/avif.c
index 25f4111..0597aa1 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -722,6 +722,51 @@
}
// ---------------------------------------------------------------------------
+
+avifBool avifAreGridDimensionsValid(avifPixelFormat yuvFormat, uint32_t imageW, uint32_t imageH, uint32_t tileW, uint32_t tileH, avifDiagnostics * diag)
+{
+ // ISO/IEC 23000-22:2019, Section 7.3.11.4.2:
+ // - the tile_width shall be greater than or equal to 64, and should be a multiple of 64
+ // - the tile_height shall be greater than or equal to 64, and should be a multiple of 64
+ // The "should" part is ignored here.
+ if ((tileW < 64) || (tileH < 64)) {
+ avifDiagnosticsPrintf(diag,
+ "Grid image tile width (%u) or height (%u) cannot be smaller than 64. "
+ "See MIAF (ISO/IEC 23000-22:2019), Section 7.3.11.4.2",
+ tileW,
+ tileH);
+ return AVIF_FALSE;
+ }
+
+ // ISO/IEC 23000-22:2019, Section 7.3.11.4.2:
+ // - when the images are in the 4:2:2 chroma sampling format the horizontal tile offsets and widths,
+ // and the output width, shall be even numbers;
+ // - when the images are in the 4:2:0 chroma sampling format both the horizontal and vertical tile
+ // offsets and widths, and the output width and height, shall be even numbers.
+ // If the rules above were not respected, the following problematic situation may happen:
+ // Some 4:2:0 image is 650 pixels wide and has 10 cell columns, each being 65 pixels wide.
+ // The chroma plane of the whole image is 325 pixels wide. The chroma plane of each cell is 33 pixels wide.
+ // 33*10 - 325 gives 5 extra pixels with no specified destination in the reconstructed image.
+
+ // Tile offsets are not enforced since they depend on tile size (ISO/IEC 23008-12:2017, Section 6.6.2.3.1):
+ // The reconstructed image is formed by tiling the input images into a grid [...] without gap or overlap
+ if ((((yuvFormat == AVIF_PIXEL_FORMAT_YUV420) || (yuvFormat == AVIF_PIXEL_FORMAT_YUV422)) &&
+ (((imageW % 2) != 0) || ((tileW % 2) != 0))) ||
+ ((yuvFormat == AVIF_PIXEL_FORMAT_YUV420) && (((imageH % 2) != 0) || ((tileH % 2) != 0)))) {
+ avifDiagnosticsPrintf(diag,
+ "Grid image width (%u) or height (%u) or tile width (%u) or height (%u) "
+ "shall be even if chroma is subsampled in that dimension. "
+ "See MIAF (ISO/IEC 23000-22:2019), Section 7.3.11.4.2",
+ imageW,
+ imageH,
+ tileW,
+ tileH);
+ return AVIF_FALSE;
+ }
+ return AVIF_TRUE;
+}
+
+// ---------------------------------------------------------------------------
// avifCodecSpecificOption
static char * avifStrdup(const char * str)
diff --git a/src/read.c b/src/read.c
index 3ca86a5..09b4852 100644
--- a/src/read.c
+++ b/src/read.c
@@ -1295,37 +1295,17 @@
"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 (%ux%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) {
+ assert(firstTile->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400);
}
- 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;
- }
- }
+ if (!avifAreGridDimensionsValid(firstTile->image->yuvFormat,
+ grid->outputWidth,
+ grid->outputHeight,
+ firstTile->image->width,
+ firstTile->image->height,
+ data->diag)) {
+ return AVIF_FALSE;
}
// Lazily populate dstImage with the new frame's properties. If we're decoding alpha,
diff --git a/src/write.c b/src/write.c
index 657bfaa..cd0a274 100644
--- a/src/write.c
+++ b/src/write.c
@@ -567,15 +567,28 @@
return AVIF_RESULT_NO_CONTENT;
}
- if ((cellCount > 1) && ((firstCell->width < 64) || (firstCell->height < 64))) {
+ if ((cellCount > 1) && !avifAreGridDimensionsValid(firstCell->yuvFormat,
+ gridCols * firstCell->width,
+ gridRows * firstCell->height,
+ firstCell->width,
+ firstCell->height,
+ &encoder->diag)) {
return AVIF_RESULT_INVALID_IMAGE_GRID;
}
for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
const avifImage * cellImage = cellImages[cellIndex];
- if ((cellImage->depth != firstCell->depth) || (cellImage->width != firstCell->width) ||
- (cellImage->height != firstCell->height) || (!!cellImage->alphaPlane != !!firstCell->alphaPlane) ||
- (cellImage->alphaPremultiplied != firstCell->alphaPremultiplied)) {
+ // HEIF (ISO 23008-12:2017), Section 6.6.2.3.1:
+ // All input images shall have exactly the same width and height; call those tile_width and tile_height.
+ // MIAF (ISO 23000-22:2019), Section 7.3.11.4.1:
+ // All input images of a grid image item shall use the same coding format, chroma sampling format, and the
+ // same decoder configuration (see 7.3.6.2).
+ if ((cellImage->width != firstCell->width) || (cellImage->height != firstCell->height) ||
+ (cellImage->depth != firstCell->depth) || (cellImage->yuvFormat != firstCell->yuvFormat) ||
+ (cellImage->yuvRange != firstCell->yuvRange) || (cellImage->colorPrimaries != firstCell->colorPrimaries) ||
+ (cellImage->transferCharacteristics != firstCell->transferCharacteristics) ||
+ (cellImage->matrixCoefficients != firstCell->matrixCoefficients) || (cellImage->alphaRange != firstCell->alphaRange) ||
+ (!!cellImage->alphaPlane != !!firstCell->alphaPlane) || (cellImage->alphaPremultiplied != firstCell->alphaPremultiplied)) {
return AVIF_RESULT_INVALID_IMAGE_GRID;
}