libjxl

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

decode_api_test.cc (47343B)


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