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