Coverage Report

Created: 2025-12-31 07:22

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