Refactor AllocateGridImagePlanes() for non-grid (#2059)
Also factor dstImage logic from
avifDecoderDataCopyTileToImage() to
avifDecoderDecodeTiles().
diff --git a/src/read.c b/src/read.c
index 6d204cc..c4b3e5d 100644
--- a/src/read.c
+++ b/src/read.c
@@ -1502,27 +1502,45 @@
return AVIF_RESULT_OK;
}
-// Allocates the dstImage based on the grid image requirements. Also verifies some spec compliance rules for grids.
-static avifResult avifDecoderDataAllocateGridImagePlanes(avifDecoderData * data, const avifTileInfo * info, avifImage * dstImage)
+// Allocates the dstImage. Also verifies some spec compliance rules for grids, if relevant.
+static avifResult avifDecoderDataAllocateImagePlanes(avifDecoderData * data, const avifTileInfo * info, avifImage * dstImage)
{
- const avifImageGrid * grid = &info->grid;
const avifTile * tile = &data->tiles.tile[info->firstTileIndex];
+ uint32_t dstWidth;
+ uint32_t dstHeight;
- // Validate grid image size and tile size.
- //
- // HEIF (ISO/IEC 23008-12:2017), Section 6.6.2.3.1:
- // The tiled input images shall completely "cover" the reconstructed image grid canvas, ...
- if (((tile->image->width * grid->columns) < grid->outputWidth) || ((tile->image->height * grid->rows) < grid->outputHeight)) {
- avifDiagnosticsPrintf(data->diag,
- "Grid image tiles do not completely cover the image (HEIF (ISO/IEC 23008-12:2017), Section 6.6.2.3.1)");
- return AVIF_RESULT_INVALID_IMAGE_GRID;
- }
- // Tiles in the rightmost column and bottommost row must overlap the reconstructed image grid canvas. See MIAF (ISO/IEC 23000-22:2019), Section 7.3.11.4.2, Figure 2.
- if (((tile->image->width * (grid->columns - 1)) >= grid->outputWidth) ||
- ((tile->image->height * (grid->rows - 1)) >= grid->outputHeight)) {
- avifDiagnosticsPrintf(data->diag,
- "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_RESULT_INVALID_IMAGE_GRID;
+ if (info->grid.rows > 0 && info->grid.columns > 0) {
+ const avifImageGrid * grid = &info->grid;
+ // Validate grid image size and tile size.
+ //
+ // HEIF (ISO/IEC 23008-12:2017), Section 6.6.2.3.1:
+ // The tiled input images shall completely "cover" the reconstructed image grid canvas, ...
+ if (((tile->image->width * grid->columns) < grid->outputWidth) || ((tile->image->height * grid->rows) < grid->outputHeight)) {
+ avifDiagnosticsPrintf(data->diag,
+ "Grid image tiles do not completely cover the image (HEIF (ISO/IEC 23008-12:2017), Section 6.6.2.3.1)");
+ return AVIF_RESULT_INVALID_IMAGE_GRID;
+ }
+ // Tiles in the rightmost column and bottommost row must overlap the reconstructed image grid canvas. See MIAF (ISO/IEC 23000-22:2019), Section 7.3.11.4.2, Figure 2.
+ if (((tile->image->width * (grid->columns - 1)) >= grid->outputWidth) ||
+ ((tile->image->height * (grid->rows - 1)) >= grid->outputHeight)) {
+ avifDiagnosticsPrintf(data->diag,
+ "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_RESULT_INVALID_IMAGE_GRID;
+ }
+ if (!avifAreGridDimensionsValid(tile->image->yuvFormat,
+ grid->outputWidth,
+ grid->outputHeight,
+ tile->image->width,
+ tile->image->height,
+ data->diag)) {
+ return AVIF_RESULT_INVALID_IMAGE_GRID;
+ }
+ dstWidth = grid->outputWidth;
+ dstHeight = grid->outputHeight;
+ } else {
+ // Only one tile. Width and height are inherited from the 'ispe' property of the corresponding avifDecoderItem.
+ dstWidth = tile->width;
+ dstHeight = tile->height;
}
const avifBool alpha = avifIsAlpha(tile->input->itemCategory);
@@ -1530,13 +1548,12 @@
// An alpha tile does not contain any YUV pixels.
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;
- }
+
+ const uint32_t dstDepth = tile->image->depth;
// Lazily populate dstImage with the new frame's properties.
- const avifBool dimsOrDepthIsDifferent = (dstImage->width != grid->outputWidth) || (dstImage->height != grid->outputHeight) ||
- (dstImage->depth != tile->image->depth);
+ const avifBool dimsOrDepthIsDifferent = (dstImage->width != dstWidth) || (dstImage->height != dstHeight) ||
+ (dstImage->depth != dstDepth);
const avifBool yuvFormatIsDifferent = !alpha && (dstImage->yuvFormat != tile->image->yuvFormat);
if (dimsOrDepthIsDifferent || yuvFormatIsDifferent) {
if (alpha) {
@@ -1547,9 +1564,9 @@
if (dimsOrDepthIsDifferent) {
avifImageFreePlanes(dstImage, AVIF_PLANES_ALL);
- dstImage->width = grid->outputWidth;
- dstImage->height = grid->outputHeight;
- dstImage->depth = tile->image->depth;
+ dstImage->width = dstWidth;
+ dstImage->height = dstHeight;
+ dstImage->depth = dstDepth;
}
if (yuvFormatIsDifferent) {
avifImageFreePlanes(dstImage, AVIF_PLANES_YUV);
@@ -1573,15 +1590,14 @@
return AVIF_RESULT_OK;
}
-// After verifying that the relevant properties of the tile match those of the first tile, copies over the pixels from the tile
-// into dstImage.
+// Copies over the pixels from the tile into dstImage.
+// Verifies that the relevant properties of the tile match those of the first tile in case of a grid.
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];
if (tile != firstTile) {
// Check for tile consistency. All tiles in a grid image should match the first tile in the properties checked below.
@@ -1595,30 +1611,25 @@
}
}
- unsigned int rowIndex = tileIndex / info->grid.columns;
- unsigned int colIndex = tileIndex % info->grid.columns;
avifImage srcView;
avifImageSetDefaults(&srcView);
avifImage dstView;
avifImageSetDefaults(&dstView);
- avifCropRect dstViewRect = {
- firstTile->image->width * colIndex, firstTile->image->height * rowIndex, firstTile->image->width, firstTile->image->height
- };
- if (dstViewRect.x + dstViewRect.width > grid->outputWidth) {
- dstViewRect.width = grid->outputWidth - dstViewRect.x;
- }
- if (dstViewRect.y + dstViewRect.height > grid->outputHeight) {
- dstViewRect.height = grid->outputHeight - dstViewRect.y;
+ avifCropRect dstViewRect = { 0, 0, firstTile->image->width, firstTile->image->height };
+ if (info->grid.rows > 0 && info->grid.columns > 0) {
+ unsigned int rowIndex = tileIndex / info->grid.columns;
+ unsigned int colIndex = tileIndex % info->grid.columns;
+ dstViewRect.x = firstTile->image->width * colIndex;
+ dstViewRect.y = firstTile->image->height * rowIndex;
+ if (dstViewRect.x + dstViewRect.width > info->grid.outputWidth) {
+ dstViewRect.width = info->grid.outputWidth - dstViewRect.x;
+ }
+ if (dstViewRect.y + dstViewRect.height > info->grid.outputHeight) {
+ dstViewRect.height = info->grid.outputHeight - dstViewRect.y;
+ }
}
const avifCropRect srcViewRect = { 0, 0, dstViewRect.width, dstViewRect.height };
- avifImage * dst = dstImage;
-#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
- if (tile->input->itemCategory == AVIF_ITEM_GAIN_MAP) {
- AVIF_ASSERT_OR_RETURN(dst->gainMap && dst->gainMap->image);
- dst = dst->gainMap->image;
- }
-#endif
- AVIF_ASSERT_OR_RETURN(avifImageSetViewRect(&dstView, dst, &dstViewRect) == AVIF_RESULT_OK &&
+ AVIF_ASSERT_OR_RETURN(avifImageSetViewRect(&dstView, dstImage, &dstViewRect) == AVIF_RESULT_OK &&
avifImageSetViewRect(&srcView, tile->image, &srcViewRect) == AVIF_RESULT_OK);
avifImageCopySamples(&dstView, &srcView, avifIsAlpha(tile->input->itemCategory) ? AVIF_PLANES_A : AVIF_PLANES_YUV);
return AVIF_RESULT_OK;
@@ -5254,20 +5265,22 @@
++info->decodedTileCount;
- if ((info->grid.rows > 0) && (info->grid.columns > 0)) {
- if (tileIndex == 0) {
- avifImage * dstImage = decoder->image;
+ const avifBool isGrid = (info->grid.rows > 0) && (info->grid.columns > 0);
+ const avifBool stealPlanes = !isGrid;
+
+ if (!stealPlanes) {
+ avifImage * dstImage = decoder->image;
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
- if (tile->input->itemCategory == AVIF_ITEM_GAIN_MAP) {
- AVIF_ASSERT_OR_RETURN(dstImage->gainMap && dstImage->gainMap->image);
- dstImage = dstImage->gainMap->image;
- }
-#endif
- AVIF_CHECKRES(avifDecoderDataAllocateGridImagePlanes(decoder->data, info, dstImage));
+ if (tile->input->itemCategory == AVIF_ITEM_GAIN_MAP) {
+ AVIF_ASSERT_OR_RETURN(dstImage->gainMap && dstImage->gainMap->image);
+ dstImage = dstImage->gainMap->image;
}
- AVIF_CHECKRES(avifDecoderDataCopyTileToImage(decoder->data, info, decoder->image, tile, tileIndex));
+#endif
+ if (tileIndex == 0) {
+ AVIF_CHECKRES(avifDecoderDataAllocateImagePlanes(decoder->data, info, dstImage));
+ }
+ AVIF_CHECKRES(avifDecoderDataCopyTileToImage(decoder->data, info, dstImage, tile, tileIndex));
} else {
- // Non-grid path. Just steal the planes from the only "tile".
AVIF_ASSERT_OR_RETURN(info->tileCount == 1);
AVIF_ASSERT_OR_RETURN(tileIndex == 0);
avifImage * src = tile->image;
diff --git a/src/write.c b/src/write.c
index f7f510c..67113f1 100644
--- a/src/write.c
+++ b/src/write.c
@@ -1200,19 +1200,6 @@
return avifIsAlpha(itemCategory) ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
}
-static avifResult avifValidateImageBasicProperties(const avifImage * avifImage)
-{
- if ((avifImage->depth != 8) && (avifImage->depth != 10) && (avifImage->depth != 12)) {
- return AVIF_RESULT_UNSUPPORTED_DEPTH;
- }
-
- if (avifImage->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
- return AVIF_RESULT_NO_YUV_FORMAT_SELECTED;
- }
-
- return AVIF_RESULT_OK;
-}
-
static uint32_t avifGridWidth(uint32_t gridCols, const avifImage * firstCell, const avifImage * bottomRightCell)
{
return (gridCols - 1) * firstCell->width + bottomRightCell->width;
@@ -1331,7 +1318,8 @@
const avifImage * firstCell = cellImages[0];
const avifImage * bottomRightCell = cellImages[cellCount - 1];
- AVIF_CHECKRES(avifValidateImageBasicProperties(firstCell));
+ AVIF_CHECKERR(firstCell->depth == 8 || firstCell->depth == 10 || firstCell->depth == 12, AVIF_RESULT_UNSUPPORTED_DEPTH);
+ AVIF_CHECKERR(firstCell->yuvFormat != AVIF_PIXEL_FORMAT_NONE, AVIF_RESULT_NO_YUV_FORMAT_SELECTED);
if (!firstCell->width || !firstCell->height || !bottomRightCell->width || !bottomRightCell->height) {
return AVIF_RESULT_NO_CONTENT;
}
@@ -1393,7 +1381,10 @@
}
if (hasGainMap) {
- AVIF_CHECKRES(avifValidateImageBasicProperties(firstCell->gainMap->image));
+ AVIF_CHECKERR(firstCell->gainMap->image->depth == 8 || firstCell->gainMap->image->depth == 10 ||
+ firstCell->gainMap->image->depth == 12,
+ AVIF_RESULT_UNSUPPORTED_DEPTH);
+ AVIF_CHECKERR(firstCell->gainMap->image->yuvFormat != AVIF_PIXEL_FORMAT_NONE, AVIF_RESULT_NO_YUV_FORMAT_SELECTED);
AVIF_CHECKRES(avifValidateGrid(gridCols, gridRows, cellImages, /*validateGainMap=*/AVIF_TRUE, &encoder->diag));
if (firstCell->gainMap->image->colorPrimaries != AVIF_COLOR_PRIMARIES_UNSPECIFIED ||
firstCell->gainMap->image->transferCharacteristics != AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED) {
@@ -1548,6 +1539,7 @@
toneMappedItem->itemCategory = AVIF_ITEM_COLOR;
uint16_t toneMappedItemID = toneMappedItem->id;
+ AVIF_ASSERT_OR_RETURN(encoder->data->alternativeItemIDs.count == 0);
uint16_t * alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
*alternativeItemID = toneMappedItemID;
@@ -1647,7 +1639,9 @@
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->codec) {
const avifImage * cellImage = cellImages[item->cellIndex];
+ avifImage * cellImagePlaceholder = NULL; // May be used as a temporary, modified cellImage. Left as NULL otherwise.
const avifImage * firstCellImage = firstCell;
+
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
if (item->itemCategory == AVIF_ITEM_GAIN_MAP) {
AVIF_ASSERT_OR_RETURN(cellImage->gainMap && cellImage->gainMap->image);
@@ -1656,16 +1650,18 @@
firstCellImage = firstCell->gainMap->image;
}
#endif
- avifImage * paddedCellImage = NULL;
+
if ((cellImage->width != firstCellImage->width) || (cellImage->height != firstCellImage->height)) {
- paddedCellImage = avifImageCreateEmpty();
- AVIF_CHECKERR(paddedCellImage, AVIF_RESULT_OUT_OF_MEMORY);
- const avifResult result = avifImageCopyAndPad(paddedCellImage, cellImage, firstCellImage->width, firstCellImage->height);
+ // Pad the right-most and/or bottom-most tiles so that all tiles share the same dimensions.
+ cellImagePlaceholder = avifImageCreateEmpty();
+ AVIF_CHECKERR(cellImagePlaceholder, AVIF_RESULT_OUT_OF_MEMORY);
+ const avifResult result =
+ avifImageCopyAndPad(cellImagePlaceholder, cellImage, firstCellImage->width, firstCellImage->height);
if (result != AVIF_RESULT_OK) {
- avifImageDestroy(paddedCellImage);
+ avifImageDestroy(cellImagePlaceholder);
return result;
}
- cellImage = paddedCellImage;
+ cellImage = cellImagePlaceholder;
}
const avifBool isAlpha = avifIsAlpha(item->itemCategory);
@@ -1688,15 +1684,13 @@
/*disableLaggedOutput=*/encoder->data->alphaPresent,
addImageFlags,
item->encodeOutput);
- if (paddedCellImage) {
- avifImageDestroy(paddedCellImage);
+ if (cellImagePlaceholder) {
+ avifImageDestroy(cellImagePlaceholder);
}
if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) {
encodeResult = avifGetErrorForItemCategory(item->itemCategory);
}
- if (encodeResult != AVIF_RESULT_OK) {
- return encodeResult;
- }
+ AVIF_CHECKRES(encodeResult);
if (itemIndex == 0 && avifEncoderDataShouldForceKeyframeForAlpha(encoder->data, item, addImageFlags)) {
addImageFlags |= AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME;
}