Skip to content

Commit

Permalink
Refactor AllocateGridImagePlanes() for non-grid (#2059)
Browse files Browse the repository at this point in the history
Also factor dstImage logic from
avifDecoderDataCopyTileToImage() to
avifDecoderDecodeTiles().
  • Loading branch information
y-guyon authored Mar 24, 2024
1 parent d159843 commit 58566ee
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 84 deletions.
129 changes: 71 additions & 58 deletions src/read.c
Original file line number Diff line number Diff line change
Expand Up @@ -1502,41 +1502,58 @@ static avifResult avifDecoderGenerateImageGridTiles(avifDecoder * decoder, avifI
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];

// 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;
uint32_t dstWidth;
uint32_t dstHeight;

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);
if (alpha) {
// 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) {
Expand All @@ -1547,9 +1564,9 @@ static avifResult avifDecoderDataAllocateGridImagePlanes(avifDecoderData * data,

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);
Expand All @@ -1573,15 +1590,14 @@ static avifResult avifDecoderDataAllocateGridImagePlanes(avifDecoderData * data,
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.
Expand All @@ -1595,30 +1611,25 @@ static avifResult avifDecoderDataCopyTileToImage(avifDecoderData * data,
}
}

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;
Expand Down Expand Up @@ -5254,20 +5265,22 @@ static avifResult avifDecoderDecodeTiles(avifDecoder * decoder, uint32_t nextIma

++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;
}
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 (tileIndex == 0) {
AVIF_CHECKRES(avifDecoderDataAllocateImagePlanes(decoder->data, info, dstImage));
}
AVIF_CHECKRES(avifDecoderDataCopyTileToImage(decoder->data, info, decoder->image, tile, tileIndex));
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;
Expand Down
46 changes: 20 additions & 26 deletions src/write.c
Original file line number Diff line number Diff line change
Expand Up @@ -1200,19 +1200,6 @@ static avifResult avifGetErrorForItemCategory(avifItemCategory itemCategory)
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;
Expand Down Expand Up @@ -1331,7 +1318,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,

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;
}
Expand Down Expand Up @@ -1393,7 +1381,10 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
}

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) {
Expand Down Expand Up @@ -1548,6 +1539,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
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;
Expand Down Expand Up @@ -1647,7 +1639,9 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
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);
Expand All @@ -1656,16 +1650,18 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
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);
Expand All @@ -1688,15 +1684,13 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
/*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;
}
Expand Down

0 comments on commit 58566ee

Please sign in to comment.