Coverage Report

Created: 2026-09-11 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libavif/tests/gtest/avifincrtest_helpers.cc
Line
Count
Source
1
// Copyright 2022 Google LLC
2
// SPDX-License-Identifier: BSD-2-Clause
3
4
#include "avifincrtest_helpers.h"
5
6
#include <algorithm>
7
#include <cmath>
8
#include <cstdint>
9
#include <cstring>
10
#include <iostream>
11
#include <memory>
12
#include <vector>
13
14
#include "avif/avif.h"
15
#include "aviftest_helpers.h"
16
#include "gtest/gtest.h"
17
18
namespace avif {
19
namespace testutil {
20
namespace {
21
22
//------------------------------------------------------------------------------
23
24
// Verifies that the first (top) row_count rows of image1 and image2 are
25
// identical.
26
void ComparePartialYuva(const avifImage& image1, const avifImage& image2,
27
21.3M
                        uint32_t row_count) {
28
21.3M
  if (row_count == 0) {
29
17.1M
    return;
30
17.1M
  }
31
21.3M
  ASSERT_EQ(image1.width, image2.width);
32
4.24M
  ASSERT_GE(image1.height, row_count);
33
4.24M
  ASSERT_GE(image2.height, row_count);
34
4.24M
  ASSERT_EQ(image1.depth, image2.depth);
35
4.24M
  ASSERT_EQ(image1.yuvFormat, image2.yuvFormat);
36
4.24M
  ASSERT_EQ(image1.yuvRange, image2.yuvRange);
37
38
4.24M
  avifPixelFormatInfo info;
39
4.24M
  avifGetPixelFormatInfo(image1.yuvFormat, &info);
40
4.24M
  const uint32_t uv_height =
41
4.24M
      info.monochrome ? 0
42
4.24M
                      : ((row_count + info.chromaShiftY) >> info.chromaShiftY);
43
4.24M
  const size_t pixel_byte_count =
44
4.24M
      (image1.depth > 8) ? sizeof(uint16_t) : sizeof(uint8_t);
45
46
4.24M
  if (image1.alphaPlane) {
47
1.02M
    ASSERT_NE(image2.alphaPlane, nullptr);
48
1.02M
    ASSERT_EQ(image1.alphaPremultiplied, image2.alphaPremultiplied);
49
1.02M
  }
50
51
4.24M
  const int last_plane = image1.alphaPlane ? AVIF_CHAN_A : AVIF_CHAN_V;
52
18.0M
  for (int plane = AVIF_CHAN_Y; plane <= last_plane; ++plane) {
53
13.7M
    const size_t width_byte_count =
54
13.7M
        avifImagePlaneWidth(&image1, plane) * pixel_byte_count;
55
13.7M
    const uint32_t height =
56
13.7M
        (plane == AVIF_CHAN_Y || plane == AVIF_CHAN_A) ? row_count : uv_height;
57
13.7M
    const uint8_t* row1 = avifImagePlane(&image1, plane);
58
13.7M
    const uint8_t* row2 = avifImagePlane(&image2, plane);
59
13.7M
    const uint32_t row1_bytes = avifImagePlaneRowBytes(&image1, plane);
60
13.7M
    const uint32_t row2_bytes = avifImagePlaneRowBytes(&image2, plane);
61
1.68G
    for (uint32_t y = 0; y < height; ++y) {
62
1.67G
      ASSERT_EQ(std::memcmp(row1, row2, width_byte_count), 0);
63
1.67G
      row1 += row1_bytes;
64
1.67G
      row2 += row2_bytes;
65
1.67G
    }
66
13.7M
  }
67
68
4.24M
  if (image1.gainMap != nullptr && image1.gainMap->image != nullptr &&
69
103k
      image2.gainMap != nullptr && image2.gainMap->image != nullptr) {
70
103k
    const uint32_t gain_map_row_count = (uint32_t)roundf(
71
103k
        (float)row_count / image1.height * image1.gainMap->image->height);
72
103k
    ComparePartialYuva(*image1.gainMap->image, *image2.gainMap->image,
73
103k
                       gain_map_row_count);
74
103k
  }
75
4.24M
}
76
77
// Returns the expected number of decoded rows when available_byte_count out of
78
// byte_count were given to the decoder, for an image of height rows, split into
79
// cells of cell_height rows.
80
uint32_t GetMinDecodedRowCount(uint32_t height, uint32_t cell_height,
81
                               bool has_alpha, bool has_gain_map,
82
                               size_t available_byte_count, size_t byte_count,
83
21.2M
                               bool enable_fine_incremental_check) {
84
  // The whole image should be available when the full input is.
85
21.2M
  if (available_byte_count >= byte_count) {
86
0
    return height;
87
0
  }
88
89
  // The tests below can be hard to tune for any kind of input, especially
90
  // fuzzed grids, where tile ordering is unknown. Early exit in that case.
91
21.2M
  if (!enable_fine_incremental_check) return 0;
92
93
  // There is no valid AV1 payload smaller than 10 bytes, so all but one cell
94
  // should be decoded if at most 10 bytes are missing.
95
0
  if ((available_byte_count + 10) >= byte_count) {
96
0
    return height - cell_height;
97
0
  }
98
  // Subtract the header because decoding it does not output any pixel.
99
  // Most AVIF headers are below 500 bytes.
100
0
  if (available_byte_count <= 500) {
101
0
    return 0;
102
0
  }
103
0
  available_byte_count -= 500;
104
0
  byte_count -= 500;
105
  // Extra planes (alpha, gain map), if any, are assumed to be located before
106
  // the color planes. It's assumed that each extra planes is at most
107
  // total_size / (1 + num_extra_planes).
108
0
  const int num_extra_planes = (has_alpha ? 1 : 0) + (has_gain_map ? 1 : 0);
109
0
  const size_t max_size_of_extra_planes = static_cast<size_t>(
110
0
      (byte_count / (num_extra_planes + 1)) * num_extra_planes);
111
0
  if (available_byte_count <= max_size_of_extra_planes) {
112
0
    return 0;
113
0
  }
114
0
  available_byte_count -= max_size_of_extra_planes;
115
0
  byte_count -= max_size_of_extra_planes;
116
  // Linearly map the input availability ratio to the decoded row ratio.
117
0
  const uint32_t min_decoded_cell_row_count = static_cast<uint32_t>(
118
0
      (height / cell_height) * available_byte_count / byte_count);
119
0
  const uint32_t min_decoded_px_row_count =
120
0
      min_decoded_cell_row_count * cell_height;
121
  // One cell is the incremental decoding granularity.
122
  // It is unlikely that bytes are evenly distributed among cells. Offset two of
123
  // them.
124
0
  if (min_decoded_px_row_count <= (2 * cell_height)) {
125
0
    return 0;
126
0
  }
127
0
  return min_decoded_px_row_count - 2 * cell_height;
128
0
}
129
130
//------------------------------------------------------------------------------
131
132
struct PartialData {
133
  avifROData available;
134
  size_t full_size;
135
136
  // Only used as nonpersistent input.
137
  std::unique_ptr<uint8_t[]> nonpersistent_bytes;
138
  size_t num_nonpersistent_bytes;
139
};
140
141
// Implementation of avifIOReadFunc simulating a stream from an array. See
142
// avifIOReadFunc documentation. io->data is expected to point to PartialData.
143
avifResult PartialRead(struct avifIO* io, uint32_t read_flags,
144
50.8M
                       uint64_t offset64, size_t size, avifROData* out) {
145
50.8M
  PartialData* data = reinterpret_cast<PartialData*>(io->data);
146
50.8M
  if ((read_flags != 0) || !data || (data->full_size < offset64)) {
147
0
    return AVIF_RESULT_IO_ERROR;
148
0
  }
149
50.8M
  const size_t offset = static_cast<size_t>(offset64);
150
  // Use |offset| instead of |offset64| from this point on.
151
50.8M
  if (size > (data->full_size - offset)) {
152
6.41k
    size = data->full_size - offset;
153
6.41k
  }
154
50.8M
  if (data->available.size < (offset + size)) {
155
32.6M
    return AVIF_RESULT_WAITING_ON_IO;
156
32.6M
  }
157
18.1M
  if (io->persistent) {
158
8.78M
    out->data = data->available.data + offset;
159
9.39M
  } else {
160
    // Dedicated buffer containing just the available bytes and nothing more.
161
9.39M
    std::unique_ptr<uint8_t[]> bytes(new uint8_t[size]);
162
9.39M
    std::copy(data->available.data + offset,
163
9.39M
              data->available.data + offset + size, bytes.get());
164
9.39M
    out->data = bytes.get();
165
    // Flip the previously returned bytes to make sure the values changed.
166
899M
    for (size_t i = 0; i < data->num_nonpersistent_bytes; ++i) {
167
890M
      data->nonpersistent_bytes[i] = ~data->nonpersistent_bytes[i];
168
890M
    }
169
    // Free the memory to invalidate the old pointer. Only do that after
170
    // allocating the new bytes to make sure to have a different pointer.
171
9.39M
    data->nonpersistent_bytes = std::move(bytes);
172
9.39M
    data->num_nonpersistent_bytes = size;
173
9.39M
  }
174
18.1M
  out->size = size;
175
18.1M
  return AVIF_RESULT_OK;
176
50.8M
}
177
178
//------------------------------------------------------------------------------
179
180
// Encodes the image as a grid of at most grid_cols*grid_rows cells.
181
// The cell count is reduced to fit libavif or AVIF format constraints. If
182
// impossible, the encoded output is returned empty. The final cell_width and
183
// cell_height are output.
184
void EncodeAsGrid(const avifImage& image, uint32_t grid_cols,
185
                  uint32_t grid_rows, avifRWData* output, uint32_t* cell_width,
186
0
                  uint32_t* cell_height) {
187
  // Chroma subsampling requires even dimensions. See ISO 23000-22 - 7.3.11.4.2
188
0
  const bool need_even_widths =
189
0
      ((image.yuvFormat == AVIF_PIXEL_FORMAT_YUV420) ||
190
0
       (image.yuvFormat == AVIF_PIXEL_FORMAT_YUV422));
191
0
  const bool need_even_heights = (image.yuvFormat == AVIF_PIXEL_FORMAT_YUV420);
192
193
0
  ASSERT_GT(grid_cols * grid_rows, 0u);
194
0
  *cell_width = image.width / grid_cols;
195
0
  *cell_height = image.height / grid_rows;
196
197
  // avifEncoderAddImageGrid() only accepts grids that evenly split the image
198
  // into cells at least 64 pixels wide and tall.
199
0
  while ((grid_cols > 1) &&
200
0
         (((*cell_width * grid_cols) != image.width) || (*cell_width < 64) ||
201
0
          (need_even_widths && ((*cell_width & 1) != 0)))) {
202
0
    --grid_cols;
203
0
    *cell_width = image.width / grid_cols;
204
0
  }
205
0
  while ((grid_rows > 1) &&
206
0
         (((*cell_height * grid_rows) != image.height) || (*cell_height < 64) ||
207
0
          (need_even_heights && ((*cell_height & 1) != 0)))) {
208
0
    --grid_rows;
209
0
    *cell_height = image.height / grid_rows;
210
0
  }
211
212
0
  std::vector<ImagePtr> cell_images;
213
0
  cell_images.reserve(grid_cols * grid_rows);
214
0
  for (uint32_t row = 0; row < grid_rows; ++row) {
215
0
    for (uint32_t col = 0; col < grid_cols; ++col) {
216
0
      avifCropRect cell;
217
0
      cell.x = col * *cell_width;
218
0
      cell.y = row * *cell_height;
219
0
      cell.width = ((cell.x + *cell_width) <= image.width)
220
0
                       ? *cell_width
221
0
                       : (image.width - cell.x);
222
0
      cell.height = ((cell.y + *cell_height) <= image.height)
223
0
                        ? *cell_height
224
0
                        : (image.height - cell.y);
225
0
      cell_images.emplace_back(avifImageCreateEmpty());
226
0
      ASSERT_NE(cell_images.back(), nullptr);
227
0
      ASSERT_EQ(avifImageSetViewRect(cell_images.back().get(), &image, &cell),
228
0
                AVIF_RESULT_OK);
229
0
    }
230
0
  }
231
232
0
  EncoderPtr encoder(avifEncoderCreate());
233
0
  ASSERT_NE(encoder, nullptr);
234
0
  encoder->speed = AVIF_SPEED_FASTEST;
235
  // Just here to match libavif API.
236
0
  std::vector<avifImage*> cell_image_ptrs(cell_images.size());
237
0
  for (size_t i = 0; i < cell_images.size(); ++i) {
238
0
    cell_image_ptrs[i] = cell_images[i].get();
239
0
  }
240
0
  ASSERT_EQ(avifEncoderAddImageGrid(encoder.get(), grid_cols, grid_rows,
241
0
                                    cell_image_ptrs.data(),
242
0
                                    AVIF_ADD_IMAGE_FLAG_SINGLE),
243
0
            AVIF_RESULT_OK);
244
0
  ASSERT_EQ(avifEncoderFinish(encoder.get(), output), AVIF_RESULT_OK);
245
0
}
246
247
// Encodes the image to be decoded incrementally.
248
void EncodeAsIncremental(const avifImage& image, bool flat_cells,
249
                         avifRWData* output, uint32_t* cell_width,
250
0
                         uint32_t* cell_height) {
251
0
  const uint32_t grid_cols = image.width / 64;  // 64px is the min cell width.
252
0
  const uint32_t grid_rows = flat_cells ? 1 : (image.height / 64);
253
0
  EncodeAsGrid(image, (grid_cols > 1) ? grid_cols : 1,
254
0
               (grid_rows > 1) ? grid_rows : 1, output, cell_width,
255
0
               cell_height);
256
0
}
257
258
}  // namespace
259
260
void EncodeRectAsIncremental(const avifImage& image, uint32_t width,
261
                             uint32_t height, bool create_alpha_if_none,
262
                             bool flat_cells, avifRWData* output,
263
0
                             uint32_t* cell_width, uint32_t* cell_height) {
264
0
  ImagePtr sub_image(avifImageCreateEmpty());
265
0
  ASSERT_NE(sub_image, nullptr);
266
0
  ASSERT_LE(width, image.width);
267
0
  ASSERT_LE(height, image.height);
268
  // Encode the centered rect of dimensions width*height from the image.
269
0
  avifCropRect rect{/*x=*/(image.width - width) / 2,
270
0
                    /*y=*/(image.height - height) / 2, width, height};
271
0
  avifPixelFormatInfo info;
272
0
  avifGetPixelFormatInfo(image.yuvFormat, &info);
273
0
  if (!info.monochrome) {
274
    // Use even coordinates in subsampled dimensions.
275
0
    rect.x &= ~info.chromaShiftX;
276
0
    rect.y &= ~info.chromaShiftY;
277
0
  }
278
0
  ASSERT_EQ(avifImageSetViewRect(sub_image.get(), &image, &rect),
279
0
            AVIF_RESULT_OK);
280
0
  if (create_alpha_if_none && !sub_image->alphaPlane) {
281
0
    ASSERT_NE(image.yuvPlanes[AVIF_CHAN_Y], nullptr)
282
0
        << "No luma plane to simulate an alpha plane";
283
0
    sub_image->alphaPlane = image.yuvPlanes[AVIF_CHAN_Y];
284
0
    sub_image->alphaRowBytes = image.yuvRowBytes[AVIF_CHAN_Y];
285
0
    sub_image->alphaPremultiplied = AVIF_FALSE;
286
0
    sub_image->imageOwnsAlphaPlane = AVIF_FALSE;
287
0
  }
288
0
  EncodeAsIncremental(*sub_image, flat_cells, output, cell_width, cell_height);
289
0
}
290
291
//------------------------------------------------------------------------------
292
293
avifResult DecodeIncrementally(const avifRWData& encoded_avif,
294
                               avifDecoder* decoder, bool is_persistent,
295
                               bool give_size_hint, bool use_nth_image_api,
296
                               const avifImage& reference, uint32_t cell_height,
297
                               bool enable_fine_incremental_check,
298
                               bool expect_whole_file_read,
299
13.8k
                               bool expect_parse_success_from_partial_file) {
300
  // AVIF cells are at least 64 pixels tall.
301
13.8k
  if (cell_height != reference.height) {
302
773
    AVIF_CHECKERR(cell_height >= 64u, AVIF_RESULT_INVALID_ARGUMENT);
303
773
  }
304
305
  // Emulate a byte-by-byte stream.
306
13.8k
  PartialData data = {
307
13.8k
      /*available=*/{encoded_avif.data, 0}, /*fullSize=*/encoded_avif.size,
308
13.8k
      /*nonpersistent_bytes=*/nullptr, /*num_nonpersistent_bytes=*/0};
309
13.8k
  avifIO io = {
310
13.8k
      /*destroy=*/nullptr, PartialRead,
311
13.8k
      /*write=*/nullptr,   give_size_hint ? encoded_avif.size : 0,
312
13.8k
      is_persistent,       &data};
313
13.8k
  avifDecoderSetIO(decoder, &io);
314
  // Reset the decoder's IO to nullptr before 'io' goes out of scope and becomes
315
  // invalid.
316
13.8k
  auto cleanup_io_fn = [](avifDecoder* decoder) {
317
13.8k
    avifDecoderSetIO(decoder, nullptr);
318
13.8k
  };
319
13.8k
  std::unique_ptr<avifDecoder, decltype(cleanup_io_fn)> cleanup_io(
320
13.8k
      decoder, cleanup_io_fn);  // Call automatically at end of scope.
321
322
13.8k
  decoder->allowIncremental = AVIF_TRUE;
323
13.8k
  const size_t step = std::max<size_t>(1, data.full_size / 10000);
324
325
  // Parsing is not incremental.
326
13.8k
  avifResult parse_result = avifDecoderParse(decoder);
327
5.71M
  while (parse_result == AVIF_RESULT_WAITING_ON_IO) {
328
5.70M
    if (data.available.size >= data.full_size) {
329
0
      std::cerr << "avifDecoderParse() returned WAITING_ON_IO instead of OK"
330
0
                << std::endl;
331
0
      return AVIF_RESULT_TRUNCATED_DATA;
332
0
    }
333
5.70M
    data.available.size = std::min(data.available.size + step, data.full_size);
334
5.70M
    parse_result = avifDecoderParse(decoder);
335
5.70M
  }
336
13.8k
  if ((decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA) &&
337
13.8k
      data.available.size == data.full_size &&
338
1.12k
      expect_parse_success_from_partial_file) {
339
    // Can happen if the data is in 'idat', or if some metadata is at the end of
340
    // the file. But ideally this should be avoided.
341
0
    printf(
342
0
        "ERROR: had to provide the whole file for avifDecoderParse() to "
343
0
        "succeed\n");
344
0
    AVIF_CHECKERR(false, AVIF_RESULT_INVALID_ARGUMENT);
345
0
  }
346
13.8k
  AVIF_CHECKRES(parse_result);
347
348
  // Decoding is incremental.
349
13.4k
  uint32_t previously_decoded_row_count = 0;
350
13.4k
  avifResult next_image_result = use_nth_image_api
351
13.4k
                                     ? avifDecoderNthImage(decoder, 0)
352
13.4k
                                     : avifDecoderNextImage(decoder);
353
21.2M
  while (next_image_result == AVIF_RESULT_WAITING_ON_IO) {
354
21.2M
    if (data.available.size >= data.full_size) {
355
0
      std::cerr << (use_nth_image_api ? "avifDecoderNthImage(0)"
356
0
                                      : "avifDecoderNextImage()")
357
0
                << " returned WAITING_ON_IO instead of OK";
358
0
      AVIF_CHECKERR(false, AVIF_RESULT_INVALID_ARGUMENT);
359
0
    }
360
21.2M
    const uint32_t decoded_row_count = avifDecoderDecodedRowCount(decoder);
361
21.2M
    if (decoded_row_count < previously_decoded_row_count) {
362
0
      printf("ERROR: decoded row count decreased from %d to %d\n",
363
0
             previously_decoded_row_count, decoded_row_count);
364
0
      AVIF_CHECKERR(false, AVIF_RESULT_INVALID_ARGUMENT);
365
0
    }
366
21.2M
    if (decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA) {
367
21.2M
      const uint32_t min_decoded_row_count = GetMinDecodedRowCount(
368
21.2M
          reference.height, cell_height, reference.alphaPlane != nullptr,
369
21.2M
          reference.gainMap != nullptr, data.available.size, data.full_size,
370
21.2M
          enable_fine_incremental_check);
371
21.2M
      if (decoded_row_count < min_decoded_row_count) {
372
0
        printf(
373
0
            "ERROR: expected to have decoded at least %d rows with %zu "
374
0
            "available bytes, but only %d were decoded\n",
375
0
            min_decoded_row_count, data.available.size, decoded_row_count);
376
0
        AVIF_CHECKERR(false, AVIF_RESULT_INVALID_ARGUMENT);
377
0
      }
378
21.2M
    } else if (decoded_row_count != 0) {
379
      // avifDecoderDecodedRowCount() is only for decoder->image->yuvPlanes[0].
380
0
      printf(
381
0
          "ERROR: decoded row count shall be 0 unless "
382
0
          "decoder->imageContentToDecode&AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA\n");
383
0
      AVIF_CHECKERR(false, AVIF_RESULT_INVALID_ARGUMENT);
384
0
    }
385
21.2M
    ComparePartialYuva(reference, *decoder->image, decoded_row_count);
386
387
21.2M
    previously_decoded_row_count = decoded_row_count;
388
21.2M
    data.available.size = std::min(data.available.size + step, data.full_size);
389
21.2M
    next_image_result = use_nth_image_api ? avifDecoderNthImage(decoder, 0)
390
21.2M
                                          : avifDecoderNextImage(decoder);
391
21.2M
  }
392
13.4k
  AVIF_CHECKRES(next_image_result);
393
13.4k
  if (expect_whole_file_read) {
394
7.02k
    AVIF_CHECKERR(data.available.size == data.full_size,
395
7.02k
                  AVIF_RESULT_INVALID_ARGUMENT);
396
7.02k
  }
397
13.4k
  const uint32_t decoded_row_count = avifDecoderDecodedRowCount(decoder);
398
13.4k
  if (decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA) {
399
13.4k
    AVIF_CHECKERR(decoded_row_count == decoder->image->height,
400
13.4k
                  AVIF_RESULT_INVALID_ARGUMENT);
401
13.4k
    ComparePartialYuva(reference, *decoder->image, reference.height);
402
13.4k
  } else if (decoded_row_count != 0) {
403
    // avifDecoderDecodedRowCount() is only for decoder->image->yuvPlanes[0].
404
0
    printf(
405
0
        "ERROR: decoded row count shall be 0 unless "
406
0
        "decoder->imageContentToDecode&AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA\n");
407
0
    AVIF_CHECKERR(false, AVIF_RESULT_INVALID_ARGUMENT);
408
0
  }
409
13.4k
  return AVIF_RESULT_OK;
410
13.4k
}
411
412
avifResult DecodeNonIncrementallyAndIncrementally(
413
    const avifRWData& encoded_avif, avifDecoder* decoder, bool is_persistent,
414
    bool give_size_hint, bool use_nth_image_api, uint32_t cell_height,
415
    bool enable_fine_incremental_check, bool expect_whole_file_read,
416
10.4k
    bool expect_parse_success_from_partial_file) {
417
10.4k
  ImagePtr reference(avifImageCreateEmpty());
418
10.4k
  if (reference == nullptr) return AVIF_RESULT_INVALID_ARGUMENT;
419
10.4k
  decoder->allowIncremental = AVIF_FALSE;
420
10.4k
  AVIF_CHECKRES(avifDecoderReadMemory(decoder, reference.get(),
421
10.4k
                                      encoded_avif.data, encoded_avif.size));
422
423
10.4k
  const avifResult result = DecodeIncrementally(
424
10.4k
      encoded_avif, decoder, is_persistent, give_size_hint, use_nth_image_api,
425
10.4k
      *reference, cell_height, enable_fine_incremental_check,
426
10.4k
      expect_whole_file_read, expect_parse_success_from_partial_file);
427
10.4k
  return result;
428
10.4k
}
429
430
//------------------------------------------------------------------------------
431
432
}  // namespace testutil
433
}  // namespace avif