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_