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 (28298B)


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