libjxl

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

input_suspension_test.cc (21791B)


      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/test_utils.h"
     12 #include "lib/jpegli/testing.h"
     13 #include "lib/jxl/base/byte_order.h"
     14 #include "lib/jxl/base/status.h"
     15 #include "lib/jxl/sanitizers.h"
     16 
     17 namespace jpegli {
     18 namespace {
     19 
     20 constexpr uint8_t kFakeEoiMarker[2] = {0xff, 0xd9};
     21 
     22 struct SourceManager {
     23   SourceManager(const uint8_t* data, size_t len, size_t max_chunk_size,
     24                 bool is_partial_file)
     25       : data_(data),
     26         len_(len),
     27         pos_(0),
     28         max_chunk_size_(max_chunk_size),
     29         is_partial_file_(is_partial_file) {
     30     pub_.init_source = init_source;
     31     pub_.fill_input_buffer = fill_input_buffer;
     32     pub_.next_input_byte = nullptr;
     33     pub_.bytes_in_buffer = 0;
     34     pub_.skip_input_data = skip_input_data;
     35     pub_.resync_to_restart = jpegli_resync_to_restart;
     36     pub_.term_source = term_source;
     37     if (max_chunk_size_ == 0) max_chunk_size_ = len;
     38   }
     39 
     40   ~SourceManager() {
     41     EXPECT_EQ(0, pub_.bytes_in_buffer);
     42     if (!is_partial_file_) {
     43       EXPECT_EQ(len_, pos_);
     44     }
     45   }
     46 
     47   bool LoadNextChunk() {
     48     if (pos_ >= len_ && !is_partial_file_) {
     49       return false;
     50     }
     51     if (pub_.bytes_in_buffer > 0) {
     52       EXPECT_LE(pub_.bytes_in_buffer, buffer_.size());
     53       memmove(buffer_.data(), pub_.next_input_byte, pub_.bytes_in_buffer);
     54     }
     55     size_t chunk_size =
     56         pos_ < len_ ? std::min(len_ - pos_, max_chunk_size_) : 2;
     57     buffer_.resize(pub_.bytes_in_buffer + chunk_size);
     58     memcpy(&buffer_[pub_.bytes_in_buffer],
     59            pos_ < len_ ? data_ + pos_ : kFakeEoiMarker, chunk_size);
     60     pub_.next_input_byte = buffer_.data();
     61     pub_.bytes_in_buffer += chunk_size;
     62     pos_ += chunk_size;
     63     return true;
     64   }
     65 
     66  private:
     67   jpeg_source_mgr pub_;
     68   std::vector<uint8_t> buffer_;
     69   const uint8_t* data_;
     70   size_t len_;
     71   size_t pos_;
     72   size_t max_chunk_size_;
     73   bool is_partial_file_;
     74 
     75   static void init_source(j_decompress_ptr cinfo) {
     76     auto* src = reinterpret_cast<SourceManager*>(cinfo->src);
     77     src->pub_.next_input_byte = nullptr;
     78     src->pub_.bytes_in_buffer = 0;
     79   }
     80 
     81   static boolean fill_input_buffer(j_decompress_ptr cinfo) { return FALSE; }
     82 
     83   static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
     84     auto* src = reinterpret_cast<SourceManager*>(cinfo->src);
     85     if (num_bytes <= 0) {
     86       return;
     87     }
     88     if (src->pub_.bytes_in_buffer >= static_cast<size_t>(num_bytes)) {
     89       src->pub_.bytes_in_buffer -= num_bytes;
     90       src->pub_.next_input_byte += num_bytes;
     91     } else {
     92       src->pos_ += num_bytes - src->pub_.bytes_in_buffer;
     93       src->pub_.bytes_in_buffer = 0;
     94     }
     95   }
     96 
     97   static void term_source(j_decompress_ptr cinfo) {}
     98 };
     99 
    100 uint8_t markers_seen[kMarkerSequenceLen];
    101 size_t num_markers_seen = 0;
    102 
    103 uint8_t get_next_byte(j_decompress_ptr cinfo) {
    104   cinfo->src->bytes_in_buffer--;
    105   return *cinfo->src->next_input_byte++;
    106 }
    107 
    108 boolean test_marker_processor(j_decompress_ptr cinfo) {
    109   markers_seen[num_markers_seen] = cinfo->unread_marker;
    110   if (cinfo->src->bytes_in_buffer < 2) {
    111     return FALSE;
    112   }
    113   size_t marker_len = (get_next_byte(cinfo) << 8) + get_next_byte(cinfo);
    114   EXPECT_EQ(2 + ((num_markers_seen + 2) % sizeof(kMarkerData)), marker_len);
    115   if (marker_len > 2) {
    116     (*cinfo->src->skip_input_data)(cinfo, marker_len - 2);
    117   }
    118   ++num_markers_seen;
    119   return TRUE;
    120 }
    121 
    122 void ReadOutputImage(const DecompressParams& dparams, j_decompress_ptr cinfo,
    123                      SourceManager* src, TestImage* output) {
    124   output->ysize = cinfo->output_height;
    125   output->xsize = cinfo->output_width;
    126   output->components = cinfo->num_components;
    127   if (cinfo->raw_data_out) {
    128     output->color_space = cinfo->jpeg_color_space;
    129     for (int c = 0; c < cinfo->num_components; ++c) {
    130       size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
    131       size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
    132       std::vector<uint8_t> plane(ysize * xsize);
    133       output->raw_data.emplace_back(std::move(plane));
    134     }
    135   } else {
    136     output->color_space = cinfo->out_color_space;
    137     output->AllocatePixels();
    138   }
    139   size_t total_output_lines = 0;
    140   while (cinfo->output_scanline < cinfo->output_height) {
    141     size_t max_lines;
    142     size_t num_output_lines;
    143     if (cinfo->raw_data_out) {
    144       size_t iMCU_height = cinfo->max_v_samp_factor * DCTSIZE;
    145       EXPECT_EQ(cinfo->output_scanline, cinfo->output_iMCU_row * iMCU_height);
    146       max_lines = iMCU_height;
    147       std::vector<std::vector<JSAMPROW>> rowdata(cinfo->num_components);
    148       std::vector<JSAMPARRAY> data(cinfo->num_components);
    149       for (int c = 0; c < cinfo->num_components; ++c) {
    150         size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
    151         size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
    152         size_t num_lines = cinfo->comp_info[c].v_samp_factor * DCTSIZE;
    153         rowdata[c].resize(num_lines);
    154         size_t y0 = cinfo->output_iMCU_row * num_lines;
    155         for (size_t i = 0; i < num_lines; ++i) {
    156           rowdata[c][i] =
    157               y0 + i < ysize ? &output->raw_data[c][(y0 + i) * xsize] : nullptr;
    158         }
    159         data[c] = rowdata[c].data();
    160       }
    161       while ((num_output_lines =
    162                   jpegli_read_raw_data(cinfo, data.data(), max_lines)) == 0) {
    163         JXL_CHECK(src && src->LoadNextChunk());
    164       }
    165     } else {
    166       size_t max_output_lines = dparams.max_output_lines;
    167       if (max_output_lines == 0) max_output_lines = cinfo->output_height;
    168       size_t lines_left = cinfo->output_height - cinfo->output_scanline;
    169       max_lines = std::min<size_t>(max_output_lines, lines_left);
    170       size_t stride = cinfo->output_width * cinfo->num_components;
    171       std::vector<JSAMPROW> scanlines(max_lines);
    172       for (size_t i = 0; i < max_lines; ++i) {
    173         size_t yidx = cinfo->output_scanline + i;
    174         scanlines[i] = &output->pixels[yidx * stride];
    175       }
    176       while ((num_output_lines = jpegli_read_scanlines(cinfo, scanlines.data(),
    177                                                        max_lines)) == 0) {
    178         JXL_CHECK(src && src->LoadNextChunk());
    179       }
    180     }
    181     total_output_lines += num_output_lines;
    182     EXPECT_EQ(total_output_lines, cinfo->output_scanline);
    183     if (num_output_lines < max_lines) {
    184       JXL_CHECK(src && src->LoadNextChunk());
    185     }
    186   }
    187 }
    188 
    189 struct TestConfig {
    190   std::string fn;
    191   std::string fn_desc;
    192   TestImage input;
    193   CompressParams jparams;
    194   DecompressParams dparams;
    195   float max_rms_dist = 1.0f;
    196 };
    197 
    198 std::vector<uint8_t> GetTestJpegData(TestConfig& config) {
    199   if (!config.fn.empty()) {
    200     return ReadTestData(config.fn);
    201   }
    202   GeneratePixels(&config.input);
    203   std::vector<uint8_t> compressed;
    204   JXL_CHECK(EncodeWithJpegli(config.input, config.jparams, &compressed));
    205   return compressed;
    206 }
    207 
    208 bool IsSequential(const TestConfig& config) {
    209   if (!config.fn.empty()) {
    210     return config.fn_desc.find("PROGR") == std::string::npos;
    211   }
    212   return config.jparams.progressive_mode <= 0;
    213 }
    214 
    215 class InputSuspensionTestParam : public ::testing::TestWithParam<TestConfig> {};
    216 
    217 TEST_P(InputSuspensionTestParam, InputOutputLockStepNonBuffered) {
    218   TestConfig config = GetParam();
    219   const DecompressParams& dparams = config.dparams;
    220   std::vector<uint8_t> compressed = GetTestJpegData(config);
    221   bool is_partial = config.dparams.size_factor < 1.0f;
    222   if (is_partial) {
    223     compressed.resize(compressed.size() * config.dparams.size_factor);
    224   }
    225   SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size,
    226                     is_partial);
    227   TestImage output0;
    228   jpeg_decompress_struct cinfo;
    229   const auto try_catch_block = [&]() -> bool {
    230     ERROR_HANDLER_SETUP(jpegli);
    231     jpegli_create_decompress(&cinfo);
    232     cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
    233 
    234     if (config.jparams.add_marker) {
    235       jpegli_save_markers(&cinfo, kSpecialMarker0, 0xffff);
    236       jpegli_save_markers(&cinfo, kSpecialMarker1, 0xffff);
    237       num_markers_seen = 0;
    238       jpegli_set_marker_processor(&cinfo, 0xe6, test_marker_processor);
    239       jpegli_set_marker_processor(&cinfo, 0xe7, test_marker_processor);
    240       jpegli_set_marker_processor(&cinfo, 0xe8, test_marker_processor);
    241     }
    242     while (jpegli_read_header(&cinfo, TRUE) == JPEG_SUSPENDED) {
    243       JXL_CHECK(src.LoadNextChunk());
    244     }
    245     SetDecompressParams(dparams, &cinfo);
    246     jpegli_set_output_format(&cinfo, dparams.data_type, dparams.endianness);
    247     if (config.jparams.add_marker) {
    248       EXPECT_EQ(num_markers_seen, kMarkerSequenceLen);
    249       EXPECT_EQ(0, memcmp(markers_seen, kMarkerSequence, num_markers_seen));
    250     }
    251     VerifyHeader(config.jparams, &cinfo);
    252     cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA);
    253 
    254     if (dparams.output_mode == COEFFICIENTS) {
    255       jvirt_barray_ptr* coef_arrays;
    256       while ((coef_arrays = jpegli_read_coefficients(&cinfo)) == nullptr) {
    257         JXL_CHECK(src.LoadNextChunk());
    258       }
    259       CopyCoefficients(&cinfo, coef_arrays, &output0);
    260     } else {
    261       while (!jpegli_start_decompress(&cinfo)) {
    262         JXL_CHECK(src.LoadNextChunk());
    263       }
    264       ReadOutputImage(dparams, &cinfo, &src, &output0);
    265     }
    266 
    267     while (!jpegli_finish_decompress(&cinfo)) {
    268       JXL_CHECK(src.LoadNextChunk());
    269     }
    270     return true;
    271   };
    272   ASSERT_TRUE(try_catch_block());
    273   jpegli_destroy_decompress(&cinfo);
    274 
    275   TestImage output1;
    276   DecodeWithLibjpeg(config.jparams, dparams, compressed, &output1);
    277   VerifyOutputImage(output1, output0, config.max_rms_dist);
    278 }
    279 
    280 TEST_P(InputSuspensionTestParam, InputOutputLockStepBuffered) {
    281   TestConfig config = GetParam();
    282   if (config.jparams.add_marker) return;
    283   const DecompressParams& dparams = config.dparams;
    284   std::vector<uint8_t> compressed = GetTestJpegData(config);
    285   bool is_partial = config.dparams.size_factor < 1.0f;
    286   if (is_partial) {
    287     compressed.resize(compressed.size() * config.dparams.size_factor);
    288   }
    289   SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size,
    290                     is_partial);
    291   std::vector<TestImage> output_progression0;
    292   jpeg_decompress_struct cinfo;
    293   const auto try_catch_block = [&]() -> bool {
    294     ERROR_HANDLER_SETUP(jpegli);
    295     jpegli_create_decompress(&cinfo);
    296 
    297     cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
    298 
    299     while (jpegli_read_header(&cinfo, TRUE) == JPEG_SUSPENDED) {
    300       JXL_CHECK(src.LoadNextChunk());
    301     }
    302     SetDecompressParams(dparams, &cinfo);
    303     jpegli_set_output_format(&cinfo, dparams.data_type, dparams.endianness);
    304 
    305     cinfo.buffered_image = TRUE;
    306     cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA);
    307 
    308     EXPECT_TRUE(jpegli_start_decompress(&cinfo));
    309     EXPECT_FALSE(jpegli_input_complete(&cinfo));
    310     EXPECT_EQ(0, cinfo.output_scan_number);
    311 
    312     int sos_marker_cnt = 1;  // read_header reads the first SOS marker
    313     while (!jpegli_input_complete(&cinfo)) {
    314       EXPECT_EQ(cinfo.input_scan_number, sos_marker_cnt);
    315       EXPECT_TRUE(jpegli_start_output(&cinfo, cinfo.input_scan_number));
    316       // start output sets output_scan_number, but does not change
    317       // input_scan_number
    318       EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number);
    319       EXPECT_EQ(cinfo.input_scan_number, sos_marker_cnt);
    320       TestImage output;
    321       ReadOutputImage(dparams, &cinfo, &src, &output);
    322       output_progression0.emplace_back(std::move(output));
    323       // read scanlines/read raw data does not change input/output scan number
    324       EXPECT_EQ(cinfo.input_scan_number, sos_marker_cnt);
    325       EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number);
    326       while (!jpegli_finish_output(&cinfo)) {
    327         JXL_CHECK(src.LoadNextChunk());
    328       }
    329       ++sos_marker_cnt;  // finish output reads the next SOS marker or EOI
    330       if (dparams.output_mode == COEFFICIENTS) {
    331         jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(&cinfo);
    332         JXL_CHECK(coef_arrays != nullptr);
    333         CopyCoefficients(&cinfo, coef_arrays, &output_progression0.back());
    334       }
    335     }
    336 
    337     EXPECT_TRUE(jpegli_finish_decompress(&cinfo));
    338     return true;
    339   };
    340   ASSERT_TRUE(try_catch_block());
    341   jpegli_destroy_decompress(&cinfo);
    342 
    343   std::vector<TestImage> output_progression1;
    344   DecodeAllScansWithLibjpeg(config.jparams, dparams, compressed,
    345                             &output_progression1);
    346   ASSERT_EQ(output_progression0.size(), output_progression1.size());
    347   for (size_t i = 0; i < output_progression0.size(); ++i) {
    348     const TestImage& output = output_progression0[i];
    349     const TestImage& expected = output_progression1[i];
    350     VerifyOutputImage(expected, output, config.max_rms_dist);
    351   }
    352 }
    353 
    354 TEST_P(InputSuspensionTestParam, PreConsumeInputBuffered) {
    355   TestConfig config = GetParam();
    356   if (config.jparams.add_marker) return;
    357   const DecompressParams& dparams = config.dparams;
    358   std::vector<uint8_t> compressed = GetTestJpegData(config);
    359   bool is_partial = config.dparams.size_factor < 1.0f;
    360   if (is_partial) {
    361     compressed.resize(compressed.size() * config.dparams.size_factor);
    362   }
    363   std::vector<TestImage> output_progression1;
    364   DecodeAllScansWithLibjpeg(config.jparams, dparams, compressed,
    365                             &output_progression1);
    366   SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size,
    367                     is_partial);
    368   TestImage output0;
    369   jpeg_decompress_struct cinfo;
    370   const auto try_catch_block = [&]() -> bool {
    371     ERROR_HANDLER_SETUP(jpegli);
    372     jpegli_create_decompress(&cinfo);
    373     cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
    374 
    375     int status;
    376     while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_SOS) {
    377       if (status == JPEG_SUSPENDED) {
    378         JXL_CHECK(src.LoadNextChunk());
    379       }
    380     }
    381     EXPECT_EQ(JPEG_REACHED_SOS, jpegli_consume_input(&cinfo));
    382     cinfo.buffered_image = TRUE;
    383     cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA);
    384     cinfo.do_block_smoothing = TO_JXL_BOOL(dparams.do_block_smoothing);
    385 
    386     EXPECT_TRUE(jpegli_start_decompress(&cinfo));
    387     EXPECT_FALSE(jpegli_input_complete(&cinfo));
    388     EXPECT_EQ(1, cinfo.input_scan_number);
    389     EXPECT_EQ(0, cinfo.output_scan_number);
    390 
    391     while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_EOI) {
    392       if (status == JPEG_SUSPENDED) {
    393         JXL_CHECK(src.LoadNextChunk());
    394       }
    395     }
    396 
    397     EXPECT_TRUE(jpegli_input_complete(&cinfo));
    398     EXPECT_EQ(output_progression1.size(), cinfo.input_scan_number);
    399     EXPECT_EQ(0, cinfo.output_scan_number);
    400 
    401     EXPECT_TRUE(jpegli_start_output(&cinfo, cinfo.input_scan_number));
    402     EXPECT_EQ(output_progression1.size(), cinfo.input_scan_number);
    403     EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number);
    404 
    405     ReadOutputImage(dparams, &cinfo, nullptr, &output0);
    406     EXPECT_EQ(output_progression1.size(), cinfo.input_scan_number);
    407     EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number);
    408 
    409     EXPECT_TRUE(jpegli_finish_output(&cinfo));
    410     if (dparams.output_mode == COEFFICIENTS) {
    411       jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(&cinfo);
    412       JXL_CHECK(coef_arrays != nullptr);
    413       CopyCoefficients(&cinfo, coef_arrays, &output0);
    414     }
    415     EXPECT_TRUE(jpegli_finish_decompress(&cinfo));
    416     return true;
    417   };
    418   ASSERT_TRUE(try_catch_block());
    419   jpegli_destroy_decompress(&cinfo);
    420 
    421   VerifyOutputImage(output_progression1.back(), output0, config.max_rms_dist);
    422 }
    423 
    424 TEST_P(InputSuspensionTestParam, PreConsumeInputNonBuffered) {
    425   TestConfig config = GetParam();
    426   if (config.jparams.add_marker || IsSequential(config)) return;
    427   const DecompressParams& dparams = config.dparams;
    428   std::vector<uint8_t> compressed = GetTestJpegData(config);
    429   bool is_partial = config.dparams.size_factor < 1.0f;
    430   if (is_partial) {
    431     compressed.resize(compressed.size() * config.dparams.size_factor);
    432   }
    433   SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size,
    434                     is_partial);
    435   TestImage output0;
    436   jpeg_decompress_struct cinfo;
    437   const auto try_catch_block = [&]() -> bool {
    438     ERROR_HANDLER_SETUP(jpegli);
    439     jpegli_create_decompress(&cinfo);
    440     cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
    441 
    442     int status;
    443     while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_SOS) {
    444       if (status == JPEG_SUSPENDED) {
    445         JXL_CHECK(src.LoadNextChunk());
    446       }
    447     }
    448     EXPECT_EQ(JPEG_REACHED_SOS, jpegli_consume_input(&cinfo));
    449     cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA);
    450     cinfo.do_block_smoothing = TO_JXL_BOOL(dparams.do_block_smoothing);
    451 
    452     if (dparams.output_mode == COEFFICIENTS) {
    453       jpegli_read_coefficients(&cinfo);
    454     } else {
    455       while (!jpegli_start_decompress(&cinfo)) {
    456         JXL_CHECK(src.LoadNextChunk());
    457       }
    458     }
    459 
    460     while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_EOI) {
    461       if (status == JPEG_SUSPENDED) {
    462         JXL_CHECK(src.LoadNextChunk());
    463       }
    464     }
    465 
    466     if (dparams.output_mode == COEFFICIENTS) {
    467       jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(&cinfo);
    468       JXL_CHECK(coef_arrays != nullptr);
    469       CopyCoefficients(&cinfo, coef_arrays, &output0);
    470     } else {
    471       ReadOutputImage(dparams, &cinfo, nullptr, &output0);
    472     }
    473 
    474     EXPECT_TRUE(jpegli_finish_decompress(&cinfo));
    475     return true;
    476   };
    477   ASSERT_TRUE(try_catch_block());
    478   jpegli_destroy_decompress(&cinfo);
    479 
    480   TestImage output1;
    481   DecodeWithLibjpeg(config.jparams, dparams, compressed, &output1);
    482   VerifyOutputImage(output1, output0, config.max_rms_dist);
    483 }
    484 
    485 std::vector<TestConfig> GenerateTests() {
    486   std::vector<TestConfig> all_tests;
    487   std::vector<std::pair<std::string, std::string>> testfiles({
    488       {"jxl/flower/flower.png.im_q85_444.jpg", "Q85YUV444"},
    489       {"jxl/flower/flower.png.im_q85_420_R13B.jpg", "Q85YUV420R13B"},
    490       {"jxl/flower/flower.png.im_q85_420_progr.jpg", "Q85YUV420PROGR"},
    491   });
    492   for (const auto& it : testfiles) {
    493     for (size_t chunk_size : {1, 64, 65536}) {
    494       for (size_t max_output_lines : {0, 1, 8, 16}) {
    495         TestConfig config;
    496         config.fn = it.first;
    497         config.fn_desc = it.second;
    498         config.dparams.chunk_size = chunk_size;
    499         config.dparams.max_output_lines = max_output_lines;
    500         all_tests.push_back(config);
    501         if (max_output_lines == 16) {
    502           config.dparams.output_mode = RAW_DATA;
    503           all_tests.push_back(config);
    504           config.dparams.output_mode = COEFFICIENTS;
    505           all_tests.push_back(config);
    506         }
    507       }
    508     }
    509   }
    510   for (size_t r : {1, 17, 1024}) {
    511     for (size_t chunk_size : {1, 65536}) {
    512       TestConfig config;
    513       config.dparams.chunk_size = chunk_size;
    514       config.jparams.progressive_mode = 2;
    515       config.jparams.restart_interval = r;
    516       all_tests.push_back(config);
    517     }
    518   }
    519   for (size_t chunk_size : {1, 4, 1024}) {
    520     TestConfig config;
    521     config.input.xsize = 256;
    522     config.input.ysize = 256;
    523     config.dparams.chunk_size = chunk_size;
    524     config.jparams.add_marker = true;
    525     all_tests.push_back(config);
    526   }
    527   // Tests for partial input.
    528   for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f}) {
    529     for (int progr : {0, 1, 3}) {
    530       for (int samp : {1, 2}) {
    531         for (JpegIOMode output_mode : {PIXELS, RAW_DATA}) {
    532           TestConfig config;
    533           config.input.xsize = 517;
    534           config.input.ysize = 523;
    535           config.jparams.h_sampling = {samp, 1, 1};
    536           config.jparams.v_sampling = {samp, 1, 1};
    537           config.jparams.progressive_mode = progr;
    538           config.dparams.size_factor = size_factor;
    539           config.dparams.output_mode = output_mode;
    540           // The last partially available block can behave differently.
    541           // TODO(szabadka) Figure out if we can make the behaviour more
    542           // similar.
    543           config.max_rms_dist = samp == 1 ? 1.75f : 3.0f;
    544           all_tests.push_back(config);
    545         }
    546       }
    547     }
    548   }
    549   // Tests for block smoothing.
    550   for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f, 1.0f}) {
    551     for (int samp : {1, 2}) {
    552       TestConfig config;
    553       config.input.xsize = 517;
    554       config.input.ysize = 523;
    555       config.jparams.h_sampling = {samp, 1, 1};
    556       config.jparams.v_sampling = {samp, 1, 1};
    557       config.jparams.progressive_mode = 2;
    558       config.dparams.size_factor = size_factor;
    559       config.dparams.do_block_smoothing = true;
    560       // libjpeg does smoothing for incomplete scans differently at
    561       // the border between current and previous scans.
    562       config.max_rms_dist = 8.0f;
    563       all_tests.push_back(config);
    564     }
    565   }
    566   return all_tests;
    567 }
    568 
    569 std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
    570   if (!c.fn.empty()) {
    571     os << c.fn_desc;
    572   } else {
    573     os << c.input;
    574   }
    575   os << c.jparams;
    576   if (c.dparams.chunk_size == 0) {
    577     os << "CompleteInput";
    578   } else {
    579     os << "InputChunks" << c.dparams.chunk_size;
    580   }
    581   if (c.dparams.size_factor < 1.0f) {
    582     os << "Partial" << static_cast<int>(c.dparams.size_factor * 100) << "p";
    583   }
    584   if (c.dparams.max_output_lines == 0) {
    585     os << "CompleteOutput";
    586   } else {
    587     os << "OutputLines" << c.dparams.max_output_lines;
    588   }
    589   if (c.dparams.output_mode == RAW_DATA) {
    590     os << "RawDataOut";
    591   } else if (c.dparams.output_mode == COEFFICIENTS) {
    592     os << "CoeffsOut";
    593   }
    594   if (c.dparams.do_block_smoothing) {
    595     os << "BlockSmoothing";
    596   }
    597   return os;
    598 }
    599 
    600 std::string TestDescription(
    601     const testing::TestParamInfo<InputSuspensionTestParam::ParamType>& info) {
    602   std::stringstream name;
    603   name << info.param;
    604   return name.str();
    605 }
    606 
    607 JPEGLI_INSTANTIATE_TEST_SUITE_P(InputSuspensionTest, InputSuspensionTestParam,
    608                                 testing::ValuesIn(GenerateTests()),
    609                                 TestDescription);
    610 
    611 }  // namespace
    612 }  // namespace jpegli