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