Coverage Report

Created: 2024-05-21 06:41

/src/libjxl/lib/jxl/test_utils.cc
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2
//
3
// Use of this source code is governed by a BSD-style
4
// license that can be found in the LICENSE file.
5
6
#include "lib/jxl/test_utils.h"
7
8
#include <jxl/cms.h>
9
#include <jxl/cms_interface.h>
10
#include <jxl/memory_manager.h>
11
#include <jxl/types.h>
12
13
#include <cstddef>
14
#include <fstream>
15
#include <memory>
16
#include <string>
17
#include <utility>
18
#include <vector>
19
20
#include "lib/extras/metrics.h"
21
#include "lib/extras/packed_image_convert.h"
22
#include "lib/jxl/base/compiler_specific.h"
23
#include "lib/jxl/base/data_parallel.h"
24
#include "lib/jxl/base/float.h"
25
#include "lib/jxl/base/printf_macros.h"
26
#include "lib/jxl/base/status.h"
27
#include "lib/jxl/codec_in_out.h"
28
#include "lib/jxl/enc_aux_out.h"
29
#include "lib/jxl/enc_bit_writer.h"
30
#include "lib/jxl/enc_butteraugli_comparator.h"
31
#include "lib/jxl/enc_cache.h"
32
#include "lib/jxl/enc_external_image.h"
33
#include "lib/jxl/enc_fields.h"
34
#include "lib/jxl/enc_frame.h"
35
#include "lib/jxl/enc_icc_codec.h"
36
#include "lib/jxl/enc_params.h"
37
#include "lib/jxl/frame_header.h"
38
#include "lib/jxl/icc_codec.h"
39
#include "lib/jxl/image.h"
40
#include "lib/jxl/image_bundle.h"
41
#include "lib/jxl/padded_bytes.h"
42
43
#if !defined(TEST_DATA_PATH)
44
#include "tools/cpp/runfiles/runfiles.h"
45
#endif
46
47
namespace jxl {
48
namespace test {
49
50
#if defined(TEST_DATA_PATH)
51
0
std::string GetTestDataPath(const std::string& filename) {
52
0
  return std::string(TEST_DATA_PATH "/") + filename;
53
0
}
54
#else
55
using bazel::tools::cpp::runfiles::Runfiles;
56
const std::unique_ptr<Runfiles> kRunfiles(Runfiles::Create(""));
57
std::string GetTestDataPath(const std::string& filename) {
58
  std::string root(JPEGXL_ROOT_PACKAGE "/testdata/");
59
  return kRunfiles->Rlocation(root + filename);
60
}
61
#endif
62
63
0
std::vector<uint8_t> ReadTestData(const std::string& filename) {
64
0
  std::string full_path = GetTestDataPath(filename);
65
0
  fprintf(stderr, "ReadTestData %s\n", full_path.c_str());
66
0
  std::ifstream file(full_path, std::ios::binary);
67
0
  std::vector<char> str((std::istreambuf_iterator<char>(file)),
68
0
                        std::istreambuf_iterator<char>());
69
0
  JXL_CHECK(file.good());
70
0
  const uint8_t* raw = reinterpret_cast<const uint8_t*>(str.data());
71
0
  std::vector<uint8_t> data(raw, raw + str.size());
72
0
  printf("Test data %s is %d bytes long.\n", filename.c_str(),
73
0
         static_cast<int>(data.size()));
74
0
  return data;
75
0
}
76
77
0
void DefaultAcceptedFormats(extras::JXLDecompressParams& dparams) {
78
0
  if (dparams.accepted_formats.empty()) {
79
0
    for (const uint32_t num_channels : {1, 2, 3, 4}) {
80
0
      dparams.accepted_formats.push_back(
81
0
          {num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0});
82
0
    }
83
0
  }
84
0
}
85
86
Status DecodeFile(extras::JXLDecompressParams dparams,
87
                  const Span<const uint8_t> file, CodecInOut* JXL_RESTRICT io,
88
0
                  ThreadPool* pool) {
89
0
  DefaultAcceptedFormats(dparams);
90
0
  SetThreadParallelRunner(dparams, pool);
91
0
  extras::PackedPixelFile ppf;
92
0
  JXL_RETURN_IF_ERROR(DecodeImageJXL(file.data(), file.size(), dparams,
93
0
                                     /*decoded_bytes=*/nullptr, &ppf));
94
0
  JXL_RETURN_IF_ERROR(ConvertPackedPixelFileToCodecInOut(ppf, pool, io));
95
0
  return true;
96
0
}
97
98
void JxlBasicInfoSetFromPixelFormat(JxlBasicInfo* basic_info,
99
0
                                    const JxlPixelFormat* pixel_format) {
100
0
  JxlEncoderInitBasicInfo(basic_info);
101
0
  switch (pixel_format->data_type) {
102
0
    case JXL_TYPE_FLOAT:
103
0
      basic_info->bits_per_sample = 32;
104
0
      basic_info->exponent_bits_per_sample = 8;
105
0
      break;
106
0
    case JXL_TYPE_FLOAT16:
107
0
      basic_info->bits_per_sample = 16;
108
0
      basic_info->exponent_bits_per_sample = 5;
109
0
      break;
110
0
    case JXL_TYPE_UINT8:
111
0
      basic_info->bits_per_sample = 8;
112
0
      basic_info->exponent_bits_per_sample = 0;
113
0
      break;
114
0
    case JXL_TYPE_UINT16:
115
0
      basic_info->bits_per_sample = 16;
116
0
      basic_info->exponent_bits_per_sample = 0;
117
0
      break;
118
0
    default:
119
0
      JXL_ABORT("Unhandled JxlDataType");
120
0
  }
121
0
  if (pixel_format->num_channels < 3) {
122
0
    basic_info->num_color_channels = 1;
123
0
  } else {
124
0
    basic_info->num_color_channels = 3;
125
0
  }
126
0
  if (pixel_format->num_channels == 2 || pixel_format->num_channels == 4) {
127
0
    basic_info->alpha_exponent_bits = basic_info->exponent_bits_per_sample;
128
0
    basic_info->alpha_bits = basic_info->bits_per_sample;
129
0
    basic_info->num_extra_channels = 1;
130
0
  } else {
131
0
    basic_info->alpha_exponent_bits = 0;
132
0
    basic_info->alpha_bits = 0;
133
0
  }
134
0
}
135
136
0
ColorEncoding ColorEncodingFromDescriptor(const ColorEncodingDescriptor& desc) {
137
0
  ColorEncoding c;
138
0
  c.SetColorSpace(desc.color_space);
139
0
  if (desc.color_space != ColorSpace::kXYB) {
140
0
    JXL_CHECK(c.SetWhitePointType(desc.white_point));
141
0
    if (desc.color_space != ColorSpace::kGray) {
142
0
      JXL_CHECK(c.SetPrimariesType(desc.primaries));
143
0
    }
144
0
    c.Tf().SetTransferFunction(desc.tf);
145
0
  }
146
0
  c.SetRenderingIntent(desc.rendering_intent);
147
0
  JXL_CHECK(c.CreateICC());
148
0
  return c;
149
0
}
150
151
namespace {
152
void CheckSameEncodings(const std::vector<ColorEncoding>& a,
153
                        const std::vector<ColorEncoding>& b,
154
                        const std::string& check_name,
155
0
                        std::stringstream& failures) {
156
0
  JXL_CHECK(a.size() == b.size());
157
0
  for (size_t i = 0; i < a.size(); ++i) {
158
0
    if ((a[i].ICC() == b[i].ICC()) ||
159
0
        ((a[i].GetPrimariesType() == b[i].GetPrimariesType()) &&
160
0
         a[i].Tf().IsSame(b[i].Tf()))) {
161
0
      continue;
162
0
    }
163
0
    failures << "CheckSameEncodings " << check_name << ": " << i
164
0
             << "-th encoding mismatch\n";
165
0
  }
166
0
}
167
}  // namespace
168
169
bool Roundtrip(const CodecInOut* io, const CompressParams& cparams,
170
               extras::JXLDecompressParams dparams,
171
               CodecInOut* JXL_RESTRICT io2, std::stringstream& failures,
172
0
               size_t* compressed_size, ThreadPool* pool) {
173
0
  DefaultAcceptedFormats(dparams);
174
0
  if (compressed_size) {
175
0
    *compressed_size = static_cast<size_t>(-1);
176
0
  }
177
0
  std::vector<uint8_t> compressed;
178
179
0
  std::vector<ColorEncoding> original_metadata_encodings;
180
0
  std::vector<ColorEncoding> original_current_encodings;
181
0
  std::vector<ColorEncoding> metadata_encodings_1;
182
0
  std::vector<ColorEncoding> metadata_encodings_2;
183
0
  std::vector<ColorEncoding> current_encodings_2;
184
0
  original_metadata_encodings.reserve(io->frames.size());
185
0
  original_current_encodings.reserve(io->frames.size());
186
0
  metadata_encodings_1.reserve(io->frames.size());
187
0
  metadata_encodings_2.reserve(io->frames.size());
188
0
  current_encodings_2.reserve(io->frames.size());
189
190
0
  for (const ImageBundle& ib : io->frames) {
191
    // Remember original encoding, will be returned by decoder.
192
0
    original_metadata_encodings.push_back(ib.metadata()->color_encoding);
193
    // c_current should not change during encoding.
194
0
    original_current_encodings.push_back(ib.c_current());
195
0
  }
196
197
0
  JXL_CHECK(test::EncodeFile(cparams, io, &compressed, pool));
198
199
0
  for (const ImageBundle& ib1 : io->frames) {
200
0
    metadata_encodings_1.push_back(ib1.metadata()->color_encoding);
201
0
  }
202
203
  // Should still be in the same color space after encoding.
204
0
  CheckSameEncodings(metadata_encodings_1, original_metadata_encodings,
205
0
                     "original vs after encoding", failures);
206
207
0
  JXL_CHECK(DecodeFile(dparams, Bytes(compressed), io2, pool));
208
0
  JXL_CHECK(io2->frames.size() == io->frames.size());
209
210
0
  for (const ImageBundle& ib2 : io2->frames) {
211
0
    metadata_encodings_2.push_back(ib2.metadata()->color_encoding);
212
0
    current_encodings_2.push_back(ib2.c_current());
213
0
  }
214
215
  // We always produce the original color encoding if a color transform hook is
216
  // set.
217
0
  CheckSameEncodings(current_encodings_2, original_current_encodings,
218
0
                     "current: original vs decoded", failures);
219
220
  // Decoder returns the originals passed to the encoder.
221
0
  CheckSameEncodings(metadata_encodings_2, original_metadata_encodings,
222
0
                     "metadata: original vs decoded", failures);
223
224
0
  if (compressed_size) {
225
0
    *compressed_size = compressed.size();
226
0
  }
227
228
0
  return failures.str().empty();
229
0
}
230
231
size_t Roundtrip(const extras::PackedPixelFile& ppf_in,
232
                 const extras::JXLCompressParams& cparams,
233
                 extras::JXLDecompressParams dparams, ThreadPool* pool,
234
0
                 extras::PackedPixelFile* ppf_out) {
235
0
  DefaultAcceptedFormats(dparams);
236
0
  SetThreadParallelRunner(cparams, pool);
237
0
  SetThreadParallelRunner(dparams, pool);
238
0
  std::vector<uint8_t> compressed;
239
0
  JXL_CHECK(extras::EncodeImageJXL(cparams, ppf_in, /*jpeg_bytes=*/nullptr,
240
0
                                   &compressed));
241
0
  size_t decoded_bytes = 0;
242
0
  JXL_CHECK(extras::DecodeImageJXL(compressed.data(), compressed.size(),
243
0
                                   dparams, &decoded_bytes, ppf_out));
244
0
  JXL_CHECK(decoded_bytes == compressed.size());
245
0
  return compressed.size();
246
0
}
247
248
0
std::vector<ColorEncodingDescriptor> AllEncodings() {
249
0
  std::vector<ColorEncodingDescriptor> all_encodings;
250
0
  all_encodings.reserve(300);
251
252
0
  for (ColorSpace cs : Values<ColorSpace>()) {
253
0
    if (cs == ColorSpace::kUnknown || cs == ColorSpace::kXYB ||
254
0
        cs == ColorSpace::kGray) {
255
0
      continue;
256
0
    }
257
258
0
    for (WhitePoint wp : Values<WhitePoint>()) {
259
0
      if (wp == WhitePoint::kCustom) continue;
260
0
      for (Primaries primaries : Values<Primaries>()) {
261
0
        if (primaries == Primaries::kCustom) continue;
262
0
        for (TransferFunction tf : Values<TransferFunction>()) {
263
0
          if (tf == TransferFunction::kUnknown) continue;
264
0
          for (RenderingIntent ri : Values<RenderingIntent>()) {
265
0
            ColorEncodingDescriptor cdesc;
266
0
            cdesc.color_space = cs;
267
0
            cdesc.white_point = wp;
268
0
            cdesc.primaries = primaries;
269
0
            cdesc.tf = tf;
270
0
            cdesc.rendering_intent = ri;
271
0
            all_encodings.push_back(cdesc);
272
0
          }
273
0
        }
274
0
      }
275
0
    }
276
0
  }
277
278
0
  return all_encodings;
279
0
}
280
281
jxl::CodecInOut SomeTestImageToCodecInOut(const std::vector<uint8_t>& buf,
282
                                          size_t num_channels, size_t xsize,
283
0
                                          size_t ysize) {
284
0
  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
285
0
  jxl::CodecInOut io{memory_manager};
286
0
  io.SetSize(xsize, ysize);
287
0
  io.metadata.m.SetAlphaBits(16);
288
0
  io.metadata.m.color_encoding = jxl::ColorEncoding::SRGB(
289
0
      /*is_gray=*/num_channels == 1 || num_channels == 2);
290
0
  JxlPixelFormat format = {static_cast<uint32_t>(num_channels), JXL_TYPE_UINT16,
291
0
                           JXL_BIG_ENDIAN, 0};
292
0
  JXL_CHECK(ConvertFromExternal(
293
0
      jxl::Bytes(buf.data(), buf.size()), xsize, ysize,
294
0
      jxl::ColorEncoding::SRGB(/*is_gray=*/num_channels < 3),
295
0
      /*bits_per_sample=*/16, format,
296
0
      /*pool=*/nullptr,
297
0
      /*ib=*/&io.Main()));
298
0
  return io;
299
0
}
300
301
0
bool Near(double expected, double value, double max_dist) {
302
0
  double dist = expected > value ? expected - value : value - expected;
303
0
  return dist <= max_dist;
304
0
}
305
306
0
float LoadLEFloat16(const uint8_t* p) {
307
0
  uint16_t bits16 = LoadLE16(p);
308
0
  return detail::LoadFloat16(bits16);
309
0
}
310
311
0
float LoadBEFloat16(const uint8_t* p) {
312
0
  uint16_t bits16 = LoadBE16(p);
313
0
  return detail::LoadFloat16(bits16);
314
0
}
315
316
0
size_t GetPrecision(JxlDataType data_type) {
317
0
  switch (data_type) {
318
0
    case JXL_TYPE_UINT8:
319
0
      return 8;
320
0
    case JXL_TYPE_UINT16:
321
0
      return 16;
322
0
    case JXL_TYPE_FLOAT:
323
      // Floating point mantissa precision
324
0
      return 24;
325
0
    case JXL_TYPE_FLOAT16:
326
0
      return 11;
327
0
    default:
328
0
      JXL_ABORT("Unhandled JxlDataType");
329
0
  }
330
0
}
331
332
0
size_t GetDataBits(JxlDataType data_type) {
333
0
  switch (data_type) {
334
0
    case JXL_TYPE_UINT8:
335
0
      return 8;
336
0
    case JXL_TYPE_UINT16:
337
0
      return 16;
338
0
    case JXL_TYPE_FLOAT:
339
0
      return 32;
340
0
    case JXL_TYPE_FLOAT16:
341
0
      return 16;
342
0
    default:
343
0
      JXL_ABORT("Unhandled JxlDataType");
344
0
  }
345
0
}
346
347
std::vector<double> ConvertToRGBA32(const uint8_t* pixels, size_t xsize,
348
                                    size_t ysize, const JxlPixelFormat& format,
349
0
                                    double factor) {
350
0
  std::vector<double> result(xsize * ysize * 4);
351
0
  size_t num_channels = format.num_channels;
352
0
  bool gray = num_channels == 1 || num_channels == 2;
353
0
  bool alpha = num_channels == 2 || num_channels == 4;
354
0
  JxlEndianness endianness = format.endianness;
355
  // Compute actual type:
356
0
  if (endianness == JXL_NATIVE_ENDIAN) {
357
0
    endianness = IsLittleEndian() ? JXL_LITTLE_ENDIAN : JXL_BIG_ENDIAN;
358
0
  }
359
360
0
  size_t stride =
361
0
      xsize * jxl::DivCeil(GetDataBits(format.data_type) * num_channels,
362
0
                           jxl::kBitsPerByte);
363
0
  if (format.align > 1) stride = jxl::RoundUpTo(stride, format.align);
364
365
0
  if (format.data_type == JXL_TYPE_UINT8) {
366
    // Multiplier to bring to 0-1.0 range
367
0
    double mul = factor > 0.0 ? factor : 1.0 / 255.0;
368
0
    for (size_t y = 0; y < ysize; ++y) {
369
0
      for (size_t x = 0; x < xsize; ++x) {
370
0
        size_t j = (y * xsize + x) * 4;
371
0
        size_t i = y * stride + x * num_channels;
372
0
        double r = pixels[i];
373
0
        double g = gray ? r : pixels[i + 1];
374
0
        double b = gray ? r : pixels[i + 2];
375
0
        double a = alpha ? pixels[i + num_channels - 1] : 255;
376
0
        result[j + 0] = r * mul;
377
0
        result[j + 1] = g * mul;
378
0
        result[j + 2] = b * mul;
379
0
        result[j + 3] = a * mul;
380
0
      }
381
0
    }
382
0
  } else if (format.data_type == JXL_TYPE_UINT16) {
383
0
    JXL_ASSERT(endianness != JXL_NATIVE_ENDIAN);
384
    // Multiplier to bring to 0-1.0 range
385
0
    double mul = factor > 0.0 ? factor : 1.0 / 65535.0;
386
0
    for (size_t y = 0; y < ysize; ++y) {
387
0
      for (size_t x = 0; x < xsize; ++x) {
388
0
        size_t j = (y * xsize + x) * 4;
389
0
        size_t i = y * stride + x * num_channels * 2;
390
0
        double r;
391
0
        double g;
392
0
        double b;
393
0
        double a;
394
0
        if (endianness == JXL_BIG_ENDIAN) {
395
0
          r = (pixels[i + 0] << 8) + pixels[i + 1];
396
0
          g = gray ? r : (pixels[i + 2] << 8) + pixels[i + 3];
397
0
          b = gray ? r : (pixels[i + 4] << 8) + pixels[i + 5];
398
0
          a = alpha ? (pixels[i + num_channels * 2 - 2] << 8) +
399
0
                          pixels[i + num_channels * 2 - 1]
400
0
                    : 65535;
401
0
        } else {
402
0
          r = (pixels[i + 1] << 8) + pixels[i + 0];
403
0
          g = gray ? r : (pixels[i + 3] << 8) + pixels[i + 2];
404
0
          b = gray ? r : (pixels[i + 5] << 8) + pixels[i + 4];
405
0
          a = alpha ? (pixels[i + num_channels * 2 - 1] << 8) +
406
0
                          pixels[i + num_channels * 2 - 2]
407
0
                    : 65535;
408
0
        }
409
0
        result[j + 0] = r * mul;
410
0
        result[j + 1] = g * mul;
411
0
        result[j + 2] = b * mul;
412
0
        result[j + 3] = a * mul;
413
0
      }
414
0
    }
415
0
  } else if (format.data_type == JXL_TYPE_FLOAT) {
416
0
    JXL_ASSERT(endianness != JXL_NATIVE_ENDIAN);
417
0
    for (size_t y = 0; y < ysize; ++y) {
418
0
      for (size_t x = 0; x < xsize; ++x) {
419
0
        size_t j = (y * xsize + x) * 4;
420
0
        size_t i = y * stride + x * num_channels * 4;
421
0
        double r;
422
0
        double g;
423
0
        double b;
424
0
        double a;
425
0
        if (endianness == JXL_BIG_ENDIAN) {
426
0
          r = LoadBEFloat(pixels + i);
427
0
          g = gray ? r : LoadBEFloat(pixels + i + 4);
428
0
          b = gray ? r : LoadBEFloat(pixels + i + 8);
429
0
          a = alpha ? LoadBEFloat(pixels + i + num_channels * 4 - 4) : 1.0;
430
0
        } else {
431
0
          r = LoadLEFloat(pixels + i);
432
0
          g = gray ? r : LoadLEFloat(pixels + i + 4);
433
0
          b = gray ? r : LoadLEFloat(pixels + i + 8);
434
0
          a = alpha ? LoadLEFloat(pixels + i + num_channels * 4 - 4) : 1.0;
435
0
        }
436
0
        result[j + 0] = r;
437
0
        result[j + 1] = g;
438
0
        result[j + 2] = b;
439
0
        result[j + 3] = a;
440
0
      }
441
0
    }
442
0
  } else if (format.data_type == JXL_TYPE_FLOAT16) {
443
0
    JXL_ASSERT(endianness != JXL_NATIVE_ENDIAN);
444
0
    for (size_t y = 0; y < ysize; ++y) {
445
0
      for (size_t x = 0; x < xsize; ++x) {
446
0
        size_t j = (y * xsize + x) * 4;
447
0
        size_t i = y * stride + x * num_channels * 2;
448
0
        double r;
449
0
        double g;
450
0
        double b;
451
0
        double a;
452
0
        if (endianness == JXL_BIG_ENDIAN) {
453
0
          r = LoadBEFloat16(pixels + i);
454
0
          g = gray ? r : LoadBEFloat16(pixels + i + 2);
455
0
          b = gray ? r : LoadBEFloat16(pixels + i + 4);
456
0
          a = alpha ? LoadBEFloat16(pixels + i + num_channels * 2 - 2) : 1.0;
457
0
        } else {
458
0
          r = LoadLEFloat16(pixels + i);
459
0
          g = gray ? r : LoadLEFloat16(pixels + i + 2);
460
0
          b = gray ? r : LoadLEFloat16(pixels + i + 4);
461
0
          a = alpha ? LoadLEFloat16(pixels + i + num_channels * 2 - 2) : 1.0;
462
0
        }
463
0
        result[j + 0] = r;
464
0
        result[j + 1] = g;
465
0
        result[j + 2] = b;
466
0
        result[j + 3] = a;
467
0
      }
468
0
    }
469
0
  } else {
470
0
    JXL_ASSERT(false);  // Unsupported type
471
0
  }
472
0
  return result;
473
0
}
474
475
size_t ComparePixels(const uint8_t* a, const uint8_t* b, size_t xsize,
476
                     size_t ysize, const JxlPixelFormat& format_a,
477
                     const JxlPixelFormat& format_b,
478
0
                     double threshold_multiplier) {
479
  // Convert both images to equal full precision for comparison.
480
0
  std::vector<double> a_full = ConvertToRGBA32(a, xsize, ysize, format_a);
481
0
  std::vector<double> b_full = ConvertToRGBA32(b, xsize, ysize, format_b);
482
0
  bool gray_a = format_a.num_channels < 3;
483
0
  bool gray_b = format_b.num_channels < 3;
484
0
  bool alpha_a = ((format_a.num_channels & 1) == 0);
485
0
  bool alpha_b = ((format_b.num_channels & 1) == 0);
486
0
  size_t bits_a = GetPrecision(format_a.data_type);
487
0
  size_t bits_b = GetPrecision(format_b.data_type);
488
0
  size_t bits = std::min(bits_a, bits_b);
489
  // How much distance is allowed in case of pixels with lower bit depths, given
490
  // that the double precision float images use range 0-1.0.
491
  // E.g. in case of 1-bit this is 0.5 since 0.499 must map to 0 and 0.501 must
492
  // map to 1.
493
0
  double precision = 0.5 * threshold_multiplier / ((1ull << bits) - 1ull);
494
0
  if (format_a.data_type == JXL_TYPE_FLOAT16 ||
495
0
      format_b.data_type == JXL_TYPE_FLOAT16) {
496
    // Lower the precision for float16, because it currently looks like the
497
    // scalar and wasm implementations of hwy have 1 less bit of precision
498
    // than the x86 implementations.
499
    // TODO(lode): Set the required precision back to 11 bits when possible.
500
0
    precision = 0.5 * threshold_multiplier / ((1ull << (bits - 1)) - 1ull);
501
0
  }
502
0
  if (format_b.data_type == JXL_TYPE_UINT8) {
503
    // Increase the threshold by the maximum difference introduced by dithering.
504
0
    precision += 63.0 / 128.0;
505
0
  }
506
0
  size_t numdiff = 0;
507
0
  for (size_t y = 0; y < ysize; y++) {
508
0
    for (size_t x = 0; x < xsize; x++) {
509
0
      size_t i = (y * xsize + x) * 4;
510
0
      bool ok = true;
511
0
      if (gray_a || gray_b) {
512
0
        if (!Near(a_full[i + 0], b_full[i + 0], precision)) ok = false;
513
        // If the input was grayscale and the output not, then the output must
514
        // have all channels equal.
515
0
        if (gray_a && b_full[i + 0] != b_full[i + 1] &&
516
0
            b_full[i + 2] != b_full[i + 2]) {
517
0
          ok = false;
518
0
        }
519
0
      } else {
520
0
        if (!Near(a_full[i + 0], b_full[i + 0], precision) ||
521
0
            !Near(a_full[i + 1], b_full[i + 1], precision) ||
522
0
            !Near(a_full[i + 2], b_full[i + 2], precision)) {
523
0
          ok = false;
524
0
        }
525
0
      }
526
0
      if (alpha_a && alpha_b) {
527
0
        if (!Near(a_full[i + 3], b_full[i + 3], precision)) ok = false;
528
0
      } else {
529
        // If the input had no alpha channel, the output should be opaque
530
        // after roundtrip.
531
0
        if (alpha_b && !Near(1.0, b_full[i + 3], precision)) ok = false;
532
0
      }
533
0
      if (!ok) numdiff++;
534
0
    }
535
0
  }
536
0
  return numdiff;
537
0
}
538
539
double DistanceRMS(const uint8_t* a, const uint8_t* b, size_t xsize,
540
0
                   size_t ysize, const JxlPixelFormat& format) {
541
  // Convert both images to equal full precision for comparison.
542
0
  std::vector<double> a_full = ConvertToRGBA32(a, xsize, ysize, format);
543
0
  std::vector<double> b_full = ConvertToRGBA32(b, xsize, ysize, format);
544
0
  double sum = 0.0;
545
0
  for (size_t y = 0; y < ysize; y++) {
546
0
    double row_sum = 0.0;
547
0
    for (size_t x = 0; x < xsize; x++) {
548
0
      size_t i = (y * xsize + x) * 4;
549
0
      for (size_t c = 0; c < format.num_channels; ++c) {
550
0
        double diff = a_full[i + c] - b_full[i + c];
551
0
        row_sum += diff * diff;
552
0
      }
553
0
    }
554
0
    sum += row_sum;
555
0
  }
556
0
  sum /= (xsize * ysize);
557
0
  return sqrt(sum);
558
0
}
559
560
float ButteraugliDistance(const extras::PackedPixelFile& a,
561
0
                          const extras::PackedPixelFile& b, ThreadPool* pool) {
562
0
  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
563
0
  CodecInOut io0{memory_manager};
564
0
  JXL_CHECK(ConvertPackedPixelFileToCodecInOut(a, pool, &io0));
565
0
  CodecInOut io1{memory_manager};
566
0
  JXL_CHECK(ConvertPackedPixelFileToCodecInOut(b, pool, &io1));
567
  // TODO(eustas): simplify?
568
0
  return ButteraugliDistance(io0.frames, io1.frames, ButteraugliParams(),
569
0
                             *JxlGetDefaultCms(),
570
0
                             /*distmap=*/nullptr, pool);
571
0
}
572
573
float ButteraugliDistance(const ImageBundle& rgb0, const ImageBundle& rgb1,
574
                          const ButteraugliParams& params,
575
                          const JxlCmsInterface& cms, ImageF* distmap,
576
0
                          ThreadPool* pool, bool ignore_alpha) {
577
0
  JxlButteraugliComparator comparator(params, cms);
578
0
  float distance;
579
0
  JXL_CHECK(ComputeScore(rgb0, rgb1, &comparator, cms, &distance, distmap, pool,
580
0
                         ignore_alpha));
581
0
  return distance;
582
0
}
583
584
float ButteraugliDistance(const std::vector<ImageBundle>& frames0,
585
                          const std::vector<ImageBundle>& frames1,
586
                          const ButteraugliParams& params,
587
                          const JxlCmsInterface& cms, ImageF* distmap,
588
0
                          ThreadPool* pool) {
589
0
  JxlButteraugliComparator comparator(params, cms);
590
0
  JXL_ASSERT(frames0.size() == frames1.size());
591
0
  float max_dist = 0.0f;
592
0
  for (size_t i = 0; i < frames0.size(); ++i) {
593
0
    float frame_score;
594
0
    JXL_CHECK(ComputeScore(frames0[i], frames1[i], &comparator, cms,
595
0
                           &frame_score, distmap, pool));
596
0
    max_dist = std::max(max_dist, frame_score);
597
0
  }
598
0
  return max_dist;
599
0
}
600
601
float Butteraugli3Norm(const extras::PackedPixelFile& a,
602
0
                       const extras::PackedPixelFile& b, ThreadPool* pool) {
603
0
  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
604
0
  CodecInOut io0{memory_manager};
605
0
  JXL_CHECK(ConvertPackedPixelFileToCodecInOut(a, pool, &io0));
606
0
  CodecInOut io1{memory_manager};
607
0
  JXL_CHECK(ConvertPackedPixelFileToCodecInOut(b, pool, &io1));
608
0
  ButteraugliParams ba;
609
0
  ImageF distmap;
610
0
  ButteraugliDistance(io0.frames, io1.frames, ba, *JxlGetDefaultCms(), &distmap,
611
0
                      pool);
612
0
  return ComputeDistanceP(distmap, ba, 3);
613
0
}
614
615
float ComputeDistance2(const extras::PackedPixelFile& a,
616
0
                       const extras::PackedPixelFile& b) {
617
0
  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
618
0
  CodecInOut io0{memory_manager};
619
0
  JXL_CHECK(ConvertPackedPixelFileToCodecInOut(a, nullptr, &io0));
620
0
  CodecInOut io1{memory_manager};
621
0
  JXL_CHECK(ConvertPackedPixelFileToCodecInOut(b, nullptr, &io1));
622
0
  return ComputeDistance2(io0.Main(), io1.Main(), *JxlGetDefaultCms());
623
0
}
624
625
float ComputePSNR(const extras::PackedPixelFile& a,
626
0
                  const extras::PackedPixelFile& b) {
627
0
  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
628
0
  CodecInOut io0{memory_manager};
629
0
  JXL_CHECK(ConvertPackedPixelFileToCodecInOut(a, nullptr, &io0));
630
0
  CodecInOut io1{memory_manager};
631
0
  JXL_CHECK(ConvertPackedPixelFileToCodecInOut(b, nullptr, &io1));
632
0
  return ComputePSNR(io0.Main(), io1.Main(), *JxlGetDefaultCms());
633
0
}
634
635
bool SameAlpha(const extras::PackedPixelFile& a,
636
0
               const extras::PackedPixelFile& b) {
637
0
  JXL_CHECK(a.info.xsize == b.info.xsize);
638
0
  JXL_CHECK(a.info.ysize == b.info.ysize);
639
0
  JXL_CHECK(a.info.alpha_bits == b.info.alpha_bits);
640
0
  JXL_CHECK(a.info.alpha_exponent_bits == b.info.alpha_exponent_bits);
641
0
  JXL_CHECK(a.info.alpha_bits > 0);
642
0
  JXL_CHECK(a.frames.size() == b.frames.size());
643
0
  for (size_t i = 0; i < a.frames.size(); ++i) {
644
0
    const extras::PackedImage& color_a = a.frames[i].color;
645
0
    const extras::PackedImage& color_b = b.frames[i].color;
646
0
    JXL_CHECK(color_a.format.num_channels == color_b.format.num_channels);
647
0
    JXL_CHECK(color_a.format.data_type == color_b.format.data_type);
648
0
    JXL_CHECK(color_a.format.endianness == color_b.format.endianness);
649
0
    JXL_CHECK(color_a.pixels_size == color_b.pixels_size);
650
0
    size_t pwidth =
651
0
        extras::PackedImage::BitsPerChannel(color_a.format.data_type) / 8;
652
0
    size_t num_color = color_a.format.num_channels < 3 ? 1 : 3;
653
0
    const uint8_t* p_a = reinterpret_cast<const uint8_t*>(color_a.pixels());
654
0
    const uint8_t* p_b = reinterpret_cast<const uint8_t*>(color_b.pixels());
655
0
    for (size_t y = 0; y < a.info.ysize; ++y) {
656
0
      for (size_t x = 0; x < a.info.xsize; ++x) {
657
0
        size_t idx =
658
0
            ((y * a.info.xsize + x) * color_a.format.num_channels + num_color) *
659
0
            pwidth;
660
0
        if (memcmp(&p_a[idx], &p_b[idx], pwidth) != 0) {
661
0
          return false;
662
0
        }
663
0
      }
664
0
    }
665
0
  }
666
0
  return true;
667
0
}
668
669
0
bool SamePixels(const extras::PackedImage& a, const extras::PackedImage& b) {
670
0
  JXL_CHECK(a.xsize == b.xsize);
671
0
  JXL_CHECK(a.ysize == b.ysize);
672
0
  JXL_CHECK(a.format.num_channels == b.format.num_channels);
673
0
  JXL_CHECK(a.format.data_type == b.format.data_type);
674
0
  JXL_CHECK(a.format.endianness == b.format.endianness);
675
0
  JXL_CHECK(a.pixels_size == b.pixels_size);
676
0
  const uint8_t* p_a = reinterpret_cast<const uint8_t*>(a.pixels());
677
0
  const uint8_t* p_b = reinterpret_cast<const uint8_t*>(b.pixels());
678
0
  for (size_t y = 0; y < a.ysize; ++y) {
679
0
    for (size_t x = 0; x < a.xsize; ++x) {
680
0
      size_t idx = (y * a.xsize + x) * a.pixel_stride();
681
0
      if (memcmp(&p_a[idx], &p_b[idx], a.pixel_stride()) != 0) {
682
0
        printf("Mismatch at row %" PRIuS " col %" PRIuS "\n", y, x);
683
0
        printf("  a: ");
684
0
        for (size_t j = 0; j < a.pixel_stride(); ++j) {
685
0
          printf(" %3u", p_a[idx + j]);
686
0
        }
687
0
        printf("\n  b: ");
688
0
        for (size_t j = 0; j < a.pixel_stride(); ++j) {
689
0
          printf(" %3u", p_b[idx + j]);
690
0
        }
691
0
        printf("\n");
692
0
        return false;
693
0
      }
694
0
    }
695
0
  }
696
0
  return true;
697
0
}
698
699
bool SamePixels(const extras::PackedPixelFile& a,
700
0
                const extras::PackedPixelFile& b) {
701
0
  JXL_CHECK(a.info.xsize == b.info.xsize);
702
0
  JXL_CHECK(a.info.ysize == b.info.ysize);
703
0
  JXL_CHECK(a.info.bits_per_sample == b.info.bits_per_sample);
704
0
  JXL_CHECK(a.info.exponent_bits_per_sample == b.info.exponent_bits_per_sample);
705
0
  JXL_CHECK(a.frames.size() == b.frames.size());
706
0
  for (size_t i = 0; i < a.frames.size(); ++i) {
707
0
    const auto& frame_a = a.frames[i];
708
0
    const auto& frame_b = b.frames[i];
709
0
    if (!SamePixels(frame_a.color, frame_b.color)) {
710
0
      return false;
711
0
    }
712
0
    JXL_CHECK(frame_a.extra_channels.size() == frame_b.extra_channels.size());
713
0
    for (size_t j = 0; j < frame_a.extra_channels.size(); ++j) {
714
0
      if (!SamePixels(frame_a.extra_channels[i], frame_b.extra_channels[i])) {
715
0
        return false;
716
0
      }
717
0
    }
718
0
  }
719
0
  return true;
720
0
}
721
722
Status ReadICC(BitReader* JXL_RESTRICT reader,
723
0
               std::vector<uint8_t>* JXL_RESTRICT icc, size_t output_limit) {
724
0
  JxlMemoryManager* memort_manager = jxl::test::MemoryManager();
725
0
  icc->clear();
726
0
  ICCReader icc_reader{memort_manager};
727
0
  PaddedBytes icc_buffer{memort_manager};
728
0
  JXL_RETURN_IF_ERROR(icc_reader.Init(reader, output_limit));
729
0
  JXL_RETURN_IF_ERROR(icc_reader.Process(reader, &icc_buffer));
730
0
  Bytes(icc_buffer).AppendTo(*icc);
731
0
  return true;
732
0
}
733
734
namespace {  // For EncodeFile
735
Status PrepareCodecMetadataFromIO(const CompressParams& cparams,
736
                                  const CodecInOut* io,
737
0
                                  CodecMetadata* metadata) {
738
0
  *metadata = io->metadata;
739
0
  size_t ups = 1;
740
0
  if (cparams.already_downsampled) ups = cparams.resampling;
741
742
0
  JXL_RETURN_IF_ERROR(metadata->size.Set(io->xsize() * ups, io->ysize() * ups));
743
744
  // Keep ICC profile in lossless modes because a reconstructed profile may be
745
  // slightly different (quantization).
746
  // Also keep ICC in JPEG reconstruction mode as we need byte-exact profiles.
747
0
  if (!cparams.IsLossless() && !io->Main().IsJPEG() && cparams.cms_set) {
748
0
    metadata->m.color_encoding.DecideIfWantICC(cparams.cms);
749
0
  }
750
751
0
  metadata->m.xyb_encoded =
752
0
      cparams.color_transform == ColorTransform::kXYB ? true : false;
753
754
  // TODO(firsching): move this EncodeFile to test_utils / re-implement this
755
  // using API functions
756
0
  return true;
757
0
}
758
759
Status EncodePreview(const CompressParams& cparams, const ImageBundle& ib,
760
                     const CodecMetadata* metadata, const JxlCmsInterface& cms,
761
0
                     ThreadPool* pool, BitWriter* JXL_RESTRICT writer) {
762
0
  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
763
0
  BitWriter preview_writer{memory_manager};
764
  // TODO(janwas): also support generating preview by downsampling
765
0
  if (ib.HasColor()) {
766
0
    AuxOut aux_out;
767
    // TODO(lode): check if we want all extra channels and matching xyb_encoded
768
    // for the preview, such that using the main ImageMetadata object for
769
    // encoding this frame is warrented.
770
0
    FrameInfo frame_info;
771
0
    frame_info.is_preview = true;
772
0
    JXL_RETURN_IF_ERROR(EncodeFrame(memory_manager, cparams, frame_info,
773
0
                                    metadata, ib, cms, pool, &preview_writer,
774
0
                                    &aux_out));
775
0
    preview_writer.ZeroPadToByte();
776
0
  }
777
778
0
  if (preview_writer.BitsWritten() != 0) {
779
0
    writer->ZeroPadToByte();
780
0
    writer->AppendByteAligned(preview_writer);
781
0
  }
782
783
0
  return true;
784
0
}
785
786
}  // namespace
787
788
Status EncodeFile(const CompressParams& params, const CodecInOut* io,
789
0
                  std::vector<uint8_t>* compressed, ThreadPool* pool) {
790
0
  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
791
0
  compressed->clear();
792
0
  const JxlCmsInterface& cms = *JxlGetDefaultCms();
793
0
  io->CheckMetadata();
794
0
  BitWriter writer{memory_manager};
795
796
0
  CompressParams cparams = params;
797
0
  if (io->Main().color_transform != ColorTransform::kNone) {
798
    // Set the color transform to YCbCr or XYB if the original image is such.
799
0
    cparams.color_transform = io->Main().color_transform;
800
0
  }
801
802
0
  JXL_RETURN_IF_ERROR(ParamsPostInit(&cparams));
803
804
0
  std::unique_ptr<CodecMetadata> metadata = jxl::make_unique<CodecMetadata>();
805
0
  JXL_RETURN_IF_ERROR(PrepareCodecMetadataFromIO(cparams, io, metadata.get()));
806
0
  JXL_RETURN_IF_ERROR(
807
0
      WriteCodestreamHeaders(metadata.get(), &writer, /*aux_out*/ nullptr));
808
809
  // Only send ICC (at least several hundred bytes) if fields aren't enough.
810
0
  if (metadata->m.color_encoding.WantICC()) {
811
0
    JXL_RETURN_IF_ERROR(WriteICC(metadata->m.color_encoding.ICC(), &writer,
812
0
                                 kLayerHeader, /* aux_out */ nullptr));
813
0
  }
814
815
0
  if (metadata->m.have_preview) {
816
0
    JXL_RETURN_IF_ERROR(EncodePreview(cparams, io->preview_frame,
817
0
                                      metadata.get(), cms, pool, &writer));
818
0
  }
819
820
  // Each frame should start on byte boundaries.
821
0
  BitWriter::Allotment allotment(&writer, 8);
822
0
  writer.ZeroPadToByte();
823
0
  allotment.ReclaimAndCharge(&writer, kLayerHeader, /* aux_out */ nullptr);
824
825
0
  for (size_t i = 0; i < io->frames.size(); i++) {
826
0
    FrameInfo info;
827
0
    info.is_last = i == io->frames.size() - 1;
828
0
    if (io->frames[i].use_for_next_frame) {
829
0
      info.save_as_reference = 1;
830
0
    }
831
0
    JXL_RETURN_IF_ERROR(EncodeFrame(memory_manager, cparams, info,
832
0
                                    metadata.get(), io->frames[i], cms, pool,
833
0
                                    &writer,
834
0
                                    /* aux_out */ nullptr));
835
0
  }
836
837
0
  PaddedBytes output = std::move(writer).TakeBytes();
838
0
  Bytes(output).AppendTo(*compressed);
839
0
  return true;
840
0
}
841
842
namespace {
843
921k
void* TestAlloc(void* /* opaque*/, size_t size) { return malloc(size); }
844
921k
void TestFree(void* /* opaque*/, void* address) { free(address); }
845
JxlMemoryManager kMemoryManager{nullptr, &TestAlloc, &TestFree};
846
}  // namespace
847
848
6.06k
JxlMemoryManager* MemoryManager() { return &kMemoryManager; };
849
850
}  // namespace test
851
852
0
bool operator==(const jxl::Bytes& a, const jxl::Bytes& b) {
853
0
  if (a.size() != b.size()) return false;
854
0
  if (memcmp(a.data(), b.data(), a.size()) != 0) return false;
855
0
  return true;
856
0
}
857
858
// Allow using EXPECT_EQ on jxl::Bytes
859
0
bool operator!=(const jxl::Bytes& a, const jxl::Bytes& b) { return !(a == b); }
860
861
}  // namespace jxl