libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

test_utils.cc (31009B)


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