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