1 // Copyright 2022 Google LLC
2 // SPDX-License-Identifier: BSD-2-Clause
3 
4 #include <fstream>
5 #include <iostream>
6 #include <string>
7 
8 #include "avif/avif.h"
9 #include "aviftest_helpers.h"
10 #include "gtest/gtest.h"
11 
12 using testing::Bool;
13 using testing::Combine;
14 using testing::Values;
15 
16 namespace avif {
17 namespace {
18 
19 // Used to pass the data folder path to the GoogleTest suites.
20 const char* data_path = nullptr;
21 
22 // Verifies that the first (top) row_count rows of image1 and image2 are
23 // identical.
ComparePartialYuva(const avifImage & image1,const avifImage & image2,uint32_t row_count)24 void ComparePartialYuva(const avifImage& image1, const avifImage& image2,
25                         uint32_t row_count) {
26   if (row_count == 0) {
27     return;
28   }
29   ASSERT_EQ(image1.width, image2.width);
30   ASSERT_GE(image1.height, row_count);
31   ASSERT_GE(image2.height, row_count);
32   ASSERT_EQ(image1.depth, image2.depth);
33   ASSERT_EQ(image1.yuvFormat, image2.yuvFormat);
34   ASSERT_EQ(image1.yuvRange, image2.yuvRange);
35 
36   avifPixelFormatInfo info;
37   avifGetPixelFormatInfo(image1.yuvFormat, &info);
38   const uint32_t uv_height =
39       info.monochrome ? 0
40                       : ((row_count + info.chromaShiftY) >> info.chromaShiftY);
41   const size_t pixel_byte_count =
42       (image1.depth > 8) ? sizeof(uint16_t) : sizeof(uint8_t);
43 
44   if (image1.alphaPlane) {
45     ASSERT_NE(image2.alphaPlane, nullptr);
46     ASSERT_EQ(image1.alphaPremultiplied, image2.alphaPremultiplied);
47   }
48 
49   const int last_plane = image1.alphaPlane ? AVIF_CHAN_A : AVIF_CHAN_V;
50   for (int plane = AVIF_CHAN_Y; plane <= last_plane; ++plane) {
51     const size_t width_byte_count =
52         avifImagePlaneWidth(&image1, plane) * pixel_byte_count;
53     const uint32_t height =
54         (plane == AVIF_CHAN_Y || plane == AVIF_CHAN_A) ? row_count : uv_height;
55     const uint8_t* row1 = avifImagePlane(&image1, plane);
56     ASSERT_NE(row1, nullptr);
57     const uint8_t* row2 = avifImagePlane(&image2, plane);
58     ASSERT_NE(row2, nullptr);
59     const uint32_t row1_bytes = avifImagePlaneRowBytes(&image1, plane);
60     const uint32_t row2_bytes = avifImagePlaneRowBytes(&image2, plane);
61     for (uint32_t y = 0; y < height; ++y) {
62       ASSERT_EQ(memcmp(row1, row2, width_byte_count), 0);
63       row1 += row1_bytes;
64       row2 += row2_bytes;
65     }
66   }
67 }
68 
69 // Returns the expected number of decoded rows when available_byte_count out of
70 // byte_count were given to the decoder, for an image of height rows, split into
71 // cells of cell_height rows.
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)72 uint32_t GetMinDecodedRowCount(uint32_t height, uint32_t cell_height,
73                                bool has_alpha, size_t available_byte_count,
74                                size_t byte_count,
75                                bool enable_fine_incremental_check) {
76   // The whole image should be available when the full input is.
77   if (available_byte_count >= byte_count) {
78     return height;
79   }
80   // All but one cell should be decoded if at most 10 bytes are missing.
81   if ((available_byte_count + 10) >= byte_count) {
82     return height - cell_height;
83   }
84 
85   // The tests below can be hard to tune for any kind of input, especially
86   // fuzzed grids. Early exit in that case.
87   if (!enable_fine_incremental_check) return 0;
88 
89   // Subtract the header because decoding it does not output any pixel.
90   // Most AVIF headers are below 500 bytes.
91   if (available_byte_count <= 500) {
92     return 0;
93   }
94   available_byte_count -= 500;
95   byte_count -= 500;
96   // Alpha, if any, is assumed to be located before the other planes and to
97   // represent at most 50% of the payload.
98   if (has_alpha) {
99     if (available_byte_count <= (byte_count / 2)) {
100       return 0;
101     }
102     available_byte_count -= byte_count / 2;
103     byte_count -= byte_count / 2;
104   }
105   // Linearly map the input availability ratio to the decoded row ratio.
106   const uint32_t min_decoded_cell_row_count = static_cast<uint32_t>(
107       (height / cell_height) * available_byte_count / byte_count);
108   const uint32_t min_decoded_px_row_count =
109       min_decoded_cell_row_count * cell_height;
110   // One cell is the incremental decoding granularity.
111   // It is unlikely that bytes are evenly distributed among cells. Offset two of
112   // them.
113   if (min_decoded_px_row_count <= (2 * cell_height)) {
114     return 0;
115   }
116   return min_decoded_px_row_count - 2 * cell_height;
117 }
118 
119 struct PartialData {
120   avifROData available;
121   size_t full_size;
122 
123   // Only used as nonpersistent input.
124   std::unique_ptr<uint8_t[]> nonpersistent_bytes;
125   size_t num_nonpersistent_bytes;
126 };
127 
128 // Implementation of avifIOReadFunc simulating a stream from an array. See
129 // avifIOReadFunc documentation. io->data is expected to point to PartialData.
PartialRead(struct avifIO * io,uint32_t read_flags,uint64_t offset64,size_t size,avifROData * out)130 avifResult PartialRead(struct avifIO* io, uint32_t read_flags,
131                        uint64_t offset64, size_t size, avifROData* out) {
132   PartialData* data = reinterpret_cast<PartialData*>(io->data);
133   if ((read_flags != 0) || !data || (data->full_size < offset64)) {
134     return AVIF_RESULT_IO_ERROR;
135   }
136   const size_t offset = static_cast<size_t>(offset64);
137   // Use |offset| instead of |offset64| from this point on.
138   if (size > (data->full_size - offset)) {
139     size = data->full_size - offset;
140   }
141   if (data->available.size < (offset + size)) {
142     return AVIF_RESULT_WAITING_ON_IO;
143   }
144   if (io->persistent) {
145     out->data = data->available.data + offset;
146   } else {
147     // Dedicated buffer containing just the available bytes and nothing more.
148     std::unique_ptr<uint8_t[]> bytes(new uint8_t[size]);
149     std::copy(data->available.data + offset,
150               data->available.data + offset + size, bytes.get());
151     out->data = bytes.get();
152     // Flip the previously returned bytes to make sure the values changed.
153     for (size_t i = 0; i < data->num_nonpersistent_bytes; ++i) {
154       data->nonpersistent_bytes[i] = ~data->nonpersistent_bytes[i];
155     }
156     // Free the memory to invalidate the old pointer. Only do that after
157     // allocating the new bytes to make sure to have a different pointer.
158     data->nonpersistent_bytes = std::move(bytes);
159     data->num_nonpersistent_bytes = size;
160   }
161   out->size = size;
162   return AVIF_RESULT_OK;
163 }
164 
DecodeIncrementally(const avifRWData & encoded_avif,avifDecoder * decoder,bool is_persistent,bool give_size_hint,bool use_nth_image_api,const avifImage & reference,uint32_t cell_height,bool enable_fine_incremental_check,bool expect_whole_file_read)165 avifResult DecodeIncrementally(const avifRWData& encoded_avif,
166                                avifDecoder* decoder, bool is_persistent,
167                                bool give_size_hint, bool use_nth_image_api,
168                                const avifImage& reference, uint32_t cell_height,
169                                bool enable_fine_incremental_check,
170                                bool expect_whole_file_read) {
171   // AVIF cells are at least 64 pixels tall.
172   if (cell_height != reference.height) {
173     AVIF_CHECKERR(cell_height >= 64u, AVIF_RESULT_INVALID_ARGUMENT);
174   }
175 
176   // Emulate a byte-by-byte stream.
177   PartialData data = {
178       /*available=*/{encoded_avif.data, 0}, /*fullSize=*/encoded_avif.size,
179       /*nonpersistent_bytes=*/nullptr, /*num_nonpersistent_bytes=*/0};
180   avifIO io = {
181       /*destroy=*/nullptr, PartialRead,
182       /*write=*/nullptr,   give_size_hint ? encoded_avif.size : 0,
183       is_persistent,       &data};
184   avifDecoderSetIO(decoder, &io);
185   decoder->allowIncremental = AVIF_TRUE;
186   const size_t step = std::max<size_t>(1, data.full_size / 10000);
187 
188   // Parsing is not incremental.
189   avifResult parse_result = avifDecoderParse(decoder);
190   while (parse_result == AVIF_RESULT_WAITING_ON_IO) {
191     if (data.available.size >= data.full_size) {
192       std::cerr << "avifDecoderParse() returned WAITING_ON_IO instead of OK"
193                 << std::endl;
194       return AVIF_RESULT_TRUNCATED_DATA;
195     }
196     data.available.size = std::min(data.available.size + step, data.full_size);
197     parse_result = avifDecoderParse(decoder);
198   }
199   EXPECT_EQ(parse_result, AVIF_RESULT_OK);
200 
201   // Decoding is incremental.
202   uint32_t previously_decoded_row_count = 0;
203   avifResult next_image_result = use_nth_image_api
204                                      ? avifDecoderNextImage(decoder)
205                                      : avifDecoderNextImage(decoder);
206   while (next_image_result == AVIF_RESULT_WAITING_ON_IO) {
207     if (data.available.size >= data.full_size) {
208       std::cerr << (use_nth_image_api ? "avifDecoderNthImage(0)"
209                                       : "avifDecoderNextImage()")
210                 << " returned WAITING_ON_IO instead of OK";
211       return AVIF_RESULT_INVALID_ARGUMENT;
212     }
213     const uint32_t decoded_row_count = avifDecoderDecodedRowCount(decoder);
214     EXPECT_GE(decoded_row_count, previously_decoded_row_count);
215     const uint32_t min_decoded_row_count = GetMinDecodedRowCount(
216         reference.height, cell_height, reference.alphaPlane != nullptr,
217         data.available.size, data.full_size, enable_fine_incremental_check);
218     EXPECT_GE(decoded_row_count, min_decoded_row_count);
219     if (decoded_row_count > 0) {
220       ComparePartialYuva(reference, *decoder->image, decoded_row_count);
221     }
222 
223     previously_decoded_row_count = decoded_row_count;
224     data.available.size = std::min(data.available.size + step, data.full_size);
225     next_image_result = use_nth_image_api ? avifDecoderNextImage(decoder)
226                                           : avifDecoderNextImage(decoder);
227   }
228   EXPECT_EQ(next_image_result, AVIF_RESULT_OK);
229   if (expect_whole_file_read) {
230     EXPECT_EQ(data.available.size, data.full_size);
231   }
232   EXPECT_EQ(avifDecoderDecodedRowCount(decoder), decoder->image->height);
233 
234   ComparePartialYuva(reference, *decoder->image, reference.height);
235   return AVIF_RESULT_OK;
236 }
237 
get_file_name(const char * file_name)238 std::string get_file_name(const char* file_name) {
239   return std::string(data_path) + file_name;
240 }
241 
242 // Check that non-incremental and incremental decodings of a grid AVIF produce
243 // the same pixels.
TEST(IncrementalTest,Decode)244 TEST(IncrementalTest, Decode) {
245   auto file_data =
246       testutil::read_file(get_file_name("sofa_grid1x5_420.avif").c_str());
247   avifRWData encoded_avif = {.data = file_data.data(),
248                              .size = file_data.size()};
249   ASSERT_NE(encoded_avif.size, 0u);
250   ImagePtr reference(avifImageCreateEmpty());
251   ASSERT_NE(reference, nullptr);
252   DecoderPtr decoder(avifDecoderCreate());
253   ASSERT_NE(decoder, nullptr);
254   ASSERT_EQ(avifDecoderReadMemory(decoder.get(), reference.get(),
255                                   encoded_avif.data, encoded_avif.size),
256             AVIF_RESULT_OK);
257 
258   DecoderPtr decoder2(avifDecoderCreate());
259   ASSERT_NE(decoder2, nullptr);
260 
261   // Cell height is hardcoded because there is no API to extract it from an
262   // encoded payload.
263   ASSERT_EQ(DecodeIncrementally(encoded_avif, decoder2.get(),
264                                 /*is_persistent=*/true, /*give_size_hint=*/true,
265                                 /*use_nth_image_api=*/false, *reference,
266                                 /*cell_height=*/154,
267                                 /*enable_fine_incremental_check=*/true, true),
268             AVIF_RESULT_OK);
269 }
270 
271 }  // namespace
272 }  // namespace avif
273 
main(int argc,char ** argv)274 int main(int argc, char** argv) {
275   ::testing::InitGoogleTest(&argc, argv);
276   if (argc < 2) {
277     std::cerr
278         << "The path to the test data folder must be provided as an argument"
279         << std::endl;
280     return 1;
281   }
282   avif::data_path = argv[1];
283   return RUN_ALL_TESTS();
284 }
285