tor-browser

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

decode_api_test.cc (48453B)


      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 <jxl/types.h>
      7 
      8 #include <algorithm>
      9 #include <cstdint>
     10 #include <cstdio>
     11 #include <cstdlib>
     12 #include <cstring>
     13 #include <ostream>
     14 #include <sstream>
     15 #include <string>
     16 #include <utility>
     17 #include <vector>
     18 
     19 #include "lib/jpegli/decode.h"
     20 #include "lib/jpegli/encode.h"
     21 #include "lib/jpegli/libjpeg_test_util.h"
     22 #include "lib/jpegli/test_params.h"
     23 #include "lib/jpegli/test_utils.h"
     24 #include "lib/jpegli/testing.h"
     25 #include "lib/jpegli/types.h"
     26 #include "lib/jxl/base/status.h"
     27 
     28 namespace jpegli {
     29 namespace {
     30 
     31 constexpr uint8_t kFakeEoiMarker[2] = {0xff, 0xd9};
     32 constexpr size_t kNumSourceBuffers = 4;
     33 
     34 // Custom source manager that refills the input buffer in chunks, simulating
     35 // a file reader with a fixed buffer size.
     36 class SourceManager {
     37 public:
     38  SourceManager(const uint8_t* data, size_t len, size_t max_chunk_size)
     39      : data_(data), len_(len), max_chunk_size_(max_chunk_size) {
     40    pub_.skip_input_data = skip_input_data;
     41    pub_.resync_to_restart = jpegli_resync_to_restart;
     42    pub_.term_source = term_source;
     43    pub_.init_source = init_source;
     44    pub_.fill_input_buffer = fill_input_buffer;
     45    if (max_chunk_size_ == 0) max_chunk_size_ = len;
     46    buffers_.resize(kNumSourceBuffers, std::vector<uint8_t>(max_chunk_size_));
     47    Reset();
     48  }
     49 
     50  void Reset() {
     51    pub_.next_input_byte = nullptr;
     52    pub_.bytes_in_buffer = 0;
     53    pos_ = 0;
     54    chunk_idx_ = 0;
     55  }
     56 
     57  ~SourceManager() {
     58    EXPECT_EQ(0, pub_.bytes_in_buffer);
     59    EXPECT_EQ(len_, pos_);
     60  }
     61 
     62 private:
     63  jpeg_source_mgr pub_;
     64  const uint8_t* data_;
     65  size_t len_;
     66  size_t chunk_idx_;
     67  size_t pos_;
     68  size_t max_chunk_size_;
     69  std::vector<std::vector<uint8_t>> buffers_;
     70 
     71  static void init_source(j_decompress_ptr cinfo) {}
     72 
     73  static boolean fill_input_buffer(j_decompress_ptr cinfo) {
     74    auto* src = reinterpret_cast<SourceManager*>(cinfo->src);
     75    if (src->pos_ < src->len_) {
     76      size_t chunk_size = std::min(src->len_ - src->pos_, src->max_chunk_size_);
     77      size_t next_idx = ++src->chunk_idx_ % kNumSourceBuffers;
     78      uint8_t* next_buffer = src->buffers_[next_idx].data();
     79      memcpy(next_buffer, src->data_ + src->pos_, chunk_size);
     80      src->pub_.next_input_byte = next_buffer;
     81      src->pub_.bytes_in_buffer = chunk_size;
     82    } else {
     83      src->pub_.next_input_byte = kFakeEoiMarker;
     84      src->pub_.bytes_in_buffer = 2;
     85      src->len_ += 2;
     86    }
     87    src->pos_ += src->pub_.bytes_in_buffer;
     88    return TRUE;
     89  }
     90 
     91  static void skip_input_data(j_decompress_ptr cinfo,
     92                              long num_bytes /* NOLINT */) {
     93    auto* src = reinterpret_cast<SourceManager*>(cinfo->src);
     94    if (num_bytes <= 0) {
     95      return;
     96    }
     97    if (src->pub_.bytes_in_buffer >= static_cast<size_t>(num_bytes)) {
     98      src->pub_.bytes_in_buffer -= num_bytes;
     99      src->pub_.next_input_byte += num_bytes;
    100    } else {
    101      src->pos_ += num_bytes - src->pub_.bytes_in_buffer;
    102      src->pub_.bytes_in_buffer = 0;
    103    }
    104  }
    105 
    106  static void term_source(j_decompress_ptr cinfo) {}
    107 };
    108 
    109 uint8_t markers_seen[kMarkerSequenceLen];
    110 size_t num_markers_seen = 0;
    111 
    112 uint8_t get_next_byte(j_decompress_ptr cinfo) {
    113  if (cinfo->src->bytes_in_buffer == 0) {
    114    (*cinfo->src->fill_input_buffer)(cinfo);
    115  }
    116  cinfo->src->bytes_in_buffer--;
    117  return *cinfo->src->next_input_byte++;
    118 }
    119 
    120 boolean test_marker_processor(j_decompress_ptr cinfo) {
    121  markers_seen[num_markers_seen] = cinfo->unread_marker;
    122  size_t marker_len = (get_next_byte(cinfo) << 8) + get_next_byte(cinfo);
    123  EXPECT_EQ(2 + ((num_markers_seen + 2) % sizeof(kMarkerData)), marker_len);
    124  if (marker_len > 2) {
    125    (*cinfo->src->skip_input_data)(cinfo, marker_len - 2);
    126  }
    127  ++num_markers_seen;
    128  return TRUE;
    129 }
    130 
    131 void ReadOutputImage(const DecompressParams& dparams, j_decompress_ptr cinfo,
    132                     TestImage* output) {
    133  JDIMENSION xoffset = 0;
    134  JDIMENSION yoffset = 0;
    135  JDIMENSION xsize_cropped = cinfo->output_width;
    136  JDIMENSION ysize_cropped = cinfo->output_height;
    137  if (dparams.crop_output) {
    138    xoffset = xsize_cropped = cinfo->output_width / 3;
    139    yoffset = ysize_cropped = cinfo->output_height / 3;
    140    jpegli_crop_scanline(cinfo, &xoffset, &xsize_cropped);
    141  }
    142  output->ysize = ysize_cropped;
    143  output->xsize = cinfo->output_width;
    144  output->components = cinfo->out_color_components;
    145  output->data_type = dparams.data_type;
    146  output->endianness = dparams.endianness;
    147  size_t bytes_per_sample = jpegli_bytes_per_sample(dparams.data_type);
    148  if (cinfo->raw_data_out) {
    149    output->color_space = cinfo->jpeg_color_space;
    150    for (int c = 0; c < cinfo->num_components; ++c) {
    151      size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
    152      size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
    153      std::vector<uint8_t> plane(ysize * xsize * bytes_per_sample);
    154      output->raw_data.emplace_back(std::move(plane));
    155    }
    156  } else {
    157    output->color_space = cinfo->out_color_space;
    158    output->AllocatePixels();
    159  }
    160  size_t total_output_lines = 0;
    161  while (cinfo->output_scanline < cinfo->output_height) {
    162    size_t max_lines;
    163    size_t num_output_lines;
    164    if (cinfo->raw_data_out) {
    165      size_t iMCU_height = cinfo->max_v_samp_factor * DCTSIZE;
    166      EXPECT_EQ(cinfo->output_scanline, cinfo->output_iMCU_row * iMCU_height);
    167      max_lines = iMCU_height;
    168      std::vector<std::vector<JSAMPROW>> rowdata(cinfo->num_components);
    169      std::vector<JSAMPARRAY> data(cinfo->num_components);
    170      for (int c = 0; c < cinfo->num_components; ++c) {
    171        size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
    172        size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
    173        size_t num_lines = cinfo->comp_info[c].v_samp_factor * DCTSIZE;
    174        rowdata[c].resize(num_lines);
    175        size_t y0 = cinfo->output_iMCU_row * num_lines;
    176        for (size_t i = 0; i < num_lines; ++i) {
    177          rowdata[c][i] =
    178              y0 + i < ysize ? &output->raw_data[c][(y0 + i) * xsize] : nullptr;
    179        }
    180        data[c] = rowdata[c].data();
    181      }
    182      num_output_lines = jpegli_read_raw_data(cinfo, data.data(), max_lines);
    183    } else {
    184      size_t max_output_lines = dparams.max_output_lines;
    185      if (max_output_lines == 0) max_output_lines = cinfo->output_height;
    186      if (cinfo->output_scanline < yoffset) {
    187        max_lines = yoffset - cinfo->output_scanline;
    188        num_output_lines = jpegli_skip_scanlines(cinfo, max_lines);
    189      } else if (cinfo->output_scanline >= yoffset + ysize_cropped) {
    190        max_lines = cinfo->output_height - cinfo->output_scanline;
    191        num_output_lines = jpegli_skip_scanlines(cinfo, max_lines);
    192      } else {
    193        size_t lines_left = yoffset + ysize_cropped - cinfo->output_scanline;
    194        max_lines = std::min<size_t>(max_output_lines, lines_left);
    195        size_t stride = cinfo->output_width * cinfo->out_color_components *
    196                        bytes_per_sample;
    197        std::vector<JSAMPROW> scanlines(max_lines);
    198        for (size_t i = 0; i < max_lines; ++i) {
    199          size_t yidx = cinfo->output_scanline - yoffset + i;
    200          scanlines[i] = &output->pixels[yidx * stride];
    201        }
    202        num_output_lines =
    203            jpegli_read_scanlines(cinfo, scanlines.data(), max_lines);
    204        if (cinfo->quantize_colors) {
    205          for (size_t i = 0; i < num_output_lines; ++i) {
    206            UnmapColors(scanlines[i], cinfo->output_width,
    207                        cinfo->out_color_components, cinfo->colormap,
    208                        cinfo->actual_number_of_colors);
    209          }
    210        }
    211      }
    212    }
    213    total_output_lines += num_output_lines;
    214    EXPECT_EQ(total_output_lines, cinfo->output_scanline);
    215    EXPECT_EQ(num_output_lines, max_lines);
    216  }
    217  EXPECT_EQ(cinfo->total_iMCU_rows,
    218            DivCeil(cinfo->image_height, cinfo->max_v_samp_factor * DCTSIZE));
    219 }
    220 
    221 struct TestConfig {
    222  std::string fn;
    223  std::string fn_desc;
    224  TestImage input;
    225  CompressParams jparams;
    226  DecompressParams dparams;
    227  bool compare_to_orig = false;
    228  float max_tolerance_factor = 1.01f;
    229  float max_rms_dist = 1.0f;
    230  float max_diff = 35.0f;
    231 };
    232 
    233 jxl::StatusOr<std::vector<uint8_t>> GetTestJpegData(TestConfig& config) {
    234  std::vector<uint8_t> compressed;
    235  if (!config.fn.empty()) {
    236    JXL_ASSIGN_OR_RETURN(compressed, ReadTestData(config.fn));
    237  } else {
    238    GeneratePixels(&config.input);
    239    JXL_RETURN_IF_ERROR(
    240        EncodeWithJpegli(config.input, config.jparams, &compressed));
    241  }
    242  if (config.dparams.size_factor < 1.0f) {
    243    compressed.resize(compressed.size() * config.dparams.size_factor);
    244  }
    245  return compressed;
    246 }
    247 
    248 void TestAPINonBuffered(const CompressParams& jparams,
    249                        const DecompressParams& dparams,
    250                        const TestImage& expected_output,
    251                        j_decompress_ptr cinfo, TestImage* output) {
    252  if (jparams.add_marker) {
    253    jpegli_save_markers(cinfo, kSpecialMarker0, 0xffff);
    254    jpegli_save_markers(cinfo, kSpecialMarker1, 0xffff);
    255    num_markers_seen = 0;
    256    jpegli_set_marker_processor(cinfo, 0xe6, test_marker_processor);
    257    jpegli_set_marker_processor(cinfo, 0xe7, test_marker_processor);
    258    jpegli_set_marker_processor(cinfo, 0xe8, test_marker_processor);
    259  }
    260  if (!jparams.icc.empty()) {
    261    jpegli_save_markers(cinfo, JPEG_APP0 + 2, 0xffff);
    262  }
    263  jpegli_read_header(cinfo, /*require_image=*/TRUE);
    264  if (jparams.add_marker) {
    265    EXPECT_EQ(num_markers_seen, kMarkerSequenceLen);
    266    EXPECT_EQ(0, memcmp(markers_seen, kMarkerSequence, num_markers_seen));
    267  }
    268  if (!jparams.icc.empty()) {
    269    uint8_t* icc_data = nullptr;
    270    unsigned int icc_len;
    271    ASSERT_TRUE(jpegli_read_icc_profile(cinfo, &icc_data, &icc_len));
    272    ASSERT_TRUE(icc_data);
    273    EXPECT_EQ(0, memcmp(jparams.icc.data(), icc_data, icc_len));
    274    free(icc_data);
    275  }
    276  // Check that jpegli_calc_output_dimensions can be called multiple times
    277  // even with different parameters.
    278  if (!cinfo->raw_data_out) {
    279    cinfo->scale_num = 1;
    280    cinfo->scale_denom = 2;
    281  }
    282  jpegli_calc_output_dimensions(cinfo);
    283  SetDecompressParams(dparams, cinfo);
    284  jpegli_set_output_format(cinfo, dparams.data_type, dparams.endianness);
    285  VerifyHeader(jparams, cinfo);
    286  jpegli_calc_output_dimensions(cinfo);
    287  EXPECT_LE(expected_output.xsize, cinfo->output_width);
    288  if (!dparams.crop_output) {
    289    EXPECT_EQ(expected_output.xsize, cinfo->output_width);
    290  }
    291  if (dparams.output_mode == COEFFICIENTS) {
    292    jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(cinfo);
    293    ASSERT_TRUE(coef_arrays != nullptr);
    294    CopyCoefficients(cinfo, coef_arrays, output);
    295  } else {
    296    jpegli_start_decompress(cinfo);
    297    VerifyScanHeader(jparams, cinfo);
    298    ReadOutputImage(dparams, cinfo, output);
    299  }
    300  jpegli_finish_decompress(cinfo);
    301 }
    302 
    303 void TestAPIBuffered(const CompressParams& jparams,
    304                     const DecompressParams& dparams, j_decompress_ptr cinfo,
    305                     std::vector<TestImage>* output_progression) {
    306  EXPECT_EQ(JPEG_REACHED_SOS,
    307            jpegli_read_header(cinfo, /*require_image=*/TRUE));
    308  cinfo->buffered_image = TRUE;
    309  SetDecompressParams(dparams, cinfo);
    310  jpegli_set_output_format(cinfo, dparams.data_type, dparams.endianness);
    311  VerifyHeader(jparams, cinfo);
    312  bool has_multiple_scans = FROM_JXL_BOOL(jpegli_has_multiple_scans(cinfo));
    313  EXPECT_TRUE(jpegli_start_decompress(cinfo));
    314  // start decompress should not read the whole input in buffered image mode
    315  EXPECT_FALSE(jpegli_input_complete(cinfo));
    316  EXPECT_EQ(0, cinfo->output_scan_number);
    317  int sos_marker_cnt = 1;  // read_header reads the first SOS marker
    318  while (!jpegli_input_complete(cinfo)) {
    319    EXPECT_EQ(cinfo->input_scan_number, sos_marker_cnt);
    320    if (dparams.skip_scans && (cinfo->input_scan_number % 2) != 1) {
    321      int result = JPEG_SUSPENDED;
    322      while (result != JPEG_REACHED_SOS && result != JPEG_REACHED_EOI) {
    323        result = jpegli_consume_input(cinfo);
    324      }
    325      if (result == JPEG_REACHED_SOS) ++sos_marker_cnt;
    326      continue;
    327    }
    328    SetScanDecompressParams(dparams, cinfo, cinfo->input_scan_number);
    329    EXPECT_TRUE(jpegli_start_output(cinfo, cinfo->input_scan_number));
    330    // start output sets output_scan_number, but does not change
    331    // input_scan_number
    332    EXPECT_EQ(cinfo->output_scan_number, cinfo->input_scan_number);
    333    EXPECT_EQ(cinfo->input_scan_number, sos_marker_cnt);
    334    VerifyScanHeader(jparams, cinfo);
    335    TestImage output;
    336    ReadOutputImage(dparams, cinfo, &output);
    337    output_progression->emplace_back(std::move(output));
    338    // read scanlines/read raw data does not change input/output scan number
    339    EXPECT_EQ(cinfo->input_scan_number, sos_marker_cnt);
    340    EXPECT_EQ(cinfo->output_scan_number, cinfo->input_scan_number);
    341    EXPECT_TRUE(jpegli_finish_output(cinfo));
    342    ++sos_marker_cnt;  // finish output reads the next SOS marker or EOI
    343    if (dparams.output_mode == COEFFICIENTS) {
    344      jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(cinfo);
    345      ASSERT_TRUE(coef_arrays != nullptr);
    346      CopyCoefficients(cinfo, coef_arrays, &output_progression->back());
    347    }
    348  }
    349  jpegli_finish_decompress(cinfo);
    350  if (dparams.size_factor == 1.0f) {
    351    EXPECT_EQ(has_multiple_scans, cinfo->input_scan_number > 1);
    352  }
    353 }
    354 
    355 TEST(DecodeAPITest, ReuseCinfo) {
    356  TestImage input;
    357  TestImage output;
    358  TestImage expected;
    359  std::vector<TestImage> output_progression;
    360  std::vector<TestImage> expected_output_progression;
    361  CompressParams jparams;
    362  DecompressParams dparams;
    363  std::vector<uint8_t> compressed;
    364  jpeg_decompress_struct cinfo;
    365  const auto try_catch_block = [&]() -> bool {
    366    ERROR_HANDLER_SETUP(jpegli);
    367    jpegli_create_decompress(&cinfo);
    368    input.xsize = 129;
    369    input.ysize = 73;
    370    GeneratePixels(&input);
    371    for (int h_samp : {2, 1}) {
    372      for (int v_samp : {2, 1}) {
    373        for (int progr : {0, 2}) {
    374          jparams.h_sampling = {h_samp, 1, 1};
    375          jparams.v_sampling = {v_samp, 1, 1};
    376          jparams.progressive_mode = progr;
    377          printf(
    378              "Generating input with %dx%d chroma subsampling "
    379              "progressive level %d\n",
    380              h_samp, v_samp, progr);
    381          JPEGLI_TEST_ENSURE_TRUE(
    382              EncodeWithJpegli(input, jparams, &compressed));
    383          for (JpegIOMode output_mode : {PIXELS, RAW_DATA, COEFFICIENTS}) {
    384            for (bool crop : {true, false}) {
    385              if (crop && output_mode != PIXELS) continue;
    386              for (int scale_num : {1, 2, 3, 4, 7, 8, 13, 16}) {
    387                if (scale_num != 8 && output_mode != PIXELS) continue;
    388                int scale_denom = 8;
    389                while (scale_num % 2 == 0 && scale_denom % 2 == 0) {
    390                  scale_num /= 2;
    391                  scale_denom /= 2;
    392                }
    393                printf("Decoding with output mode %d output scaling %d/%d %s\n",
    394                       output_mode, scale_num, scale_denom,
    395                       crop ? "with cropped output" : "");
    396                dparams.output_mode = output_mode;
    397                dparams.scale_num = scale_num;
    398                dparams.scale_denom = scale_denom;
    399                expected.Clear();
    400                DecodeWithLibjpeg(jparams, dparams, compressed, &expected);
    401                output.Clear();
    402                cinfo.buffered_image = JXL_FALSE;
    403                cinfo.raw_data_out = JXL_FALSE;
    404                cinfo.scale_num = cinfo.scale_denom = 1;
    405                SourceManager src(compressed.data(), compressed.size(),
    406                                  1u << 12);
    407                cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
    408                jpegli_read_header(&cinfo, /*require_image=*/TRUE);
    409                jpegli_abort_decompress(&cinfo);
    410                src.Reset();
    411                TestAPINonBuffered(jparams, dparams, expected, &cinfo, &output);
    412                float max_rms = output_mode == COEFFICIENTS ? 0.0f : 1.0f;
    413                if (scale_num == 1 && scale_denom == 8 && h_samp != v_samp) {
    414                  max_rms = 5.0f;  // libjpeg does not do fancy upsampling
    415                }
    416                VerifyOutputImage(expected, output, max_rms);
    417                printf("Decoding in buffered image mode\n");
    418                expected_output_progression.clear();
    419                DecodeAllScansWithLibjpeg(jparams, dparams, compressed,
    420                                          &expected_output_progression);
    421                output_progression.clear();
    422                src.Reset();
    423                TestAPIBuffered(jparams, dparams, &cinfo, &output_progression);
    424                JPEGLI_TEST_ENSURE_TRUE(output_progression.size() ==
    425                                        expected_output_progression.size());
    426                for (size_t i = 0; i < output_progression.size(); ++i) {
    427                  const TestImage& output = output_progression[i];
    428                  const TestImage& expected = expected_output_progression[i];
    429                  VerifyOutputImage(expected, output, max_rms);
    430                }
    431              }
    432            }
    433          }
    434        }
    435      }
    436    }
    437    return true;
    438  };
    439  ASSERT_TRUE(try_catch_block());
    440  jpegli_destroy_decompress(&cinfo);
    441 }
    442 
    443 std::vector<TestConfig> GenerateBasicConfigs() {
    444  std::vector<TestConfig> all_configs;
    445  for (int samp : {1, 2}) {
    446    for (int progr : {0, 2}) {
    447      TestConfig config;
    448      config.input.xsize = 257 + samp * 37;
    449      config.input.ysize = 265 + (progr / 2) * 17;
    450      config.jparams.h_sampling = {samp, 1, 1};
    451      config.jparams.v_sampling = {samp, 1, 1};
    452      config.jparams.progressive_mode = progr;
    453      GeneratePixels(&config.input);
    454      all_configs.push_back(config);
    455    }
    456  }
    457  return all_configs;
    458 }
    459 
    460 TEST(DecodeAPITest, ReuseCinfoSameMemSource) {
    461  std::vector<TestConfig> all_configs = GenerateBasicConfigs();
    462  uint8_t* buffer = nullptr;
    463  unsigned long buffer_size = 0;  // NOLINT
    464  {
    465    jpeg_compress_struct cinfo;
    466    const auto try_catch_block = [&]() -> bool {
    467      ERROR_HANDLER_SETUP(jpegli);
    468      jpegli_create_compress(&cinfo);
    469      jpegli_mem_dest(&cinfo, &buffer, &buffer_size);
    470      for (const TestConfig& config : all_configs) {
    471        EncodeWithJpegli(config.input, config.jparams, &cinfo);
    472      }
    473      return true;
    474    };
    475    EXPECT_TRUE(try_catch_block());
    476    jpegli_destroy_compress(&cinfo);
    477  }
    478  std::vector<TestImage> all_outputs(all_configs.size());
    479  {
    480    jpeg_decompress_struct cinfo;
    481    const auto try_catch_block = [&]() -> bool {
    482      ERROR_HANDLER_SETUP(jpegli);
    483      jpegli_create_decompress(&cinfo);
    484      jpegli_mem_src(&cinfo, buffer, buffer_size);
    485      for (size_t i = 0; i < all_configs.size(); ++i) {
    486        TestAPINonBuffered(all_configs[i].jparams, DecompressParams(),
    487                           all_configs[i].input, &cinfo, &all_outputs[i]);
    488      }
    489      return true;
    490    };
    491    EXPECT_TRUE(try_catch_block());
    492    jpegli_destroy_decompress(&cinfo);
    493  }
    494  for (size_t i = 0; i < all_configs.size(); ++i) {
    495    VerifyOutputImage(all_configs[i].input, all_outputs[i], 2.35f);
    496  }
    497  if (buffer) free(buffer);
    498 }
    499 
    500 TEST(DecodeAPITest, ReuseCinfoSameStdSource) {
    501  std::vector<TestConfig> all_configs = GenerateBasicConfigs();
    502  FILE* tmpf = tmpfile();
    503  ASSERT_TRUE(tmpf);
    504  {
    505    jpeg_compress_struct cinfo;
    506    const auto try_catch_block = [&]() -> bool {
    507      ERROR_HANDLER_SETUP(jpegli);
    508      jpegli_create_compress(&cinfo);
    509      jpegli_stdio_dest(&cinfo, tmpf);
    510      for (const TestConfig& config : all_configs) {
    511        EncodeWithJpegli(config.input, config.jparams, &cinfo);
    512      }
    513      return true;
    514    };
    515    EXPECT_TRUE(try_catch_block());
    516    jpegli_destroy_compress(&cinfo);
    517  }
    518  fseek(tmpf, 0, SEEK_SET);
    519  std::vector<TestImage> all_outputs(all_configs.size());
    520  {
    521    jpeg_decompress_struct cinfo;
    522    const auto try_catch_block = [&]() -> bool {
    523      ERROR_HANDLER_SETUP(jpegli);
    524      jpegli_create_decompress(&cinfo);
    525      jpegli_stdio_src(&cinfo, tmpf);
    526      for (size_t i = 0; i < all_configs.size(); ++i) {
    527        TestAPINonBuffered(all_configs[i].jparams, DecompressParams(),
    528                           all_configs[i].input, &cinfo, &all_outputs[i]);
    529      }
    530      return true;
    531    };
    532    EXPECT_TRUE(try_catch_block());
    533    jpegli_destroy_decompress(&cinfo);
    534  }
    535  for (size_t i = 0; i < all_configs.size(); ++i) {
    536    VerifyOutputImage(all_configs[i].input, all_outputs[i], 2.35f);
    537  }
    538  fclose(tmpf);
    539 }
    540 
    541 TEST(DecodeAPITest, AbbreviatedStreams) {
    542  uint8_t* table_stream = nullptr;
    543  unsigned long table_stream_size = 0;  // NOLINT
    544  uint8_t* data_stream = nullptr;
    545  unsigned long data_stream_size = 0;  // NOLINT
    546  {
    547    jpeg_compress_struct cinfo;
    548    const auto try_catch_block = [&]() -> bool {
    549      ERROR_HANDLER_SETUP(jpegli);
    550      jpegli_create_compress(&cinfo);
    551      jpegli_mem_dest(&cinfo, &table_stream, &table_stream_size);
    552      cinfo.input_components = 3;
    553      cinfo.in_color_space = JCS_RGB;
    554      jpegli_set_defaults(&cinfo);
    555      jpegli_write_tables(&cinfo);
    556      jpegli_mem_dest(&cinfo, &data_stream, &data_stream_size);
    557      cinfo.image_width = 1;
    558      cinfo.image_height = 1;
    559      cinfo.optimize_coding = FALSE;
    560      jpegli_set_progressive_level(&cinfo, 0);
    561      jpegli_start_compress(&cinfo, FALSE);
    562      JSAMPLE image[3] = {0};
    563      JSAMPROW row[] = {image};
    564      jpegli_write_scanlines(&cinfo, row, 1);
    565      jpegli_finish_compress(&cinfo);
    566      return true;
    567    };
    568    EXPECT_TRUE(try_catch_block());
    569    EXPECT_LT(data_stream_size, 50);
    570    jpegli_destroy_compress(&cinfo);
    571  }
    572  {
    573    jpeg_decompress_struct cinfo = {};
    574    const auto try_catch_block = [&]() -> bool {
    575      ERROR_HANDLER_SETUP(jpegli);
    576      jpegli_create_decompress(&cinfo);
    577      jpegli_mem_src(&cinfo, table_stream, table_stream_size);
    578      jpegli_read_header(&cinfo, FALSE);
    579      jpegli_mem_src(&cinfo, data_stream, data_stream_size);
    580      jpegli_read_header(&cinfo, TRUE);
    581      EXPECT_EQ(1, cinfo.image_width);
    582      EXPECT_EQ(1, cinfo.image_height);
    583      EXPECT_EQ(3, cinfo.num_components);
    584      jpegli_start_decompress(&cinfo);
    585      JSAMPLE image[3] = {0};
    586      JSAMPROW row[] = {image};
    587      jpegli_read_scanlines(&cinfo, row, 1);
    588      EXPECT_EQ(0, image[0]);
    589      EXPECT_EQ(0, image[1]);
    590      EXPECT_EQ(0, image[2]);
    591      jpegli_finish_decompress(&cinfo);
    592      return true;
    593    };
    594    EXPECT_TRUE(try_catch_block());
    595    jpegli_destroy_decompress(&cinfo);
    596  }
    597  if (table_stream) free(table_stream);
    598  if (data_stream) free(data_stream);
    599 }
    600 
    601 class DecodeAPITestParam : public ::testing::TestWithParam<TestConfig> {};
    602 
    603 TEST_P(DecodeAPITestParam, TestAPI) {
    604  TestConfig config = GetParam();
    605  const DecompressParams& dparams = config.dparams;
    606  if (dparams.skip_scans) return;
    607  JXL_ASSIGN_OR_QUIT(std::vector<uint8_t> compressed, GetTestJpegData(config),
    608                     "Failed to create test data");
    609  SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size);
    610 
    611  TestImage output1;
    612  DecodeWithLibjpeg(config.jparams, dparams, compressed, &output1);
    613 
    614  TestImage output0;
    615  jpeg_decompress_struct cinfo;
    616  const auto try_catch_block = [&]() -> bool {
    617    ERROR_HANDLER_SETUP(jpegli);
    618    jpegli_create_decompress(&cinfo);
    619    cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
    620    TestAPINonBuffered(config.jparams, dparams, output1, &cinfo, &output0);
    621    return true;
    622  };
    623  ASSERT_TRUE(try_catch_block());
    624  jpegli_destroy_decompress(&cinfo);
    625 
    626  if (config.compare_to_orig) {
    627    double rms0 = DistanceRms(config.input, output0);
    628    double rms1 = DistanceRms(config.input, output1);
    629    printf("rms: %f  vs  %f\n", rms0, rms1);
    630    EXPECT_LE(rms0, rms1 * config.max_tolerance_factor);
    631  } else {
    632    VerifyOutputImage(output0, output1, config.max_rms_dist, config.max_diff);
    633  }
    634 }
    635 
    636 class DecodeAPITestParamBuffered : public ::testing::TestWithParam<TestConfig> {
    637 };
    638 
    639 TEST_P(DecodeAPITestParamBuffered, TestAPI) {
    640  TestConfig config = GetParam();
    641  const DecompressParams& dparams = config.dparams;
    642  JXL_ASSIGN_OR_QUIT(std::vector<uint8_t> compressed, GetTestJpegData(config),
    643                     "Failed to create test data.");
    644  SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size);
    645 
    646  std::vector<TestImage> output_progression1;
    647  DecodeAllScansWithLibjpeg(config.jparams, dparams, compressed,
    648                            &output_progression1);
    649 
    650  std::vector<TestImage> output_progression0;
    651  jpeg_decompress_struct cinfo;
    652  const auto try_catch_block = [&]() -> bool {
    653    ERROR_HANDLER_SETUP(jpegli);
    654    jpegli_create_decompress(&cinfo);
    655    cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
    656    TestAPIBuffered(config.jparams, dparams, &cinfo, &output_progression0);
    657    return true;
    658  };
    659  ASSERT_TRUE(try_catch_block());
    660  jpegli_destroy_decompress(&cinfo);
    661 
    662  ASSERT_EQ(output_progression0.size(), output_progression1.size());
    663  for (size_t i = 0; i < output_progression0.size(); ++i) {
    664    const TestImage& output = output_progression0[i];
    665    const TestImage& expected = output_progression1[i];
    666    if (config.compare_to_orig) {
    667      double rms0 = DistanceRms(config.input, output);
    668      double rms1 = DistanceRms(config.input, expected);
    669      printf("rms: %f  vs  %f\n", rms0, rms1);
    670      EXPECT_LE(rms0, rms1 * config.max_tolerance_factor);
    671    } else {
    672      VerifyOutputImage(expected, output, config.max_rms_dist, config.max_diff);
    673    }
    674  }
    675 }
    676 
    677 std::vector<TestConfig> GenerateTests(bool buffered) {
    678  std::vector<TestConfig> all_tests;
    679  {
    680    std::vector<std::pair<std::string, std::string>> testfiles({
    681        {"jxl/flower/flower.png.im_q85_420_progr.jpg", "Q85YUV420PROGR"},
    682        {"jxl/flower/flower.png.im_q85_420_R13B.jpg", "Q85YUV420R13B"},
    683        {"jxl/flower/flower.png.im_q85_444.jpg", "Q85YUV444"},
    684    });
    685    for (size_t i = 0; i < (buffered ? 1u : testfiles.size()); ++i) {
    686      TestConfig config;
    687      config.fn = testfiles[i].first;
    688      config.fn_desc = testfiles[i].second;
    689      for (size_t chunk_size : {0, 1, 64, 65536}) {
    690        config.dparams.chunk_size = chunk_size;
    691        for (size_t max_output_lines : {0, 1, 8, 16}) {
    692          config.dparams.max_output_lines = max_output_lines;
    693          config.dparams.output_mode = PIXELS;
    694          all_tests.push_back(config);
    695        }
    696        {
    697          config.dparams.max_output_lines = 16;
    698          config.dparams.output_mode = RAW_DATA;
    699          all_tests.push_back(config);
    700        }
    701      }
    702    }
    703  }
    704 
    705  {
    706    std::vector<std::pair<std::string, std::string>> testfiles({
    707        {"jxl/flower/flower_small.q85_444_non_interleaved.jpg",
    708         "Q85YUV444NonInterleaved"},
    709        {"jxl/flower/flower_small.q85_420_non_interleaved.jpg",
    710         "Q85YUV420NonInterleaved"},
    711        {"jxl/flower/flower_small.q85_444_partially_interleaved.jpg",
    712         "Q85YUV444PartiallyInterleaved"},
    713        {"jxl/flower/flower_small.q85_420_partially_interleaved.jpg",
    714         "Q85YUV420PartiallyInterleaved"},
    715        {"jxl/flower/flower.png.im_q85_422.jpg", "Q85YUV422"},
    716        {"jxl/flower/flower.png.im_q85_440.jpg", "Q85YUV440"},
    717        {"jxl/flower/flower.png.im_q85_444_1x2.jpg", "Q85YUV444_1x2"},
    718        {"jxl/flower/flower.png.im_q85_asymmetric.jpg", "Q85Asymmetric"},
    719        {"jxl/flower/flower.png.im_q85_gray.jpg", "Q85Gray"},
    720        {"jxl/flower/flower.png.im_q85_luma_subsample.jpg", "Q85LumaSubsample"},
    721        {"jxl/flower/flower.png.im_q85_rgb.jpg", "Q85RGB"},
    722        {"jxl/flower/flower.png.im_q85_rgb_subsample_blue.jpg",
    723         "Q85RGBSubsampleBlue"},
    724        {"jxl/flower/flower_small.cmyk.jpg", "CMYK"},
    725    });
    726    for (size_t i = 0; i < (buffered ? 4u : testfiles.size()); ++i) {
    727      for (JpegIOMode output_mode : {PIXELS, RAW_DATA}) {
    728        TestConfig config;
    729        config.fn = testfiles[i].first;
    730        config.fn_desc = testfiles[i].second;
    731        config.dparams.output_mode = output_mode;
    732        all_tests.push_back(config);
    733      }
    734    }
    735  }
    736 
    737  // Tests for common chroma subsampling and output modes.
    738  for (JpegIOMode output_mode : {PIXELS, RAW_DATA, COEFFICIENTS}) {
    739    for (int h_samp : {1, 2}) {
    740      for (int v_samp : {1, 2}) {
    741        for (bool fancy : {true, false}) {
    742          if (!fancy && (output_mode != PIXELS || h_samp * v_samp == 1)) {
    743            continue;
    744          }
    745          TestConfig config;
    746          config.dparams.output_mode = output_mode;
    747          config.dparams.do_fancy_upsampling = fancy;
    748          config.jparams.progressive_mode = 2;
    749          config.jparams.h_sampling = {h_samp, 1, 1};
    750          config.jparams.v_sampling = {v_samp, 1, 1};
    751          if (output_mode == COEFFICIENTS) {
    752            config.max_rms_dist = 0.0f;
    753          }
    754          all_tests.push_back(config);
    755        }
    756      }
    757    }
    758  }
    759 
    760  // Tests for partial input.
    761  for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f}) {
    762    for (int progr : {0, 1, 3}) {
    763      for (int samp : {1, 2}) {
    764        for (bool skip_scans : {false, true}) {
    765          if (skip_scans && (progr != 1 || size_factor < 0.5f)) continue;
    766          for (JpegIOMode output_mode : {PIXELS, RAW_DATA}) {
    767            TestConfig config;
    768            config.input.xsize = 517;
    769            config.input.ysize = 523;
    770            config.jparams.h_sampling = {samp, 1, 1};
    771            config.jparams.v_sampling = {samp, 1, 1};
    772            config.jparams.progressive_mode = progr;
    773            config.dparams.size_factor = size_factor;
    774            config.dparams.output_mode = output_mode;
    775            config.dparams.skip_scans = skip_scans;
    776            // The last partially available block can behave differently.
    777            // TODO(szabadka) Figure out if we can make the behaviour more
    778            // similar.
    779            config.max_rms_dist = samp == 1 ? 1.75f : 3.0f;
    780            config.max_diff = 255.0f;
    781            all_tests.push_back(config);
    782          }
    783        }
    784      }
    785    }
    786  }
    787 
    788  // Tests for block smoothing.
    789  for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f, 1.0f}) {
    790    for (int samp : {1, 2}) {
    791      for (bool skip_scans : {false, true}) {
    792        if (skip_scans && size_factor < 0.3f) continue;
    793        TestConfig config;
    794        config.input.xsize = 517;
    795        config.input.ysize = 523;
    796        config.jparams.h_sampling = {samp, 1, 1};
    797        config.jparams.v_sampling = {samp, 1, 1};
    798        config.jparams.progressive_mode = 2;
    799        config.dparams.size_factor = size_factor;
    800        config.dparams.do_block_smoothing = true;
    801        config.dparams.skip_scans = skip_scans;
    802        // libjpeg does smoothing for incomplete scans differently at
    803        // the border between current and previous scans.
    804        config.max_rms_dist = 8.0f;
    805        config.max_diff = 255.0f;
    806        all_tests.push_back(config);
    807      }
    808    }
    809  }
    810 
    811  // Test for switching output color quantization modes between scans.
    812  if (buffered) {
    813    TestConfig config;
    814    config.jparams.progressive_mode = 2;
    815    config.dparams.quantize_colors = true;
    816    config.dparams.scan_params = {
    817        {3, JDITHER_NONE, CQUANT_1PASS},  {4, JDITHER_ORDERED, CQUANT_1PASS},
    818        {5, JDITHER_FS, CQUANT_1PASS},    {6, JDITHER_NONE, CQUANT_EXTERNAL},
    819        {8, JDITHER_NONE, CQUANT_REUSE},  {9, JDITHER_NONE, CQUANT_EXTERNAL},
    820        {10, JDITHER_NONE, CQUANT_2PASS}, {11, JDITHER_NONE, CQUANT_REUSE},
    821        {12, JDITHER_NONE, CQUANT_2PASS}, {13, JDITHER_FS, CQUANT_2PASS},
    822    };
    823    config.compare_to_orig = true;
    824    config.max_tolerance_factor = 1.04f;
    825    all_tests.push_back(config);
    826  }
    827 
    828  if (buffered) {
    829    return all_tests;
    830  }
    831 
    832  // Tests for output color quantization.
    833  for (int num_colors : {8, 64, 256}) {
    834    for (ColorQuantMode mode : {CQUANT_1PASS, CQUANT_EXTERNAL, CQUANT_2PASS}) {
    835      if (mode == CQUANT_EXTERNAL && num_colors != 256) continue;
    836      for (J_DITHER_MODE dither : {JDITHER_NONE, JDITHER_ORDERED, JDITHER_FS}) {
    837        if (mode == CQUANT_EXTERNAL && dither != JDITHER_NONE) continue;
    838        if (mode != CQUANT_1PASS && dither == JDITHER_ORDERED) continue;
    839        for (bool crop : {false, true}) {
    840          for (bool scale : {false, true}) {
    841            for (bool samp : {false, true}) {
    842              if ((num_colors != 256) && (crop || scale || samp)) {
    843                continue;
    844              }
    845              if (mode == CQUANT_2PASS && crop) continue;
    846              TestConfig config;
    847              config.input.xsize = 1024;
    848              config.input.ysize = 768;
    849              config.dparams.quantize_colors = true;
    850              config.dparams.desired_number_of_colors = num_colors;
    851              config.dparams.scan_params = {{kLastScan, dither, mode}};
    852              config.dparams.crop_output = crop;
    853              if (scale) {
    854                config.dparams.scale_num = 7;
    855                config.dparams.scale_denom = 8;
    856              }
    857              if (samp) {
    858                config.jparams.h_sampling = {2, 1, 1};
    859                config.jparams.v_sampling = {2, 1, 1};
    860              }
    861              if (!scale && !crop) {
    862                config.compare_to_orig = true;
    863                if (dither != JDITHER_NONE) {
    864                  config.max_tolerance_factor = 1.05f;
    865                }
    866                if (mode == CQUANT_2PASS &&
    867                    (num_colors == 8 || dither == JDITHER_FS)) {
    868                  // TODO(szabadka) Lower this bound.
    869                  config.max_tolerance_factor = 1.5f;
    870                }
    871              } else {
    872                // We only test for buffer overflows, etc.
    873                config.max_rms_dist = 100.0f;
    874                config.max_diff = 255.0f;
    875              }
    876              all_tests.push_back(config);
    877            }
    878          }
    879        }
    880      }
    881    }
    882  }
    883 
    884  // Tests for output formats.
    885  for (JpegliDataType type :
    886       {JPEGLI_TYPE_UINT8, JPEGLI_TYPE_UINT16, JPEGLI_TYPE_FLOAT}) {
    887    for (JpegliEndianness endianness :
    888         {JPEGLI_NATIVE_ENDIAN, JPEGLI_LITTLE_ENDIAN, JPEGLI_BIG_ENDIAN}) {
    889      if (type == JPEGLI_TYPE_UINT8 && endianness != JPEGLI_NATIVE_ENDIAN) {
    890        continue;
    891      }
    892      for (int channels = 1; channels <= 4; ++channels) {
    893        TestConfig config;
    894        config.dparams.data_type = type;
    895        config.dparams.endianness = endianness;
    896        config.input.color_space = JCS_UNKNOWN;
    897        config.input.components = channels;
    898        config.dparams.set_out_color_space = true;
    899        config.dparams.out_color_space = JCS_UNKNOWN;
    900        all_tests.push_back(config);
    901      }
    902    }
    903  }
    904  // Test for output cropping.
    905  {
    906    TestConfig config;
    907    config.dparams.crop_output = true;
    908    all_tests.push_back(config);
    909  }
    910  // Tests for color transforms.
    911  for (J_COLOR_SPACE out_color_space :
    912       {JCS_RGB, JCS_GRAYSCALE, JCS_EXT_RGB, JCS_EXT_BGR, JCS_EXT_RGBA,
    913        JCS_EXT_BGRA, JCS_EXT_ARGB, JCS_EXT_ABGR}) {
    914    TestConfig config;
    915    config.input.xsize = config.input.ysize = 256;
    916    config.input.color_space = JCS_GRAYSCALE;
    917    config.dparams.set_out_color_space = true;
    918    config.dparams.out_color_space = out_color_space;
    919    all_tests.push_back(config);
    920  }
    921  for (J_COLOR_SPACE jpeg_color_space : {JCS_RGB, JCS_YCbCr}) {
    922    for (J_COLOR_SPACE out_color_space :
    923         {JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE, JCS_EXT_RGB, JCS_EXT_BGR,
    924          JCS_EXT_RGBA, JCS_EXT_BGRA, JCS_EXT_ARGB, JCS_EXT_ABGR}) {
    925      if (jpeg_color_space == JCS_RGB && out_color_space == JCS_YCbCr) continue;
    926      TestConfig config;
    927      config.input.xsize = config.input.ysize = 256;
    928      config.jparams.set_jpeg_colorspace = true;
    929      config.jparams.jpeg_color_space = jpeg_color_space;
    930      config.dparams.set_out_color_space = true;
    931      config.dparams.out_color_space = out_color_space;
    932      all_tests.push_back(config);
    933    }
    934  }
    935  for (J_COLOR_SPACE jpeg_color_space : {JCS_CMYK, JCS_YCCK}) {
    936    for (J_COLOR_SPACE out_color_space : {JCS_CMYK, JCS_YCCK}) {
    937      if (jpeg_color_space == JCS_CMYK && out_color_space == JCS_YCCK) continue;
    938      TestConfig config;
    939      config.input.xsize = config.input.ysize = 256;
    940      config.input.color_space = JCS_CMYK;
    941      config.jparams.set_jpeg_colorspace = true;
    942      config.jparams.jpeg_color_space = jpeg_color_space;
    943      config.dparams.set_out_color_space = true;
    944      config.dparams.out_color_space = out_color_space;
    945      all_tests.push_back(config);
    946    }
    947  }
    948  // Tests for progressive levels.
    949  for (int p = 0; p < 3 + NumTestScanScripts(); ++p) {
    950    TestConfig config;
    951    config.jparams.progressive_mode = p;
    952    all_tests.push_back(config);
    953  }
    954  // Tests for RST markers.
    955  for (size_t r : {1, 17, 1024}) {
    956    for (size_t chunk_size : {1, 65536}) {
    957      for (int progr : {0, 2}) {
    958        TestConfig config;
    959        config.dparams.chunk_size = chunk_size;
    960        config.jparams.progressive_mode = progr;
    961        config.jparams.restart_interval = r;
    962        all_tests.push_back(config);
    963      }
    964    }
    965  }
    966  for (size_t rr : {1, 3, 8, 100}) {
    967    TestConfig config;
    968    config.jparams.restart_in_rows = rr;
    969    all_tests.push_back(config);
    970  }
    971  // Tests for custom quantization tables.
    972  for (int type : {0, 1, 10, 100, 10000}) {
    973    for (int scale : {1, 50, 100, 200, 500}) {
    974      for (bool add_raw : {false, true}) {
    975        for (bool baseline : {true, false}) {
    976          if (!baseline && (add_raw || type * scale < 25500)) continue;
    977          TestConfig config;
    978          config.input.xsize = 64;
    979          config.input.ysize = 64;
    980          CustomQuantTable table;
    981          table.table_type = type;
    982          table.scale_factor = scale;
    983          table.force_baseline = baseline;
    984          table.add_raw = add_raw;
    985          table.Generate();
    986          config.jparams.quant_tables.push_back(table);
    987          config.jparams.quant_indexes = {0, 0, 0};
    988          config.compare_to_orig = true;
    989          config.max_tolerance_factor = 1.02;
    990          all_tests.push_back(config);
    991        }
    992      }
    993    }
    994  }
    995  for (int qidx = 0; qidx < 8; ++qidx) {
    996    if (qidx == 3) continue;
    997    TestConfig config;
    998    config.input.xsize = 256;
    999    config.input.ysize = 256;
   1000    config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1,
   1001                                    (qidx >> 0) & 1};
   1002    all_tests.push_back(config);
   1003  }
   1004  for (int qidx = 0; qidx < 8; ++qidx) {
   1005    for (int slot_idx = 0; slot_idx < 2; ++slot_idx) {
   1006      if (qidx == 0 && slot_idx == 0) continue;
   1007      TestConfig config;
   1008      config.input.xsize = 256;
   1009      config.input.ysize = 256;
   1010      config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1,
   1011                                      (qidx >> 0) & 1};
   1012      CustomQuantTable table;
   1013      table.slot_idx = slot_idx;
   1014      table.Generate();
   1015      config.jparams.quant_tables.push_back(table);
   1016      all_tests.push_back(config);
   1017    }
   1018  }
   1019  for (int qidx = 0; qidx < 8; ++qidx) {
   1020    for (bool xyb : {false, true}) {
   1021      TestConfig config;
   1022      config.input.xsize = 256;
   1023      config.input.ysize = 256;
   1024      config.jparams.xyb_mode = xyb;
   1025      config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1,
   1026                                      (qidx >> 0) & 1};
   1027      {
   1028        CustomQuantTable table;
   1029        table.slot_idx = 0;
   1030        table.Generate();
   1031        config.jparams.quant_tables.push_back(table);
   1032      }
   1033      {
   1034        CustomQuantTable table;
   1035        table.slot_idx = 1;
   1036        table.table_type = 20;
   1037        table.Generate();
   1038        config.jparams.quant_tables.push_back(table);
   1039      }
   1040      config.compare_to_orig = true;
   1041      all_tests.push_back(config);
   1042    }
   1043  }
   1044  for (bool xyb : {false, true}) {
   1045    TestConfig config;
   1046    config.input.xsize = 256;
   1047    config.input.ysize = 256;
   1048    config.jparams.xyb_mode = xyb;
   1049    config.jparams.quant_indexes = {0, 1, 2};
   1050    {
   1051      CustomQuantTable table;
   1052      table.slot_idx = 0;
   1053      table.Generate();
   1054      config.jparams.quant_tables.push_back(table);
   1055    }
   1056    {
   1057      CustomQuantTable table;
   1058      table.slot_idx = 1;
   1059      table.table_type = 20;
   1060      table.Generate();
   1061      config.jparams.quant_tables.push_back(table);
   1062    }
   1063    {
   1064      CustomQuantTable table;
   1065      table.slot_idx = 2;
   1066      table.table_type = 30;
   1067      table.Generate();
   1068      config.jparams.quant_tables.push_back(table);
   1069    }
   1070    config.compare_to_orig = true;
   1071    all_tests.push_back(config);
   1072  }
   1073  // Tests for fixed (and custom) prefix codes.
   1074  for (J_COLOR_SPACE jpeg_color_space : {JCS_RGB, JCS_YCbCr}) {
   1075    for (bool flat_dc_luma : {false, true}) {
   1076      TestConfig config;
   1077      config.jparams.set_jpeg_colorspace = true;
   1078      config.jparams.jpeg_color_space = jpeg_color_space;
   1079      config.jparams.progressive_mode = 0;
   1080      config.jparams.optimize_coding = 0;
   1081      config.jparams.use_flat_dc_luma_code = flat_dc_luma;
   1082      all_tests.push_back(config);
   1083    }
   1084  }
   1085  for (J_COLOR_SPACE jpeg_color_space : {JCS_CMYK, JCS_YCCK}) {
   1086    for (bool flat_dc_luma : {false, true}) {
   1087      TestConfig config;
   1088      config.input.color_space = JCS_CMYK;
   1089      config.jparams.set_jpeg_colorspace = true;
   1090      config.jparams.jpeg_color_space = jpeg_color_space;
   1091      config.jparams.progressive_mode = 0;
   1092      config.jparams.optimize_coding = 0;
   1093      config.jparams.use_flat_dc_luma_code = flat_dc_luma;
   1094      all_tests.push_back(config);
   1095    }
   1096  }
   1097  // Test for jpeg without DHT marker.
   1098  {
   1099    TestConfig config;
   1100    config.jparams.progressive_mode = 0;
   1101    config.jparams.optimize_coding = 0;
   1102    config.jparams.omit_standard_tables = true;
   1103    all_tests.push_back(config);
   1104  }
   1105  // Test for custom component ids.
   1106  {
   1107    TestConfig config;
   1108    config.input.xsize = config.input.ysize = 128;
   1109    config.jparams.comp_ids = {7, 17, 177};
   1110    all_tests.push_back(config);
   1111  }
   1112  // Tests for JFIF/Adobe markers.
   1113  for (int override_JFIF : {-1, 0, 1}) {
   1114    for (int override_Adobe : {-1, 0, 1}) {
   1115      if (override_JFIF == -1 && override_Adobe == -1) continue;
   1116      TestConfig config;
   1117      config.input.xsize = config.input.ysize = 128;
   1118      config.jparams.override_JFIF = override_JFIF;
   1119      config.jparams.override_Adobe = override_Adobe;
   1120      all_tests.push_back(config);
   1121    }
   1122  }
   1123  // Tests for small images.
   1124  for (int xsize : {1, 7, 8, 9, 15, 16, 17}) {
   1125    for (int ysize : {1, 7, 8, 9, 15, 16, 17}) {
   1126      TestConfig config;
   1127      config.input.xsize = xsize;
   1128      config.input.ysize = ysize;
   1129      config.jparams.h_sampling = {1, 1, 1};
   1130      config.jparams.v_sampling = {1, 1, 1};
   1131      all_tests.push_back(config);
   1132    }
   1133  }
   1134  // Tests for custom marker processor.
   1135  for (size_t chunk_size : {0, 1, 64, 65536}) {
   1136    TestConfig config;
   1137    config.input.xsize = config.input.ysize = 256;
   1138    config.dparams.chunk_size = chunk_size;
   1139    config.jparams.add_marker = true;
   1140    all_tests.push_back(config);
   1141  }
   1142  // Tests for icc profile decoding.
   1143  for (size_t icc_size : {728, 70000, 1000000}) {
   1144    TestConfig config;
   1145    config.input.xsize = config.input.ysize = 256;
   1146    config.jparams.icc.resize(icc_size);
   1147    for (size_t i = 0; i < icc_size; ++i) {
   1148      config.jparams.icc[i] = (i * 17) & 0xff;
   1149    }
   1150    all_tests.push_back(config);
   1151  }
   1152  // Tests for unusual sampling factors.
   1153  for (int h0_samp : {1, 2, 3, 4}) {
   1154    for (int v0_samp : {1, 2, 3, 4}) {
   1155      for (int dxb = 0; dxb < h0_samp; ++dxb) {
   1156        for (int dyb = 0; dyb < v0_samp; ++dyb) {
   1157          for (int dx = 0; dx < 2; ++dx) {
   1158            for (int dy = 0; dy < 2; ++dy) {
   1159              TestConfig config;
   1160              config.input.xsize = 128 + dyb * 8 + dy;
   1161              config.input.ysize = 256 + dxb * 8 + dx;
   1162              config.jparams.progressive_mode = 2;
   1163              config.jparams.h_sampling = {h0_samp, 1, 1};
   1164              config.jparams.v_sampling = {v0_samp, 1, 1};
   1165              config.compare_to_orig = true;
   1166              all_tests.push_back(config);
   1167            }
   1168          }
   1169        }
   1170      }
   1171    }
   1172  }
   1173  for (int h0_samp : {1, 2, 4}) {
   1174    for (int v0_samp : {1, 2, 4}) {
   1175      for (int h2_samp : {1, 2, 4}) {
   1176        for (int v2_samp : {1, 2, 4}) {
   1177          TestConfig config;
   1178          config.input.xsize = 137;
   1179          config.input.ysize = 75;
   1180          config.jparams.progressive_mode = 2;
   1181          config.jparams.h_sampling = {h0_samp, 1, h2_samp};
   1182          config.jparams.v_sampling = {v0_samp, 1, v2_samp};
   1183          config.compare_to_orig = true;
   1184          all_tests.push_back(config);
   1185        }
   1186      }
   1187    }
   1188  }
   1189  {
   1190    TestConfig config;
   1191    config.input.xsize = 137;
   1192    config.input.ysize = 80;
   1193    config.jparams.progressive_mode = 0;
   1194    config.jparams.h_sampling = {1, 1, 1};
   1195    config.jparams.v_sampling = {4, 2, 1};
   1196    config.compare_to_orig = true;
   1197    all_tests.push_back(config);
   1198  }
   1199  for (int h0_samp : {1, 3}) {
   1200    for (int v0_samp : {1, 3}) {
   1201      for (int h2_samp : {1, 3}) {
   1202        for (int v2_samp : {1, 3}) {
   1203          TestConfig config;
   1204          config.input.xsize = 205;
   1205          config.input.ysize = 99;
   1206          config.jparams.progressive_mode = 2;
   1207          config.jparams.h_sampling = {h0_samp, 1, h2_samp};
   1208          config.jparams.v_sampling = {v0_samp, 1, v2_samp};
   1209          all_tests.push_back(config);
   1210        }
   1211      }
   1212    }
   1213  }
   1214  // Tests for output scaling.
   1215  for (int scale_num = 1; scale_num <= 16; ++scale_num) {
   1216    if (scale_num == 8) continue;
   1217    for (bool crop : {false, true}) {
   1218      for (int samp : {1, 2}) {
   1219        for (int progr : {0, 2}) {
   1220          TestConfig config;
   1221          config.jparams.h_sampling = {samp, 1, 1};
   1222          config.jparams.v_sampling = {samp, 1, 1};
   1223          config.jparams.progressive_mode = progr;
   1224          config.dparams.scale_num = scale_num;
   1225          config.dparams.scale_denom = 8;
   1226          config.dparams.crop_output = crop;
   1227          all_tests.push_back(config);
   1228        }
   1229      }
   1230    }
   1231  }
   1232  return all_tests;
   1233 }
   1234 
   1235 std::string QuantMode(ColorQuantMode mode) {
   1236  switch (mode) {
   1237    case CQUANT_1PASS:
   1238      return "1pass";
   1239    case CQUANT_EXTERNAL:
   1240      return "External";
   1241    case CQUANT_2PASS:
   1242      return "2pass";
   1243    case CQUANT_REUSE:
   1244      return "Reuse";
   1245  }
   1246  return "";
   1247 }
   1248 
   1249 std::string DitherMode(J_DITHER_MODE mode) {
   1250  switch (mode) {
   1251    case JDITHER_NONE:
   1252      return "No";
   1253    case JDITHER_ORDERED:
   1254      return "Ordered";
   1255    case JDITHER_FS:
   1256      return "FS";
   1257  }
   1258  return "";
   1259 }
   1260 
   1261 std::ostream& operator<<(std::ostream& os, const DecompressParams& dparams) {
   1262  if (dparams.chunk_size == 0) {
   1263    os << "CompleteInput";
   1264  } else {
   1265    os << "InputChunks" << dparams.chunk_size;
   1266  }
   1267  if (dparams.size_factor < 1.0f) {
   1268    os << "Partial" << static_cast<int>(dparams.size_factor * 100) << "p";
   1269  }
   1270  if (dparams.max_output_lines == 0) {
   1271    os << "CompleteOutput";
   1272  } else {
   1273    os << "OutputLines" << dparams.max_output_lines;
   1274  }
   1275  if (dparams.output_mode == RAW_DATA) {
   1276    os << "RawDataOut";
   1277  } else if (dparams.output_mode == COEFFICIENTS) {
   1278    os << "CoeffsOut";
   1279  }
   1280  os << IOMethodName(dparams.data_type, dparams.endianness);
   1281  if (dparams.set_out_color_space) {
   1282    os << "OutColor"
   1283       << ColorSpaceName(static_cast<J_COLOR_SPACE>(dparams.out_color_space));
   1284  }
   1285  if (dparams.crop_output) {
   1286    os << "Crop";
   1287  }
   1288  if (dparams.do_block_smoothing) {
   1289    os << "BlockSmoothing";
   1290  }
   1291  if (!dparams.do_fancy_upsampling) {
   1292    os << "NoFancyUpsampling";
   1293  }
   1294  if (dparams.scale_num != 1 || dparams.scale_denom != 1) {
   1295    os << "Scale" << dparams.scale_num << "_" << dparams.scale_denom;
   1296  }
   1297  if (dparams.quantize_colors) {
   1298    os << "Quant" << dparams.desired_number_of_colors << "colors";
   1299    for (size_t i = 0; i < dparams.scan_params.size(); ++i) {
   1300      if (i > 0) os << "_";
   1301      const auto& sparam = dparams.scan_params[i];
   1302      os << QuantMode(sparam.color_quant_mode);
   1303      os << DitherMode(static_cast<J_DITHER_MODE>(sparam.dither_mode))
   1304         << "Dither";
   1305    }
   1306  }
   1307  if (dparams.skip_scans) {
   1308    os << "SkipScans";
   1309  }
   1310  return os;
   1311 }
   1312 
   1313 std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
   1314  if (!c.fn.empty()) {
   1315    os << c.fn_desc;
   1316  } else {
   1317    os << c.input;
   1318  }
   1319  os << c.jparams;
   1320  os << c.dparams;
   1321  return os;
   1322 }
   1323 
   1324 std::string TestDescription(const testing::TestParamInfo<TestConfig>& info) {
   1325  std::stringstream name;
   1326  name << info.param;
   1327  return name.str();
   1328 }
   1329 
   1330 JPEGLI_INSTANTIATE_TEST_SUITE_P(DecodeAPITest, DecodeAPITestParam,
   1331                                testing::ValuesIn(GenerateTests(false)),
   1332                                TestDescription);
   1333 
   1334 JPEGLI_INSTANTIATE_TEST_SUITE_P(DecodeAPITestBuffered,
   1335                                DecodeAPITestParamBuffered,
   1336                                testing::ValuesIn(GenerateTests(true)),
   1337                                TestDescription);
   1338 
   1339 }  // namespace
   1340 }  // namespace jpegli