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