tor-browser

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

test_utils.cc (30572B)


      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/jpegli/test_utils.h"
      7 
      8 #include <cmath>
      9 #include <cstdint>
     10 #include <cstring>
     11 #include <fstream>
     12 #include <sstream>
     13 
     14 #include "lib/jpegli/decode.h"
     15 #include "lib/jpegli/encode.h"
     16 #include "lib/jxl/base/byte_order.h"
     17 #include "lib/jxl/base/compiler_specific.h"
     18 #include "lib/jxl/base/printf_macros.h"
     19 #include "lib/jxl/base/sanitizers.h"
     20 #include "lib/jxl/base/status.h"
     21 
     22 #if !defined(TEST_DATA_PATH)
     23 #include "tools/cpp/runfiles/runfiles.h"
     24 #endif
     25 
     26 namespace jpegli {
     27 
     28 namespace {
     29 void Check(bool ok) {
     30  if (!ok) {
     31    JXL_CRASH();
     32  }
     33 }
     34 #define QUIT(M) Check(false);
     35 }  // namespace
     36 
     37 #define JPEG_API_FN(name) jpegli_##name
     38 #include "lib/jpegli/test_utils-inl.h"
     39 #undef JPEG_API_FN
     40 
     41 #if defined(TEST_DATA_PATH)
     42 std::string GetTestDataPath(const std::string& filename) {
     43  return std::string(TEST_DATA_PATH "/") + filename;
     44 }
     45 #else
     46 using ::bazel::tools::cpp::runfiles::Runfiles;
     47 const std::unique_ptr<Runfiles> kRunfiles(Runfiles::Create(""));
     48 std::string GetTestDataPath(const std::string& filename) {
     49  std::string root(JPEGXL_ROOT_PACKAGE "/testdata/");
     50  return kRunfiles->Rlocation(root + filename);
     51 }
     52 #endif
     53 
     54 jxl::StatusOr<std::vector<uint8_t>> ReadTestData(const std::string& filename) {
     55  std::vector<uint8_t> data;
     56  std::string full_path = GetTestDataPath(filename);
     57  fprintf(stderr, "ReadTestData %s\n", full_path.c_str());
     58  std::ifstream file(full_path, std::ios::binary);
     59  std::vector<char> str((std::istreambuf_iterator<char>(file)),
     60                        std::istreambuf_iterator<char>());
     61  JXL_ENSURE(file.good());
     62  const uint8_t* raw = reinterpret_cast<const uint8_t*>(str.data());
     63  data = std::vector<uint8_t>(raw, raw + str.size());
     64  printf("Test data %s is %d bytes long.\n", filename.c_str(),
     65         static_cast<int>(data.size()));
     66  return data;
     67 }
     68 
     69 void CustomQuantTable::Generate() {
     70  basic_table.resize(DCTSIZE2);
     71  quantval.resize(DCTSIZE2);
     72  switch (table_type) {
     73    case 0: {
     74      for (int k = 0; k < DCTSIZE2; ++k) {
     75        basic_table[k] = k + 1;
     76      }
     77      break;
     78    }
     79    default:
     80      for (int k = 0; k < DCTSIZE2; ++k) {
     81        basic_table[k] = table_type;
     82      }
     83  }
     84  for (int k = 0; k < DCTSIZE2; ++k) {
     85    quantval[k] = (basic_table[k] * scale_factor + 50U) / 100U;
     86    quantval[k] = std::max(quantval[k], 1U);
     87    quantval[k] = std::min(quantval[k], 65535U);
     88    if (!add_raw) {
     89      quantval[k] = std::min(quantval[k], force_baseline ? 255U : 32767U);
     90    }
     91  }
     92 }
     93 
     94 bool PNMParser::ParseHeader(const uint8_t** pos, size_t* xsize, size_t* ysize,
     95                            size_t* num_channels, size_t* bitdepth) {
     96  if (pos_[0] != 'P' || (pos_[1] != '5' && pos_[1] != '6')) {
     97    fprintf(stderr, "Invalid PNM header.");
     98    return false;
     99  }
    100  *num_channels = (pos_[1] == '5' ? 1 : 3);
    101  pos_ += 2;
    102 
    103  size_t maxval;
    104  if (!SkipWhitespace() || !ParseUnsigned(xsize) || !SkipWhitespace() ||
    105      !ParseUnsigned(ysize) || !SkipWhitespace() || !ParseUnsigned(&maxval) ||
    106      !SkipWhitespace()) {
    107    return false;
    108  }
    109  if (maxval == 0 || maxval >= 65536) {
    110    fprintf(stderr, "Invalid maxval value.\n");
    111    return false;
    112  }
    113  bool found_bitdepth = false;
    114  for (int bits = 1; bits <= 16; ++bits) {
    115    if (maxval == (1u << bits) - 1) {
    116      *bitdepth = bits;
    117      found_bitdepth = true;
    118      break;
    119    }
    120  }
    121  if (!found_bitdepth) {
    122    fprintf(stderr, "Invalid maxval value.\n");
    123    return false;
    124  }
    125 
    126  *pos = pos_;
    127  return true;
    128 }
    129 
    130 bool PNMParser::ParseUnsigned(size_t* number) {
    131  if (pos_ == end_ || *pos_ < '0' || *pos_ > '9') {
    132    fprintf(stderr, "Expected unsigned number.\n");
    133    return false;
    134  }
    135  *number = 0;
    136  while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
    137    *number *= 10;
    138    *number += *pos_ - '0';
    139    ++pos_;
    140  }
    141 
    142  return true;
    143 }
    144 
    145 bool PNMParser::SkipWhitespace() {
    146  if (pos_ == end_ || !IsWhitespace(*pos_)) {
    147    fprintf(stderr, "Expected whitespace.\n");
    148    return false;
    149  }
    150  while (pos_ < end_ && IsWhitespace(*pos_)) {
    151    ++pos_;
    152  }
    153  return true;
    154 }
    155 
    156 bool ReadPNM(const std::vector<uint8_t>& data, size_t* xsize, size_t* ysize,
    157             size_t* num_channels, size_t* bitdepth,
    158             std::vector<uint8_t>* pixels) {
    159  if (data.size() < 2) {
    160    fprintf(stderr, "PNM file too small.\n");
    161    return false;
    162  }
    163  PNMParser parser(data.data(), data.size());
    164  const uint8_t* pos = nullptr;
    165  if (!parser.ParseHeader(&pos, xsize, ysize, num_channels, bitdepth)) {
    166    return false;
    167  }
    168  pixels->resize(data.data() + data.size() - pos);
    169  memcpy(pixels->data(), pos, pixels->size());
    170  return true;
    171 }
    172 
    173 std::string ColorSpaceName(J_COLOR_SPACE colorspace) {
    174  switch (colorspace) {
    175    case JCS_UNKNOWN:
    176      return "UNKNOWN";
    177    case JCS_GRAYSCALE:
    178      return "GRAYSCALE";
    179    case JCS_RGB:
    180      return "RGB";
    181    case JCS_YCbCr:
    182      return "YCbCr";
    183    case JCS_CMYK:
    184      return "CMYK";
    185    case JCS_YCCK:
    186      return "YCCK";
    187    case JCS_EXT_RGB:
    188      return "EXT_RGB";
    189    case JCS_EXT_BGR:
    190      return "EXT_BGR";
    191    case JCS_EXT_RGBA:
    192      return "EXT_RGBA";
    193    case JCS_EXT_BGRA:
    194      return "EXT_BGRA";
    195    case JCS_EXT_ARGB:
    196      return "EXT_ARGB";
    197    case JCS_EXT_ABGR:
    198      return "EXT_ABGR";
    199    default:
    200      return "";
    201  }
    202 }
    203 
    204 std::string IOMethodName(JpegliDataType data_type,
    205                         JpegliEndianness endianness) {
    206  std::string retval;
    207  if (data_type == JPEGLI_TYPE_UINT8) {
    208    return "";
    209  } else if (data_type == JPEGLI_TYPE_UINT16) {
    210    retval = "UINT16";
    211  } else if (data_type == JPEGLI_TYPE_FLOAT) {
    212    retval = "FLOAT";
    213  }
    214  if (endianness == JPEGLI_LITTLE_ENDIAN) {
    215    retval += "LE";
    216  } else if (endianness == JPEGLI_BIG_ENDIAN) {
    217    retval += "BE";
    218  }
    219  return retval;
    220 }
    221 
    222 std::string SamplingId(const CompressParams& jparams) {
    223  std::stringstream os;
    224  Check(jparams.h_sampling.size() == jparams.v_sampling.size());
    225  if (!jparams.h_sampling.empty()) {
    226    size_t len = jparams.h_sampling.size();
    227    while (len > 1 && jparams.h_sampling[len - 1] == 1 &&
    228           jparams.v_sampling[len - 1] == 1) {
    229      --len;
    230    }
    231    os << "SAMP";
    232    for (size_t i = 0; i < len; ++i) {
    233      if (i > 0) os << "_";
    234      os << jparams.h_sampling[i] << "x" << jparams.v_sampling[i];
    235    }
    236  }
    237  return os.str();
    238 }
    239 
    240 std::ostream& operator<<(std::ostream& os, const TestImage& input) {
    241  os << input.xsize << "x" << input.ysize;
    242  os << IOMethodName(input.data_type, input.endianness);
    243  if (input.color_space != JCS_RGB) {
    244    os << "InputColor"
    245       << ColorSpaceName(static_cast<J_COLOR_SPACE>(input.color_space));
    246  }
    247  if (input.color_space == JCS_UNKNOWN) {
    248    os << input.components;
    249  }
    250  return os;
    251 }
    252 
    253 std::ostream& operator<<(std::ostream& os, const CompressParams& jparams) {
    254  os << "Q" << jparams.quality;
    255  os << SamplingId(jparams);
    256  if (jparams.set_jpeg_colorspace) {
    257    os << "JpegColor"
    258       << ColorSpaceName(static_cast<J_COLOR_SPACE>(jparams.jpeg_color_space));
    259  }
    260  if (!jparams.comp_ids.empty()) {
    261    os << "CID";
    262    for (int cid : jparams.comp_ids) {
    263      os << cid;
    264    }
    265  }
    266  if (!jparams.quant_indexes.empty()) {
    267    os << "QIDX";
    268    for (int qi : jparams.quant_indexes) {
    269      os << qi;
    270    }
    271    for (const auto& table : jparams.quant_tables) {
    272      os << "TABLE" << table.slot_idx << "T" << table.table_type << "F"
    273         << table.scale_factor
    274         << (table.add_raw          ? "R"
    275             : table.force_baseline ? "B"
    276                                    : "");
    277    }
    278  }
    279  if (jparams.progressive_mode >= 0) {
    280    os << "P" << jparams.progressive_mode;
    281  } else if (jparams.simple_progression) {
    282    os << "Psimple";
    283  }
    284  if (jparams.optimize_coding == 1) {
    285    os << "OptimizedCode";
    286  } else if (jparams.optimize_coding == 0) {
    287    os << "FixedCode";
    288    if (jparams.use_flat_dc_luma_code) {
    289      os << "FlatDCLuma";
    290    } else if (jparams.omit_standard_tables) {
    291      os << "OmitDHT";
    292    }
    293  }
    294  if (!jparams.use_adaptive_quantization) {
    295    os << "NoAQ";
    296  }
    297  if (jparams.restart_interval > 0) {
    298    os << "R" << jparams.restart_interval;
    299  }
    300  if (jparams.restart_in_rows > 0) {
    301    os << "RR" << jparams.restart_in_rows;
    302  }
    303  if (jparams.xyb_mode) {
    304    os << "XYB";
    305  } else if (jparams.libjpeg_mode) {
    306    os << "Libjpeg";
    307  }
    308  if (jparams.override_JFIF >= 0) {
    309    os << (jparams.override_JFIF ? "AddJFIF" : "NoJFIF");
    310  }
    311  if (jparams.override_Adobe >= 0) {
    312    os << (jparams.override_Adobe ? "AddAdobe" : "NoAdobe");
    313  }
    314  if (jparams.add_marker) {
    315    os << "AddMarker";
    316  }
    317  if (!jparams.icc.empty()) {
    318    os << "ICCSize" << jparams.icc.size();
    319  }
    320  if (jparams.smoothing_factor != 0) {
    321    os << "SF" << jparams.smoothing_factor;
    322  }
    323  return os;
    324 }
    325 
    326 jxl::Status SetNumChannels(J_COLOR_SPACE colorspace, size_t* channels) {
    327  if (colorspace == JCS_GRAYSCALE) {
    328    *channels = 1;
    329  } else if (colorspace == JCS_RGB || colorspace == JCS_YCbCr ||
    330             colorspace == JCS_EXT_RGB || colorspace == JCS_EXT_BGR) {
    331    *channels = 3;
    332  } else if (colorspace == JCS_CMYK || colorspace == JCS_YCCK ||
    333             colorspace == JCS_EXT_RGBA || colorspace == JCS_EXT_BGRA ||
    334             colorspace == JCS_EXT_ARGB || colorspace == JCS_EXT_ABGR) {
    335    *channels = 4;
    336  } else if (colorspace == JCS_UNKNOWN) {
    337    JXL_ENSURE(*channels <= 4);
    338  } else {
    339    return JXL_FAILURE("Unsupported colorspace: %d",
    340                       static_cast<int>(colorspace));
    341  }
    342  return true;
    343 }
    344 
    345 void RGBToYCbCr(float r, float g, float b, float* y, float* cb, float* cr) {
    346  *y = 0.299f * r + 0.587f * g + 0.114f * b;
    347  *cb = -0.168736f * r - 0.331264f * g + 0.5f * b + 0.5f;
    348  *cr = 0.5f * r - 0.418688f * g - 0.081312f * b + 0.5f;
    349 }
    350 
    351 void ConvertPixel(const uint8_t* input_rgb, uint8_t* out,
    352                  J_COLOR_SPACE colorspace, size_t num_channels,
    353                  JpegliDataType data_type = JPEGLI_TYPE_UINT8,
    354                  JXL_BOOL swap_endianness = JPEGLI_NATIVE_ENDIAN) {
    355  const float kMul = 255.0f;
    356  float r = input_rgb[0] / kMul;
    357  float g = input_rgb[1] / kMul;
    358  float b = input_rgb[2] / kMul;
    359  uint8_t out8[MAX_COMPONENTS];
    360  if (colorspace == JCS_GRAYSCALE) {
    361    const float Y = 0.299f * r + 0.587f * g + 0.114f * b;
    362    out8[0] = static_cast<uint8_t>(std::round(Y * kMul));
    363  } else if (colorspace == JCS_RGB || colorspace == JCS_EXT_RGB ||
    364             colorspace == JCS_EXT_RGBA) {
    365    out8[0] = input_rgb[0];
    366    out8[1] = input_rgb[1];
    367    out8[2] = input_rgb[2];
    368    if (colorspace == JCS_EXT_RGBA) out8[3] = 255;
    369  } else if (colorspace == JCS_EXT_BGR || colorspace == JCS_EXT_BGRA) {
    370    out8[2] = input_rgb[0];
    371    out8[1] = input_rgb[1];
    372    out8[0] = input_rgb[2];
    373    if (colorspace == JCS_EXT_BGRA) out8[3] = 255;
    374  } else if (colorspace == JCS_EXT_ABGR) {
    375    out8[0] = 255;
    376    out8[3] = input_rgb[0];
    377    out8[2] = input_rgb[1];
    378    out8[1] = input_rgb[2];
    379  } else if (colorspace == JCS_EXT_ARGB) {
    380    out8[0] = 255;
    381    out8[1] = input_rgb[0];
    382    out8[2] = input_rgb[1];
    383    out8[3] = input_rgb[2];
    384  } else if (colorspace == JCS_UNKNOWN) {
    385    for (size_t c = 0; c < num_channels; ++c) {
    386      out8[c] = input_rgb[std::min<size_t>(2, c)];
    387    }
    388  } else if (colorspace == JCS_YCbCr) {
    389    float Y;
    390    float Cb;
    391    float Cr;
    392    RGBToYCbCr(r, g, b, &Y, &Cb, &Cr);
    393    out8[0] = static_cast<uint8_t>(std::round(Y * kMul));
    394    out8[1] = static_cast<uint8_t>(std::round(Cb * kMul));
    395    out8[2] = static_cast<uint8_t>(std::round(Cr * kMul));
    396  } else if (colorspace == JCS_CMYK || colorspace == JCS_YCCK) {
    397    float K = 1.0f - std::max(r, std::max(g, b));
    398    float scaleK = 1.0f / (1.0f - K);
    399    r *= scaleK;
    400    g *= scaleK;
    401    b *= scaleK;
    402    if (colorspace == JCS_CMYK) {
    403      out8[0] = static_cast<uint8_t>(std::round((1.0f - r) * kMul));
    404      out8[1] = static_cast<uint8_t>(std::round((1.0f - g) * kMul));
    405      out8[2] = static_cast<uint8_t>(std::round((1.0f - b) * kMul));
    406    } else if (colorspace == JCS_YCCK) {
    407      float Y;
    408      float Cb;
    409      float Cr;
    410      RGBToYCbCr(r, g, b, &Y, &Cb, &Cr);
    411      out8[0] = static_cast<uint8_t>(std::round(Y * kMul));
    412      out8[1] = static_cast<uint8_t>(std::round(Cb * kMul));
    413      out8[2] = static_cast<uint8_t>(std::round(Cr * kMul));
    414    }
    415    out8[3] = static_cast<uint8_t>(std::round(K * kMul));
    416  } else {
    417    Check(false);
    418  }
    419  if (data_type == JPEGLI_TYPE_UINT8) {
    420    memcpy(out, out8, num_channels);
    421  } else if (data_type == JPEGLI_TYPE_UINT16) {
    422    for (size_t c = 0; c < num_channels; ++c) {
    423      uint16_t val = (out8[c] << 8) + out8[c];
    424      val |= 0x40;  // Make little-endian and big-endian asymmetric
    425      if (swap_endianness) {
    426        val = JXL_BSWAP16(val);
    427      }
    428      memcpy(&out[sizeof(val) * c], &val, sizeof(val));
    429    }
    430  } else if (data_type == JPEGLI_TYPE_FLOAT) {
    431    for (size_t c = 0; c < num_channels; ++c) {
    432      float val = out8[c] / 255.0f;
    433      if (swap_endianness) {
    434        val = BSwapFloat(val);
    435      }
    436      memcpy(&out[sizeof(val) * c], &val, sizeof(val));
    437    }
    438  }
    439 }
    440 
    441 void ConvertToGrayscale(TestImage* img) {
    442  if (img->color_space == JCS_GRAYSCALE) return;
    443  Check(img->data_type == JPEGLI_TYPE_UINT8);
    444  bool rgb_pre_alpha =
    445      img->color_space == JCS_EXT_ARGB || img->color_space == JCS_EXT_ABGR;
    446  bool rgb_post_alpha =
    447      img->color_space == JCS_EXT_RGBA || img->color_space == JCS_EXT_BGRA;
    448  bool rgb_alpha = rgb_pre_alpha || rgb_post_alpha;
    449  bool is_rgb = img->color_space == JCS_RGB ||
    450                img->color_space == JCS_EXT_RGB ||
    451                img->color_space == JCS_EXT_BGR || rgb_alpha;
    452  bool switch_br = img->color_space == JCS_EXT_BGR ||
    453                   img->color_space == JCS_EXT_ABGR ||
    454                   img->color_space == JCS_EXT_BGRA;
    455  size_t stride = rgb_alpha ? 4 : 3;
    456  size_t offset = rgb_pre_alpha ? 1 : 0;
    457  for (size_t i = offset; i < img->pixels.size(); i += stride) {
    458    if (is_rgb) {
    459      if (switch_br) std::swap(img->pixels[i], img->pixels[i + 2]);
    460      ConvertPixel(&img->pixels[i], &img->pixels[i / stride], JCS_GRAYSCALE, 1);
    461    } else if (img->color_space == JCS_YCbCr) {
    462      img->pixels[i / 3] = img->pixels[i];
    463    }
    464  }
    465  img->pixels.resize(img->pixels.size() / 3);
    466  img->color_space = JCS_GRAYSCALE;
    467  img->components = 1;
    468 }
    469 
    470 void GeneratePixels(TestImage* img) {
    471  JXL_ASSIGN_OR_QUIT(std::vector<uint8_t> imgdata,
    472                     ReadTestData("jxl/flower/flower.pnm"),
    473                     "Failed to read test data");
    474  size_t xsize;
    475  size_t ysize;
    476  size_t channels;
    477  size_t bitdepth;
    478  std::vector<uint8_t> pixels;
    479  Check(ReadPNM(imgdata, &xsize, &ysize, &channels, &bitdepth, &pixels));
    480  if (img->xsize == 0) img->xsize = xsize;
    481  if (img->ysize == 0) img->ysize = ysize;
    482  Check(img->xsize <= xsize);
    483  Check(img->ysize <= ysize);
    484  Check(3 == channels);
    485  Check(8 == bitdepth);
    486  size_t in_bytes_per_pixel = channels;
    487  size_t in_stride = xsize * in_bytes_per_pixel;
    488  size_t x0 = (xsize - img->xsize) / 2;
    489  size_t y0 = (ysize - img->ysize) / 2;
    490  Check(SetNumChannels(static_cast<J_COLOR_SPACE>(img->color_space),
    491                       &img->components));
    492  size_t out_bytes_per_pixel =
    493      jpegli_bytes_per_sample(img->data_type) * img->components;
    494  size_t out_stride = img->xsize * out_bytes_per_pixel;
    495  bool swap_endianness =
    496      (img->endianness == JPEGLI_LITTLE_ENDIAN && !IsLittleEndian()) ||
    497      (img->endianness == JPEGLI_BIG_ENDIAN && IsLittleEndian());
    498  img->pixels.resize(img->ysize * out_stride);
    499  for (size_t iy = 0; iy < img->ysize; ++iy) {
    500    size_t y = y0 + iy;
    501    for (size_t ix = 0; ix < img->xsize; ++ix) {
    502      size_t x = x0 + ix;
    503      size_t idx_in = y * in_stride + x * in_bytes_per_pixel;
    504      size_t idx_out = iy * out_stride + ix * out_bytes_per_pixel;
    505      ConvertPixel(&pixels[idx_in], &img->pixels[idx_out],
    506                   static_cast<J_COLOR_SPACE>(img->color_space),
    507                   img->components, img->data_type,
    508                   TO_JXL_BOOL(swap_endianness));
    509    }
    510  }
    511 }
    512 
    513 void GenerateRawData(const CompressParams& jparams, TestImage* img) {
    514  for (size_t c = 0; c < img->components; ++c) {
    515    size_t xsize = jparams.comp_width(*img, c);
    516    size_t ysize = jparams.comp_height(*img, c);
    517    size_t factor_y = jparams.max_v_sample() / jparams.v_samp(c);
    518    size_t factor_x = jparams.max_h_sample() / jparams.h_samp(c);
    519    size_t factor = factor_x * factor_y;
    520    std::vector<uint8_t> plane(ysize * xsize);
    521    size_t bytes_per_pixel = img->components;
    522    for (size_t y = 0; y < ysize; ++y) {
    523      for (size_t x = 0; x < xsize; ++x) {
    524        int result = 0;
    525        for (size_t iy = 0; iy < factor_y; ++iy) {
    526          size_t yy = std::min(y * factor_y + iy, img->ysize - 1);
    527          for (size_t ix = 0; ix < factor_x; ++ix) {
    528            size_t xx = std::min(x * factor_x + ix, img->xsize - 1);
    529            size_t pixel_ix = (yy * img->xsize + xx) * bytes_per_pixel + c;
    530            result += img->pixels[pixel_ix];
    531          }
    532        }
    533        result = static_cast<uint8_t>((result + factor / 2) / factor);
    534        plane[y * xsize + x] = result;
    535      }
    536    }
    537    img->raw_data.emplace_back(std::move(plane));
    538  }
    539 }
    540 
    541 void GenerateCoeffs(const CompressParams& jparams, TestImage* img) {
    542  for (size_t c = 0; c < img->components; ++c) {
    543    int xsize_blocks = jparams.comp_width(*img, c) / DCTSIZE;
    544    int ysize_blocks = jparams.comp_height(*img, c) / DCTSIZE;
    545    std::vector<JCOEF> plane(ysize_blocks * xsize_blocks * DCTSIZE2);
    546    for (int by = 0; by < ysize_blocks; ++by) {
    547      for (int bx = 0; bx < xsize_blocks; ++bx) {
    548        JCOEF* block = &plane[(by * xsize_blocks + bx) * DCTSIZE2];
    549        for (int k = 0; k < DCTSIZE2; ++k) {
    550          block[k] = (bx - by) / (k + 1);
    551        }
    552      }
    553    }
    554    img->coeffs.emplace_back(std::move(plane));
    555  }
    556 }
    557 
    558 void EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
    559                      j_compress_ptr cinfo) {
    560  cinfo->image_width = input.xsize;
    561  cinfo->image_height = input.ysize;
    562  cinfo->input_components = input.components;
    563  if (jparams.xyb_mode) {
    564    jpegli_set_xyb_mode(cinfo);
    565  }
    566  if (jparams.libjpeg_mode) {
    567    jpegli_enable_adaptive_quantization(cinfo, FALSE);
    568    jpegli_use_standard_quant_tables(cinfo);
    569    jpegli_set_progressive_level(cinfo, 0);
    570  }
    571  jpegli_set_defaults(cinfo);
    572  cinfo->in_color_space = static_cast<J_COLOR_SPACE>(input.color_space);
    573  jpegli_default_colorspace(cinfo);
    574  if (jparams.override_JFIF >= 0) {
    575    cinfo->write_JFIF_header = jparams.override_JFIF;
    576  }
    577  if (jparams.override_Adobe >= 0) {
    578    cinfo->write_Adobe_marker = jparams.override_Adobe;
    579  }
    580  if (jparams.set_jpeg_colorspace) {
    581    jpegli_set_colorspace(cinfo,
    582                          static_cast<J_COLOR_SPACE>(jparams.jpeg_color_space));
    583  }
    584  if (!jparams.comp_ids.empty()) {
    585    for (int c = 0; c < cinfo->num_components; ++c) {
    586      cinfo->comp_info[c].component_id = jparams.comp_ids[c];
    587    }
    588  }
    589  if (!jparams.h_sampling.empty()) {
    590    for (int c = 0; c < cinfo->num_components; ++c) {
    591      cinfo->comp_info[c].h_samp_factor = jparams.h_sampling[c];
    592      cinfo->comp_info[c].v_samp_factor = jparams.v_sampling[c];
    593    }
    594  }
    595  jpegli_set_quality(cinfo, jparams.quality, TRUE);
    596  if (!jparams.quant_indexes.empty()) {
    597    for (int c = 0; c < cinfo->num_components; ++c) {
    598      cinfo->comp_info[c].quant_tbl_no = jparams.quant_indexes[c];
    599    }
    600    for (const auto& table : jparams.quant_tables) {
    601      if (table.add_raw) {
    602        cinfo->quant_tbl_ptrs[table.slot_idx] =
    603            jpegli_alloc_quant_table(reinterpret_cast<j_common_ptr>(cinfo));
    604        for (int k = 0; k < DCTSIZE2; ++k) {
    605          cinfo->quant_tbl_ptrs[table.slot_idx]->quantval[k] =
    606              table.quantval[k];
    607        }
    608        cinfo->quant_tbl_ptrs[table.slot_idx]->sent_table = FALSE;
    609      } else {
    610        jpegli_add_quant_table(cinfo, table.slot_idx, table.basic_table.data(),
    611                               table.scale_factor,
    612                               TO_JXL_BOOL(table.force_baseline));
    613      }
    614    }
    615  }
    616  if (jparams.simple_progression) {
    617    jpegli_simple_progression(cinfo);
    618    Check(jparams.progressive_mode == -1);
    619  }
    620  if (jparams.progressive_mode > 2) {
    621    const ScanScript& script = kTestScript[jparams.progressive_mode - 3];
    622    cinfo->scan_info = script.scans;
    623    cinfo->num_scans = script.num_scans;
    624  } else if (jparams.progressive_mode >= 0) {
    625    jpegli_set_progressive_level(cinfo, jparams.progressive_mode);
    626  }
    627  jpegli_set_input_format(cinfo, input.data_type, input.endianness);
    628  jpegli_enable_adaptive_quantization(
    629      cinfo, TO_JXL_BOOL(jparams.use_adaptive_quantization));
    630  cinfo->restart_interval = jparams.restart_interval;
    631  cinfo->restart_in_rows = jparams.restart_in_rows;
    632  cinfo->smoothing_factor = jparams.smoothing_factor;
    633  if (jparams.optimize_coding == 1) {
    634    cinfo->optimize_coding = TRUE;
    635  } else if (jparams.optimize_coding == 0) {
    636    cinfo->optimize_coding = FALSE;
    637  }
    638  cinfo->raw_data_in = TO_JXL_BOOL(!input.raw_data.empty());
    639  if (jparams.optimize_coding == 0 && jparams.use_flat_dc_luma_code) {
    640    JHUFF_TBL* tbl = cinfo->dc_huff_tbl_ptrs[0];
    641    memset(tbl, 0, sizeof(*tbl));
    642    tbl->bits[4] = 15;
    643    for (int i = 0; i < 15; ++i) tbl->huffval[i] = i;
    644  }
    645  if (input.coeffs.empty()) {
    646    bool write_all_tables = TRUE;
    647    if (jparams.optimize_coding == 0 && !jparams.use_flat_dc_luma_code &&
    648        jparams.omit_standard_tables) {
    649      write_all_tables = FALSE;
    650      cinfo->dc_huff_tbl_ptrs[0]->sent_table = TRUE;
    651      cinfo->dc_huff_tbl_ptrs[1]->sent_table = TRUE;
    652      cinfo->ac_huff_tbl_ptrs[0]->sent_table = TRUE;
    653      cinfo->ac_huff_tbl_ptrs[1]->sent_table = TRUE;
    654    }
    655    jpegli_start_compress(cinfo, TO_JXL_BOOL(write_all_tables));
    656    if (jparams.add_marker) {
    657      jpegli_write_marker(cinfo, kSpecialMarker0, kMarkerData,
    658                          sizeof(kMarkerData));
    659      jpegli_write_m_header(cinfo, kSpecialMarker1, sizeof(kMarkerData));
    660      for (uint8_t c : kMarkerData) {
    661        jpegli_write_m_byte(cinfo, c);
    662      }
    663      for (size_t i = 0; i < kMarkerSequenceLen; ++i) {
    664        jpegli_write_marker(cinfo, kMarkerSequence[i], kMarkerData,
    665                            ((i + 2) % sizeof(kMarkerData)));
    666      }
    667    }
    668    if (!jparams.icc.empty()) {
    669      jpegli_write_icc_profile(cinfo, jparams.icc.data(), jparams.icc.size());
    670    }
    671  }
    672  if (cinfo->raw_data_in) {
    673    // Need to copy because jpeg API requires non-const pointers.
    674    std::vector<std::vector<uint8_t>> raw_data = input.raw_data;
    675    size_t max_lines = jparams.max_v_sample() * DCTSIZE;
    676    std::vector<std::vector<JSAMPROW>> rowdata(cinfo->num_components);
    677    std::vector<JSAMPARRAY> data(cinfo->num_components);
    678    for (int c = 0; c < cinfo->num_components; ++c) {
    679      rowdata[c].resize(jparams.v_samp(c) * DCTSIZE);
    680      data[c] = rowdata[c].data();
    681    }
    682    while (cinfo->next_scanline < cinfo->image_height) {
    683      for (int c = 0; c < cinfo->num_components; ++c) {
    684        size_t cwidth = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
    685        size_t cheight = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
    686        size_t num_lines = jparams.v_samp(c) * DCTSIZE;
    687        size_t y0 = (cinfo->next_scanline / max_lines) * num_lines;
    688        for (size_t i = 0; i < num_lines; ++i) {
    689          rowdata[c][i] =
    690              (y0 + i < cheight ? &raw_data[c][(y0 + i) * cwidth] : nullptr);
    691        }
    692      }
    693      size_t num_lines = jpegli_write_raw_data(cinfo, data.data(), max_lines);
    694      Check(num_lines == max_lines);
    695    }
    696  } else if (!input.coeffs.empty()) {
    697    j_common_ptr comptr = reinterpret_cast<j_common_ptr>(cinfo);
    698    jvirt_barray_ptr* coef_arrays = reinterpret_cast<jvirt_barray_ptr*>((
    699        *cinfo->mem->alloc_small)(
    700        comptr, JPOOL_IMAGE, cinfo->num_components * sizeof(jvirt_barray_ptr)));
    701    for (int c = 0; c < cinfo->num_components; ++c) {
    702      size_t xsize_blocks = jparams.comp_width(input, c) / DCTSIZE;
    703      size_t ysize_blocks = jparams.comp_height(input, c) / DCTSIZE;
    704      coef_arrays[c] = (*cinfo->mem->request_virt_barray)(
    705          comptr, JPOOL_IMAGE, FALSE, xsize_blocks, ysize_blocks,
    706          cinfo->comp_info[c].v_samp_factor);
    707    }
    708    jpegli_write_coefficients(cinfo, coef_arrays);
    709    if (jparams.add_marker) {
    710      jpegli_write_marker(cinfo, kSpecialMarker0, kMarkerData,
    711                          sizeof(kMarkerData));
    712      jpegli_write_m_header(cinfo, kSpecialMarker1, sizeof(kMarkerData));
    713      for (uint8_t c : kMarkerData) {
    714        jpegli_write_m_byte(cinfo, c);
    715      }
    716    }
    717    for (int c = 0; c < cinfo->num_components; ++c) {
    718      jpeg_component_info* comp = &cinfo->comp_info[c];
    719      for (size_t by = 0; by < comp->height_in_blocks; ++by) {
    720        JBLOCKARRAY blocks = (*cinfo->mem->access_virt_barray)(
    721            comptr, coef_arrays[c], by, 1, TRUE);
    722        size_t stride = comp->width_in_blocks * sizeof(JBLOCK);
    723        size_t offset = by * comp->width_in_blocks * DCTSIZE2;
    724        memcpy(blocks[0], &input.coeffs[c][offset], stride);
    725      }
    726    }
    727  } else {
    728    size_t stride = cinfo->image_width * cinfo->input_components *
    729                    jpegli_bytes_per_sample(input.data_type);
    730    std::vector<uint8_t> row_bytes(stride);
    731    for (size_t y = 0; y < cinfo->image_height; ++y) {
    732      memcpy(row_bytes.data(), &input.pixels[y * stride], stride);
    733      JSAMPROW row[] = {row_bytes.data()};
    734      jpegli_write_scanlines(cinfo, row, 1);
    735    }
    736  }
    737  jpegli_finish_compress(cinfo);
    738 }
    739 
    740 bool EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
    741                      std::vector<uint8_t>* compressed) {
    742  uint8_t* buffer = nullptr;
    743  unsigned long buffer_size = 0;  // NOLINT
    744  jpeg_compress_struct cinfo;
    745  const auto try_catch_block = [&]() -> bool {
    746    ERROR_HANDLER_SETUP(jpegli);
    747    jpegli_create_compress(&cinfo);
    748    jpegli_mem_dest(&cinfo, &buffer, &buffer_size);
    749    EncodeWithJpegli(input, jparams, &cinfo);
    750    return true;
    751  };
    752  bool success = try_catch_block();
    753  jpegli_destroy_compress(&cinfo);
    754  if (success) {
    755    compressed->resize(buffer_size);
    756    std::copy_n(buffer, buffer_size, compressed->data());
    757  }
    758  if (buffer) std::free(buffer);
    759  return success;
    760 }
    761 
    762 int NumTestScanScripts() { return kNumTestScripts; }
    763 
    764 void DumpImage(const TestImage& image, const std::string& fn) {
    765  Check(image.components == 1 || image.components == 3);
    766  size_t bytes_per_sample = jpegli_bytes_per_sample(image.data_type);
    767  uint32_t maxval = (1u << (8 * bytes_per_sample)) - 1;
    768  char type = image.components == 1 ? '5' : '6';
    769  std::ofstream out(fn.c_str(), std::ofstream::binary);
    770  out << "P" << type << "\n"
    771      << image.xsize << " " << image.ysize << "\n"
    772      << maxval << "\n";
    773  out.write(reinterpret_cast<const char*>(image.pixels.data()),
    774            image.pixels.size());
    775  out.close();
    776 }
    777 
    778 double DistanceRms(const TestImage& input, const TestImage& output,
    779                   size_t start_line, size_t num_lines, double* max_diff) {
    780  size_t stride = input.xsize * input.components;
    781  size_t start_offset = start_line * stride;
    782  auto get_sample = [&](const TestImage& im, const std::vector<uint8_t>& data,
    783                        size_t idx) -> double {
    784    size_t bytes_per_sample = jpegli_bytes_per_sample(im.data_type);
    785    bool is_little_endian =
    786        (im.endianness == JPEGLI_LITTLE_ENDIAN ||
    787         (im.endianness == JPEGLI_NATIVE_ENDIAN && IsLittleEndian()));
    788    size_t offset = start_offset + idx * bytes_per_sample;
    789    Check(offset < data.size());
    790    const uint8_t* p = &data[offset];
    791    if (im.data_type == JPEGLI_TYPE_UINT8) {
    792      static const double mul8 = 1.0 / 255.0;
    793      return p[0] * mul8;
    794    } else if (im.data_type == JPEGLI_TYPE_UINT16) {
    795      static const double mul16 = 1.0 / 65535.0;
    796      return (is_little_endian ? LoadLE16(p) : LoadBE16(p)) * mul16;
    797    } else if (im.data_type == JPEGLI_TYPE_FLOAT) {
    798      return (is_little_endian ? LoadLEFloat(p) : LoadBEFloat(p));
    799    }
    800    return 0.0;
    801  };
    802  double diff2 = 0.0;
    803  size_t num_samples = 0;
    804  if (max_diff) *max_diff = 0.0;
    805  if (!input.pixels.empty() && !output.pixels.empty()) {
    806    num_samples = num_lines * stride;
    807    for (size_t i = 0; i < num_samples; ++i) {
    808      double sample_orig = get_sample(input, input.pixels, i);
    809      double sample_output = get_sample(output, output.pixels, i);
    810      double diff = sample_orig - sample_output;
    811      if (max_diff) *max_diff = std::max(*max_diff, 255.0 * std::abs(diff));
    812      diff2 += diff * diff;
    813    }
    814  } else {
    815    Check(!input.raw_data.empty());
    816    Check(!output.raw_data.empty());
    817    for (size_t c = 0; c < input.raw_data.size(); ++c) {
    818      Check(c < output.raw_data.size());
    819      num_samples += input.raw_data[c].size();
    820      for (size_t i = 0; i < input.raw_data[c].size(); ++i) {
    821        double sample_orig = get_sample(input, input.raw_data[c], i);
    822        double sample_output = get_sample(output, output.raw_data[c], i);
    823        double diff = sample_orig - sample_output;
    824        if (max_diff) *max_diff = std::max(*max_diff, 255.0 * std::abs(diff));
    825        diff2 += diff * diff;
    826      }
    827    }
    828  }
    829  return std::sqrt(diff2 / num_samples) * 255.0;
    830 }
    831 
    832 double DistanceRms(const TestImage& input, const TestImage& output,
    833                   double* max_diff) {
    834  return DistanceRms(input, output, 0, output.ysize, max_diff);
    835 }
    836 
    837 void VerifyOutputImage(const TestImage& input, const TestImage& output,
    838                       size_t start_line, size_t num_lines, double max_rms,
    839                       double max_diff) {
    840  double max_d;
    841  double rms = DistanceRms(input, output, start_line, num_lines, &max_d);
    842  printf("rms: %f, max_rms: %f, max_d: %f,  max_diff: %f\n", rms, max_rms,
    843         max_d, max_diff);
    844  Check(rms <= max_rms);
    845  Check(max_d <= max_diff);
    846 }
    847 
    848 void VerifyOutputImage(const TestImage& input, const TestImage& output,
    849                       double max_rms, double max_diff) {
    850  Check(output.xsize == input.xsize);
    851  Check(output.ysize == input.ysize);
    852  Check(output.components == input.components);
    853  Check(output.color_space == input.color_space);
    854  if (!input.coeffs.empty()) {
    855    Check(input.coeffs.size() == input.components);
    856    Check(output.coeffs.size() == input.components);
    857    for (size_t c = 0; c < input.components; ++c) {
    858      Check(output.coeffs[c].size() == input.coeffs[c].size());
    859      Check(0 == memcmp(input.coeffs[c].data(), output.coeffs[c].data(),
    860                        input.coeffs[c].size()));
    861    }
    862  } else {
    863    VerifyOutputImage(input, output, 0, output.ysize, max_rms, max_diff);
    864  }
    865 }
    866 
    867 }  // namespace jpegli