Convert tests/gtest/avifincrtest to C++ and GTest

diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index b625499..d7a7214 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -42,16 +42,6 @@
 ################################################################################
 # GoogleTest
 
-add_library(avifincrtest_helpers OBJECT gtest/avifincrtest_helpers.c)
-target_link_libraries(avifincrtest_helpers avif ${AVIF_PLATFORM_LIBRARIES})
-
-add_executable(avifincrtest gtest/avifincrtest.c)
-if(AVIF_LOCAL_LIBGAV1)
-    set_target_properties(avifincrtest PROPERTIES LINKER_LANGUAGE "CXX")
-endif()
-target_link_libraries(avifincrtest avifincrtest_helpers)
-add_test(NAME avifincrtest COMMAND avifincrtest ${CMAKE_CURRENT_SOURCE_DIR}/data/sofa_grid1x5_420.avif)
-
 if(AVIF_ENABLE_GTEST)
     enable_language(CXX)
     set(CMAKE_CXX_STANDARD 11)
@@ -81,6 +71,14 @@
     target_include_directories(avifgridapitest PRIVATE ${GTEST_INCLUDE_DIRS})
     add_test(NAME avifgridapitest COMMAND avifgridapitest)
 
+    add_library(avifincrtest_helpers OBJECT gtest/avifincrtest_helpers.cc)
+    target_link_libraries(avifincrtest_helpers avif ${AVIF_PLATFORM_LIBRARIES} ${GTEST_LIBRARIES})
+    target_include_directories(avifincrtest_helpers PUBLIC ${GTEST_INCLUDE_DIRS})
+
+    add_executable(avifincrtest gtest/avifincrtest.cc)
+    target_link_libraries(avifincrtest aviftest_helpers avifincrtest_helpers)
+    add_test(NAME avifincrtest COMMAND avifincrtest ${CMAKE_CURRENT_SOURCE_DIR}/data/)
+
     add_executable(avifmetadatatest gtest/avifmetadatatest.cc)
     target_link_libraries(avifmetadatatest aviftest_helpers ${GTEST_BOTH_LIBRARIES})
     target_include_directories(avifmetadatatest PRIVATE ${GTEST_INCLUDE_DIRS})
diff --git a/tests/gtest/avifincrtest.c b/tests/gtest/avifincrtest.c
deleted file mode 100644
index fe04616..0000000
--- a/tests/gtest/avifincrtest.c
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2022 Google LLC. All rights reserved.
-// SPDX-License-Identifier: BSD-2-Clause
-
-#include "avif/avif.h"
-#include "avifincrtest_helpers.h"
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-//------------------------------------------------------------------------------
-
-// Reads the file at path into bytes or returns false.
-static avifBool readFile(const char * path, avifRWData * bytes)
-{
-    FILE * file;
-    avifRWDataFree(bytes);
-    file = fopen(path, "rb");
-    if (!file) {
-        return AVIF_FALSE;
-    }
-    if (fseek(file, 0, SEEK_END)) {
-        fclose(file);
-        return AVIF_FALSE;
-    }
-    avifRWDataRealloc(bytes, ftell(file));
-    rewind(file);
-    if (fread(bytes->data, bytes->size, 1, file) != 1) {
-        avifRWDataFree(bytes);
-        fclose(file);
-        return AVIF_FALSE;
-    }
-    fclose(file);
-    return AVIF_TRUE;
-}
-
-//------------------------------------------------------------------------------
-
-// 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,
-                                                             uint32_t width,
-                                                             uint32_t height,
-                                                             avifBool createAlphaIfNone,
-                                                             avifBool flatCells,
-                                                             avifBool encodedAvifIsPersistent,
-                                                             avifBool giveSizeHint,
-                                                             avifBool useNthImageApi)
-{
-    avifBool success = AVIF_FALSE;
-    avifRWData encodedAvif = { 0 };
-    uint32_t cellWidth, cellHeight;
-    if (!encodeRectAsIncremental(image, width, height, createAlphaIfNone, flatCells, &encodedAvif, &cellWidth, &cellHeight)) {
-        goto cleanup;
-    }
-    if (!decodeNonIncrementallyAndIncrementally(&encodedAvif, encodedAvifIsPersistent, giveSizeHint, useNthImageApi, cellHeight)) {
-        goto cleanup;
-    }
-    success = AVIF_TRUE;
-cleanup:
-    avifRWDataFree(&encodedAvif);
-    return success;
-}
-
-//------------------------------------------------------------------------------
-
-int main(int argc, char * argv[])
-{
-    int exitCode = EXIT_FAILURE;
-    avifRWData encodedAvif = { NULL, 0 };
-
-    if (argc != 2) {
-        printf("ERROR: bad arguments\n");
-        printf("Usage: avifincrtest <AVIF>\n");
-        goto cleanup;
-    }
-    const char * const avifFilePath = argv[1];
-
-    if (!readFile(avifFilePath, &encodedAvif)) {
-        printf("ERROR: cannot read AVIF: %s\n", avifFilePath);
-        goto cleanup;
-    }
-
-    // First test: decode the input image incrementally and compare it with a non-incrementally decoded reference.
-    avifImage * reference = avifImageCreateEmpty();
-    if (!reference || !decodeNonIncrementally(&encodedAvif, reference)) {
-        goto cleanup;
-    }
-    // Cell height is hardcoded because there is no API to extract it from an encoded payload.
-    if (!decodeIncrementally(&encodedAvif,
-                             /*isPersistent=*/AVIF_TRUE,
-                             /*giveSizeHint=*/AVIF_TRUE,
-                             /*useNthImageApi=*/AVIF_FALSE,
-                             reference,
-                             /*cellHeight=*/154)) {
-        goto cleanup;
-    }
-
-    // Second test: encode a bunch of different dimension combinations and decode them incrementally and non-incrementally.
-    // Chroma subsampling requires even dimensions. See ISO 23000-22 section 7.3.11.4.2.
-    const uint32_t widths[] = { 1, 64, 66 };
-    const uint32_t heights[] = { 1, 64, 66 };
-    for (uint32_t w = 0; w < sizeof(widths) / sizeof(widths[0]); ++w) {
-        for (uint32_t h = 0; h < sizeof(heights) / sizeof(heights[0]); ++h) {
-            // avifEncoderAddImageInternal() only accepts grids of one unique cell, or grids where width and height are both at least 64.
-            if ((widths[w] >= 64) != (heights[h] >= 64)) {
-                continue;
-            }
-
-            for (avifBool createAlpha = AVIF_FALSE; createAlpha <= AVIF_TRUE; ++createAlpha) {
-                for (avifBool flatCells = AVIF_FALSE; flatCells <= AVIF_TRUE; ++flatCells) {
-                    for (avifBool encodedAvifIsPersistent = AVIF_FALSE; encodedAvifIsPersistent <= AVIF_TRUE; ++encodedAvifIsPersistent) {
-                        for (avifBool giveSizeHint = AVIF_FALSE; giveSizeHint <= AVIF_TRUE; ++giveSizeHint) {
-                            for (avifBool useNthImageApi = AVIF_FALSE; useNthImageApi <= AVIF_TRUE; ++useNthImageApi) {
-                                if (!encodeDecodeNonIncrementallyAndIncrementally(reference,
-                                                                                  widths[w],
-                                                                                  heights[h],
-                                                                                  createAlpha,
-                                                                                  flatCells,
-                                                                                  encodedAvifIsPersistent,
-                                                                                  giveSizeHint,
-                                                                                  useNthImageApi)) {
-                                    goto cleanup;
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    // Third test: full image.
-    for (avifBool flatCells = AVIF_FALSE; flatCells <= AVIF_TRUE; ++flatCells) {
-        if (!encodeDecodeNonIncrementallyAndIncrementally(reference,
-                                                          reference->width,
-                                                          reference->height,
-                                                          /*createAlphaIfNone=*/AVIF_TRUE,
-                                                          flatCells,
-                                                          /*encodedAvifIsPersistent=*/AVIF_TRUE,
-                                                          /*giveSizeHint=*/AVIF_TRUE,
-                                                          /*useNthImageApi=*/AVIF_FALSE)) {
-            goto cleanup;
-        }
-    }
-
-    exitCode = EXIT_SUCCESS;
-cleanup:
-    avifRWDataFree(&encodedAvif);
-    return exitCode;
-}
diff --git a/tests/gtest/avifincrtest.cc b/tests/gtest/avifincrtest.cc
new file mode 100644
index 0000000..6aae233
--- /dev/null
+++ b/tests/gtest/avifincrtest.cc
@@ -0,0 +1,142 @@
+// Copyright 2022 Google LLC. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "avif/avif.h"
+
+#include <fstream>
+#include <iostream>
+#include <tuple>
+
+#include "avifincrtest_helpers.h"
+#include "aviftest_helpers.h"
+#include "gtest/gtest.h"
+
+using testing::Bool;
+using testing::Combine;
+using testing::Values;
+
+namespace libavif
+{
+namespace
+{
+
+//------------------------------------------------------------------------------
+
+// Used to pass the data folder path to the GoogleTest suites.
+const char * dataPath = nullptr;
+
+// Reads the file with fileName into bytes and returns them.
+testutil::avifRWDataCleaner readFile(const char * fileName)
+{
+    std::ifstream file(std::string(dataPath) + fileName, std::ios::binary | std::ios::ate);
+    testutil::avifRWDataCleaner bytes;
+    avifRWDataRealloc(&bytes, file.good() ? static_cast<size_t>(file.tellg()) : 0);
+    file.seekg(0, std::ios::beg);
+    file.read(reinterpret_cast<char *>(bytes.data), static_cast<std::streamsize>(bytes.size));
+    return bytes;
+}
+
+//------------------------------------------------------------------------------
+
+// Check that non-incremental and incremental decodings of a grid AVIF produce the same pixels.
+TEST(IncrementalTest, Decode)
+{
+    const testutil::avifRWDataCleaner encodedAvif = readFile("sofa_grid1x5_420.avif");
+    ASSERT_NE(encodedAvif.size, 0u);
+    testutil::avifImagePtr reference(avifImageCreateEmpty(), avifImageDestroy);
+    ASSERT_NE(reference, nullptr);
+    testutil::avifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
+    ASSERT_NE(decoder, nullptr);
+    ASSERT_EQ(avifDecoderReadMemory(decoder.get(), reference.get(), encodedAvif.data, encodedAvif.size), AVIF_RESULT_OK);
+
+    // Cell height is hardcoded because there is no API to extract it from an encoded payload.
+    testutil::decodeIncrementally(encodedAvif, /*isPersistent=*/true, /*giveSizeHint=*/true, /*useNthImageApi=*/false, *reference, /*cellHeight=*/154);
+}
+
+//------------------------------------------------------------------------------
+
+class IncrementalTest : public testing::TestWithParam<std::tuple</*width=*/uint32_t,
+                                                                 /*height=*/uint32_t,
+                                                                 /*createAlpha=*/bool,
+                                                                 /*flatCells=*/bool,
+                                                                 /*encodedAvifIsPersistent=*/bool,
+                                                                 /*giveSizeHint=*/bool,
+                                                                 /*useNthImageApi=*/bool>>
+{
+};
+
+// 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.
+TEST_P(IncrementalTest, EncodeDecode)
+{
+    const uint32_t width = std::get<0>(GetParam());
+    const uint32_t height = std::get<1>(GetParam());
+    const bool createAlpha = std::get<2>(GetParam());
+    const bool flatCells = std::get<3>(GetParam());
+    const bool encodedAvifIsPersistent = std::get<4>(GetParam());
+    const bool giveSizeHint = std::get<5>(GetParam());
+    const bool useNthImageApi = std::get<6>(GetParam());
+
+    // Load an image. It does not matter that it comes from an AVIF file.
+    testutil::avifImagePtr image(avifImageCreateEmpty(), avifImageDestroy);
+    ASSERT_NE(image, nullptr);
+    const testutil::avifRWDataCleaner imageBytes = readFile("sofa_grid1x5_420.avif");
+    ASSERT_NE(imageBytes.size, 0u);
+    testutil::avifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
+    ASSERT_NE(decoder, nullptr);
+    ASSERT_EQ(avifDecoderReadMemory(decoder.get(), image.get(), imageBytes.data, imageBytes.size), AVIF_RESULT_OK);
+
+    // Encode then decode it.
+    testutil::avifRWDataCleaner encodedAvif;
+    uint32_t cellWidth, cellHeight;
+    testutil::encodeRectAsIncremental(*image, width, height, createAlpha, flatCells, &encodedAvif, &cellWidth, &cellHeight);
+    testutil::decodeNonIncrementallyAndIncrementally(encodedAvif, encodedAvifIsPersistent, giveSizeHint, useNthImageApi, cellHeight);
+}
+
+INSTANTIATE_TEST_SUITE_P(WholeImage,
+                         IncrementalTest,
+                         Combine(/*width=*/Values(1024),
+                                 /*height=*/Values(770),
+                                 /*createAlpha=*/Values(true),
+                                 /*flatCells=*/Bool(),
+                                 /*encodedAvifIsPersistent=*/Values(true),
+                                 /*giveSizeHint=*/Values(true),
+                                 /*useNthImageApi=*/Values(false)));
+
+// avifEncoderAddImageInternal() only accepts grids of one unique cell, or grids where width and height are both at least 64.
+INSTANTIATE_TEST_SUITE_P(SingleCell,
+                         IncrementalTest,
+                         Combine(/*width=*/Values(1),
+                                 /*height=*/Values(1),
+                                 /*createAlpha=*/Bool(),
+                                 /*flatCells=*/Bool(),
+                                 /*encodedAvifIsPersistent=*/Bool(),
+                                 /*giveSizeHint=*/Bool(),
+                                 /*useNthImageApi=*/Bool()));
+
+// Chroma subsampling requires even dimensions. See ISO 23000-22 section 7.3.11.4.2.
+INSTANTIATE_TEST_SUITE_P(SinglePixel,
+                         IncrementalTest,
+                         Combine(/*width=*/Values(64, 66),
+                                 /*height=*/Values(64, 66),
+                                 /*createAlpha=*/Bool(),
+                                 /*flatCells=*/Bool(),
+                                 /*encodedAvifIsPersistent=*/Bool(),
+                                 /*giveSizeHint=*/Bool(),
+                                 /*useNthImageApi=*/Bool()));
+
+//------------------------------------------------------------------------------
+
+} // namespace
+} // namespace libavif
+
+int main(int argc, char ** argv)
+{
+    ::testing::InitGoogleTest(&argc, argv);
+    if (argc < 2) {
+        std::cerr << "The path to the test data folder must be provided as an argument" << std::endl;
+        return 1;
+    }
+    libavif::dataPath = argv[1];
+    return RUN_ALL_TESTS();
+}
diff --git a/tests/gtest/avifincrtest_helpers.c b/tests/gtest/avifincrtest_helpers.c
deleted file mode 100644
index b15f740..0000000
--- a/tests/gtest/avifincrtest_helpers.c
+++ /dev/null
@@ -1,423 +0,0 @@
-// Copyright 2022 Google LLC. All rights reserved.
-// SPDX-License-Identifier: BSD-2-Clause
-
-#include "avifincrtest_helpers.h"
-#include "avif/avif.h"
-
-#include <assert.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;
-}
-
-//------------------------------------------------------------------------------
-
-typedef struct
-{
-    avifROData available;
-    size_t fullSize;
-} avifROPartialData;
-
-// Implementation of avifIOReadFunc simulating a stream from an array. See avifIOReadFunc documentation.
-// io->data is expected to point to avifROPartialData.
-static avifResult avifIOPartialRead(struct avifIO * io, uint32_t readFlags, uint64_t offset, size_t size, avifROData * out)
-{
-    const avifROPartialData * data = (avifROPartialData *)io->data;
-    if ((readFlags != 0) || !data || (data->fullSize < offset)) {
-        return AVIF_RESULT_IO_ERROR;
-    }
-    if (data->fullSize < (offset + size)) {
-        size = data->fullSize - offset;
-    }
-    if (data->available.size < (offset + size)) {
-        return AVIF_RESULT_WAITING_ON_IO;
-    }
-    out->data = data->available.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,
-                             avifBool isPersistent,
-                             avifBool giveSizeHint,
-                             avifBool useNthImageApi,
-                             const avifImage * reference,
-                             uint32_t cellHeight)
-{
-    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.
-    avifROPartialData data = { /*available=*/ { encodedAvif->data, 0 }, /*fullSize=*/encodedAvif->size };
-    avifIO io = { 0 };
-    io.read = avifIOPartialRead;
-    if (giveSizeHint) {
-        io.sizeHint = encodedAvif->size;
-    }
-    io.persistent = isPersistent;
-    io.data = &data;
-
-    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 (data.available.size >= data.fullSize) {
-            printf("ERROR: avifDecoderParse() returned WAITING_ON_IO instead of OK\n");
-            goto cleanup;
-        }
-        data.available.size = data.available.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 (data.available.size >= data.fullSize) {
-            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, data.available.size, data.fullSize);
-        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;
-        data.available.size = data.available.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 (data.available.size != data.fullSize) {
-        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,
-                                                avifBool isPersistent,
-                                                avifBool giveSizeHint,
-                                                avifBool useNthImageApi,
-                                                uint32_t cellHeight)
-{
-    // TODO(wtc): Remove this assertion when this file is converted to C++.
-    assert((useNthImageApi == AVIF_FALSE) || (useNthImageApi == AVIF_TRUE));
-    avifBool success = AVIF_FALSE;
-    avifImage * reference = avifImageCreateEmpty();
-    if (!reference) {
-        goto cleanup;
-    }
-    if (!decodeNonIncrementally(encodedAvif, reference)) {
-        goto cleanup;
-    }
-    if (!decodeIncrementally(encodedAvif, isPersistent, giveSizeHint, useNthImageApi, reference, cellHeight)) {
-        goto cleanup;
-    }
-    success = AVIF_TRUE;
-cleanup:
-    if (reference) {
-        avifImageDestroy(reference);
-    }
-    return success;
-}
-
-//------------------------------------------------------------------------------
diff --git a/tests/gtest/avifincrtest_helpers.cc b/tests/gtest/avifincrtest_helpers.cc
new file mode 100644
index 0000000..2078daa
--- /dev/null
+++ b/tests/gtest/avifincrtest_helpers.cc
@@ -0,0 +1,299 @@
+// Copyright 2022 Google LLC. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "avif/avif.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "avifincrtest_helpers.h"
+#include "aviftest_helpers.h"
+#include "gtest/gtest.h"
+
+namespace libavif
+{
+namespace testutil
+{
+namespace
+{
+
+//------------------------------------------------------------------------------
+
+// Verifies that the first (top) rowCount rows of image1 and image2 are identical.
+void comparePartialYUVA(const avifImage & image1, const avifImage & image2, uint32_t rowCount)
+{
+    if (rowCount == 0) {
+        return;
+    }
+    ASSERT_EQ(image1.width, image2.width);
+    ASSERT_GE(image1.height, rowCount);
+    ASSERT_GE(image2.height, rowCount);
+    ASSERT_EQ(image1.depth, image2.depth);
+    ASSERT_EQ(image1.yuvFormat, image2.yuvFormat);
+    ASSERT_EQ(image1.yuvRange, image2.yuvRange);
+
+    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) {
+            ASSERT_TRUE(std::equal(data1, data1 + widthByteCount, data2));
+            data1 += image1.yuvRowBytes[plane];
+            data2 += image2.yuvRowBytes[plane];
+        }
+    }
+
+    if (image1.alphaPlane) {
+        ASSERT_NE(image2.alphaPlane, nullptr);
+        ASSERT_EQ(image1.alphaPremultiplied, image2.alphaPremultiplied);
+        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) {
+            ASSERT_TRUE(std::equal(data1, data1 + widthByteCount, data2));
+            data1 += image1.alphaRowBytes;
+            data2 += image2.alphaRowBytes;
+        }
+    }
+}
+
+// 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.
+uint32_t getMinDecodedRowCount(uint32_t height, uint32_t cellHeight, bool 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;
+}
+
+//------------------------------------------------------------------------------
+
+struct avifROPartialData
+{
+    avifROData available;
+    size_t fullSize;
+};
+
+// Implementation of avifIOReadFunc simulating a stream from an array. See avifIOReadFunc documentation.
+// io->data is expected to point to avifROPartialData.
+avifResult avifIOPartialRead(struct avifIO * io, uint32_t readFlags, uint64_t offset, size_t size, avifROData * out)
+{
+    const avifROPartialData * data = (avifROPartialData *)io->data;
+    if ((readFlags != 0) || !data || (data->fullSize < offset)) {
+        return AVIF_RESULT_IO_ERROR;
+    }
+    if (data->fullSize < (offset + size)) {
+        size = data->fullSize - offset;
+    }
+    if (data->available.size < (offset + size)) {
+        return AVIF_RESULT_WAITING_ON_IO;
+    }
+    out->data = data->available.data + offset;
+    out->size = size;
+    return AVIF_RESULT_OK;
+}
+
+//------------------------------------------------------------------------------
+
+// Encodes the image as a grid of at most gridCols*gridRows cells.
+// The cell count is reduced to fit libavif or AVIF format constraints. If impossible, the encoded output is returned empty.
+// The final cellWidth and cellHeight are output.
+void encodeAsGrid(const avifImage & image, uint32_t gridCols, uint32_t gridRows, avifRWData * output, uint32_t * cellWidth, uint32_t * cellHeight)
+{
+    // Chroma subsampling requires even dimensions. See ISO 23000-22 - 7.3.11.4.2
+    const bool needEvenWidths = ((image.yuvFormat == AVIF_PIXEL_FORMAT_YUV420) || (image.yuvFormat == AVIF_PIXEL_FORMAT_YUV422));
+    const bool needEvenHeights = (image.yuvFormat == AVIF_PIXEL_FORMAT_YUV420);
+
+    ASSERT_GT(gridCols * gridRows, 0u);
+    *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;
+    }
+
+    std::vector<testutil::avifImagePtr> cellImages;
+    cellImages.reserve(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.emplace_back(avifImageCreateEmpty(), avifImageDestroy);
+            ASSERT_NE(cellImages.back(), nullptr);
+            ASSERT_EQ(avifImageSetViewRect(cellImages.back().get(), &image, &cell), AVIF_RESULT_OK);
+        }
+    }
+
+    testutil::avifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
+    ASSERT_NE(encoder, nullptr);
+    encoder->speed = AVIF_SPEED_FASTEST;
+    std::vector<avifImage *> cellImagePtrs(cellImages.size()); // Just here to match libavif API.
+    for (size_t i = 0; i < cellImages.size(); ++i) {
+        cellImagePtrs[i] = cellImages[i].get();
+    }
+    ASSERT_EQ(avifEncoderAddImageGrid(encoder.get(), gridCols, gridRows, cellImagePtrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE), AVIF_RESULT_OK);
+    ASSERT_EQ(avifEncoderFinish(encoder.get(), output), AVIF_RESULT_OK);
+}
+
+// Encodes the image to be decoded incrementally.
+void encodeAsIncremental(const avifImage & image, bool 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);
+    encodeAsGrid(image, (gridCols > 1) ? gridCols : 1, (gridRows > 1) ? gridRows : 1, output, cellWidth, cellHeight);
+}
+
+} // namespace
+
+void encodeRectAsIncremental(const avifImage & image,
+                             uint32_t width,
+                             uint32_t height,
+                             bool createAlphaIfNone,
+                             bool flatCells,
+                             avifRWData * output,
+                             uint32_t * cellWidth,
+                             uint32_t * cellHeight)
+{
+    avifImagePtr subImage(avifImageCreateEmpty(), avifImageDestroy);
+    ASSERT_NE(subImage, nullptr);
+    ASSERT_LE(width, image.width);
+    ASSERT_LE(height, image.height);
+    avifPixelFormatInfo info;
+    avifGetPixelFormatInfo(image.yuvFormat, &info);
+    const avifCropRect rect {
+        /*x=*/((image.width - width) / 2) & ~info.chromaShiftX, /*y=*/((image.height - height) / 2) & ~info.chromaShiftX, width, height
+    };
+    ASSERT_EQ(avifImageSetViewRect(subImage.get(), &image, &rect), AVIF_RESULT_OK);
+    if (createAlphaIfNone && !subImage->alphaPlane) {
+        ASSERT_NE(image.yuvPlanes[AVIF_CHAN_Y], nullptr) << "No luma plane to simulate an alpha plane";
+        subImage->alphaPlane = image.yuvPlanes[AVIF_CHAN_Y];
+        subImage->alphaRowBytes = image.yuvRowBytes[AVIF_CHAN_Y];
+        subImage->alphaPremultiplied = AVIF_FALSE;
+        subImage->imageOwnsAlphaPlane = AVIF_FALSE;
+    }
+    encodeAsIncremental(*subImage, flatCells, output, cellWidth, cellHeight);
+}
+
+//------------------------------------------------------------------------------
+
+void decodeIncrementally(const avifRWData & encodedAvif,
+                         bool isPersistent,
+                         bool giveSizeHint,
+                         bool useNthImageApi,
+                         const avifImage & reference,
+                         uint32_t cellHeight)
+{
+    // AVIF cells are at least 64 pixels tall.
+    if (cellHeight != reference.height) {
+        ASSERT_GE(cellHeight, 64u);
+    }
+
+    // Emulate a byte-by-byte stream.
+    avifROPartialData data = { /*available=*/ { encodedAvif.data, 0 }, /*fullSize=*/encodedAvif.size };
+    avifIO io = { /*destroy=*/nullptr, avifIOPartialRead,
+                  /*write=*/nullptr,   giveSizeHint ? encodedAvif.size : 0,
+                  isPersistent,        &data };
+
+    testutil::avifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
+    ASSERT_NE(decoder, nullptr);
+    avifDecoderSetIO(decoder.get(), &io);
+    decoder->allowIncremental = AVIF_TRUE;
+
+    // Parsing is not incremental.
+    avifResult parseResult = avifDecoderParse(decoder.get());
+    while (parseResult == AVIF_RESULT_WAITING_ON_IO) {
+        ASSERT_LT(data.available.size, data.fullSize) << "avifDecoderParse() returned WAITING_ON_IO instead of OK";
+        data.available.size = data.available.size + 1;
+        parseResult = avifDecoderParse(decoder.get());
+    }
+    ASSERT_EQ(parseResult, AVIF_RESULT_OK);
+
+    // Decoding is incremental.
+    uint32_t previouslyDecodedRowCount = 0;
+    avifResult nextImageResult = useNthImageApi ? avifDecoderNthImage(decoder.get(), 0) : avifDecoderNextImage(decoder.get());
+    while (nextImageResult == AVIF_RESULT_WAITING_ON_IO) {
+        ASSERT_LT(data.available.size, data.fullSize)
+            << (useNthImageApi ? "avifDecoderNthImage(0)" : "avifDecoderNextImage()") << " returned WAITING_ON_IO instead of OK";
+        const uint32_t decodedRowCount = avifDecoderDecodedRowCount(decoder.get());
+        ASSERT_GE(decodedRowCount, previouslyDecodedRowCount);
+        const uint32_t minDecodedRowCount =
+            getMinDecodedRowCount(reference.height, cellHeight, reference.alphaPlane != nullptr, data.available.size, data.fullSize);
+        ASSERT_GE(decodedRowCount, minDecodedRowCount);
+        comparePartialYUVA(reference, *decoder->image, decodedRowCount);
+
+        previouslyDecodedRowCount = decodedRowCount;
+        data.available.size = data.available.size + 1;
+        nextImageResult = useNthImageApi ? avifDecoderNthImage(decoder.get(), 0) : avifDecoderNextImage(decoder.get());
+    }
+    ASSERT_EQ(nextImageResult, AVIF_RESULT_OK);
+    ASSERT_EQ(data.available.size, data.fullSize);
+    ASSERT_EQ(avifDecoderDecodedRowCount(decoder.get()), decoder->image->height);
+
+    comparePartialYUVA(reference, *decoder->image, reference.height);
+}
+
+void decodeNonIncrementallyAndIncrementally(const avifRWData & encodedAvif, bool isPersistent, bool giveSizeHint, bool useNthImageApi, uint32_t cellHeight)
+{
+    avifImagePtr reference(avifImageCreateEmpty(), avifImageDestroy);
+    ASSERT_NE(reference, nullptr);
+    testutil::avifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
+    ASSERT_NE(decoder, nullptr);
+    ASSERT_EQ(avifDecoderReadMemory(decoder.get(), reference.get(), encodedAvif.data, encodedAvif.size), AVIF_RESULT_OK);
+
+    decodeIncrementally(encodedAvif, isPersistent, giveSizeHint, useNthImageApi, *reference, cellHeight);
+}
+
+//------------------------------------------------------------------------------
+
+} // namespace testutil
+} // namespace libavif
diff --git a/tests/gtest/avifincrtest_helpers.h b/tests/gtest/avifincrtest_helpers.h
index 82e4061..e0f9986 100644
--- a/tests/gtest/avifincrtest_helpers.h
+++ b/tests/gtest/avifincrtest_helpers.h
@@ -6,44 +6,41 @@
 
 #include "avif/avif.h"
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+namespace libavif
+{
+namespace testutil
+{
 
 // 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);
+void encodeRectAsIncremental(const avifImage & image,
+                             uint32_t width,
+                             uint32_t height,
+                             bool createAlphaIfNone,
+                             bool flatCells,
+                             avifRWData * output,
+                             uint32_t * cellWidth,
+                             uint32_t * cellHeight);
 
 // Decodes incrementally the encodedAvif and compares the pixels with the given reference.
 // If isPersistent is true, the input encodedAvif is considered as accessible during the whole decoding.
 // If giveSizeHint is true, the whole encodedAvif size is given as a hint to the decoder.
 // useNthImageApi describes whether the NthImage or NextImage decoder API will be used.
 // The cellHeight of all planes of the encodedAvif is given to estimate the incremental granularity.
-avifBool decodeIncrementally(const avifRWData * encodedAvif,
-                             avifBool isPersistent,
-                             avifBool giveSizeHint,
-                             avifBool useNthImageApi,
-                             const avifImage * reference,
-                             uint32_t cellHeight);
+void decodeIncrementally(const avifRWData & encodedAvif,
+                         bool isPersistent,
+                         bool giveSizeHint,
+                         bool useNthImageApi,
+                         const avifImage & reference,
+                         uint32_t cellHeight);
 
-// Calls decodeIncrementally() with the output of decodeNonIncrementally() as reference.
-avifBool decodeNonIncrementallyAndIncrementally(const avifRWData * encodedAvif,
-                                                avifBool isPersistent,
-                                                avifBool giveSizeHint,
-                                                avifBool useNthImageApi,
-                                                uint32_t cellHeight);
+// Calls decodeIncrementally() with the reference being a regular decoding of encodedAvif.
+void decodeNonIncrementallyAndIncrementally(const avifRWData & encodedAvif,
+                                            bool isPersistent,
+                                            bool giveSizeHint,
+                                            bool useNthImageApi,
+                                            uint32_t cellHeight);
 
-#ifdef __cplusplus
-} // extern "C"
-#endif
+} // namespace testutil
+} // namespace libavif
 
 #endif // LIBAVIF_TESTS_AVIFINCRTEST_HELPERS_H_