Add automatic tile scaling to the item's ispe or track's dims
(rough draft, scale.c needs more work)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7d78025..9cbf4bc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -206,6 +206,7 @@
src/read.c
src/reformat.c
src/reformat_libyuv.c
+ src/scale.c
src/stream.c
src/utils.c
src/write.c
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 2f3d576..5fd8a89 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -156,6 +156,12 @@
avifResult avifRGBImageUnpremultiplyAlphaLibYUV(avifRGBImage * rgb);
// ---------------------------------------------------------------------------
+// Scaling
+
+// This scales the YUV/A planes in-place.
+avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, avifDiagnostics * diag);
+
+// ---------------------------------------------------------------------------
// avifCodecDecodeInput
typedef struct avifDecodeSample
diff --git a/src/read.c b/src/read.c
index fe5e619..4c0ac5e 100644
--- a/src/read.c
+++ b/src/read.c
@@ -155,6 +155,8 @@
uint8_t type[4];
size_t size;
uint32_t idatID; // If non-zero, offset is relative to this idat box (iloc construction_method==1)
+ uint32_t width; // Set from this item's ispe property, if present
+ uint32_t height; // Set from this item's ispe property, if present
avifContentType contentType;
avifPropertyArray properties;
avifExtentArray extents; // All extent offsets/sizes
@@ -622,6 +624,8 @@
avifCodecDecodeInput * input;
struct avifCodec * codec;
avifImage * image;
+ uint32_t width; // Either avifTrack.width or avifDecoderItem.width
+ uint32_t height; // Either avifTrack.height or avifDecoderItem.height
uint8_t operatingPointIndex;
} avifTile;
AVIF_ARRAY_DECLARE(avifTileArray, avifTile, tile);
@@ -763,11 +767,13 @@
}
}
-static avifTile * avifDecoderDataCreateTile(avifDecoderData * data, uint8_t operatingPointIndex)
+static avifTile * avifDecoderDataCreateTile(avifDecoderData * data, uint32_t width, uint32_t height, uint8_t operatingPointIndex)
{
avifTile * tile = (avifTile *)avifArrayPushPtr(&data->tiles);
tile->image = avifImageCreateEmpty();
tile->input = avifCodecDecodeInputCreate();
+ tile->width = width;
+ tile->height = height;
tile->operatingPointIndex = operatingPointIndex;
return tile;
}
@@ -1125,7 +1131,7 @@
continue;
}
- avifTile * tile = avifDecoderDataCreateTile(decoder->data, avifDecoderItemOperatingPoint(item));
+ avifTile * tile = avifDecoderDataCreateTile(decoder->data, item->width, item->height, avifDecoderItemOperatingPoint(item));
if (!avifCodecDecodeInputFillFromDecoderItem(tile->input,
item,
decoder->allowProgressive,
@@ -2305,6 +2311,11 @@
track->width = width >> 16;
track->height = height >> 16;
+ if ((track->width == 0) || (track->height == 0) || (track->width > (AVIF_MAX_IMAGE_SIZE / track->height))) {
+ avifDiagnosticsPrintf(diag, "Track ID [%u] has an invalid size [%ux%u]", track->id, track->width, track->height);
+ return AVIF_FALSE;
+ }
+
// TODO: support scaling based on width/height track header info?
track->id = trackID;
@@ -2986,6 +2997,37 @@
return parseResult;
}
+ // Walk the decoded items (if any) and harvest ispe
+ avifDecoderData * data = decoder->data;
+ for (uint32_t itemIndex = 0; itemIndex < data->meta->items.count; ++itemIndex) {
+ avifDecoderItem * item = &data->meta->items.item[itemIndex];
+ if (!item->size) {
+ continue;
+ }
+ if (item->hasUnsupportedEssentialProperty) {
+ // An essential property isn't supported by libavif; ignore the item.
+ continue;
+ }
+ avifBool isGrid = (memcmp(item->type, "grid", 4) == 0);
+ if (memcmp(item->type, "av01", 4) && !isGrid) {
+ // probably exif or some other data
+ continue;
+ }
+
+ const avifProperty * ispeProp = avifPropertyArrayFind(&item->properties, "ispe");
+ if (ispeProp) {
+ item->width = ispeProp->u.ispe.width;
+ item->height = ispeProp->u.ispe.height;
+
+ if ((item->width == 0) || (item->height == 0) || (item->width > (AVIF_MAX_IMAGE_SIZE / item->height))) {
+ avifDiagnosticsPrintf(data->diag, "Item ID [%u] has an invalid size [%ux%u]", item->id, item->width, item->height);
+ return AVIF_RESULT_BMFF_PARSE_FAILED;
+ }
+ } else {
+ avifDiagnosticsPrintf(data->diag, "Item ID [%u] is missing a mandatory ispe property", item->id);
+ return AVIF_RESULT_BMFF_PARSE_FAILED;
+ }
+ }
return avifDecoderReset(decoder);
}
@@ -3122,7 +3164,7 @@
alphaTrack = &data->tracks.track[alphaTrackIndex];
}
- avifTile * colorTile = avifDecoderDataCreateTile(data, 0); // No way to set operating point via tracks
+ avifTile * colorTile = avifDecoderDataCreateTile(data, colorTrack->width, colorTrack->height, 0); // No way to set operating point via tracks
if (!avifCodecDecodeInputFillFromSampleTable(colorTile->input,
colorTrack->sampleTable,
decoder->imageCountLimit,
@@ -3133,7 +3175,7 @@
data->colorTileCount = 1;
if (alphaTrack) {
- avifTile * alphaTile = avifDecoderDataCreateTile(data, 0); // No way to set operating point via tracks
+ avifTile * alphaTile = avifDecoderDataCreateTile(data, alphaTrack->width, alphaTrack->height, 0); // No way to set operating point via tracks
if (!avifCodecDecodeInputFillFromSampleTable(alphaTile->input,
alphaTrack->sampleTable,
decoder->imageCountLimit,
@@ -3284,7 +3326,8 @@
return AVIF_RESULT_NO_AV1_ITEMS_FOUND;
}
- avifTile * colorTile = avifDecoderDataCreateTile(data, avifDecoderItemOperatingPoint(colorItem));
+ avifTile * colorTile =
+ avifDecoderDataCreateTile(data, colorItem->width, colorItem->height, avifDecoderItemOperatingPoint(colorItem));
if (!avifCodecDecodeInputFillFromDecoderItem(colorTile->input,
colorItem,
decoder->allowProgressive,
@@ -3315,7 +3358,8 @@
return AVIF_RESULT_NO_AV1_ITEMS_FOUND;
}
- avifTile * alphaTile = avifDecoderDataCreateTile(data, avifDecoderItemOperatingPoint(alphaItem));
+ avifTile * alphaTile =
+ avifDecoderDataCreateTile(data, alphaItem->width, alphaItem->height, avifDecoderItemOperatingPoint(alphaItem));
if (!avifCodecDecodeInputFillFromDecoderItem(alphaTile->input,
alphaItem,
decoder->allowProgressive,
@@ -3332,14 +3376,8 @@
decoder->ioStats.colorOBUSize = colorItem->size;
decoder->ioStats.alphaOBUSize = alphaItem ? alphaItem->size : 0;
- const avifProperty * ispeProp = avifPropertyArrayFind(colorProperties, "ispe");
- if (ispeProp) {
- decoder->image->width = ispeProp->u.ispe.width;
- decoder->image->height = ispeProp->u.ispe.height;
- } else {
- decoder->image->width = 0;
- decoder->image->height = 0;
- }
+ decoder->image->width = colorItem->width;
+ decoder->image->height = colorItem->height;
decoder->alphaPresent = (alphaItem != NULL);
decoder->image->alphaPremultiplied = decoder->alphaPresent && (colorItem->premByID == alphaItem->id);
@@ -3515,6 +3553,13 @@
if (!tile->codec->getNextImage(tile->codec, decoder, sample, tile->input->alpha, tile->image)) {
return tile->input->alpha ? AVIF_RESULT_DECODE_ALPHA_FAILED : AVIF_RESULT_DECODE_COLOR_FAILED;
}
+
+ // 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->diag)) {
+ return tile->input->alpha ? AVIF_RESULT_DECODE_ALPHA_FAILED : AVIF_RESULT_DECODE_COLOR_FAILED;
+ }
+ }
}
if (decoder->data->tiles.count != (decoder->data->colorTileCount + decoder->data->alphaTileCount)) {
diff --git a/src/scale.c b/src/scale.c
new file mode 100644
index 0000000..528370f
--- /dev/null
+++ b/src/scale.c
@@ -0,0 +1,122 @@
+// Copyright 2021 Joe Drago. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "avif/internal.h"
+
+#if !defined(AVIF_LIBYUV_ENABLED)
+
+avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, avifDiagnostics * diag)
+{
+ (void)image;
+ (void)dstWidth;
+ (void)dstHeight;
+ avifDiagnosticsPrintf(diag, "avifImageScale() called, but is unimplemented without libyuv!");
+ return AVIF_FALSE;
+}
+
+#else
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wstrict-prototypes" // "this function declaration is not a prototype"
+#endif
+#include <libyuv.h>
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+// This should be configurable and/or smarter
+#define AVIF_LIBYUV_FILTER_MODE kFilterBox
+
+avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, avifDiagnostics * diag)
+{
+ if ((image->width == dstWidth) && (image->height == dstHeight)) {
+ // Nothing to do
+ return AVIF_TRUE;
+ }
+
+ if ((dstWidth == 0) || (dstHeight == 0) || (dstWidth > (AVIF_MAX_IMAGE_SIZE / dstHeight))) {
+ avifDiagnosticsPrintf(diag, "avifImageScale requested invalid dst dimensions [%ux%u]", dstWidth, dstHeight);
+ return AVIF_FALSE;
+ }
+
+ uint8_t * srcYUVPlanes[AVIF_PLANE_COUNT_YUV];
+ uint32_t srcYUVRowBytes[AVIF_PLANE_COUNT_YUV];
+ for (int i = 0; i < AVIF_PLANE_COUNT_YUV; ++i) {
+ srcYUVPlanes[i] = image->yuvPlanes[i];
+ image->yuvPlanes[i] = NULL;
+ srcYUVRowBytes[i] = image->yuvRowBytes[i];
+ image->yuvRowBytes[i] = 0;
+ }
+ const avifBool srcImageOwnsYUVPlanes = image->imageOwnsYUVPlanes;
+ image->imageOwnsYUVPlanes = AVIF_FALSE;
+
+ uint8_t * srcAlphaPlane = image->alphaPlane;
+ image->alphaPlane = NULL;
+ uint32_t srcAlphaRowBytes = image->alphaRowBytes;
+ image->alphaRowBytes = 0;
+ const avifBool srcImageOwnsAlphaPlane = image->imageOwnsAlphaPlane;
+ image->imageOwnsAlphaPlane = AVIF_FALSE;
+
+ const uint32_t srcWidth = image->width;
+ image->width = dstWidth;
+ const uint32_t srcHeight = image->height;
+ image->height = dstHeight;
+
+ if (srcYUVPlanes[0]) {
+ avifImageAllocatePlanes(image, AVIF_PLANES_YUV);
+
+ avifPixelFormatInfo formatInfo;
+ avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
+ const uint32_t srcUVWidth = (srcWidth + formatInfo.chromaShiftX) >> formatInfo.chromaShiftX;
+ const uint32_t srcUVHeight = (srcHeight + formatInfo.chromaShiftY) >> formatInfo.chromaShiftY;
+ const uint32_t dstUVWidth = (dstWidth + formatInfo.chromaShiftX) >> formatInfo.chromaShiftX;
+ const uint32_t dstUVHeight = (dstHeight + formatInfo.chromaShiftY) >> formatInfo.chromaShiftY;
+
+ for (int i = 0; i < AVIF_PLANE_COUNT_YUV; ++i) {
+ if (!srcYUVPlanes[i]) {
+ continue;
+ }
+
+ const uint32_t srcW = (i == AVIF_CHAN_Y) ? srcWidth : srcUVWidth;
+ const uint32_t srcH = (i == AVIF_CHAN_Y) ? srcHeight : srcUVHeight;
+ const uint32_t dstW = (i == AVIF_CHAN_Y) ? dstWidth : dstUVWidth;
+ const uint32_t dstH = (i == AVIF_CHAN_Y) ? dstHeight : dstUVHeight;
+ if (image->depth > 8) {
+ uint16_t * srcPlane = (uint16_t *)srcYUVPlanes[i];
+ uint16_t * dstPlane = (uint16_t *)image->yuvPlanes[i];
+ ScalePlane_12(srcPlane, srcYUVRowBytes[i], srcW, srcH, dstPlane, image->yuvRowBytes[i], dstW, dstH, AVIF_LIBYUV_FILTER_MODE);
+ } else {
+ uint8_t * srcPlane = srcYUVPlanes[i];
+ uint8_t * dstPlane = image->yuvPlanes[i];
+ ScalePlane(srcPlane, srcYUVRowBytes[i], srcW, srcH, dstPlane, image->yuvRowBytes[i], dstW, dstH, AVIF_LIBYUV_FILTER_MODE);
+ }
+
+ if (srcImageOwnsYUVPlanes) {
+ avifFree(srcYUVPlanes[i]);
+ }
+ }
+ }
+
+ if (srcAlphaPlane) {
+ avifImageAllocatePlanes(image, AVIF_PLANES_A);
+
+ if (image->depth > 8) {
+ uint16_t * srcPlane = (uint16_t *)srcAlphaPlane;
+ uint16_t * dstPlane = (uint16_t *)image->alphaPlane;
+ ScalePlane_12(srcPlane, srcAlphaRowBytes, srcWidth, srcHeight, dstPlane, image->alphaRowBytes, dstWidth, dstHeight, AVIF_LIBYUV_FILTER_MODE);
+ } else {
+ uint8_t * srcPlane = srcAlphaPlane;
+ uint8_t * dstPlane = image->alphaPlane;
+ ScalePlane(srcPlane, srcAlphaRowBytes, srcWidth, srcHeight, dstPlane, image->alphaRowBytes, dstWidth, dstHeight, AVIF_LIBYUV_FILTER_MODE);
+ }
+
+ if (srcImageOwnsAlphaPlane) {
+ avifFree(srcAlphaPlane);
+ }
+ }
+
+ return AVIF_TRUE;
+}
+
+#endif