// Copyright 2022 Google LLC // SPDX-License-Identifier: BSD-2-Clause #include "avifincrtest_helpers.h" #include #include #include #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, bool enable_fine_incremental_check) { // 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; } // The tests below can be hard to tune for any kind of input, especially // fuzzed grids. Early exit in that case. if (!enable_fine_incremental_check) return 0; // 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( (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 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(io->data); if ((read_flags != 0) || !data || (data->full_size < offset64)) { return AVIF_RESULT_IO_ERROR; } const size_t offset = static_cast(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 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 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 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); // Encode the centered rect of dimensions width*height from the image. avifCropRect rect{/*x=*/(image.width - width) / 2, /*y=*/(image.height - height) / 2, width, height}; avifPixelFormatInfo info; avifGetPixelFormatInfo(image.yuvFormat, &info); if (!info.monochrome) { // Use even coordinates in subsampled dimensions. rect.x &= ~info.chromaShiftX; rect.y &= ~info.chromaShiftY; } 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, bool enable_fine_incremental_check) { // 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(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, enable_fine_incremental_check); 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, bool enable_fine_incremental_check) { 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, enable_fine_incremental_check); } //------------------------------------------------------------------------------ } // namespace testutil } // namespace libavif