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