|  | // Copyright 2022 Google LLC | 
|  | // SPDX-License-Identifier: BSD-2-Clause | 
|  |  | 
|  | #include "avifincrtest_helpers.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cstring> | 
|  | #include <vector> | 
|  |  | 
|  | #include "avif/avif.h" | 
|  | #include "aviftest_helpers.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | namespace libavif { | 
|  | namespace testutil { | 
|  | namespace { | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | // Verifies that the first (top) row_count rows of image1 and image2 are | 
|  | // identical. | 
|  | void ComparePartialYuva(const avifImage& image1, const avifImage& image2, | 
|  | uint32_t row_count) { | 
|  | if (row_count == 0) { | 
|  | return; | 
|  | } | 
|  | ASSERT_EQ(image1.width, image2.width); | 
|  | ASSERT_GE(image1.height, row_count); | 
|  | ASSERT_GE(image2.height, row_count); | 
|  | 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 uv_height = | 
|  | info.monochrome ? 0 | 
|  | : ((row_count + info.chromaShiftY) >> info.chromaShiftY); | 
|  | const size_t pixel_byte_count = | 
|  | (image1.depth > 8) ? sizeof(uint16_t) : sizeof(uint8_t); | 
|  |  | 
|  | if (image1.alphaPlane) { | 
|  | ASSERT_NE(image2.alphaPlane, nullptr); | 
|  | ASSERT_EQ(image1.alphaPremultiplied, image2.alphaPremultiplied); | 
|  | } | 
|  |  | 
|  | const int last_plane = image1.alphaPlane ? AVIF_CHAN_A : AVIF_CHAN_V; | 
|  | for (int plane = AVIF_CHAN_Y; plane <= last_plane; ++plane) { | 
|  | const size_t width_byte_count = | 
|  | avifImagePlaneWidth(&image1, plane) * pixel_byte_count; | 
|  | const uint32_t height = | 
|  | (plane == AVIF_CHAN_Y || plane == AVIF_CHAN_A) ? row_count : uv_height; | 
|  | const uint8_t* row1 = avifImagePlane(&image1, plane); | 
|  | const uint8_t* row2 = avifImagePlane(&image2, plane); | 
|  | const uint32_t row1_bytes = avifImagePlaneRowBytes(&image1, plane); | 
|  | const uint32_t row2_bytes = avifImagePlaneRowBytes(&image2, plane); | 
|  | for (uint32_t y = 0; y < height; ++y) { | 
|  | ASSERT_EQ(std::memcmp(row1, row2, width_byte_count), 0); | 
|  | row1 += row1_bytes; | 
|  | row2 += row2_bytes; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Returns the expected number of decoded rows when available_byte_count out of | 
|  | // byte_count were given to the decoder, for an image of height rows, split into | 
|  | // cells of cell_height rows. | 
|  | uint32_t GetMinDecodedRowCount(uint32_t height, uint32_t cell_height, | 
|  | bool has_alpha, size_t available_byte_count, | 
|  | size_t byte_count) { | 
|  | // The whole image should be available when the full input is. | 
|  | if (available_byte_count >= byte_count) { | 
|  | return height; | 
|  | } | 
|  | // All but one cell should be decoded if at most 10 bytes are missing. | 
|  | if ((available_byte_count + 10) >= byte_count) { | 
|  | return height - cell_height; | 
|  | } | 
|  |  | 
|  | // Subtract the header because decoding it does not output any pixel. | 
|  | // Most AVIF headers are below 500 bytes. | 
|  | if (available_byte_count <= 500) { | 
|  | return 0; | 
|  | } | 
|  | available_byte_count -= 500; | 
|  | byte_count -= 500; | 
|  | // Alpha, if any, is assumed to be located before the other planes and to | 
|  | // represent at most 50% of the payload. | 
|  | if (has_alpha) { | 
|  | if (available_byte_count <= (byte_count / 2)) { | 
|  | return 0; | 
|  | } | 
|  | available_byte_count -= byte_count / 2; | 
|  | byte_count -= byte_count / 2; | 
|  | } | 
|  | // Linearly map the input availability ratio to the decoded row ratio. | 
|  | const uint32_t min_decoded_cell_row_count = static_cast<uint32_t>( | 
|  | (height / cell_height) * available_byte_count / byte_count); | 
|  | const uint32_t min_decoded_px_row_count = | 
|  | min_decoded_cell_row_count * cell_height; | 
|  | // One cell is the incremental decoding granularity. | 
|  | // It is unlikely that bytes are evenly distributed among cells. Offset two of | 
|  | // them. | 
|  | if (min_decoded_px_row_count <= (2 * cell_height)) { | 
|  | return 0; | 
|  | } | 
|  | return min_decoded_px_row_count - 2 * cell_height; | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | struct PartialData { | 
|  | avifROData available; | 
|  | size_t full_size; | 
|  |  | 
|  | // Only used as nonpersistent input. | 
|  | std::unique_ptr<uint8_t[]> nonpersistent_bytes; | 
|  | size_t num_nonpersistent_bytes; | 
|  | }; | 
|  |  | 
|  | // Implementation of avifIOReadFunc simulating a stream from an array. See | 
|  | // avifIOReadFunc documentation. io->data is expected to point to PartialData. | 
|  | avifResult PartialRead(struct avifIO* io, uint32_t read_flags, | 
|  | uint64_t offset64, size_t size, avifROData* out) { | 
|  | PartialData* data = reinterpret_cast<PartialData*>(io->data); | 
|  | if ((read_flags != 0) || !data || (data->full_size < offset64)) { | 
|  | return AVIF_RESULT_IO_ERROR; | 
|  | } | 
|  | const size_t offset = static_cast<size_t>(offset64); | 
|  | // Use |offset| instead of |offset64| from this point on. | 
|  | if (size > (data->full_size - offset)) { | 
|  | size = data->full_size - offset; | 
|  | } | 
|  | if (data->available.size < (offset + size)) { | 
|  | return AVIF_RESULT_WAITING_ON_IO; | 
|  | } | 
|  | if (io->persistent) { | 
|  | out->data = data->available.data + offset; | 
|  | } else { | 
|  | // Dedicated buffer containing just the available bytes and nothing more. | 
|  | std::unique_ptr<uint8_t[]> bytes(new uint8_t[size]); | 
|  | std::copy(data->available.data + offset, | 
|  | data->available.data + offset + size, bytes.get()); | 
|  | out->data = bytes.get(); | 
|  | // Flip the previously returned bytes to make sure the values changed. | 
|  | for (size_t i = 0; i < data->num_nonpersistent_bytes; ++i) { | 
|  | data->nonpersistent_bytes[i] = ~data->nonpersistent_bytes[i]; | 
|  | } | 
|  | // Free the memory to invalidate the old pointer. Only do that after | 
|  | // allocating the new bytes to make sure to have a different pointer. | 
|  | data->nonpersistent_bytes = std::move(bytes); | 
|  | data->num_nonpersistent_bytes = size; | 
|  | } | 
|  | out->size = size; | 
|  | return AVIF_RESULT_OK; | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | // Encodes the image as a grid of at most grid_cols*grid_rows cells. | 
|  | // The cell count is reduced to fit libavif or AVIF format constraints. If | 
|  | // impossible, the encoded output is returned empty. The final cell_width and | 
|  | // cell_height are output. | 
|  | void EncodeAsGrid(const avifImage& image, uint32_t grid_cols, | 
|  | uint32_t grid_rows, avifRWData* output, uint32_t* cell_width, | 
|  | uint32_t* cell_height) { | 
|  | // Chroma subsampling requires even dimensions. See ISO 23000-22 - 7.3.11.4.2 | 
|  | const bool need_even_widths = | 
|  | ((image.yuvFormat == AVIF_PIXEL_FORMAT_YUV420) || | 
|  | (image.yuvFormat == AVIF_PIXEL_FORMAT_YUV422)); | 
|  | const bool need_even_heights = (image.yuvFormat == AVIF_PIXEL_FORMAT_YUV420); | 
|  |  | 
|  | ASSERT_GT(grid_cols * grid_rows, 0u); | 
|  | *cell_width = image.width / grid_cols; | 
|  | *cell_height = image.height / grid_rows; | 
|  |  | 
|  | // avifEncoderAddImageGrid() only accepts grids that evenly split the image | 
|  | // into cells at least 64 pixels wide and tall. | 
|  | while ((grid_cols > 1) && | 
|  | (((*cell_width * grid_cols) != image.width) || (*cell_width < 64) || | 
|  | (need_even_widths && ((*cell_width & 1) != 0)))) { | 
|  | --grid_cols; | 
|  | *cell_width = image.width / grid_cols; | 
|  | } | 
|  | while ((grid_rows > 1) && | 
|  | (((*cell_height * grid_rows) != image.height) || (*cell_height < 64) || | 
|  | (need_even_heights && ((*cell_height & 1) != 0)))) { | 
|  | --grid_rows; | 
|  | *cell_height = image.height / grid_rows; | 
|  | } | 
|  |  | 
|  | std::vector<testutil::AvifImagePtr> cell_images; | 
|  | cell_images.reserve(grid_cols * grid_rows); | 
|  | for (uint32_t row = 0, i_cell = 0; row < grid_rows; ++row) { | 
|  | for (uint32_t col = 0; col < grid_cols; ++col, ++i_cell) { | 
|  | avifCropRect cell; | 
|  | cell.x = col * *cell_width; | 
|  | cell.y = row * *cell_height; | 
|  | cell.width = ((cell.x + *cell_width) <= image.width) | 
|  | ? *cell_width | 
|  | : (image.width - cell.x); | 
|  | cell.height = ((cell.y + *cell_height) <= image.height) | 
|  | ? *cell_height | 
|  | : (image.height - cell.y); | 
|  | cell_images.emplace_back(avifImageCreateEmpty(), avifImageDestroy); | 
|  | ASSERT_NE(cell_images.back(), nullptr); | 
|  | ASSERT_EQ(avifImageSetViewRect(cell_images.back().get(), &image, &cell), | 
|  | AVIF_RESULT_OK); | 
|  | } | 
|  | } | 
|  |  | 
|  | testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy); | 
|  | ASSERT_NE(encoder, nullptr); | 
|  | encoder->speed = AVIF_SPEED_FASTEST; | 
|  | // Just here to match libavif API. | 
|  | std::vector<avifImage*> cell_image_ptrs(cell_images.size()); | 
|  | for (size_t i = 0; i < cell_images.size(); ++i) { | 
|  | cell_image_ptrs[i] = cell_images[i].get(); | 
|  | } | 
|  | ASSERT_EQ(avifEncoderAddImageGrid(encoder.get(), grid_cols, grid_rows, | 
|  | cell_image_ptrs.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 flat_cells, | 
|  | avifRWData* output, uint32_t* cell_width, | 
|  | uint32_t* cell_height) { | 
|  | const uint32_t grid_cols = image.width / 64;  // 64px is the min cell width. | 
|  | const uint32_t grid_rows = flat_cells ? 1 : (image.height / 64); | 
|  | EncodeAsGrid(image, (grid_cols > 1) ? grid_cols : 1, | 
|  | (grid_rows > 1) ? grid_rows : 1, output, cell_width, | 
|  | cell_height); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | void EncodeRectAsIncremental(const avifImage& image, uint32_t width, | 
|  | uint32_t height, bool create_alpha_if_none, | 
|  | bool flat_cells, avifRWData* output, | 
|  | uint32_t* cell_width, uint32_t* cell_height) { | 
|  | AvifImagePtr sub_image(avifImageCreateEmpty(), avifImageDestroy); | 
|  | ASSERT_NE(sub_image, 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(sub_image.get(), &image, &rect), | 
|  | AVIF_RESULT_OK); | 
|  | if (create_alpha_if_none && !sub_image->alphaPlane) { | 
|  | ASSERT_NE(image.yuvPlanes[AVIF_CHAN_Y], nullptr) | 
|  | << "No luma plane to simulate an alpha plane"; | 
|  | sub_image->alphaPlane = image.yuvPlanes[AVIF_CHAN_Y]; | 
|  | sub_image->alphaRowBytes = image.yuvRowBytes[AVIF_CHAN_Y]; | 
|  | sub_image->alphaPremultiplied = AVIF_FALSE; | 
|  | sub_image->imageOwnsAlphaPlane = AVIF_FALSE; | 
|  | } | 
|  | EncodeAsIncremental(*sub_image, flat_cells, output, cell_width, cell_height); | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | void DecodeIncrementally(const avifRWData& encoded_avif, bool is_persistent, | 
|  | bool give_size_hint, bool use_nth_image_api, | 
|  | const avifImage& reference, uint32_t cell_height) { | 
|  | // AVIF cells are at least 64 pixels tall. | 
|  | if (cell_height != reference.height) { | 
|  | ASSERT_GE(cell_height, 64u); | 
|  | } | 
|  |  | 
|  | // Emulate a byte-by-byte stream. | 
|  | PartialData data = { | 
|  | /*available=*/{encoded_avif.data, 0}, /*fullSize=*/encoded_avif.size, | 
|  | /*nonpersistent_bytes=*/nullptr, /*num_nonpersistent_bytes=*/0}; | 
|  | avifIO io = { | 
|  | /*destroy=*/nullptr, PartialRead, | 
|  | /*write=*/nullptr,   give_size_hint ? encoded_avif.size : 0, | 
|  | is_persistent,       &data}; | 
|  |  | 
|  | testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy); | 
|  | ASSERT_NE(decoder, nullptr); | 
|  | avifDecoderSetIO(decoder.get(), &io); | 
|  | decoder->allowIncremental = AVIF_TRUE; | 
|  | const size_t step = std::max<size_t>(1, data.full_size / 10000); | 
|  |  | 
|  | // Parsing is not incremental. | 
|  | avifResult parse_result = avifDecoderParse(decoder.get()); | 
|  | while (parse_result == AVIF_RESULT_WAITING_ON_IO) { | 
|  | ASSERT_LT(data.available.size, data.full_size) | 
|  | << "avifDecoderParse() returned WAITING_ON_IO instead of OK"; | 
|  | data.available.size = std::min(data.available.size + step, data.full_size); | 
|  | parse_result = avifDecoderParse(decoder.get()); | 
|  | } | 
|  | ASSERT_EQ(parse_result, AVIF_RESULT_OK); | 
|  |  | 
|  | // Decoding is incremental. | 
|  | uint32_t previously_decoded_row_count = 0; | 
|  | avifResult next_image_result = use_nth_image_api | 
|  | ? avifDecoderNthImage(decoder.get(), 0) | 
|  | : avifDecoderNextImage(decoder.get()); | 
|  | while (next_image_result == AVIF_RESULT_WAITING_ON_IO) { | 
|  | ASSERT_LT(data.available.size, data.full_size) | 
|  | << (use_nth_image_api ? "avifDecoderNthImage(0)" | 
|  | : "avifDecoderNextImage()") | 
|  | << " returned WAITING_ON_IO instead of OK"; | 
|  | const uint32_t decoded_row_count = | 
|  | avifDecoderDecodedRowCount(decoder.get()); | 
|  | ASSERT_GE(decoded_row_count, previously_decoded_row_count); | 
|  | const uint32_t min_decoded_row_count = GetMinDecodedRowCount( | 
|  | reference.height, cell_height, reference.alphaPlane != nullptr, | 
|  | data.available.size, data.full_size); | 
|  | ASSERT_GE(decoded_row_count, min_decoded_row_count); | 
|  | ComparePartialYuva(reference, *decoder->image, decoded_row_count); | 
|  |  | 
|  | previously_decoded_row_count = decoded_row_count; | 
|  | data.available.size = std::min(data.available.size + step, data.full_size); | 
|  | next_image_result = use_nth_image_api | 
|  | ? avifDecoderNthImage(decoder.get(), 0) | 
|  | : avifDecoderNextImage(decoder.get()); | 
|  | } | 
|  | ASSERT_EQ(next_image_result, AVIF_RESULT_OK); | 
|  | ASSERT_EQ(data.available.size, data.full_size); | 
|  | ASSERT_EQ(avifDecoderDecodedRowCount(decoder.get()), decoder->image->height); | 
|  |  | 
|  | ComparePartialYuva(reference, *decoder->image, reference.height); | 
|  | } | 
|  |  | 
|  | void DecodeNonIncrementallyAndIncrementally(const avifRWData& encoded_avif, | 
|  | bool is_persistent, | 
|  | bool give_size_hint, | 
|  | bool use_nth_image_api, | 
|  | uint32_t cell_height) { | 
|  | AvifImagePtr reference(avifImageCreateEmpty(), avifImageDestroy); | 
|  | ASSERT_NE(reference, nullptr); | 
|  | testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy); | 
|  | ASSERT_NE(decoder, nullptr); | 
|  | ASSERT_EQ(avifDecoderReadMemory(decoder.get(), reference.get(), | 
|  | encoded_avif.data, encoded_avif.size), | 
|  | AVIF_RESULT_OK); | 
|  |  | 
|  | DecodeIncrementally(encoded_avif, is_persistent, give_size_hint, | 
|  | use_nth_image_api, *reference, cell_height); | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | }  // namespace testutil | 
|  | }  // namespace libavif |