tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_utils.cc (34069B)


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