test_utils.cc (28298B)
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 "lib/jpegli/test_utils.h" 7 8 #include <cmath> 9 #include <cstdint> 10 #include <fstream> 11 12 #include "lib/jpegli/decode.h" 13 #include "lib/jpegli/encode.h" 14 #include "lib/jxl/base/byte_order.h" 15 #include "lib/jxl/base/printf_macros.h" 16 #include "lib/jxl/base/status.h" 17 #include "lib/jxl/sanitizers.h" 18 19 #if !defined(TEST_DATA_PATH) 20 #include "tools/cpp/runfiles/runfiles.h" 21 #endif 22 23 namespace jpegli { 24 25 #define JPEG_API_FN(name) jpegli_##name 26 #include "lib/jpegli/test_utils-inl.h" 27 #undef JPEG_API_FN 28 29 #if defined(TEST_DATA_PATH) 30 std::string GetTestDataPath(const std::string& filename) { 31 return std::string(TEST_DATA_PATH "/") + filename; 32 } 33 #else 34 using bazel::tools::cpp::runfiles::Runfiles; 35 const std::unique_ptr<Runfiles> kRunfiles(Runfiles::Create("")); 36 std::string GetTestDataPath(const std::string& filename) { 37 std::string root(JPEGXL_ROOT_PACKAGE "/testdata/"); 38 return kRunfiles->Rlocation(root + filename); 39 } 40 #endif 41 42 std::vector<uint8_t> ReadTestData(const std::string& filename) { 43 std::string full_path = GetTestDataPath(filename); 44 fprintf(stderr, "ReadTestData %s\n", full_path.c_str()); 45 std::ifstream file(full_path, std::ios::binary); 46 std::vector<char> str((std::istreambuf_iterator<char>(file)), 47 std::istreambuf_iterator<char>()); 48 JXL_CHECK(file.good()); 49 const uint8_t* raw = reinterpret_cast<const uint8_t*>(str.data()); 50 std::vector<uint8_t> data(raw, raw + str.size()); 51 printf("Test data %s is %d bytes long.\n", filename.c_str(), 52 static_cast<int>(data.size())); 53 return data; 54 } 55 56 void CustomQuantTable::Generate() { 57 basic_table.resize(DCTSIZE2); 58 quantval.resize(DCTSIZE2); 59 switch (table_type) { 60 case 0: { 61 for (int k = 0; k < DCTSIZE2; ++k) { 62 basic_table[k] = k + 1; 63 } 64 break; 65 } 66 default: 67 for (int k = 0; k < DCTSIZE2; ++k) { 68 basic_table[k] = table_type; 69 } 70 } 71 for (int k = 0; k < DCTSIZE2; ++k) { 72 quantval[k] = (basic_table[k] * scale_factor + 50U) / 100U; 73 quantval[k] = std::max(quantval[k], 1U); 74 quantval[k] = std::min(quantval[k], 65535U); 75 if (!add_raw) { 76 quantval[k] = std::min(quantval[k], force_baseline ? 255U : 32767U); 77 } 78 } 79 } 80 81 bool PNMParser::ParseHeader(const uint8_t** pos, size_t* xsize, size_t* ysize, 82 size_t* num_channels, size_t* bitdepth) { 83 if (pos_[0] != 'P' || (pos_[1] != '5' && pos_[1] != '6')) { 84 fprintf(stderr, "Invalid PNM header."); 85 return false; 86 } 87 *num_channels = (pos_[1] == '5' ? 1 : 3); 88 pos_ += 2; 89 90 size_t maxval; 91 if (!SkipWhitespace() || !ParseUnsigned(xsize) || !SkipWhitespace() || 92 !ParseUnsigned(ysize) || !SkipWhitespace() || !ParseUnsigned(&maxval) || 93 !SkipWhitespace()) { 94 return false; 95 } 96 if (maxval == 0 || maxval >= 65536) { 97 fprintf(stderr, "Invalid maxval value.\n"); 98 return false; 99 } 100 bool found_bitdepth = false; 101 for (int bits = 1; bits <= 16; ++bits) { 102 if (maxval == (1u << bits) - 1) { 103 *bitdepth = bits; 104 found_bitdepth = true; 105 break; 106 } 107 } 108 if (!found_bitdepth) { 109 fprintf(stderr, "Invalid maxval value.\n"); 110 return false; 111 } 112 113 *pos = pos_; 114 return true; 115 } 116 117 bool PNMParser::ParseUnsigned(size_t* number) { 118 if (pos_ == end_ || *pos_ < '0' || *pos_ > '9') { 119 fprintf(stderr, "Expected unsigned number.\n"); 120 return false; 121 } 122 *number = 0; 123 while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { 124 *number *= 10; 125 *number += *pos_ - '0'; 126 ++pos_; 127 } 128 129 return true; 130 } 131 132 bool PNMParser::SkipWhitespace() { 133 if (pos_ == end_ || !IsWhitespace(*pos_)) { 134 fprintf(stderr, "Expected whitespace.\n"); 135 return false; 136 } 137 while (pos_ < end_ && IsWhitespace(*pos_)) { 138 ++pos_; 139 } 140 return true; 141 } 142 143 bool ReadPNM(const std::vector<uint8_t>& data, size_t* xsize, size_t* ysize, 144 size_t* num_channels, size_t* bitdepth, 145 std::vector<uint8_t>* pixels) { 146 if (data.size() < 2) { 147 fprintf(stderr, "PNM file too small.\n"); 148 return false; 149 } 150 PNMParser parser(data.data(), data.size()); 151 const uint8_t* pos = nullptr; 152 if (!parser.ParseHeader(&pos, xsize, ysize, num_channels, bitdepth)) { 153 return false; 154 } 155 pixels->resize(data.data() + data.size() - pos); 156 memcpy(pixels->data(), pos, pixels->size()); 157 return true; 158 } 159 160 std::string ColorSpaceName(J_COLOR_SPACE colorspace) { 161 switch (colorspace) { 162 case JCS_UNKNOWN: 163 return "UNKNOWN"; 164 case JCS_GRAYSCALE: 165 return "GRAYSCALE"; 166 case JCS_RGB: 167 return "RGB"; 168 case JCS_YCbCr: 169 return "YCbCr"; 170 case JCS_CMYK: 171 return "CMYK"; 172 case JCS_YCCK: 173 return "YCCK"; 174 default: 175 return ""; 176 } 177 } 178 179 std::string IOMethodName(JpegliDataType data_type, 180 JpegliEndianness endianness) { 181 std::string retval; 182 if (data_type == JPEGLI_TYPE_UINT8) { 183 return ""; 184 } else if (data_type == JPEGLI_TYPE_UINT16) { 185 retval = "UINT16"; 186 } else if (data_type == JPEGLI_TYPE_FLOAT) { 187 retval = "FLOAT"; 188 } 189 if (endianness == JPEGLI_LITTLE_ENDIAN) { 190 retval += "LE"; 191 } else if (endianness == JPEGLI_BIG_ENDIAN) { 192 retval += "BE"; 193 } 194 return retval; 195 } 196 197 std::string SamplingId(const CompressParams& jparams) { 198 std::stringstream os; 199 JXL_CHECK(jparams.h_sampling.size() == jparams.v_sampling.size()); 200 if (!jparams.h_sampling.empty()) { 201 size_t len = jparams.h_sampling.size(); 202 while (len > 1 && jparams.h_sampling[len - 1] == 1 && 203 jparams.v_sampling[len - 1] == 1) { 204 --len; 205 } 206 os << "SAMP"; 207 for (size_t i = 0; i < len; ++i) { 208 if (i > 0) os << "_"; 209 os << jparams.h_sampling[i] << "x" << jparams.v_sampling[i]; 210 } 211 } 212 return os.str(); 213 } 214 215 std::ostream& operator<<(std::ostream& os, const TestImage& input) { 216 os << input.xsize << "x" << input.ysize; 217 os << IOMethodName(input.data_type, input.endianness); 218 if (input.color_space != JCS_RGB) { 219 os << "InputColor" 220 << ColorSpaceName(static_cast<J_COLOR_SPACE>(input.color_space)); 221 } 222 if (input.color_space == JCS_UNKNOWN) { 223 os << input.components; 224 } 225 return os; 226 } 227 228 std::ostream& operator<<(std::ostream& os, const CompressParams& jparams) { 229 os << "Q" << jparams.quality; 230 os << SamplingId(jparams); 231 if (jparams.set_jpeg_colorspace) { 232 os << "JpegColor" 233 << ColorSpaceName(static_cast<J_COLOR_SPACE>(jparams.jpeg_color_space)); 234 } 235 if (!jparams.comp_ids.empty()) { 236 os << "CID"; 237 for (int cid : jparams.comp_ids) { 238 os << cid; 239 } 240 } 241 if (!jparams.quant_indexes.empty()) { 242 os << "QIDX"; 243 for (int qi : jparams.quant_indexes) { 244 os << qi; 245 } 246 for (const auto& table : jparams.quant_tables) { 247 os << "TABLE" << table.slot_idx << "T" << table.table_type << "F" 248 << table.scale_factor 249 << (table.add_raw ? "R" 250 : table.force_baseline ? "B" 251 : ""); 252 } 253 } 254 if (jparams.progressive_mode >= 0) { 255 os << "P" << jparams.progressive_mode; 256 } else if (jparams.simple_progression) { 257 os << "Psimple"; 258 } 259 if (jparams.optimize_coding == 1) { 260 os << "OptimizedCode"; 261 } else if (jparams.optimize_coding == 0) { 262 os << "FixedCode"; 263 if (jparams.use_flat_dc_luma_code) { 264 os << "FlatDCLuma"; 265 } else if (jparams.omit_standard_tables) { 266 os << "OmitDHT"; 267 } 268 } 269 if (!jparams.use_adaptive_quantization) { 270 os << "NoAQ"; 271 } 272 if (jparams.restart_interval > 0) { 273 os << "R" << jparams.restart_interval; 274 } 275 if (jparams.restart_in_rows > 0) { 276 os << "RR" << jparams.restart_in_rows; 277 } 278 if (jparams.xyb_mode) { 279 os << "XYB"; 280 } else if (jparams.libjpeg_mode) { 281 os << "Libjpeg"; 282 } 283 if (jparams.override_JFIF >= 0) { 284 os << (jparams.override_JFIF ? "AddJFIF" : "NoJFIF"); 285 } 286 if (jparams.override_Adobe >= 0) { 287 os << (jparams.override_Adobe ? "AddAdobe" : "NoAdobe"); 288 } 289 if (jparams.add_marker) { 290 os << "AddMarker"; 291 } 292 if (!jparams.icc.empty()) { 293 os << "ICCSize" << jparams.icc.size(); 294 } 295 if (jparams.smoothing_factor != 0) { 296 os << "SF" << jparams.smoothing_factor; 297 } 298 return os; 299 } 300 301 void SetNumChannels(J_COLOR_SPACE colorspace, size_t* channels) { 302 if (colorspace == JCS_GRAYSCALE) { 303 *channels = 1; 304 } else if (colorspace == JCS_RGB || colorspace == JCS_YCbCr) { 305 *channels = 3; 306 } else if (colorspace == JCS_CMYK || colorspace == JCS_YCCK) { 307 *channels = 4; 308 } else if (colorspace == JCS_UNKNOWN) { 309 JXL_CHECK(*channels <= 4); 310 } else { 311 JXL_ABORT(); 312 } 313 } 314 315 void RGBToYCbCr(float r, float g, float b, float* y, float* cb, float* cr) { 316 *y = 0.299f * r + 0.587f * g + 0.114f * b; 317 *cb = -0.168736f * r - 0.331264f * g + 0.5f * b + 0.5f; 318 *cr = 0.5f * r - 0.418688f * g - 0.081312f * b + 0.5f; 319 } 320 321 void ConvertPixel(const uint8_t* input_rgb, uint8_t* out, 322 J_COLOR_SPACE colorspace, size_t num_channels, 323 JpegliDataType data_type = JPEGLI_TYPE_UINT8, 324 JXL_BOOL swap_endianness = JPEGLI_NATIVE_ENDIAN) { 325 const float kMul = 255.0f; 326 float r = input_rgb[0] / kMul; 327 float g = input_rgb[1] / kMul; 328 float b = input_rgb[2] / kMul; 329 uint8_t out8[MAX_COMPONENTS]; 330 if (colorspace == JCS_GRAYSCALE) { 331 const float Y = 0.299f * r + 0.587f * g + 0.114f * b; 332 out8[0] = static_cast<uint8_t>(std::round(Y * kMul)); 333 } else if (colorspace == JCS_RGB || colorspace == JCS_UNKNOWN) { 334 for (size_t c = 0; c < num_channels; ++c) { 335 out8[c] = input_rgb[std::min<size_t>(2, c)]; 336 } 337 } else if (colorspace == JCS_YCbCr) { 338 float Y; 339 float Cb; 340 float Cr; 341 RGBToYCbCr(r, g, b, &Y, &Cb, &Cr); 342 out8[0] = static_cast<uint8_t>(std::round(Y * kMul)); 343 out8[1] = static_cast<uint8_t>(std::round(Cb * kMul)); 344 out8[2] = static_cast<uint8_t>(std::round(Cr * kMul)); 345 } else if (colorspace == JCS_CMYK || colorspace == JCS_YCCK) { 346 float K = 1.0f - std::max(r, std::max(g, b)); 347 float scaleK = 1.0f / (1.0f - K); 348 r *= scaleK; 349 g *= scaleK; 350 b *= scaleK; 351 if (colorspace == JCS_CMYK) { 352 out8[0] = static_cast<uint8_t>(std::round((1.0f - r) * kMul)); 353 out8[1] = static_cast<uint8_t>(std::round((1.0f - g) * kMul)); 354 out8[2] = static_cast<uint8_t>(std::round((1.0f - b) * kMul)); 355 } else if (colorspace == JCS_YCCK) { 356 float Y; 357 float Cb; 358 float Cr; 359 RGBToYCbCr(r, g, b, &Y, &Cb, &Cr); 360 out8[0] = static_cast<uint8_t>(std::round(Y * kMul)); 361 out8[1] = static_cast<uint8_t>(std::round(Cb * kMul)); 362 out8[2] = static_cast<uint8_t>(std::round(Cr * kMul)); 363 } 364 out8[3] = static_cast<uint8_t>(std::round(K * kMul)); 365 } else { 366 JXL_ABORT("Colorspace %d not supported", colorspace); 367 } 368 if (data_type == JPEGLI_TYPE_UINT8) { 369 memcpy(out, out8, num_channels); 370 } else if (data_type == JPEGLI_TYPE_UINT16) { 371 for (size_t c = 0; c < num_channels; ++c) { 372 uint16_t val = (out8[c] << 8) + out8[c]; 373 val |= 0x40; // Make little-endian and big-endian asymmetric 374 if (swap_endianness) { 375 val = JXL_BSWAP16(val); 376 } 377 memcpy(&out[sizeof(val) * c], &val, sizeof(val)); 378 } 379 } else if (data_type == JPEGLI_TYPE_FLOAT) { 380 for (size_t c = 0; c < num_channels; ++c) { 381 float val = out8[c] / 255.0f; 382 if (swap_endianness) { 383 val = BSwapFloat(val); 384 } 385 memcpy(&out[sizeof(val) * c], &val, sizeof(val)); 386 } 387 } 388 } 389 390 void ConvertToGrayscale(TestImage* img) { 391 if (img->color_space == JCS_GRAYSCALE) return; 392 JXL_CHECK(img->data_type == JPEGLI_TYPE_UINT8); 393 for (size_t i = 0; i < img->pixels.size(); i += 3) { 394 if (img->color_space == JCS_RGB) { 395 ConvertPixel(&img->pixels[i], &img->pixels[i / 3], JCS_GRAYSCALE, 1); 396 } else if (img->color_space == JCS_YCbCr) { 397 img->pixels[i / 3] = img->pixels[i]; 398 } 399 } 400 img->pixels.resize(img->pixels.size() / 3); 401 img->color_space = JCS_GRAYSCALE; 402 img->components = 1; 403 } 404 405 void GeneratePixels(TestImage* img) { 406 const std::vector<uint8_t> imgdata = ReadTestData("jxl/flower/flower.pnm"); 407 size_t xsize; 408 size_t ysize; 409 size_t channels; 410 size_t bitdepth; 411 std::vector<uint8_t> pixels; 412 JXL_CHECK(ReadPNM(imgdata, &xsize, &ysize, &channels, &bitdepth, &pixels)); 413 if (img->xsize == 0) img->xsize = xsize; 414 if (img->ysize == 0) img->ysize = ysize; 415 JXL_CHECK(img->xsize <= xsize); 416 JXL_CHECK(img->ysize <= ysize); 417 JXL_CHECK(3 == channels); 418 JXL_CHECK(8 == bitdepth); 419 size_t in_bytes_per_pixel = channels; 420 size_t in_stride = xsize * in_bytes_per_pixel; 421 size_t x0 = (xsize - img->xsize) / 2; 422 size_t y0 = (ysize - img->ysize) / 2; 423 SetNumChannels(static_cast<J_COLOR_SPACE>(img->color_space), 424 &img->components); 425 size_t out_bytes_per_pixel = 426 jpegli_bytes_per_sample(img->data_type) * img->components; 427 size_t out_stride = img->xsize * out_bytes_per_pixel; 428 bool swap_endianness = 429 (img->endianness == JPEGLI_LITTLE_ENDIAN && !IsLittleEndian()) || 430 (img->endianness == JPEGLI_BIG_ENDIAN && IsLittleEndian()); 431 img->pixels.resize(img->ysize * out_stride); 432 for (size_t iy = 0; iy < img->ysize; ++iy) { 433 size_t y = y0 + iy; 434 for (size_t ix = 0; ix < img->xsize; ++ix) { 435 size_t x = x0 + ix; 436 size_t idx_in = y * in_stride + x * in_bytes_per_pixel; 437 size_t idx_out = iy * out_stride + ix * out_bytes_per_pixel; 438 ConvertPixel(&pixels[idx_in], &img->pixels[idx_out], 439 static_cast<J_COLOR_SPACE>(img->color_space), 440 img->components, img->data_type, 441 TO_JXL_BOOL(swap_endianness)); 442 } 443 } 444 } 445 446 void GenerateRawData(const CompressParams& jparams, TestImage* img) { 447 for (size_t c = 0; c < img->components; ++c) { 448 size_t xsize = jparams.comp_width(*img, c); 449 size_t ysize = jparams.comp_height(*img, c); 450 size_t factor_y = jparams.max_v_sample() / jparams.v_samp(c); 451 size_t factor_x = jparams.max_h_sample() / jparams.h_samp(c); 452 size_t factor = factor_x * factor_y; 453 std::vector<uint8_t> plane(ysize * xsize); 454 size_t bytes_per_pixel = img->components; 455 for (size_t y = 0; y < ysize; ++y) { 456 for (size_t x = 0; x < xsize; ++x) { 457 int result = 0; 458 for (size_t iy = 0; iy < factor_y; ++iy) { 459 size_t yy = std::min(y * factor_y + iy, img->ysize - 1); 460 for (size_t ix = 0; ix < factor_x; ++ix) { 461 size_t xx = std::min(x * factor_x + ix, img->xsize - 1); 462 size_t pixel_ix = (yy * img->xsize + xx) * bytes_per_pixel + c; 463 result += img->pixels[pixel_ix]; 464 } 465 } 466 result = static_cast<uint8_t>((result + factor / 2) / factor); 467 plane[y * xsize + x] = result; 468 } 469 } 470 img->raw_data.emplace_back(std::move(plane)); 471 } 472 } 473 474 void GenerateCoeffs(const CompressParams& jparams, TestImage* img) { 475 for (size_t c = 0; c < img->components; ++c) { 476 int xsize_blocks = jparams.comp_width(*img, c) / DCTSIZE; 477 int ysize_blocks = jparams.comp_height(*img, c) / DCTSIZE; 478 std::vector<JCOEF> plane(ysize_blocks * xsize_blocks * DCTSIZE2); 479 for (int by = 0; by < ysize_blocks; ++by) { 480 for (int bx = 0; bx < xsize_blocks; ++bx) { 481 JCOEF* block = &plane[(by * xsize_blocks + bx) * DCTSIZE2]; 482 for (int k = 0; k < DCTSIZE2; ++k) { 483 block[k] = (bx - by) / (k + 1); 484 } 485 } 486 } 487 img->coeffs.emplace_back(std::move(plane)); 488 } 489 } 490 491 void EncodeWithJpegli(const TestImage& input, const CompressParams& jparams, 492 j_compress_ptr cinfo) { 493 cinfo->image_width = input.xsize; 494 cinfo->image_height = input.ysize; 495 cinfo->input_components = input.components; 496 if (jparams.xyb_mode) { 497 jpegli_set_xyb_mode(cinfo); 498 } 499 if (jparams.libjpeg_mode) { 500 jpegli_enable_adaptive_quantization(cinfo, FALSE); 501 jpegli_use_standard_quant_tables(cinfo); 502 jpegli_set_progressive_level(cinfo, 0); 503 } 504 jpegli_set_defaults(cinfo); 505 cinfo->in_color_space = static_cast<J_COLOR_SPACE>(input.color_space); 506 jpegli_default_colorspace(cinfo); 507 if (jparams.override_JFIF >= 0) { 508 cinfo->write_JFIF_header = jparams.override_JFIF; 509 } 510 if (jparams.override_Adobe >= 0) { 511 cinfo->write_Adobe_marker = jparams.override_Adobe; 512 } 513 if (jparams.set_jpeg_colorspace) { 514 jpegli_set_colorspace(cinfo, 515 static_cast<J_COLOR_SPACE>(jparams.jpeg_color_space)); 516 } 517 if (!jparams.comp_ids.empty()) { 518 for (int c = 0; c < cinfo->num_components; ++c) { 519 cinfo->comp_info[c].component_id = jparams.comp_ids[c]; 520 } 521 } 522 if (!jparams.h_sampling.empty()) { 523 for (int c = 0; c < cinfo->num_components; ++c) { 524 cinfo->comp_info[c].h_samp_factor = jparams.h_sampling[c]; 525 cinfo->comp_info[c].v_samp_factor = jparams.v_sampling[c]; 526 } 527 } 528 jpegli_set_quality(cinfo, jparams.quality, TRUE); 529 if (!jparams.quant_indexes.empty()) { 530 for (int c = 0; c < cinfo->num_components; ++c) { 531 cinfo->comp_info[c].quant_tbl_no = jparams.quant_indexes[c]; 532 } 533 for (const auto& table : jparams.quant_tables) { 534 if (table.add_raw) { 535 cinfo->quant_tbl_ptrs[table.slot_idx] = 536 jpegli_alloc_quant_table(reinterpret_cast<j_common_ptr>(cinfo)); 537 for (int k = 0; k < DCTSIZE2; ++k) { 538 cinfo->quant_tbl_ptrs[table.slot_idx]->quantval[k] = 539 table.quantval[k]; 540 } 541 cinfo->quant_tbl_ptrs[table.slot_idx]->sent_table = FALSE; 542 } else { 543 jpegli_add_quant_table(cinfo, table.slot_idx, table.basic_table.data(), 544 table.scale_factor, 545 TO_JXL_BOOL(table.force_baseline)); 546 } 547 } 548 } 549 if (jparams.simple_progression) { 550 jpegli_simple_progression(cinfo); 551 JXL_CHECK(jparams.progressive_mode == -1); 552 } 553 if (jparams.progressive_mode > 2) { 554 const ScanScript& script = kTestScript[jparams.progressive_mode - 3]; 555 cinfo->scan_info = script.scans; 556 cinfo->num_scans = script.num_scans; 557 } else if (jparams.progressive_mode >= 0) { 558 jpegli_set_progressive_level(cinfo, jparams.progressive_mode); 559 } 560 jpegli_set_input_format(cinfo, input.data_type, input.endianness); 561 jpegli_enable_adaptive_quantization( 562 cinfo, TO_JXL_BOOL(jparams.use_adaptive_quantization)); 563 cinfo->restart_interval = jparams.restart_interval; 564 cinfo->restart_in_rows = jparams.restart_in_rows; 565 cinfo->smoothing_factor = jparams.smoothing_factor; 566 if (jparams.optimize_coding == 1) { 567 cinfo->optimize_coding = TRUE; 568 } else if (jparams.optimize_coding == 0) { 569 cinfo->optimize_coding = FALSE; 570 } 571 cinfo->raw_data_in = TO_JXL_BOOL(!input.raw_data.empty()); 572 if (jparams.optimize_coding == 0 && jparams.use_flat_dc_luma_code) { 573 JHUFF_TBL* tbl = cinfo->dc_huff_tbl_ptrs[0]; 574 memset(tbl, 0, sizeof(*tbl)); 575 tbl->bits[4] = 15; 576 for (int i = 0; i < 15; ++i) tbl->huffval[i] = i; 577 } 578 if (input.coeffs.empty()) { 579 bool write_all_tables = TRUE; 580 if (jparams.optimize_coding == 0 && !jparams.use_flat_dc_luma_code && 581 jparams.omit_standard_tables) { 582 write_all_tables = FALSE; 583 cinfo->dc_huff_tbl_ptrs[0]->sent_table = TRUE; 584 cinfo->dc_huff_tbl_ptrs[1]->sent_table = TRUE; 585 cinfo->ac_huff_tbl_ptrs[0]->sent_table = TRUE; 586 cinfo->ac_huff_tbl_ptrs[1]->sent_table = TRUE; 587 } 588 jpegli_start_compress(cinfo, TO_JXL_BOOL(write_all_tables)); 589 if (jparams.add_marker) { 590 jpegli_write_marker(cinfo, kSpecialMarker0, kMarkerData, 591 sizeof(kMarkerData)); 592 jpegli_write_m_header(cinfo, kSpecialMarker1, sizeof(kMarkerData)); 593 for (uint8_t c : kMarkerData) { 594 jpegli_write_m_byte(cinfo, c); 595 } 596 for (size_t i = 0; i < kMarkerSequenceLen; ++i) { 597 jpegli_write_marker(cinfo, kMarkerSequence[i], kMarkerData, 598 ((i + 2) % sizeof(kMarkerData))); 599 } 600 } 601 if (!jparams.icc.empty()) { 602 jpegli_write_icc_profile(cinfo, jparams.icc.data(), jparams.icc.size()); 603 } 604 } 605 if (cinfo->raw_data_in) { 606 // Need to copy because jpeg API requires non-const pointers. 607 std::vector<std::vector<uint8_t>> raw_data = input.raw_data; 608 size_t max_lines = jparams.max_v_sample() * DCTSIZE; 609 std::vector<std::vector<JSAMPROW>> rowdata(cinfo->num_components); 610 std::vector<JSAMPARRAY> data(cinfo->num_components); 611 for (int c = 0; c < cinfo->num_components; ++c) { 612 rowdata[c].resize(jparams.v_samp(c) * DCTSIZE); 613 data[c] = rowdata[c].data(); 614 } 615 while (cinfo->next_scanline < cinfo->image_height) { 616 for (int c = 0; c < cinfo->num_components; ++c) { 617 size_t cwidth = cinfo->comp_info[c].width_in_blocks * DCTSIZE; 618 size_t cheight = cinfo->comp_info[c].height_in_blocks * DCTSIZE; 619 size_t num_lines = jparams.v_samp(c) * DCTSIZE; 620 size_t y0 = (cinfo->next_scanline / max_lines) * num_lines; 621 for (size_t i = 0; i < num_lines; ++i) { 622 rowdata[c][i] = 623 (y0 + i < cheight ? &raw_data[c][(y0 + i) * cwidth] : nullptr); 624 } 625 } 626 size_t num_lines = jpegli_write_raw_data(cinfo, data.data(), max_lines); 627 JXL_CHECK(num_lines == max_lines); 628 } 629 } else if (!input.coeffs.empty()) { 630 j_common_ptr comptr = reinterpret_cast<j_common_ptr>(cinfo); 631 jvirt_barray_ptr* coef_arrays = reinterpret_cast<jvirt_barray_ptr*>(( 632 *cinfo->mem->alloc_small)( 633 comptr, JPOOL_IMAGE, cinfo->num_components * sizeof(jvirt_barray_ptr))); 634 for (int c = 0; c < cinfo->num_components; ++c) { 635 size_t xsize_blocks = jparams.comp_width(input, c) / DCTSIZE; 636 size_t ysize_blocks = jparams.comp_height(input, c) / DCTSIZE; 637 coef_arrays[c] = (*cinfo->mem->request_virt_barray)( 638 comptr, JPOOL_IMAGE, FALSE, xsize_blocks, ysize_blocks, 639 cinfo->comp_info[c].v_samp_factor); 640 } 641 jpegli_write_coefficients(cinfo, coef_arrays); 642 if (jparams.add_marker) { 643 jpegli_write_marker(cinfo, kSpecialMarker0, kMarkerData, 644 sizeof(kMarkerData)); 645 jpegli_write_m_header(cinfo, kSpecialMarker1, sizeof(kMarkerData)); 646 for (uint8_t c : kMarkerData) { 647 jpegli_write_m_byte(cinfo, c); 648 } 649 } 650 for (int c = 0; c < cinfo->num_components; ++c) { 651 jpeg_component_info* comp = &cinfo->comp_info[c]; 652 for (size_t by = 0; by < comp->height_in_blocks; ++by) { 653 JBLOCKARRAY ba = (*cinfo->mem->access_virt_barray)( 654 comptr, coef_arrays[c], by, 1, TRUE); 655 size_t stride = comp->width_in_blocks * sizeof(JBLOCK); 656 size_t offset = by * comp->width_in_blocks * DCTSIZE2; 657 memcpy(ba[0], &input.coeffs[c][offset], stride); 658 } 659 } 660 } else { 661 size_t stride = cinfo->image_width * cinfo->input_components * 662 jpegli_bytes_per_sample(input.data_type); 663 std::vector<uint8_t> row_bytes(stride); 664 for (size_t y = 0; y < cinfo->image_height; ++y) { 665 memcpy(row_bytes.data(), &input.pixels[y * stride], stride); 666 JSAMPROW row[] = {row_bytes.data()}; 667 jpegli_write_scanlines(cinfo, row, 1); 668 } 669 } 670 jpegli_finish_compress(cinfo); 671 } 672 673 bool EncodeWithJpegli(const TestImage& input, const CompressParams& jparams, 674 std::vector<uint8_t>* compressed) { 675 uint8_t* buffer = nullptr; 676 unsigned long buffer_size = 0; 677 jpeg_compress_struct cinfo; 678 const auto try_catch_block = [&]() -> bool { 679 ERROR_HANDLER_SETUP(jpegli); 680 jpegli_create_compress(&cinfo); 681 jpegli_mem_dest(&cinfo, &buffer, &buffer_size); 682 EncodeWithJpegli(input, jparams, &cinfo); 683 return true; 684 }; 685 bool success = try_catch_block(); 686 jpegli_destroy_compress(&cinfo); 687 if (success) { 688 compressed->resize(buffer_size); 689 std::copy_n(buffer, buffer_size, compressed->data()); 690 } 691 if (buffer) std::free(buffer); 692 return success; 693 } 694 695 int NumTestScanScripts() { return kNumTestScripts; } 696 697 void DumpImage(const TestImage& image, const std::string& fn) { 698 JXL_CHECK(image.components == 1 || image.components == 3); 699 size_t bytes_per_sample = jpegli_bytes_per_sample(image.data_type); 700 uint32_t maxval = (1u << (8 * bytes_per_sample)) - 1; 701 char type = image.components == 1 ? '5' : '6'; 702 std::ofstream out(fn.c_str(), std::ofstream::binary); 703 out << "P" << type << "\n" 704 << image.xsize << " " << image.ysize << "\n" 705 << maxval << "\n"; 706 out.write(reinterpret_cast<const char*>(image.pixels.data()), 707 image.pixels.size()); 708 out.close(); 709 } 710 711 double DistanceRms(const TestImage& input, const TestImage& output, 712 size_t start_line, size_t num_lines, double* max_diff) { 713 size_t stride = input.xsize * input.components; 714 size_t start_offset = start_line * stride; 715 auto get_sample = [&](const TestImage& im, const std::vector<uint8_t>& data, 716 size_t idx) -> double { 717 size_t bytes_per_sample = jpegli_bytes_per_sample(im.data_type); 718 bool is_little_endian = 719 (im.endianness == JPEGLI_LITTLE_ENDIAN || 720 (im.endianness == JPEGLI_NATIVE_ENDIAN && IsLittleEndian())); 721 size_t offset = start_offset + idx * bytes_per_sample; 722 JXL_CHECK(offset < data.size()); 723 const uint8_t* p = &data[offset]; 724 if (im.data_type == JPEGLI_TYPE_UINT8) { 725 static const double mul8 = 1.0 / 255.0; 726 return p[0] * mul8; 727 } else if (im.data_type == JPEGLI_TYPE_UINT16) { 728 static const double mul16 = 1.0 / 65535.0; 729 return (is_little_endian ? LoadLE16(p) : LoadBE16(p)) * mul16; 730 } else if (im.data_type == JPEGLI_TYPE_FLOAT) { 731 return (is_little_endian ? LoadLEFloat(p) : LoadBEFloat(p)); 732 } 733 return 0.0; 734 }; 735 double diff2 = 0.0; 736 size_t num_samples = 0; 737 if (max_diff) *max_diff = 0.0; 738 if (!input.pixels.empty() && !output.pixels.empty()) { 739 num_samples = num_lines * stride; 740 for (size_t i = 0; i < num_samples; ++i) { 741 double sample_orig = get_sample(input, input.pixels, i); 742 double sample_output = get_sample(output, output.pixels, i); 743 double diff = sample_orig - sample_output; 744 if (max_diff) *max_diff = std::max(*max_diff, 255.0 * std::abs(diff)); 745 diff2 += diff * diff; 746 } 747 } else { 748 JXL_CHECK(!input.raw_data.empty()); 749 JXL_CHECK(!output.raw_data.empty()); 750 for (size_t c = 0; c < input.raw_data.size(); ++c) { 751 JXL_CHECK(c < output.raw_data.size()); 752 num_samples += input.raw_data[c].size(); 753 for (size_t i = 0; i < input.raw_data[c].size(); ++i) { 754 double sample_orig = get_sample(input, input.raw_data[c], i); 755 double sample_output = get_sample(output, output.raw_data[c], i); 756 double diff = sample_orig - sample_output; 757 if (max_diff) *max_diff = std::max(*max_diff, 255.0 * std::abs(diff)); 758 diff2 += diff * diff; 759 } 760 } 761 } 762 return std::sqrt(diff2 / num_samples) * 255.0; 763 } 764 765 double DistanceRms(const TestImage& input, const TestImage& output, 766 double* max_diff) { 767 return DistanceRms(input, output, 0, output.ysize, max_diff); 768 } 769 770 void VerifyOutputImage(const TestImage& input, const TestImage& output, 771 size_t start_line, size_t num_lines, double max_rms, 772 double max_diff) { 773 double max_d; 774 double rms = DistanceRms(input, output, start_line, num_lines, &max_d); 775 printf("rms: %f, max_rms: %f, max_d: %f, max_diff: %f\n", rms, max_rms, 776 max_d, max_diff); 777 JXL_CHECK(rms <= max_rms); 778 JXL_CHECK(max_d <= max_diff); 779 } 780 781 void VerifyOutputImage(const TestImage& input, const TestImage& output, 782 double max_rms, double max_diff) { 783 JXL_CHECK(output.xsize == input.xsize); 784 JXL_CHECK(output.ysize == input.ysize); 785 JXL_CHECK(output.components == input.components); 786 JXL_CHECK(output.color_space == input.color_space); 787 if (!input.coeffs.empty()) { 788 JXL_CHECK(input.coeffs.size() == input.components); 789 JXL_CHECK(output.coeffs.size() == input.components); 790 for (size_t c = 0; c < input.components; ++c) { 791 JXL_CHECK(output.coeffs[c].size() == input.coeffs[c].size()); 792 JXL_CHECK(0 == memcmp(input.coeffs[c].data(), output.coeffs[c].data(), 793 input.coeffs[c].size())); 794 } 795 } else { 796 VerifyOutputImage(input, output, 0, output.ysize, max_rms, max_diff); 797 } 798 } 799 800 } // namespace jpegli