Split avifincrtest.c into avifincrtest_helpers.c (#923)
To reduce file size and to be able to reuse some test functions from
a Google-internal C++ fuzz target.
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index faacdd0..08f26f2 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -21,7 +21,7 @@
target_link_libraries(avifgridapitest avif ${AVIF_PLATFORM_LIBRARIES})
add_test(NAME avifgridapitest COMMAND avifgridapitest)
-add_executable(avifincrtest avifincrtest.c)
+add_executable(avifincrtest avifincrtest.c avifincrtest_helpers.c)
if(AVIF_LOCAL_LIBGAV1)
set_target_properties(avifincrtest PROPERTIES LINKER_LANGUAGE "CXX")
endif()
diff --git a/tests/avifincrtest.c b/tests/avifincrtest.c
index b2162d5..532b0d7 100644
--- a/tests/avifincrtest.c
+++ b/tests/avifincrtest.c
@@ -2,11 +2,11 @@
// SPDX-License-Identifier: BSD-2-Clause
#include "avif/avif.h"
+#include "avifincrtest_helpers.h"
-#include <inttypes.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
-#include <string.h>
//------------------------------------------------------------------------------
@@ -36,421 +36,6 @@
//------------------------------------------------------------------------------
-// Returns true if the first (top) rowCount rows of image1 and image2 are identical.
-static avifBool comparePartialYUVA(const avifImage * image1, const avifImage * image2, uint32_t rowCount)
-{
- if (rowCount == 0) {
- return AVIF_TRUE;
- }
- if (!image1 || !image2 || (image1->width != image2->width) || (image1->depth != image2->depth) ||
- (image1->yuvFormat != image2->yuvFormat) || (image1->yuvRange != image2->yuvRange)) {
- printf("ERROR: input mismatch\n");
- return AVIF_FALSE;
- }
- if ((image1->height < rowCount) || (image2->height < rowCount)) {
- printf("ERROR: not enough rows to compare\n");
- return AVIF_FALSE;
- }
-
- avifPixelFormatInfo formatInfo;
- avifGetPixelFormatInfo(image1->yuvFormat, &formatInfo);
- const uint32_t uvWidth = (image1->width + formatInfo.chromaShiftX) >> formatInfo.chromaShiftX;
- const uint32_t uvHeight = (rowCount + formatInfo.chromaShiftY) >> formatInfo.chromaShiftY;
- const uint32_t pixelByteCount = (image1->depth > 8) ? sizeof(uint16_t) : sizeof(uint8_t);
-
- for (uint32_t plane = 0; plane < AVIF_PLANE_COUNT_YUV; ++plane) {
- const uint32_t width = (plane == AVIF_CHAN_Y) ? image1->width : uvWidth;
- const uint32_t widthByteCount = width * pixelByteCount;
- const uint32_t height = (plane == AVIF_CHAN_Y) ? rowCount : uvHeight;
- const uint8_t * data1 = image1->yuvPlanes[plane];
- const uint8_t * data2 = image2->yuvPlanes[plane];
- for (uint32_t y = 0; y < height; ++y) {
- if (memcmp(data1, data2, widthByteCount)) {
- printf("ERROR: different px at row %" PRIu32 ", channel %" PRIu32 "\n", y, plane);
- return AVIF_FALSE;
- }
- data1 += image1->yuvRowBytes[plane];
- data2 += image2->yuvRowBytes[plane];
- }
- }
-
- if (image1->alphaPlane) {
- if (!image2->alphaPlane ||
- (image1->alphaPremultiplied != image2->alphaPremultiplied)) {
- printf("ERROR: input mismatch\n");
- return AVIF_FALSE;
- }
- const uint32_t widthByteCount = image1->width * pixelByteCount;
- const uint8_t * data1 = image1->alphaPlane;
- const uint8_t * data2 = image2->alphaPlane;
- for (uint32_t y = 0; y < rowCount; ++y) {
- if (memcmp(data1, data2, widthByteCount)) {
- printf("ERROR: different px at row %" PRIu32 ", alpha\n", y);
- return AVIF_FALSE;
- }
- data1 += image1->alphaRowBytes;
- data2 += image2->alphaRowBytes;
- }
- }
- return AVIF_TRUE;
-}
-
-// Returns the expected number of decoded rows when availableByteCount out of byteCount were
-// given to the decoder, for an image of height rows, split into cells of cellHeight rows.
-static uint32_t getMinDecodedRowCount(uint32_t height, uint32_t cellHeight, avifBool hasAlpha, size_t availableByteCount, size_t byteCount)
-{
- // The whole image should be available when the full input is.
- if (availableByteCount >= byteCount) {
- return height;
- }
- // All but one cell should be decoded if at most 10 bytes are missing.
- if ((availableByteCount + 10) >= byteCount) {
- return height - cellHeight;
- }
-
- // Subtract the header because decoding it does not output any pixel.
- // Most AVIF headers are below 500 bytes.
- if (availableByteCount <= 500) {
- return 0;
- }
- availableByteCount -= 500;
- byteCount -= 500;
- // Alpha, if any, is assumed to be located before the other planes and to
- // represent at most 50% of the payload.
- if (hasAlpha) {
- if (availableByteCount <= (byteCount / 2)) {
- return 0;
- }
- availableByteCount -= byteCount / 2;
- byteCount -= byteCount / 2;
- }
- // Linearly map the input availability ratio to the decoded row ratio.
- const uint32_t minDecodedCellRowCount = (height / cellHeight) * availableByteCount / byteCount;
- const uint32_t minDecodedPxRowCount = minDecodedCellRowCount * cellHeight;
- // One cell is the incremental decoding granularity.
- // It is unlikely that bytes are evenly distributed among cells. Offset two of them.
- if (minDecodedPxRowCount <= (2 * cellHeight)) {
- return 0;
- }
- return minDecodedPxRowCount - 2 * cellHeight;
-}
-
-//------------------------------------------------------------------------------
-
-// Implementation of avifIOReadFunc simulating a stream from an array.
-// The full array size must be in io->sizeHint and io->data must be the available avifROData.
-static avifResult avifIOPartialRead(struct avifIO * io, uint32_t readFlags, uint64_t offset, size_t size, avifROData * out)
-{
- const uint64_t allDataSize = io->sizeHint;
- const avifROData * availableData = (avifROData *)io->data;
-
- // The behavior below is described in the comment above avifIOReadFunc's declaration.
- if (readFlags != 0) {
- return AVIF_RESULT_IO_ERROR;
- }
- if (!availableData) {
- return AVIF_RESULT_IO_ERROR;
- }
- if (allDataSize < offset) {
- return AVIF_RESULT_IO_ERROR;
- }
- if (allDataSize == offset) {
- out->data = availableData->data;
- out->size = 0;
- return AVIF_RESULT_OK;
- }
-
- if (allDataSize < (offset + size)) {
- size = allDataSize - offset;
- }
- if (availableData->size < (offset + size)) {
- return AVIF_RESULT_WAITING_ON_IO;
- }
- out->data = availableData->data + offset;
- out->size = size;
- return AVIF_RESULT_OK;
-}
-
-//------------------------------------------------------------------------------
-
-// Encodes the image as a grid of at most gridCols*gridRows cells. Returns AVIF_FALSE in case of error.
-// The cell count is reduced to fit libavif or AVIF format constraints. If impossible, AVIF_TRUE is returned with no encoded output.
-// The final cellWidth and cellHeight are output.
-static avifBool encodeAsGrid(const avifImage * image, uint32_t gridCols, uint32_t gridRows, avifRWData * output, uint32_t * cellWidth, uint32_t * cellHeight)
-{
- avifBool success = AVIF_FALSE;
- avifEncoder * encoder = NULL;
- avifImage ** cellImages = NULL;
- // Chroma subsampling requires even dimensions. See ISO 23000-22 - 7.3.11.4.2
- const avifBool needEvenWidths = ((image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV422));
- const avifBool needEvenHeights = (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420);
-
- if ((gridCols == 0) || (gridRows == 0)) {
- printf("ERROR: Bad grid dimensions\n");
- return AVIF_FALSE;
- }
-
- *cellWidth = image->width / gridCols;
- *cellHeight = image->height / gridRows;
-
- // avifEncoderAddImageGrid() only accepts grids that evenly split the image
- // into cells at least 64 pixels wide and tall.
- while ((gridCols > 1) &&
- (((*cellWidth * gridCols) != image->width) || (*cellWidth < 64) || (needEvenWidths && ((*cellWidth & 1) != 0)))) {
- --gridCols;
- *cellWidth = image->width / gridCols;
- }
- while ((gridRows > 1) &&
- (((*cellHeight * gridRows) != image->height) || (*cellHeight < 64) || (needEvenHeights && ((*cellHeight & 1) != 0)))) {
- --gridRows;
- *cellHeight = image->height / gridRows;
- }
-
- cellImages = avifAlloc(sizeof(avifImage *) * gridCols * gridRows);
- memset(cellImages, 0, sizeof(avifImage *) * gridCols * gridRows);
- for (uint32_t row = 0, iCell = 0; row < gridRows; ++row) {
- for (uint32_t col = 0; col < gridCols; ++col, ++iCell) {
- avifCropRect cell;
- cell.x = col * *cellWidth;
- cell.y = row * *cellHeight;
- cell.width = ((cell.x + *cellWidth) <= image->width) ? *cellWidth : (image->width - cell.x);
- cell.height = ((cell.y + *cellHeight) <= image->height) ? *cellHeight : (image->height - cell.y);
- cellImages[iCell] = avifImageCreateEmpty();
- if (!cellImages[iCell] || (avifImageSetViewRect(cellImages[iCell], image, &cell) != AVIF_RESULT_OK)) {
- printf("ERROR: avifImageCreateEmpty() failed\n");
- goto cleanup;
- }
- }
- }
-
- encoder = avifEncoderCreate();
- if (!encoder) {
- printf("ERROR: avifEncoderCreate() failed\n");
- goto cleanup;
- }
- encoder->speed = AVIF_SPEED_FASTEST;
- if (avifEncoderAddImageGrid(encoder, gridCols, gridRows, (const avifImage * const *)cellImages, AVIF_ADD_IMAGE_FLAG_SINGLE) !=
- AVIF_RESULT_OK) {
- printf("ERROR: avifEncoderAddImageGrid() failed\n");
- goto cleanup;
- }
- if (avifEncoderFinish(encoder, output) != AVIF_RESULT_OK) {
- printf("ERROR: avifEncoderFinish() failed\n");
- goto cleanup;
- }
-
- success = AVIF_TRUE;
-cleanup:
- if (encoder) {
- avifEncoderDestroy(encoder);
- }
- if (cellImages) {
- for (uint32_t i = 0; i < (gridCols * gridRows); ++i) {
- if (cellImages[i]) {
- avifImageDestroy(cellImages[i]);
- }
- }
- avifFree(cellImages);
- }
- return success;
-}
-
-// Encodes the image to be decoded incrementally.
-static avifBool encodeAsIncremental(const avifImage * image, avifBool flatCells, avifRWData * output, uint32_t * cellWidth, uint32_t * cellHeight)
-{
- const uint32_t gridCols = image->width / 64; // 64px is the min cell width.
- const uint32_t gridRows = flatCells ? 1 : (image->height / 64);
- return encodeAsGrid(image, (gridCols > 1) ? gridCols : 1, (gridRows > 1) ? gridRows : 1, output, cellWidth, cellHeight);
-}
-
-// Encodes a portion of the image to be decoded incrementally.
-static avifBool encodeRectAsIncremental(const avifImage * image,
- uint32_t width,
- uint32_t height,
- avifBool createAlphaIfNone,
- avifBool flatCells,
- avifRWData * output,
- uint32_t * cellWidth,
- uint32_t * cellHeight)
-{
- avifBool success = AVIF_FALSE;
- avifImage * subImage = avifImageCreateEmpty();
- if (!subImage) {
- printf("ERROR: avifImageCreateEmpty() failed\n");
- goto cleanup;
- }
- if ((width > image->width) || (height > image->height)) {
- printf("ERROR: Bad dimensions\n");
- goto cleanup;
- }
- avifPixelFormatInfo formatInfo;
- avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
- avifCropRect rect;
- rect.x = ((image->width - width) / 2) & ~formatInfo.chromaShiftX;
- rect.y = ((image->height - height) / 2) & ~formatInfo.chromaShiftX;
- rect.width = width;
- rect.height = height;
- if (avifImageSetViewRect(subImage, image, &rect) != AVIF_RESULT_OK) {
- printf("ERROR: avifImageSetViewRect() failed\n");
- goto cleanup;
- }
- if (createAlphaIfNone && !subImage->alphaPlane) {
- if (!image->yuvPlanes[AVIF_CHAN_Y]) {
- printf("ERROR: No luma plane to simulate an alpha plane\n");
- goto cleanup;
- }
- subImage->alphaPlane = image->yuvPlanes[AVIF_CHAN_Y];
- subImage->alphaRowBytes = image->yuvRowBytes[AVIF_CHAN_Y];
- subImage->alphaPremultiplied = AVIF_FALSE;
- subImage->imageOwnsAlphaPlane = AVIF_FALSE;
- }
- success = encodeAsIncremental(subImage, flatCells, output, cellWidth, cellHeight);
-cleanup:
- if (subImage) {
- avifImageDestroy(subImage);
- }
- return success;
-}
-
-//------------------------------------------------------------------------------
-
-// Decodes the data into an image.
-static avifBool decodeNonIncrementally(const avifRWData * encodedAvif, avifImage * image)
-{
- avifBool success = AVIF_FALSE;
- avifDecoder * decoder = avifDecoderCreate();
- if (avifDecoderReadMemory(decoder, image, encodedAvif->data, encodedAvif->size) != AVIF_RESULT_OK) {
- printf("ERROR: avifDecoderReadMemory() failed\n");
- goto cleanup;
- }
- success = AVIF_TRUE;
-cleanup:
- if (decoder) {
- avifDecoderDestroy(decoder);
- }
- return success;
-}
-
-// Decodes incrementally the encodedAvif and compares the pixels with the given reference.
-// The cellHeight of all planes of the encodedAvif is given to estimate the incremental granularity.
-static avifBool decodeIncrementally(const avifRWData * encodedAvif, const avifImage * reference, uint32_t cellHeight, avifBool useNthImageApi)
-{
- avifBool success = AVIF_FALSE;
- avifDecoder * decoder = NULL;
- // AVIF cells are at least 64 pixels tall.
- if ((cellHeight == 0) || ((cellHeight > reference->height) && (cellHeight != 64))) {
- printf("ERROR: cell height %" PRIu32 " is invalid\n", cellHeight);
- goto cleanup;
- }
-
- // Emulate a byte-by-byte stream.
- avifROData availableEncodedAvif = { encodedAvif->data, 0 };
- avifIO io = { 0 };
- io.read = avifIOPartialRead;
- io.sizeHint = encodedAvif->size;
- io.persistent = AVIF_TRUE;
- io.data = &availableEncodedAvif;
-
- decoder = avifDecoderCreate();
- if (!decoder) {
- printf("ERROR: avifDecoderCreate() failed\n");
- goto cleanup;
- }
- avifDecoderSetIO(decoder, &io);
- decoder->allowIncremental = AVIF_TRUE;
-
- // Parsing is not incremental.
- avifResult parseResult = avifDecoderParse(decoder);
- while (parseResult == AVIF_RESULT_WAITING_ON_IO) {
- if (availableEncodedAvif.size >= encodedAvif->size) {
- printf("ERROR: avifDecoderParse() returned WAITING_ON_IO instead of OK\n");
- goto cleanup;
- }
- availableEncodedAvif.size = availableEncodedAvif.size + 1;
- parseResult = avifDecoderParse(decoder);
- }
- if (parseResult != AVIF_RESULT_OK) {
- printf("ERROR: avifDecoderParse() failed (%s)\n", avifResultToString(parseResult));
- goto cleanup;
- }
-
- // Decoding is incremental.
- uint32_t previouslyDecodedRowCount = 0;
- avifResult nextImageResult = useNthImageApi ? avifDecoderNthImage(decoder, 0) : avifDecoderNextImage(decoder);
- while (nextImageResult == AVIF_RESULT_WAITING_ON_IO) {
- if (availableEncodedAvif.size >= encodedAvif->size) {
- printf("ERROR: avifDecoderNextImage() or avifDecoderNthImage(0) returned WAITING_ON_IO instead of OK\n");
- goto cleanup;
- }
- const uint32_t decodedRowCount = avifDecoderDecodedRowCount(decoder);
- if (decodedRowCount < previouslyDecodedRowCount) {
- printf("ERROR: %" PRIu32 " decoded rows decreased to %" PRIu32 "\n", previouslyDecodedRowCount, decodedRowCount);
- goto cleanup;
- }
- const uint32_t minDecodedRowCount = getMinDecodedRowCount(reference->height,
- cellHeight,
- reference->alphaPlane != NULL,
- availableEncodedAvif.size,
- encodedAvif->size);
- if (decodedRowCount < minDecodedRowCount) {
- printf("ERROR: %" PRIu32 " is fewer than %" PRIu32 " decoded rows\n", decodedRowCount, minDecodedRowCount);
- goto cleanup;
- }
- if (!comparePartialYUVA(reference, decoder->image, decodedRowCount)) {
- goto cleanup;
- }
-
- previouslyDecodedRowCount = decodedRowCount;
- availableEncodedAvif.size = availableEncodedAvif.size + 1;
- nextImageResult = useNthImageApi ? avifDecoderNthImage(decoder, 0) : avifDecoderNextImage(decoder);
- }
- if (nextImageResult != AVIF_RESULT_OK) {
- printf("ERROR: avifDecoderNextImage() or avifDecoderNthImage(0) failed (%s)\n", avifResultToString(nextImageResult));
- goto cleanup;
- }
- if (availableEncodedAvif.size != encodedAvif->size) {
- printf("ERROR: not all bytes were read\n");
- goto cleanup;
- }
- if (avifDecoderDecodedRowCount(decoder) != decoder->image->height) {
- printf("ERROR: avifDecoderDecodedRowCount() should be decoder->image->height after OK\n");
- goto cleanup;
- }
-
- if (!comparePartialYUVA(reference, decoder->image, reference->height)) {
- goto cleanup;
- }
- success = AVIF_TRUE;
-cleanup:
- if (decoder) {
- avifDecoderDestroy(decoder);
- }
- return success;
-}
-
-static avifBool decodeNonIncrementallyAndIncrementally(const avifRWData * encodedAvif, uint32_t cellHeight, avifBool useNthImageApi)
-{
- avifBool success = AVIF_FALSE;
- avifImage * reference = avifImageCreateEmpty();
- if (!reference) {
- goto cleanup;
- }
- if (!decodeNonIncrementally(encodedAvif, reference)) {
- goto cleanup;
- }
- if (!decodeIncrementally(encodedAvif, reference, cellHeight, useNthImageApi)) {
- goto cleanup;
- }
- success = AVIF_TRUE;
-cleanup:
- if (reference) {
- avifImageDestroy(reference);
- }
- return success;
-}
-
-//------------------------------------------------------------------------------
-
// Encodes then decodes a window of width*height pixels at the middle of the image.
// Check that non-incremental and incremental decodings produce the same pixels.
static avifBool encodeDecodeNonIncrementallyAndIncrementally(const avifImage * image,
diff --git a/tests/avifincrtest_helpers.c b/tests/avifincrtest_helpers.c
new file mode 100644
index 0000000..d740631
--- /dev/null
+++ b/tests/avifincrtest_helpers.c
@@ -0,0 +1,422 @@
+// Copyright 2022 Google LLC. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "avifincrtest_helpers.h"
+#include "avif/avif.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+//------------------------------------------------------------------------------
+
+// Returns true if the first (top) rowCount rows of image1 and image2 are identical.
+static avifBool comparePartialYUVA(const avifImage * image1, const avifImage * image2, uint32_t rowCount)
+{
+ if (rowCount == 0) {
+ return AVIF_TRUE;
+ }
+ if (!image1 || !image2 || (image1->width != image2->width) || (image1->depth != image2->depth) ||
+ (image1->yuvFormat != image2->yuvFormat) || (image1->yuvRange != image2->yuvRange)) {
+ printf("ERROR: input mismatch\n");
+ return AVIF_FALSE;
+ }
+ if ((image1->height < rowCount) || (image2->height < rowCount)) {
+ printf("ERROR: not enough rows to compare\n");
+ return AVIF_FALSE;
+ }
+
+ avifPixelFormatInfo info;
+ avifGetPixelFormatInfo(image1->yuvFormat, &info);
+ const uint32_t uvWidth = (image1->width + info.chromaShiftX) >> info.chromaShiftX;
+ const uint32_t uvHeight = (rowCount + info.chromaShiftY) >> info.chromaShiftY;
+ const uint32_t pixelByteCount = (image1->depth > 8) ? sizeof(uint16_t) : sizeof(uint8_t);
+
+ for (uint32_t plane = 0; plane < (info.monochrome ? 1 : AVIF_PLANE_COUNT_YUV); ++plane) {
+ const uint32_t width = (plane == AVIF_CHAN_Y) ? image1->width : uvWidth;
+ const uint32_t widthByteCount = width * pixelByteCount;
+ const uint32_t height = (plane == AVIF_CHAN_Y) ? rowCount : uvHeight;
+ const uint8_t * data1 = image1->yuvPlanes[plane];
+ const uint8_t * data2 = image2->yuvPlanes[plane];
+ for (uint32_t y = 0; y < height; ++y) {
+ if (memcmp(data1, data2, widthByteCount)) {
+ printf("ERROR: different px at row %" PRIu32 ", channel %" PRIu32 "\n", y, plane);
+ return AVIF_FALSE;
+ }
+ data1 += image1->yuvRowBytes[plane];
+ data2 += image2->yuvRowBytes[plane];
+ }
+ }
+
+ if (image1->alphaPlane) {
+ if (!image2->alphaPlane ||
+ (image1->alphaPremultiplied != image2->alphaPremultiplied)) {
+ printf("ERROR: input mismatch\n");
+ return AVIF_FALSE;
+ }
+ const uint32_t widthByteCount = image1->width * pixelByteCount;
+ const uint8_t * data1 = image1->alphaPlane;
+ const uint8_t * data2 = image2->alphaPlane;
+ for (uint32_t y = 0; y < rowCount; ++y) {
+ if (memcmp(data1, data2, widthByteCount)) {
+ printf("ERROR: different px at row %" PRIu32 ", alpha\n", y);
+ return AVIF_FALSE;
+ }
+ data1 += image1->alphaRowBytes;
+ data2 += image2->alphaRowBytes;
+ }
+ }
+ return AVIF_TRUE;
+}
+
+// Returns the expected number of decoded rows when availableByteCount out of byteCount were
+// given to the decoder, for an image of height rows, split into cells of cellHeight rows.
+static uint32_t getMinDecodedRowCount(uint32_t height, uint32_t cellHeight, avifBool hasAlpha, size_t availableByteCount, size_t byteCount)
+{
+ // The whole image should be available when the full input is.
+ if (availableByteCount >= byteCount) {
+ return height;
+ }
+ // All but one cell should be decoded if at most 10 bytes are missing.
+ if ((availableByteCount + 10) >= byteCount) {
+ return height - cellHeight;
+ }
+
+ // Subtract the header because decoding it does not output any pixel.
+ // Most AVIF headers are below 500 bytes.
+ if (availableByteCount <= 500) {
+ return 0;
+ }
+ availableByteCount -= 500;
+ byteCount -= 500;
+ // Alpha, if any, is assumed to be located before the other planes and to
+ // represent at most 50% of the payload.
+ if (hasAlpha) {
+ if (availableByteCount <= (byteCount / 2)) {
+ return 0;
+ }
+ availableByteCount -= byteCount / 2;
+ byteCount -= byteCount / 2;
+ }
+ // Linearly map the input availability ratio to the decoded row ratio.
+ const uint32_t minDecodedCellRowCount = (height / cellHeight) * availableByteCount / byteCount;
+ const uint32_t minDecodedPxRowCount = minDecodedCellRowCount * cellHeight;
+ // One cell is the incremental decoding granularity.
+ // It is unlikely that bytes are evenly distributed among cells. Offset two of them.
+ if (minDecodedPxRowCount <= (2 * cellHeight)) {
+ return 0;
+ }
+ return minDecodedPxRowCount - 2 * cellHeight;
+}
+
+//------------------------------------------------------------------------------
+
+// Implementation of avifIOReadFunc simulating a stream from an array.
+// The full array size must be in io->sizeHint and io->data must be the available avifROData.
+static avifResult avifIOPartialRead(struct avifIO * io, uint32_t readFlags, uint64_t offset, size_t size, avifROData * out)
+{
+ const uint64_t allDataSize = io->sizeHint;
+ const avifROData * availableData = (avifROData *)io->data;
+
+ // The behavior below is described in the comment above avifIOReadFunc's declaration.
+ if (readFlags != 0) {
+ return AVIF_RESULT_IO_ERROR;
+ }
+ if (!availableData) {
+ return AVIF_RESULT_IO_ERROR;
+ }
+ if (allDataSize < offset) {
+ return AVIF_RESULT_IO_ERROR;
+ }
+ if (allDataSize == offset) {
+ out->data = availableData->data;
+ out->size = 0;
+ return AVIF_RESULT_OK;
+ }
+
+ if (allDataSize < (offset + size)) {
+ size = allDataSize - offset;
+ }
+ if (availableData->size < (offset + size)) {
+ return AVIF_RESULT_WAITING_ON_IO;
+ }
+ out->data = availableData->data + offset;
+ out->size = size;
+ return AVIF_RESULT_OK;
+}
+
+//------------------------------------------------------------------------------
+
+// Encodes the image as a grid of at most gridCols*gridRows cells. Returns AVIF_FALSE in case of error.
+// The cell count is reduced to fit libavif or AVIF format constraints. If impossible, AVIF_TRUE is returned with no encoded output.
+// The final cellWidth and cellHeight are output.
+static avifBool encodeAsGrid(const avifImage * image, uint32_t gridCols, uint32_t gridRows, avifRWData * output, uint32_t * cellWidth, uint32_t * cellHeight)
+{
+ avifBool success = AVIF_FALSE;
+ avifEncoder * encoder = NULL;
+ avifImage ** cellImages = NULL;
+ // Chroma subsampling requires even dimensions. See ISO 23000-22 - 7.3.11.4.2
+ const avifBool needEvenWidths = ((image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV422));
+ const avifBool needEvenHeights = (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420);
+
+ if ((gridCols == 0) || (gridRows == 0)) {
+ printf("ERROR: Bad grid dimensions\n");
+ return AVIF_FALSE;
+ }
+
+ *cellWidth = image->width / gridCols;
+ *cellHeight = image->height / gridRows;
+
+ // avifEncoderAddImageGrid() only accepts grids that evenly split the image
+ // into cells at least 64 pixels wide and tall.
+ while ((gridCols > 1) &&
+ (((*cellWidth * gridCols) != image->width) || (*cellWidth < 64) || (needEvenWidths && ((*cellWidth & 1) != 0)))) {
+ --gridCols;
+ *cellWidth = image->width / gridCols;
+ }
+ while ((gridRows > 1) &&
+ (((*cellHeight * gridRows) != image->height) || (*cellHeight < 64) || (needEvenHeights && ((*cellHeight & 1) != 0)))) {
+ --gridRows;
+ *cellHeight = image->height / gridRows;
+ }
+
+ cellImages = avifAlloc(sizeof(avifImage *) * gridCols * gridRows);
+ memset(cellImages, 0, sizeof(avifImage *) * gridCols * gridRows);
+ for (uint32_t row = 0, iCell = 0; row < gridRows; ++row) {
+ for (uint32_t col = 0; col < gridCols; ++col, ++iCell) {
+ avifCropRect cell;
+ cell.x = col * *cellWidth;
+ cell.y = row * *cellHeight;
+ cell.width = ((cell.x + *cellWidth) <= image->width) ? *cellWidth : (image->width - cell.x);
+ cell.height = ((cell.y + *cellHeight) <= image->height) ? *cellHeight : (image->height - cell.y);
+ cellImages[iCell] = avifImageCreateEmpty();
+ if (!cellImages[iCell] || (avifImageSetViewRect(cellImages[iCell], image, &cell) != AVIF_RESULT_OK)) {
+ printf("ERROR: avifImageCreateEmpty() failed\n");
+ goto cleanup;
+ }
+ }
+ }
+
+ encoder = avifEncoderCreate();
+ if (!encoder) {
+ printf("ERROR: avifEncoderCreate() failed\n");
+ goto cleanup;
+ }
+ encoder->speed = AVIF_SPEED_FASTEST;
+ if (avifEncoderAddImageGrid(encoder, gridCols, gridRows, (const avifImage * const *)cellImages, AVIF_ADD_IMAGE_FLAG_SINGLE) !=
+ AVIF_RESULT_OK) {
+ printf("ERROR: avifEncoderAddImageGrid() failed\n");
+ goto cleanup;
+ }
+ if (avifEncoderFinish(encoder, output) != AVIF_RESULT_OK) {
+ printf("ERROR: avifEncoderFinish() failed\n");
+ goto cleanup;
+ }
+
+ success = AVIF_TRUE;
+cleanup:
+ if (encoder) {
+ avifEncoderDestroy(encoder);
+ }
+ if (cellImages) {
+ for (uint32_t i = 0; i < (gridCols * gridRows); ++i) {
+ if (cellImages[i]) {
+ avifImageDestroy(cellImages[i]);
+ }
+ }
+ avifFree(cellImages);
+ }
+ return success;
+}
+
+// Encodes the image to be decoded incrementally.
+static avifBool encodeAsIncremental(const avifImage * image, avifBool flatCells, avifRWData * output, uint32_t * cellWidth, uint32_t * cellHeight)
+{
+ const uint32_t gridCols = image->width / 64; // 64px is the min cell width.
+ const uint32_t gridRows = flatCells ? 1 : (image->height / 64);
+ return encodeAsGrid(image, (gridCols > 1) ? gridCols : 1, (gridRows > 1) ? gridRows : 1, output, cellWidth, cellHeight);
+}
+
+avifBool encodeRectAsIncremental(const avifImage * image,
+ uint32_t width,
+ uint32_t height,
+ avifBool createAlphaIfNone,
+ avifBool flatCells,
+ avifRWData * output,
+ uint32_t * cellWidth,
+ uint32_t * cellHeight)
+{
+ avifBool success = AVIF_FALSE;
+ avifImage * subImage = avifImageCreateEmpty();
+ if (!subImage) {
+ printf("ERROR: avifImageCreateEmpty() failed\n");
+ goto cleanup;
+ }
+ if ((width > image->width) || (height > image->height)) {
+ printf("ERROR: Bad dimensions\n");
+ goto cleanup;
+ }
+ avifPixelFormatInfo info;
+ avifGetPixelFormatInfo(image->yuvFormat, &info);
+ avifCropRect rect;
+ rect.x = ((image->width - width) / 2) & ~info.chromaShiftX;
+ rect.y = ((image->height - height) / 2) & ~info.chromaShiftX;
+ rect.width = width;
+ rect.height = height;
+ if (avifImageSetViewRect(subImage, image, &rect) != AVIF_RESULT_OK) {
+ printf("ERROR: avifImageSetViewRect() failed\n");
+ goto cleanup;
+ }
+ if (createAlphaIfNone && !subImage->alphaPlane) {
+ if (!image->yuvPlanes[AVIF_CHAN_Y]) {
+ printf("ERROR: No luma plane to simulate an alpha plane\n");
+ goto cleanup;
+ }
+ subImage->alphaPlane = image->yuvPlanes[AVIF_CHAN_Y];
+ subImage->alphaRowBytes = image->yuvRowBytes[AVIF_CHAN_Y];
+ subImage->alphaPremultiplied = AVIF_FALSE;
+ subImage->imageOwnsAlphaPlane = AVIF_FALSE;
+ }
+ success = encodeAsIncremental(subImage, flatCells, output, cellWidth, cellHeight);
+cleanup:
+ if (subImage) {
+ avifImageDestroy(subImage);
+ }
+ return success;
+}
+
+//------------------------------------------------------------------------------
+
+avifBool decodeNonIncrementally(const avifRWData * encodedAvif, avifImage * image)
+{
+ avifBool success = AVIF_FALSE;
+ avifDecoder * decoder = avifDecoderCreate();
+ if (!decoder || avifDecoderReadMemory(decoder, image, encodedAvif->data, encodedAvif->size) != AVIF_RESULT_OK) {
+ printf("ERROR: avifDecoderReadMemory() failed\n");
+ goto cleanup;
+ }
+ success = AVIF_TRUE;
+cleanup:
+ if (decoder) {
+ avifDecoderDestroy(decoder);
+ }
+ return success;
+}
+
+avifBool decodeIncrementally(const avifRWData * encodedAvif, const avifImage * reference, uint32_t cellHeight, avifBool useNthImageApi)
+{
+ avifBool success = AVIF_FALSE;
+ avifDecoder * decoder = NULL;
+ // AVIF cells are at least 64 pixels tall.
+ if ((cellHeight == 0) || ((cellHeight > reference->height) && (cellHeight != 64))) {
+ printf("ERROR: cell height %" PRIu32 " is invalid\n", cellHeight);
+ goto cleanup;
+ }
+
+ // Emulate a byte-by-byte stream.
+ avifROData availableEncodedAvif = { encodedAvif->data, 0 };
+ avifIO io = { 0 };
+ io.read = avifIOPartialRead;
+ io.sizeHint = encodedAvif->size;
+ io.persistent = AVIF_TRUE;
+ io.data = &availableEncodedAvif;
+
+ decoder = avifDecoderCreate();
+ if (!decoder) {
+ printf("ERROR: avifDecoderCreate() failed\n");
+ goto cleanup;
+ }
+ avifDecoderSetIO(decoder, &io);
+ decoder->allowIncremental = AVIF_TRUE;
+
+ // Parsing is not incremental.
+ avifResult parseResult = avifDecoderParse(decoder);
+ while (parseResult == AVIF_RESULT_WAITING_ON_IO) {
+ if (availableEncodedAvif.size >= encodedAvif->size) {
+ printf("ERROR: avifDecoderParse() returned WAITING_ON_IO instead of OK\n");
+ goto cleanup;
+ }
+ availableEncodedAvif.size = availableEncodedAvif.size + 1;
+ parseResult = avifDecoderParse(decoder);
+ }
+ if (parseResult != AVIF_RESULT_OK) {
+ printf("ERROR: avifDecoderParse() failed (%s)\n", avifResultToString(parseResult));
+ goto cleanup;
+ }
+
+ // Decoding is incremental.
+ uint32_t previouslyDecodedRowCount = 0;
+ avifResult nextImageResult = useNthImageApi ? avifDecoderNthImage(decoder, 0) : avifDecoderNextImage(decoder);
+ while (nextImageResult == AVIF_RESULT_WAITING_ON_IO) {
+ if (availableEncodedAvif.size >= encodedAvif->size) {
+ printf("ERROR: avifDecoderNextImage() or avifDecoderNthImage(0) returned WAITING_ON_IO instead of OK\n");
+ goto cleanup;
+ }
+ const uint32_t decodedRowCount = avifDecoderDecodedRowCount(decoder);
+ if (decodedRowCount < previouslyDecodedRowCount) {
+ printf("ERROR: %" PRIu32 " decoded rows decreased to %" PRIu32 "\n", previouslyDecodedRowCount, decodedRowCount);
+ goto cleanup;
+ }
+ const uint32_t minDecodedRowCount = getMinDecodedRowCount(reference->height,
+ cellHeight,
+ reference->alphaPlane != NULL,
+ availableEncodedAvif.size,
+ encodedAvif->size);
+ if (decodedRowCount < minDecodedRowCount) {
+ printf("ERROR: %" PRIu32 " is fewer than %" PRIu32 " decoded rows\n", decodedRowCount, minDecodedRowCount);
+ goto cleanup;
+ }
+ if (!comparePartialYUVA(reference, decoder->image, decodedRowCount)) {
+ goto cleanup;
+ }
+
+ previouslyDecodedRowCount = decodedRowCount;
+ availableEncodedAvif.size = availableEncodedAvif.size + 1;
+ nextImageResult = useNthImageApi ? avifDecoderNthImage(decoder, 0) : avifDecoderNextImage(decoder);
+ }
+ if (nextImageResult != AVIF_RESULT_OK) {
+ printf("ERROR: avifDecoderNextImage() or avifDecoderNthImage(0) failed (%s)\n", avifResultToString(nextImageResult));
+ goto cleanup;
+ }
+ if (availableEncodedAvif.size != encodedAvif->size) {
+ printf("ERROR: not all bytes were read\n");
+ goto cleanup;
+ }
+ if (avifDecoderDecodedRowCount(decoder) != decoder->image->height) {
+ printf("ERROR: avifDecoderDecodedRowCount() should be decoder->image->height after OK\n");
+ goto cleanup;
+ }
+
+ if (!comparePartialYUVA(reference, decoder->image, reference->height)) {
+ goto cleanup;
+ }
+ success = AVIF_TRUE;
+cleanup:
+ if (decoder) {
+ avifDecoderDestroy(decoder);
+ }
+ return success;
+}
+
+avifBool decodeNonIncrementallyAndIncrementally(const avifRWData * encodedAvif, uint32_t cellHeight, avifBool useNthImageApi)
+{
+ avifBool success = AVIF_FALSE;
+ avifImage * reference = avifImageCreateEmpty();
+ if (!reference) {
+ goto cleanup;
+ }
+ if (!decodeNonIncrementally(encodedAvif, reference)) {
+ goto cleanup;
+ }
+ if (!decodeIncrementally(encodedAvif, reference, cellHeight, useNthImageApi)) {
+ goto cleanup;
+ }
+ success = AVIF_TRUE;
+cleanup:
+ if (reference) {
+ avifImageDestroy(reference);
+ }
+ return success;
+}
+
+//------------------------------------------------------------------------------
diff --git a/tests/avifincrtest_helpers.h b/tests/avifincrtest_helpers.h
new file mode 100644
index 0000000..67f39f6
--- /dev/null
+++ b/tests/avifincrtest_helpers.h
@@ -0,0 +1,37 @@
+// Copyright 2022 Google LLC
+// SPDX-License-Identifier: BSD-2-Clause
+
+#ifndef LIBAVIF_TESTS_AVIFINCRTEST_HELPERS_H_
+#define LIBAVIF_TESTS_AVIFINCRTEST_HELPERS_H_
+
+#include "avif/avif.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Encodes a portion of the image to be decoded incrementally.
+avifBool encodeRectAsIncremental(const avifImage * image,
+ uint32_t width,
+ uint32_t height,
+ avifBool createAlphaIfNone,
+ avifBool flatCells,
+ avifRWData * output,
+ uint32_t * cellWidth,
+ uint32_t * cellHeight);
+
+// Decodes the data into an image.
+avifBool decodeNonIncrementally(const avifRWData * encodedAvif, avifImage * image);
+
+// Decodes incrementally the encodedAvif and compares the pixels with the given reference.
+// The cellHeight of all planes of the encodedAvif is given to estimate the incremental granularity.
+avifBool decodeIncrementally(const avifRWData * encodedAvif, const avifImage * reference, uint32_t cellHeight, avifBool useNthImageApi);
+
+// Calls decodeIncrementally() with the output of decodeNonIncrementally() as reference.
+avifBool decodeNonIncrementallyAndIncrementally(const avifRWData * encodedAvif, uint32_t cellHeight, avifBool useNthImageApi);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LIBAVIF_TESTS_AVIFINCRTEST_HELPERS_H_