Coverage Report

Created: 2026-07-16 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libavif/tests/gtest/aviftest_helpers.cc
Line
Count
Source
1
// Copyright 2022 Google LLC
2
// SPDX-License-Identifier: BSD-2-Clause
3
4
#include "aviftest_helpers.h"
5
6
#include <algorithm>
7
#include <cassert>
8
#include <cmath>
9
#include <cstdint>
10
#include <cstdlib>
11
#include <cstring>
12
#include <fstream>
13
#include <iostream>
14
#include <limits>
15
#include <string>
16
#include <vector>
17
18
#include "avif/avif.h"
19
#include "avif/avif_cxx.h"
20
#include "avif/internal.h"
21
#include "avifpng.h"
22
#include "avifutil.h"
23
24
namespace avif {
25
namespace testutil {
26
27
//------------------------------------------------------------------------------
28
// CopyImageSamples is a copy of avifImageCopySamples
29
30
namespace {
31
void CopyImageSamples(avifImage* dstImage, const avifImage* srcImage,
32
0
                      avifPlanesFlags planes) {
33
0
  assert(srcImage->depth == dstImage->depth);
34
0
  if (planes & AVIF_PLANES_YUV) {
35
0
    assert(srcImage->yuvFormat == dstImage->yuvFormat);
36
    // Note that there may be a mismatch between srcImage->yuvRange and
37
    // dstImage->yuvRange because libavif allows for 'colr' and AV1 OBU video
38
    // range values to differ.
39
0
  }
40
0
  const size_t bytesPerPixel = avifImageUsesU16(srcImage) ? 2 : 1;
41
42
0
  const avifBool skipColor = !(planes & AVIF_PLANES_YUV);
43
0
  const avifBool skipAlpha = !(planes & AVIF_PLANES_A);
44
0
  for (int c = AVIF_CHAN_Y; c <= AVIF_CHAN_A; ++c) {
45
0
    const avifBool alpha = c == AVIF_CHAN_A;
46
0
    if ((skipColor && !alpha) || (skipAlpha && alpha)) {
47
0
      continue;
48
0
    }
49
50
0
    const uint32_t planeWidth = avifImagePlaneWidth(srcImage, c);
51
0
    const uint32_t planeHeight = avifImagePlaneHeight(srcImage, c);
52
0
    const uint8_t* srcRow = avifImagePlane(srcImage, c);
53
0
    uint8_t* dstRow = avifImagePlane(dstImage, c);
54
0
    const uint32_t srcRowBytes = avifImagePlaneRowBytes(srcImage, c);
55
0
    const uint32_t dstRowBytes = avifImagePlaneRowBytes(dstImage, c);
56
0
    assert(!srcRow == !dstRow);
57
0
    if (!srcRow) {
58
0
      continue;
59
0
    }
60
0
    assert(planeWidth == avifImagePlaneWidth(dstImage, c));
61
0
    assert(planeHeight == avifImagePlaneHeight(dstImage, c));
62
63
0
    const size_t planeWidthBytes = planeWidth * bytesPerPixel;
64
0
    for (uint32_t y = 0; y < planeHeight; ++y) {
65
0
      memcpy(dstRow, srcRow, planeWidthBytes);
66
0
      srcRow += srcRowBytes;
67
0
      dstRow += dstRowBytes;
68
0
    }
69
0
  }
70
0
}
71
}  // namespace
72
73
//------------------------------------------------------------------------------
74
75
AvifRgbImage::AvifRgbImage(const avifImage* yuv, int rgbDepth,
76
5.09k
                           avifRGBFormat rgbFormat) {
77
5.09k
  avifRGBImageSetDefaults(this, yuv);
78
5.09k
  depth = rgbDepth;
79
5.09k
  format = rgbFormat;
80
5.09k
  if (avifRGBImageAllocatePixels(this) != AVIF_RESULT_OK) {
81
0
    std::abort();
82
0
  }
83
5.09k
}
84
85
0
AvifRwData::AvifRwData(AvifRwData&& other) : avifRWData{other} {
86
0
  other.data = nullptr;
87
0
  other.size = 0;
88
0
}
89
90
//------------------------------------------------------------------------------
91
92
0
RgbChannelOffsets GetRgbChannelOffsets(avifRGBFormat format) {
93
0
  switch (format) {
94
0
    case AVIF_RGB_FORMAT_RGB:
95
0
      return {/*r=*/0, /*g=*/1, /*b=*/2, /*a=*/0};
96
0
    case AVIF_RGB_FORMAT_RGBA:
97
0
      return {/*r=*/0, /*g=*/1, /*b=*/2, /*a=*/3};
98
0
    case AVIF_RGB_FORMAT_ARGB:
99
0
      return {/*r=*/1, /*g=*/2, /*b=*/3, /*a=*/0};
100
0
    case AVIF_RGB_FORMAT_BGR:
101
0
      return {/*r=*/2, /*g=*/1, /*b=*/0, /*a=*/0};
102
0
    case AVIF_RGB_FORMAT_BGRA:
103
0
      return {/*r=*/2, /*g=*/1, /*b=*/0, /*a=*/3};
104
0
    case AVIF_RGB_FORMAT_ABGR:
105
0
      return {/*r=*/3, /*g=*/2, /*b=*/1, /*a=*/0};
106
0
    case AVIF_RGB_FORMAT_RGB_565:
107
0
    case AVIF_RGB_FORMAT_COUNT:
108
0
    default:
109
0
      return {/*r=*/0, /*g=*/0, /*b=*/0, /*a=*/0};
110
0
  }
111
0
}
112
113
//------------------------------------------------------------------------------
114
115
ImagePtr CreateImage(int width, int height, int depth,
116
                     avifPixelFormat yuv_format, avifPlanesFlags planes,
117
0
                     avifRange yuv_range) {
118
0
  ImagePtr image(avifImageCreate(width, height, depth, yuv_format));
119
0
  if (!image) {
120
0
    return nullptr;
121
0
  }
122
0
  image->yuvRange = yuv_range;
123
0
  if (avifImageAllocatePlanes(image.get(), planes) != AVIF_RESULT_OK) {
124
0
    return nullptr;
125
0
  }
126
0
  return image;
127
0
}
128
129
0
void FillImagePlain(avifImage* image, const uint32_t yuva[4]) {
130
0
  for (avifChannelIndex c :
131
0
       {AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A}) {
132
0
    const uint32_t plane_width = avifImagePlaneWidth(image, c);
133
    // 0 for A if no alpha and 0 for UV if 4:0:0.
134
0
    const uint32_t plane_height = avifImagePlaneHeight(image, c);
135
0
    uint8_t* row = avifImagePlane(image, c);
136
0
    const uint32_t row_bytes = avifImagePlaneRowBytes(image, c);
137
0
    for (uint32_t y = 0; y < plane_height; ++y) {
138
0
      if (avifImageUsesU16(image)) {
139
0
        std::fill(reinterpret_cast<uint16_t*>(row),
140
0
                  reinterpret_cast<uint16_t*>(row) + plane_width,
141
0
                  static_cast<uint16_t>(yuva[c]));
142
0
      } else {
143
0
        std::fill(row, row + plane_width, static_cast<uint8_t>(yuva[c]));
144
0
      }
145
0
      row += row_bytes;
146
0
    }
147
0
  }
148
0
}
149
150
0
void FillImageGradient(avifImage* image, int offset) {
151
0
  for (avifChannelIndex c :
152
0
       {AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A}) {
153
0
    const uint32_t limitedRangeMin =
154
0
        c == AVIF_CHAN_Y ? 16 << (image->depth - 8) : 0;
155
0
    const uint32_t limitedRangeMax = (c == AVIF_CHAN_Y ? 219 : 224)
156
0
                                     << (image->depth - 8);
157
158
0
    const uint32_t plane_width = avifImagePlaneWidth(image, c);
159
    // 0 for A if no alpha and 0 for UV if 4:0:0.
160
0
    const uint32_t plane_height = avifImagePlaneHeight(image, c);
161
0
    uint8_t* row = avifImagePlane(image, c);
162
0
    const uint32_t row_bytes = avifImagePlaneRowBytes(image, c);
163
0
    const uint32_t max_xy_sum = plane_width + plane_height - 2;
164
0
    for (uint32_t y = 0; y < plane_height; ++y) {
165
0
      for (uint32_t x = 0; x < plane_width; ++x) {
166
0
        uint32_t value = (x + y + offset) % (max_xy_sum + 1);
167
0
        if (image->yuvRange == AVIF_RANGE_FULL || c == AVIF_CHAN_A) {
168
0
          value =
169
0
              value * ((1u << image->depth) - 1u) / std::max(1u, max_xy_sum);
170
0
        } else {
171
0
          value = limitedRangeMin + value *
172
0
                                        (limitedRangeMax - limitedRangeMin) /
173
0
                                        std::max(1u, max_xy_sum);
174
0
        }
175
0
        if (avifImageUsesU16(image)) {
176
0
          reinterpret_cast<uint16_t*>(row)[x] = static_cast<uint16_t>(value);
177
0
        } else {
178
0
          row[x] = static_cast<uint8_t>(value);
179
0
        }
180
0
      }
181
0
      row += row_bytes;
182
0
    }
183
0
  }
184
0
}
185
186
namespace {
187
template <typename PixelType>
188
void FillImageChannel(avifRGBImage* image, uint32_t channel_offset,
189
0
                      uint32_t value) {
190
0
  const uint32_t channel_count = avifRGBFormatChannelCount(image->format);
191
0
  assert(channel_offset < channel_count);
192
0
  for (uint32_t y = 0; y < image->height; ++y) {
193
0
    PixelType* pixel =
194
0
        reinterpret_cast<PixelType*>(image->pixels + image->rowBytes * y);
195
0
    for (uint32_t x = 0; x < image->width; ++x) {
196
0
      pixel[channel_offset] = static_cast<PixelType>(value);
197
0
      pixel += channel_count;
198
0
    }
199
0
  }
200
0
}
Unexecuted instantiation: aviftest_helpers.cc:void avif::testutil::(anonymous namespace)::FillImageChannel<unsigned char>(avifRGBImage*, unsigned int, unsigned int)
Unexecuted instantiation: aviftest_helpers.cc:void avif::testutil::(anonymous namespace)::FillImageChannel<unsigned short>(avifRGBImage*, unsigned int, unsigned int)
201
}  // namespace
202
203
void FillImageChannel(avifRGBImage* image, uint32_t channel_offset,
204
0
                      uint32_t value) {
205
0
  (image->depth <= 8)
206
0
      ? FillImageChannel<uint8_t>(image, channel_offset, value)
207
0
      : FillImageChannel<uint16_t>(image, channel_offset, value);
208
0
}
209
210
//------------------------------------------------------------------------------
211
212
bool AreByteSequencesEqual(const uint8_t data1[], size_t data1_length,
213
0
                           const uint8_t data2[], size_t data2_length) {
214
0
  if (data1_length != data2_length) return false;
215
0
  return data1_length == 0 || std::equal(data1, data1 + data1_length, data2);
216
0
}
217
218
0
bool AreByteSequencesEqual(const avifRWData& data1, const avifRWData& data2) {
219
0
  return AreByteSequencesEqual(data1.data, data1.size, data2.data, data2.size);
220
0
}
221
222
namespace {
223
// Returns true if all properties of image1 are present in image2 and equal.
224
bool MatchEachPropertyOfFirstImageInSecondImage(const avifImage& image1,
225
0
                                                const avifImage& image2) {
226
0
  for (size_t i = 0; i < image1.numProperties; ++i) {
227
0
    const avifImageItemProperty& property1 = image1.properties[i];
228
229
    // libavif may write a 'ccsp' box in Sample Entries.
230
    // libavif does not read and expose those except in avifImage::properties.
231
    // Ignore these boxes because it is an easy source of valid difference
232
    // between an original avifImage and a decoded avifImage.
233
0
    if (AreByteSequencesEqual(property1.boxtype, sizeof(property1.boxtype),
234
0
                              reinterpret_cast<const uint8_t*>("ccst"), 4)) {
235
0
      continue;
236
0
    }
237
238
0
    bool found = false;
239
0
    for (size_t j = 0; j < image2.numProperties; ++j) {
240
0
      const avifImageItemProperty& property2 = image2.properties[j];
241
0
      if (!AreByteSequencesEqual(property1.boxtype, sizeof(property1.boxtype),
242
0
                                 property2.boxtype,
243
0
                                 sizeof(property2.boxtype))) {
244
0
        continue;
245
0
      }
246
0
      if (AreByteSequencesEqual(property1.boxtype, sizeof(property1.boxtype),
247
0
                                reinterpret_cast<const uint8_t*>("uuid"), 4) &&
248
0
          !AreByteSequencesEqual(property1.usertype, sizeof(property1.usertype),
249
0
                                 property2.usertype,
250
0
                                 sizeof(property2.usertype))) {
251
0
        continue;
252
0
      }
253
0
      if (found) return false;  // Consider duplicates as invalid.
254
0
      found = true;
255
0
      if (!AreByteSequencesEqual(property1.boxPayload, property2.boxPayload)) {
256
0
        return false;
257
0
      }
258
0
    }
259
0
    if (!found) return false;
260
0
  }
261
0
  return true;
262
0
}
263
264
// Returns true if image1 and image2 are identical, pixel values excepted.
265
bool AreImageFeaturesEqual(const avifImage& image1, const avifImage& image2,
266
0
                           bool ignore_alpha) {
267
0
  if (image1.width != image2.width || image1.height != image2.height ||
268
0
      image1.depth != image2.depth || image1.yuvFormat != image2.yuvFormat ||
269
0
      image1.yuvRange != image2.yuvRange) {
270
0
    std::cerr
271
0
        << "Image features mismatch: dimensions, depth, yuvFormat or yuvRange"
272
0
        << std::endl;
273
0
    return false;
274
0
  }
275
0
  assert(image1.width * image1.height > 0);
276
277
0
  for (avifChannelIndex c :
278
0
       {AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A}) {
279
0
    if (ignore_alpha && c == AVIF_CHAN_A) continue;
280
0
    const uint8_t* row1 = avifImagePlane(&image1, c);
281
0
    const uint8_t* row2 = avifImagePlane(&image2, c);
282
0
    if (!row1 != !row2) {
283
      // Maybe one image contains an opaque alpha channel while the other has no
284
      // alpha channel, but the features should still be considered equal.
285
0
      if (c == AVIF_CHAN_A && avifImageIsOpaque(&image1) &&
286
0
          avifImageIsOpaque(&image2)) {
287
0
        continue;
288
0
      }
289
0
      std::cerr
290
0
          << "Image features mismatch: plane presence differs for channel " << c
291
0
          << std::endl;
292
0
      return false;
293
0
    }
294
0
    if (c == AVIF_CHAN_A && row1 != nullptr &&
295
0
        image1.alphaPremultiplied != image2.alphaPremultiplied &&
296
0
        !avifImageIsOpaque(&image1)) {
297
      // Alpha premultiplication is ignored if alpha is opaque.
298
0
      std::cerr << "Image features mismatch: alphaPremultiplied" << std::endl;
299
0
      return false;
300
0
    }
301
0
  }
302
303
0
  if (!AreByteSequencesEqual(image1.icc, image2.icc)) {
304
0
    std::cerr << "Image features mismatch: icc" << std::endl;
305
0
    return false;
306
0
  }
307
308
0
  if (image1.colorPrimaries != image2.colorPrimaries ||
309
0
      image1.transferCharacteristics != image2.transferCharacteristics ||
310
0
      image1.matrixCoefficients != image2.matrixCoefficients) {
311
0
    std::cerr << "Image features mismatch: CICP" << std::endl;
312
0
    return false;
313
0
  }
314
315
0
  if (image1.clli.maxCLL != image2.clli.maxCLL ||
316
0
      image1.clli.maxPALL != image2.clli.maxPALL) {
317
0
    std::cerr << "Image features mismatch: clli" << std::endl;
318
0
    return false;
319
0
  }
320
0
  if (image1.transformFlags != image2.transformFlags ||
321
0
      ((image1.transformFlags & AVIF_TRANSFORM_PASP) &&
322
0
       std::memcmp(&image1.pasp, &image2.pasp, sizeof(image1.pasp))) ||
323
0
      ((image1.transformFlags & AVIF_TRANSFORM_CLAP) &&
324
0
       std::memcmp(&image1.clap, &image2.clap, sizeof(image1.clap))) ||
325
0
      ((image1.transformFlags & AVIF_TRANSFORM_IROT) &&
326
0
       std::memcmp(&image1.irot, &image2.irot, sizeof(image1.irot))) ||
327
0
      ((image1.transformFlags & AVIF_TRANSFORM_IMIR) &&
328
0
       std::memcmp(&image1.imir, &image2.imir, sizeof(image1.imir)))) {
329
0
    std::cerr << "Image features mismatch: transformFlags" << std::endl;
330
0
    return false;
331
0
  }
332
333
0
  if (!AreByteSequencesEqual(image1.exif, image2.exif)) {
334
0
    std::cerr << "Image features mismatch: exif" << std::endl;
335
0
    return false;
336
0
  }
337
0
  if (!AreByteSequencesEqual(image1.xmp, image2.xmp)) {
338
0
    std::cerr << "Image features mismatch: xmp" << std::endl;
339
0
    return false;
340
0
  }
341
342
0
  if (!MatchEachPropertyOfFirstImageInSecondImage(image1, image2) ||
343
0
      !MatchEachPropertyOfFirstImageInSecondImage(image2, image1)) {
344
0
    std::cerr << "Image features mismatch: properties" << std::endl;
345
0
    return false;
346
0
  }
347
348
0
  if (!image1.gainMap != !image2.gainMap) {
349
0
    std::cerr << "Image features mismatch: gainMap presence" << std::endl;
350
0
    return false;
351
0
  }
352
0
  if (image1.gainMap != nullptr) {
353
0
    if (!avifSameGainMapMetadata(image1.gainMap, image2.gainMap) ||
354
0
        !avifSameGainMapAltMetadata(image1.gainMap, image2.gainMap)) {
355
0
      std::cerr << "Image features mismatch: gainMap metadata" << std::endl;
356
0
      return false;
357
0
    }
358
359
0
    if (!image1.gainMap->image != !image2.gainMap->image) {
360
0
      std::cerr << "Image features mismatch: gainMap image presence"
361
0
                << std::endl;
362
0
      return false;
363
0
    }
364
0
  }
365
0
  return true;
366
0
}
367
}  // namespace
368
369
// Returns true if image1 and image2 are identical.
370
bool AreImagesEqual(const avifImage& image1, const avifImage& image2,
371
0
                    bool ignore_alpha) {
372
0
  if (!AreImageFeaturesEqual(image1, image2, ignore_alpha)) {
373
0
    return false;
374
0
  }
375
376
0
  if ((image1.gainMap != nullptr) != (image2.gainMap != nullptr)) {
377
0
    std::cerr << "AreImagesEqual failed: gain map presence differs"
378
0
              << std::endl;
379
0
    return false;
380
0
  }
381
382
0
  for (avifChannelIndex c :
383
0
       {AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A}) {
384
0
    if (ignore_alpha && c == AVIF_CHAN_A) continue;
385
0
    const uint8_t* row1 = avifImagePlane(&image1, c);
386
0
    const uint8_t* row2 = avifImagePlane(&image2, c);
387
0
    if (row1 == nullptr || row2 == nullptr) {
388
0
      continue;  // Verified in AreImageFeaturesEqual().
389
0
    }
390
0
    const uint32_t row_bytes1 = avifImagePlaneRowBytes(&image1, c);
391
0
    const uint32_t row_bytes2 = avifImagePlaneRowBytes(&image2, c);
392
0
    const uint32_t plane_width = avifImagePlaneWidth(&image1, c);
393
    // 0 for A if no alpha and 0 for UV if 4:0:0.
394
0
    const uint32_t plane_height = avifImagePlaneHeight(&image1, c);
395
0
    for (uint32_t y = 0; y < plane_height; ++y) {
396
0
      if (avifImageUsesU16(&image1)) {
397
0
        if (!std::equal(reinterpret_cast<const uint16_t*>(row1),
398
0
                        reinterpret_cast<const uint16_t*>(row1) + plane_width,
399
0
                        reinterpret_cast<const uint16_t*>(row2))) {
400
0
          std::cerr << "AreImagesEqual failed: pixels differ in channel " << c
401
0
                    << " at row " << y << std::endl;
402
0
          return false;
403
0
        }
404
0
      } else {
405
0
        if (!std::equal(row1, row1 + plane_width, row2)) {
406
0
          std::cerr << "AreImagesEqual failed: pixels differ in channel " << c
407
0
                    << " at row " << y << std::endl;
408
0
          return false;
409
0
        }
410
0
      }
411
0
      row1 += row_bytes1;
412
0
      row2 += row_bytes2;
413
0
    }
414
0
  }
415
416
0
  if (image1.gainMap != nullptr && image1.gainMap->image != nullptr &&
417
0
      image2.gainMap != nullptr && image2.gainMap->image != nullptr &&
418
0
      !AreImagesEqual(*image1.gainMap->image, *image2.gainMap->image)) {
419
0
    std::cerr << "AreImagesEqual failed: gain map images differ" << std::endl;
420
0
    return false;
421
0
  }
422
0
  return true;
423
0
}
424
425
bool AreImagesSimilar(const avifImage& image1, const avifImage& image2,
426
0
                      double min_psnr, bool ignore_alpha) {
427
0
  if (!AreImageFeaturesEqual(image1, image2, ignore_alpha)) {
428
0
    return false;
429
0
  }
430
431
0
  if (GetPsnr(image1, image2, ignore_alpha) < min_psnr) {
432
0
    return true;
433
0
  }
434
435
0
  if (image1.gainMap != nullptr && image1.gainMap->image != nullptr &&
436
0
      image2.gainMap != nullptr && image2.gainMap->image != nullptr &&
437
0
      GetPsnr(*image1.gainMap->image, *image2.gainMap->image) < min_psnr) {
438
0
    return false;
439
0
  }
440
0
  return true;
441
0
}
442
443
namespace {
444
445
template <typename Sample>
446
uint64_t SquaredDiffSum(const Sample* samples1, const Sample* samples2,
447
0
                        uint32_t num_samples) {
448
0
  uint64_t sum = 0;
449
0
  for (uint32_t i = 0; i < num_samples; ++i) {
450
0
    const int32_t diff = static_cast<int32_t>(samples1[i]) - samples2[i];
451
0
    sum += diff * diff;
452
0
  }
453
0
  return sum;
454
0
}
Unexecuted instantiation: aviftest_helpers.cc:unsigned long avif::testutil::(anonymous namespace)::SquaredDiffSum<unsigned short>(unsigned short const*, unsigned short const*, unsigned int)
Unexecuted instantiation: aviftest_helpers.cc:unsigned long avif::testutil::(anonymous namespace)::SquaredDiffSum<unsigned char>(unsigned char const*, unsigned char const*, unsigned int)
455
456
}  // namespace
457
458
double GetPsnr(const avifImage& image1, const avifImage& image2,
459
0
               bool ignore_alpha) {
460
0
  if (image1.width != image2.width || image1.height != image2.height ||
461
0
      image1.depth != image2.depth || image1.yuvFormat != image2.yuvFormat ||
462
0
      image1.yuvRange != image2.yuvRange) {
463
0
    std::cerr << "Error: cannot compute PSNR because of dimensions or yuv "
464
0
                 "format mismatch ("
465
0
              << image1.width << "x" << image1.height << " format "
466
0
              << image1.yuvFormat << " vs " << image2.width << "x"
467
0
              << image2.height << " format " << image2.yuvFormat << ")"
468
0
              << std::endl;
469
0
    return -1;
470
0
  }
471
0
  assert(image1.width * image1.height > 0);
472
473
0
  if (image1.colorPrimaries != image2.colorPrimaries ||
474
0
      image1.transferCharacteristics != image2.transferCharacteristics ||
475
0
      image1.matrixCoefficients != image2.matrixCoefficients ||
476
0
      image1.yuvRange != image2.yuvRange) {
477
0
    fprintf(stderr,
478
0
            "WARNING: computing PSNR of images with different CICP: %d/%d/%d%s "
479
0
            "vs %d/%d/%d%s\n",
480
0
            image1.colorPrimaries, image1.transferCharacteristics,
481
0
            image1.matrixCoefficients,
482
0
            (image1.yuvRange == AVIF_RANGE_FULL) ? "f" : "l",
483
0
            image2.colorPrimaries, image2.transferCharacteristics,
484
0
            image2.matrixCoefficients,
485
0
            (image2.yuvRange == AVIF_RANGE_FULL) ? "f" : "l");
486
0
  }
487
488
0
  uint64_t squared_diff_sum = 0;
489
0
  uint32_t num_samples = 0;
490
0
  const uint32_t max_sample_value = (1 << image1.depth) - 1;
491
0
  for (avifChannelIndex c :
492
0
       {AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A}) {
493
0
    if (ignore_alpha && c == AVIF_CHAN_A) continue;
494
495
0
    const uint32_t plane_width = std::max(avifImagePlaneWidth(&image1, c),
496
0
                                          avifImagePlaneWidth(&image2, c));
497
0
    const uint32_t plane_height = std::max(avifImagePlaneHeight(&image1, c),
498
0
                                           avifImagePlaneHeight(&image2, c));
499
0
    if (plane_width == 0 || plane_height == 0) continue;
500
501
0
    const uint8_t* row1 = avifImagePlane(&image1, c);
502
0
    const uint8_t* row2 = avifImagePlane(&image2, c);
503
0
    if (!row1 != !row2 && c != AVIF_CHAN_A) {
504
0
      return -1;
505
0
    }
506
0
    uint32_t row_bytes1 = avifImagePlaneRowBytes(&image1, c);
507
0
    uint32_t row_bytes2 = avifImagePlaneRowBytes(&image2, c);
508
509
    // Consider missing alpha planes as samples set to the maximum value.
510
0
    std::vector<uint8_t> opaque_alpha_samples;
511
0
    if (!row1 != !row2) {
512
0
      opaque_alpha_samples.resize(std::max(row_bytes1, row_bytes2));
513
0
      if (avifImageUsesU16(&image1)) {
514
0
        uint16_t* opaque_alpha_samples_16b =
515
0
            reinterpret_cast<uint16_t*>(opaque_alpha_samples.data());
516
0
        std::fill(opaque_alpha_samples_16b,
517
0
                  opaque_alpha_samples_16b + plane_width,
518
0
                  static_cast<int16_t>(max_sample_value));
519
0
      } else {
520
0
        std::fill(opaque_alpha_samples.begin(), opaque_alpha_samples.end(),
521
0
                  uint8_t{255});
522
0
      }
523
0
      if (!row1) {
524
0
        row1 = opaque_alpha_samples.data();
525
0
        row_bytes1 = 0;
526
0
      } else {
527
0
        row2 = opaque_alpha_samples.data();
528
0
        row_bytes2 = 0;
529
0
      }
530
0
    }
531
532
0
    for (uint32_t y = 0; y < plane_height; ++y) {
533
0
      if (avifImageUsesU16(&image1)) {
534
0
        squared_diff_sum += SquaredDiffSum(
535
0
            reinterpret_cast<const uint16_t*>(row1),
536
0
            reinterpret_cast<const uint16_t*>(row2), plane_width);
537
0
      } else {
538
0
        squared_diff_sum += SquaredDiffSum(row1, row2, plane_width);
539
0
      }
540
0
      row1 += row_bytes1;
541
0
      row2 += row_bytes2;
542
0
      num_samples += plane_width;
543
0
    }
544
0
  }
545
546
0
  if (squared_diff_sum == 0) {
547
0
    return 99.0;
548
0
  }
549
0
  const double normalized_error =
550
0
      squared_diff_sum /
551
0
      (static_cast<double>(num_samples) * max_sample_value * max_sample_value);
552
0
  if (normalized_error <= std::numeric_limits<double>::epsilon()) {
553
0
    return 98.99;  // Very small distortion but not lossless.
554
0
  }
555
0
  return std::min(-10 * std::log10(normalized_error), 98.99);
556
0
}
557
558
0
bool AreImagesEqual(const avifRGBImage& image1, const avifRGBImage& image2) {
559
0
  if (image1.width != image2.width || image1.height != image2.height ||
560
0
      image1.depth != image2.depth || image1.format != image2.format ||
561
0
      image1.alphaPremultiplied != image2.alphaPremultiplied ||
562
0
      image1.isFloat != image2.isFloat) {
563
0
    std::cerr << "AreImagesEqual(avifRGBImage) failed: features do not match"
564
0
              << std::endl;
565
0
    return false;
566
0
  }
567
0
  const uint8_t* row1 = image1.pixels;
568
0
  const uint8_t* row2 = image2.pixels;
569
0
  const unsigned int row_width = image1.width * avifRGBImagePixelSize(&image1);
570
0
  for (unsigned int y = 0; y < image1.height; ++y) {
571
0
    if (!std::equal(row1, row1 + row_width, row2)) {
572
0
      std::cerr << "AreImagesEqual(avifRGBImage) failed: pixels differ at row "
573
0
                << y << std::endl;
574
0
      return false;
575
0
    }
576
0
    row1 += image1.rowBytes;
577
0
    row2 += image2.rowBytes;
578
0
  }
579
0
  return true;
580
0
}
581
582
avifResult MergeGrid(int grid_cols, int grid_rows,
583
0
                     const std::vector<ImagePtr>& cells, avifImage* merged) {
584
0
  std::vector<const avifImage*> ptrs(cells.size());
585
0
  for (size_t i = 0; i < cells.size(); ++i) {
586
0
    ptrs[i] = cells[i].get();
587
0
  }
588
0
  return MergeGrid(grid_cols, grid_rows, ptrs, merged);
589
0
}
590
591
avifResult MergeGrid(int grid_cols, int grid_rows,
592
                     const std::vector<const avifImage*>& cells,
593
0
                     avifImage* merged) {
594
0
  const uint32_t tile_width = cells[0]->width;
595
0
  const uint32_t tile_height = cells[0]->height;
596
0
  const uint32_t grid_width =
597
0
      (grid_cols - 1) * tile_width + cells.back()->width;
598
0
  const uint32_t grid_height =
599
0
      (grid_rows - 1) * tile_height + cells.back()->height;
600
601
0
  ImagePtr view(avifImageCreateEmpty());
602
0
  AVIF_CHECKERR(view, AVIF_RESULT_OUT_OF_MEMORY);
603
604
0
  avifCropRect rect = {};
605
0
  for (int j = 0; j < grid_rows; ++j) {
606
0
    rect.x = 0;
607
0
    for (int i = 0; i < grid_cols; ++i) {
608
0
      const avifImage* image = cells[j * grid_cols + i];
609
0
      rect.width = image->width;
610
0
      rect.height = image->height;
611
0
      AVIF_CHECKRES(avifImageSetViewRect(view.get(), merged, &rect));
612
0
      CopyImageSamples(/*dstImage=*/view.get(), image, AVIF_PLANES_ALL);
613
0
      assert(!view->imageOwnsYUVPlanes);
614
0
      rect.x += rect.width;
615
0
    }
616
0
    rect.y += rect.height;
617
0
  }
618
619
0
  if ((rect.x != grid_width) || (rect.y != grid_height)) {
620
0
    return AVIF_RESULT_UNKNOWN_ERROR;
621
0
  }
622
623
0
  return AVIF_RESULT_OK;
624
0
}
625
626
//------------------------------------------------------------------------------
627
628
0
testutil::AvifRwData ReadFile(const std::string& file_path) {
629
0
  std::ifstream file(file_path, std::ios::binary | std::ios::ate);
630
0
  testutil::AvifRwData bytes;
631
0
  if (avifRWDataRealloc(&bytes, file.good() ? static_cast<size_t>(file.tellg())
632
0
                                            : 0) != AVIF_RESULT_OK) {
633
0
    return {};
634
0
  }
635
0
  file.seekg(0, std::ios::beg);
636
0
  file.read(reinterpret_cast<char*>(bytes.data),
637
0
            static_cast<std::streamsize>(bytes.size));
638
0
  return bytes;
639
0
}
640
641
//------------------------------------------------------------------------------
642
643
ImagePtr ReadImage(const char* folder_path, const char* file_name,
644
                   avifPixelFormat requested_format, int requested_depth,
645
                   avifChromaDownsampling chroma_downsampling,
646
                   avifBool ignore_icc, avifBool ignore_exif,
647
0
                   avifBool ignore_xmp, avifBool ignore_gain_map) {
648
0
  ImagePtr image(avifImageCreateEmpty());
649
0
  if (!image ||
650
0
      avifReadImage((std::string(folder_path) + file_name).c_str(),
651
0
                    AVIF_APP_FILE_FORMAT_UNKNOWN /* guess format */,
652
0
                    requested_format, requested_depth, chroma_downsampling,
653
0
                    ignore_icc, ignore_exif, ignore_xmp, /*ignoreAlpha=*/false,
654
0
                    ignore_gain_map, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(),
655
0
                    /*outDepth=*/nullptr, /*sourceTiming=*/nullptr,
656
0
                    /*frameIter=*/nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) {
657
0
    return nullptr;
658
0
  }
659
0
  return image;
660
0
}
661
662
0
bool WriteImage(const avifImage* image, const char* file_path) {
663
0
  if (!image || !file_path) return false;
664
0
  const size_t str_len = std::strlen(file_path);
665
0
  if (str_len >= 4 && !std::strncmp(file_path + str_len - 4, ".png", 4)) {
666
0
    return avifPNGWrite(file_path, image, /*requestedDepth=*/0,
667
0
                        AVIF_CHROMA_UPSAMPLING_BEST_QUALITY,
668
0
                        /*compressionLevel=*/0);
669
0
  }
670
  // Other formats are not supported.
671
0
  return false;
672
0
}
673
674
0
AvifRwData Encode(const avifImage* image, int speed, int quality) {
675
0
  EncoderPtr encoder(avifEncoderCreate());
676
0
  if (!encoder) return {};
677
0
  encoder->speed = speed;
678
0
  encoder->quality = quality;
679
0
  encoder->qualityAlpha = quality;
680
0
  encoder->qualityGainMap = quality;
681
0
  testutil::AvifRwData bytes;
682
0
  if (avifEncoderWrite(encoder.get(), image, &bytes) != AVIF_RESULT_OK) {
683
0
    return {};
684
0
  }
685
0
  return bytes;
686
0
}
687
688
0
ImagePtr Decode(const uint8_t* bytes, size_t num_bytes) {
689
0
  ImagePtr decoded(avifImageCreateEmpty());
690
0
  DecoderPtr decoder(avifDecoderCreate());
691
0
  if (!decoded || !decoder ||
692
0
      (avifDecoderReadMemory(decoder.get(), decoded.get(), bytes, num_bytes) !=
693
0
       AVIF_RESULT_OK)) {
694
0
    return nullptr;
695
0
  }
696
0
  return decoded;
697
0
}
698
699
0
ImagePtr DecodeFile(const std::string& path) {
700
0
  ImagePtr decoded(avifImageCreateEmpty());
701
0
  DecoderPtr decoder(avifDecoderCreate());
702
0
  if (!decoded || !decoder ||
703
0
      (avifDecoderReadFile(decoder.get(), decoded.get(), path.c_str()) !=
704
0
       AVIF_RESULT_OK)) {
705
0
    return nullptr;
706
0
  }
707
0
  return decoded;
708
0
}
709
710
0
bool Av1EncoderAvailable() {
711
0
  const char* encoding_codec =
712
0
      avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE);
713
0
  return encoding_codec != nullptr && std::string(encoding_codec) != "avm";
714
0
}
715
716
0
bool Av1DecoderAvailable() {
717
0
  const char* decoding_codec =
718
0
      avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_DECODE);
719
0
  return decoding_codec != nullptr && std::string(decoding_codec) != "avm";
720
0
}
721
722
//------------------------------------------------------------------------------
723
724
static avifResult avifIOLimitedReaderRead(avifIO* io, uint32_t readFlags,
725
                                          uint64_t offset, size_t size,
726
0
                                          avifROData* out) {
727
0
  auto reader = reinterpret_cast<AvifIOLimitedReader*>(io);
728
729
0
  if (offset > UINT64_MAX - size) {
730
0
    return AVIF_RESULT_IO_ERROR;
731
0
  }
732
0
  if (offset + size > reader->clamp) {
733
0
    return AVIF_RESULT_WAITING_ON_IO;
734
0
  }
735
736
0
  return reader->underlyingIO->read(reader->underlyingIO, readFlags, offset,
737
0
                                    size, out);
738
0
}
739
740
0
static void avifIOLimitedReaderDestroy(avifIO* io) {
741
0
  auto reader = reinterpret_cast<AvifIOLimitedReader*>(io);
742
0
  reader->underlyingIO->destroy(reader->underlyingIO);
743
0
  delete reader;
744
0
}
745
746
0
avifIO* AvifIOCreateLimitedReader(avifIO* underlyingIO, uint64_t clamp) {
747
0
  return reinterpret_cast<avifIO*>(
748
0
      new AvifIOLimitedReader{{
749
0
                                  avifIOLimitedReaderDestroy,
750
0
                                  avifIOLimitedReaderRead,
751
0
                                  nullptr,
752
0
                                  underlyingIO->sizeHint,
753
0
                                  underlyingIO->persistent,
754
0
                                  nullptr,
755
0
                              },
756
0
                              underlyingIO,
757
0
                              clamp});
758
0
}
759
760
//------------------------------------------------------------------------------
761
762
std::vector<ImagePtr> ImageToGrid(const avifImage* image, uint32_t grid_cols,
763
11.5k
                                  uint32_t grid_rows) {
764
11.5k
  if (image->width < grid_cols || image->height < grid_rows) return {};
765
766
  // Round up, to make sure all samples are used by exactly one cell.
767
11.4k
  uint32_t cell_width = (image->width + grid_cols - 1) / grid_cols;
768
11.4k
  uint32_t cell_height = (image->height + grid_rows - 1) / grid_rows;
769
770
11.4k
  if ((grid_cols - 1) * cell_width >= image->width) {
771
    // Some cells are completely outside the image. Fallback to a grid entirely
772
    // contained within the image boundaries. Some samples will be discarded but
773
    // at least the test can go on.
774
31
    cell_width = image->width / grid_cols;
775
31
  }
776
11.4k
  if ((grid_rows - 1) * cell_height >= image->height) {
777
34
    cell_height = image->height / grid_rows;
778
34
  }
779
780
11.4k
  std::vector<ImagePtr> cells;
781
26.5k
  for (uint32_t row = 0; row < grid_rows; ++row) {
782
46.1k
    for (uint32_t col = 0; col < grid_cols; ++col) {
783
31.0k
      avifCropRect rect{col * cell_width, row * cell_height, cell_width,
784
31.0k
                        cell_height};
785
31.0k
      assert(rect.x < image->width);
786
31.0k
      assert(rect.y < image->height);
787
      // The right-most and bottom-most cells may be smaller than others.
788
      // The encoder will pad them.
789
31.0k
      if (rect.x + rect.width > image->width) {
790
1.97k
        rect.width = image->width - rect.x;
791
1.97k
      }
792
31.0k
      if (rect.y + rect.height > image->height) {
793
2.05k
        rect.height = image->height - rect.y;
794
2.05k
      }
795
31.0k
      cells.emplace_back(avifImageCreateEmpty());
796
31.0k
      if (avifImageSetViewRect(cells.back().get(), image, &rect) !=
797
31.0k
          AVIF_RESULT_OK) {
798
5
        return {};
799
5
      }
800
31.0k
    }
801
15.1k
  }
802
11.4k
  return cells;
803
11.4k
}
804
805
std::vector<const avifImage*> UniquePtrToRawPtr(
806
10.3k
    const std::vector<ImagePtr>& unique_ptrs) {
807
10.3k
  std::vector<const avifImage*> rawPtrs;
808
10.3k
  rawPtrs.reserve(unique_ptrs.size());
809
28.3k
  for (const ImagePtr& unique_ptr : unique_ptrs) {
810
28.3k
    rawPtrs.emplace_back(unique_ptr.get());
811
28.3k
  }
812
10.3k
  return rawPtrs;
813
10.3k
}
814
815
//------------------------------------------------------------------------------
816
817
}  // namespace testutil
818
}  // namespace avif