test_utils.cc (34069B)
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/jxl/test_utils.h" 7 8 #include <jxl/cms.h> 9 #include <jxl/cms_interface.h> 10 #include <jxl/memory_manager.h> 11 #include <jxl/types.h> 12 13 #include <cstddef> 14 #include <fstream> 15 #include <memory> 16 #include <string> 17 #include <utility> 18 #include <vector> 19 20 #include "lib/extras/metrics.h" 21 #include "lib/extras/packed_image_convert.h" 22 #include "lib/jxl/base/compiler_specific.h" 23 #include "lib/jxl/base/data_parallel.h" 24 #include "lib/jxl/base/float.h" 25 #include "lib/jxl/base/printf_macros.h" 26 #include "lib/jxl/base/status.h" 27 #include "lib/jxl/codec_in_out.h" 28 #include "lib/jxl/enc_aux_out.h" 29 #include "lib/jxl/enc_bit_writer.h" 30 #include "lib/jxl/enc_butteraugli_comparator.h" 31 #include "lib/jxl/enc_cache.h" 32 #include "lib/jxl/enc_external_image.h" 33 #include "lib/jxl/enc_fields.h" 34 #include "lib/jxl/enc_frame.h" 35 #include "lib/jxl/enc_icc_codec.h" 36 #include "lib/jxl/enc_params.h" 37 #include "lib/jxl/frame_header.h" 38 #include "lib/jxl/icc_codec.h" 39 #include "lib/jxl/image.h" 40 #include "lib/jxl/image_bundle.h" 41 #include "lib/jxl/padded_bytes.h" 42 #include "lib/jxl/test_memory_manager.h" 43 44 #if !defined(TEST_DATA_PATH) 45 #include "tools/cpp/runfiles/runfiles.h" 46 #endif 47 48 namespace jxl { 49 namespace test { 50 51 void Check(bool ok) { 52 if (!ok) { 53 JXL_CRASH(); 54 } 55 } 56 57 #if defined(TEST_DATA_PATH) 58 std::string GetTestDataPath(const std::string& filename) { 59 return std::string(TEST_DATA_PATH "/") + filename; 60 } 61 #else 62 using ::bazel::tools::cpp::runfiles::Runfiles; 63 const std::unique_ptr<Runfiles> kRunfiles(Runfiles::Create("")); 64 std::string GetTestDataPath(const std::string& filename) { 65 std::string root(JPEGXL_ROOT_PACKAGE "/testdata/"); 66 return kRunfiles->Rlocation(root + filename); 67 } 68 #endif 69 70 jxl::IccBytes GetIccTestProfile() { 71 return ReadTestData("external/Compact-ICC-Profiles/profiles/scRGB-v2.icc"); 72 } 73 74 std::vector<uint8_t> GetCompressedIccTestProfile() { 75 BitWriter writer(MemoryManager()); 76 const IccBytes icc = GetIccTestProfile(); 77 Check( 78 WriteICC(Span<const uint8_t>(icc), &writer, LayerType::Header, nullptr)); 79 writer.ZeroPadToByte(); 80 jxl::Bytes bytes = writer.GetSpan(); 81 return std::vector<uint8_t>(bytes.begin(), bytes.end()); 82 } 83 84 std::vector<uint8_t> ReadTestData(const std::string& filename) { 85 std::string full_path = GetTestDataPath(filename); 86 fprintf(stderr, "ReadTestData %s\n", full_path.c_str()); 87 std::ifstream file(full_path, std::ios::binary); 88 std::vector<char> str((std::istreambuf_iterator<char>(file)), 89 std::istreambuf_iterator<char>()); 90 Check(file.good()); 91 const uint8_t* raw = reinterpret_cast<const uint8_t*>(str.data()); 92 std::vector<uint8_t> data(raw, raw + str.size()); 93 printf("Test data %s is %d bytes long.\n", filename.c_str(), 94 static_cast<int>(data.size())); 95 return data; 96 } 97 98 void DefaultAcceptedFormats(extras::JXLDecompressParams& dparams) { 99 if (dparams.accepted_formats.empty()) { 100 for (const uint32_t num_channels : {1, 2, 3, 4}) { 101 dparams.accepted_formats.push_back( 102 {num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0}); 103 } 104 } 105 } 106 107 Status DecodeFile(extras::JXLDecompressParams dparams, 108 const Span<const uint8_t> file, CodecInOut* JXL_RESTRICT io, 109 ThreadPool* pool) { 110 DefaultAcceptedFormats(dparams); 111 SetThreadParallelRunner(dparams, pool); 112 extras::PackedPixelFile ppf; 113 JXL_RETURN_IF_ERROR(DecodeImageJXL(file.data(), file.size(), dparams, 114 /*decoded_bytes=*/nullptr, &ppf)); 115 JXL_RETURN_IF_ERROR(ConvertPackedPixelFileToCodecInOut(ppf, pool, io)); 116 return true; 117 } 118 119 void JxlBasicInfoSetFromPixelFormat(JxlBasicInfo* basic_info, 120 const JxlPixelFormat* pixel_format) { 121 JxlEncoderInitBasicInfo(basic_info); 122 switch (pixel_format->data_type) { 123 case JXL_TYPE_FLOAT: 124 basic_info->bits_per_sample = 32; 125 basic_info->exponent_bits_per_sample = 8; 126 break; 127 case JXL_TYPE_FLOAT16: 128 basic_info->bits_per_sample = 16; 129 basic_info->exponent_bits_per_sample = 5; 130 break; 131 case JXL_TYPE_UINT8: 132 basic_info->bits_per_sample = 8; 133 basic_info->exponent_bits_per_sample = 0; 134 break; 135 case JXL_TYPE_UINT16: 136 basic_info->bits_per_sample = 16; 137 basic_info->exponent_bits_per_sample = 0; 138 break; 139 default: 140 Check(false); 141 } 142 if (pixel_format->num_channels < 3) { 143 basic_info->num_color_channels = 1; 144 } else { 145 basic_info->num_color_channels = 3; 146 } 147 if (pixel_format->num_channels == 2 || pixel_format->num_channels == 4) { 148 basic_info->alpha_exponent_bits = basic_info->exponent_bits_per_sample; 149 basic_info->alpha_bits = basic_info->bits_per_sample; 150 basic_info->num_extra_channels = 1; 151 } else { 152 basic_info->alpha_exponent_bits = 0; 153 basic_info->alpha_bits = 0; 154 } 155 } 156 157 ColorEncoding ColorEncodingFromDescriptor(const ColorEncodingDescriptor& desc) { 158 ColorEncoding c; 159 c.SetColorSpace(desc.color_space); 160 if (desc.color_space != ColorSpace::kXYB) { 161 Check(c.SetWhitePointType(desc.white_point)); 162 if (desc.color_space != ColorSpace::kGray) { 163 Check(c.SetPrimariesType(desc.primaries)); 164 } 165 c.Tf().SetTransferFunction(desc.tf); 166 } 167 c.SetRenderingIntent(desc.rendering_intent); 168 Check(c.CreateICC()); 169 return c; 170 } 171 172 namespace { 173 void CheckSameEncodings(const std::vector<ColorEncoding>& a, 174 const std::vector<ColorEncoding>& b, 175 const std::string& check_name, 176 std::stringstream& failures) { 177 Check(a.size() == b.size()); 178 for (size_t i = 0; i < a.size(); ++i) { 179 if ((a[i].ICC() == b[i].ICC()) || 180 ((a[i].GetPrimariesType() == b[i].GetPrimariesType()) && 181 a[i].Tf().IsSame(b[i].Tf()))) { 182 continue; 183 } 184 failures << "CheckSameEncodings " << check_name << ": " << i 185 << "-th encoding mismatch\n"; 186 } 187 } 188 } // namespace 189 190 bool Roundtrip(CodecInOut* io, const CompressParams& cparams, 191 extras::JXLDecompressParams dparams, 192 CodecInOut* JXL_RESTRICT io2, std::stringstream& failures, 193 size_t* compressed_size, ThreadPool* pool) { 194 DefaultAcceptedFormats(dparams); 195 if (compressed_size) { 196 *compressed_size = static_cast<size_t>(-1); 197 } 198 std::vector<uint8_t> compressed; 199 200 std::vector<ColorEncoding> original_metadata_encodings; 201 std::vector<ColorEncoding> original_current_encodings; 202 std::vector<ColorEncoding> metadata_encodings_1; 203 std::vector<ColorEncoding> metadata_encodings_2; 204 std::vector<ColorEncoding> current_encodings_2; 205 original_metadata_encodings.reserve(io->frames.size()); 206 original_current_encodings.reserve(io->frames.size()); 207 metadata_encodings_1.reserve(io->frames.size()); 208 metadata_encodings_2.reserve(io->frames.size()); 209 current_encodings_2.reserve(io->frames.size()); 210 211 for (const ImageBundle& ib : io->frames) { 212 // Remember original encoding, will be returned by decoder. 213 original_metadata_encodings.push_back(ib.metadata()->color_encoding); 214 // c_current should not change during encoding. 215 original_current_encodings.push_back(ib.c_current()); 216 } 217 218 Check(test::EncodeFile(cparams, io, &compressed, pool)); 219 220 for (const ImageBundle& ib1 : io->frames) { 221 metadata_encodings_1.push_back(ib1.metadata()->color_encoding); 222 } 223 224 // Should still be in the same color space after encoding. 225 CheckSameEncodings(metadata_encodings_1, original_metadata_encodings, 226 "original vs after encoding", failures); 227 228 Check(DecodeFile(dparams, Bytes(compressed), io2, pool)); 229 Check(io2->frames.size() == io->frames.size()); 230 231 for (const ImageBundle& ib2 : io2->frames) { 232 metadata_encodings_2.push_back(ib2.metadata()->color_encoding); 233 current_encodings_2.push_back(ib2.c_current()); 234 } 235 236 // We always produce the original color encoding if a color transform hook is 237 // set. 238 CheckSameEncodings(current_encodings_2, original_current_encodings, 239 "current: original vs decoded", failures); 240 241 // Decoder returns the originals passed to the encoder. 242 CheckSameEncodings(metadata_encodings_2, original_metadata_encodings, 243 "metadata: original vs decoded", failures); 244 245 if (compressed_size) { 246 *compressed_size = compressed.size(); 247 } 248 249 return failures.str().empty(); 250 } 251 252 size_t Roundtrip(const extras::PackedPixelFile& ppf_in, 253 const extras::JXLCompressParams& cparams, 254 extras::JXLDecompressParams dparams, ThreadPool* pool, 255 extras::PackedPixelFile* ppf_out) { 256 DefaultAcceptedFormats(dparams); 257 SetThreadParallelRunner(cparams, pool); 258 SetThreadParallelRunner(dparams, pool); 259 std::vector<uint8_t> compressed; 260 Check(extras::EncodeImageJXL(cparams, ppf_in, /*jpeg_bytes=*/nullptr, 261 &compressed)); 262 size_t decoded_bytes = 0; 263 Check(extras::DecodeImageJXL(compressed.data(), compressed.size(), dparams, 264 &decoded_bytes, ppf_out)); 265 Check(decoded_bytes == compressed.size()); 266 return compressed.size(); 267 } 268 269 std::vector<ColorEncodingDescriptor> AllEncodings() { 270 std::vector<ColorEncodingDescriptor> all_encodings; 271 all_encodings.reserve(300); 272 273 for (ColorSpace cs : Values<ColorSpace>()) { 274 if (cs == ColorSpace::kUnknown || cs == ColorSpace::kXYB || 275 cs == ColorSpace::kGray) { 276 continue; 277 } 278 279 for (WhitePoint wp : Values<WhitePoint>()) { 280 if (wp == WhitePoint::kCustom) continue; 281 for (Primaries primaries : Values<Primaries>()) { 282 if (primaries == Primaries::kCustom) continue; 283 for (TransferFunction tf : Values<TransferFunction>()) { 284 if (tf == TransferFunction::kUnknown) continue; 285 for (RenderingIntent ri : Values<RenderingIntent>()) { 286 ColorEncodingDescriptor cdesc; 287 cdesc.color_space = cs; 288 cdesc.white_point = wp; 289 cdesc.primaries = primaries; 290 cdesc.tf = tf; 291 cdesc.rendering_intent = ri; 292 all_encodings.push_back(cdesc); 293 } 294 } 295 } 296 } 297 } 298 299 return all_encodings; 300 } 301 302 jxl::CodecInOut SomeTestImageToCodecInOut(const std::vector<uint8_t>& buf, 303 size_t num_channels, size_t xsize, 304 size_t ysize) { 305 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 306 jxl::CodecInOut io{memory_manager}; 307 Check(io.SetSize(xsize, ysize)); 308 io.metadata.m.SetAlphaBits(16); 309 io.metadata.m.color_encoding = jxl::ColorEncoding::SRGB( 310 /*is_gray=*/num_channels == 1 || num_channels == 2); 311 JxlPixelFormat format = {static_cast<uint32_t>(num_channels), JXL_TYPE_UINT16, 312 JXL_BIG_ENDIAN, 0}; 313 Check(ConvertFromExternal( 314 jxl::Bytes(buf.data(), buf.size()), xsize, ysize, 315 jxl::ColorEncoding::SRGB(/*is_gray=*/num_channels < 3), 316 /*bits_per_sample=*/16, format, 317 /*pool=*/nullptr, 318 /*ib=*/&io.Main())); 319 return io; 320 } 321 322 bool Near(double expected, double value, double max_dist) { 323 double dist = expected > value ? expected - value : value - expected; 324 return dist <= max_dist; 325 } 326 327 float LoadLEFloat16(const uint8_t* p) { 328 uint16_t bits16 = LoadLE16(p); 329 return detail::LoadFloat16(bits16); 330 } 331 332 float LoadBEFloat16(const uint8_t* p) { 333 uint16_t bits16 = LoadBE16(p); 334 return detail::LoadFloat16(bits16); 335 } 336 337 size_t GetPrecision(JxlDataType data_type) { 338 switch (data_type) { 339 case JXL_TYPE_UINT8: 340 return 8; 341 case JXL_TYPE_UINT16: 342 return 16; 343 case JXL_TYPE_FLOAT: 344 // Floating point mantissa precision 345 return 24; 346 case JXL_TYPE_FLOAT16: 347 return 11; 348 default: 349 Check(false); 350 return 0; 351 } 352 } 353 354 size_t GetDataBits(JxlDataType data_type) { 355 switch (data_type) { 356 case JXL_TYPE_UINT8: 357 return 8; 358 case JXL_TYPE_UINT16: 359 return 16; 360 case JXL_TYPE_FLOAT: 361 return 32; 362 case JXL_TYPE_FLOAT16: 363 return 16; 364 default: 365 Check(false); 366 return 0; 367 } 368 } 369 370 std::vector<double> ConvertToRGBA32(const uint8_t* pixels, size_t xsize, 371 size_t ysize, const JxlPixelFormat& format, 372 double factor) { 373 std::vector<double> result(xsize * ysize * 4); 374 size_t num_channels = format.num_channels; 375 bool gray = num_channels == 1 || num_channels == 2; 376 bool alpha = num_channels == 2 || num_channels == 4; 377 JxlEndianness endianness = format.endianness; 378 // Compute actual type: 379 if (endianness == JXL_NATIVE_ENDIAN) { 380 endianness = IsLittleEndian() ? JXL_LITTLE_ENDIAN : JXL_BIG_ENDIAN; 381 } 382 383 size_t stride = 384 xsize * jxl::DivCeil(GetDataBits(format.data_type) * num_channels, 385 jxl::kBitsPerByte); 386 if (format.align > 1) stride = jxl::RoundUpTo(stride, format.align); 387 388 if (format.data_type == JXL_TYPE_UINT8) { 389 // Multiplier to bring to 0-1.0 range 390 double mul = factor > 0.0 ? factor : 1.0 / 255.0; 391 for (size_t y = 0; y < ysize; ++y) { 392 for (size_t x = 0; x < xsize; ++x) { 393 size_t j = (y * xsize + x) * 4; 394 size_t i = y * stride + x * num_channels; 395 double r = pixels[i]; 396 double g = gray ? r : pixels[i + 1]; 397 double b = gray ? r : pixels[i + 2]; 398 double a = alpha ? pixels[i + num_channels - 1] : 255; 399 result[j + 0] = r * mul; 400 result[j + 1] = g * mul; 401 result[j + 2] = b * mul; 402 result[j + 3] = a * mul; 403 } 404 } 405 } else if (format.data_type == JXL_TYPE_UINT16) { 406 Check(endianness != JXL_NATIVE_ENDIAN); 407 // Multiplier to bring to 0-1.0 range 408 double mul = factor > 0.0 ? factor : 1.0 / 65535.0; 409 for (size_t y = 0; y < ysize; ++y) { 410 for (size_t x = 0; x < xsize; ++x) { 411 size_t j = (y * xsize + x) * 4; 412 size_t i = y * stride + x * num_channels * 2; 413 double r; 414 double g; 415 double b; 416 double a; 417 if (endianness == JXL_BIG_ENDIAN) { 418 r = (pixels[i + 0] << 8) + pixels[i + 1]; 419 g = gray ? r : (pixels[i + 2] << 8) + pixels[i + 3]; 420 b = gray ? r : (pixels[i + 4] << 8) + pixels[i + 5]; 421 a = alpha ? (pixels[i + num_channels * 2 - 2] << 8) + 422 pixels[i + num_channels * 2 - 1] 423 : 65535; 424 } else { 425 r = (pixels[i + 1] << 8) + pixels[i + 0]; 426 g = gray ? r : (pixels[i + 3] << 8) + pixels[i + 2]; 427 b = gray ? r : (pixels[i + 5] << 8) + pixels[i + 4]; 428 a = alpha ? (pixels[i + num_channels * 2 - 1] << 8) + 429 pixels[i + num_channels * 2 - 2] 430 : 65535; 431 } 432 result[j + 0] = r * mul; 433 result[j + 1] = g * mul; 434 result[j + 2] = b * mul; 435 result[j + 3] = a * mul; 436 } 437 } 438 } else if (format.data_type == JXL_TYPE_FLOAT) { 439 Check(endianness != JXL_NATIVE_ENDIAN); 440 for (size_t y = 0; y < ysize; ++y) { 441 for (size_t x = 0; x < xsize; ++x) { 442 size_t j = (y * xsize + x) * 4; 443 size_t i = y * stride + x * num_channels * 4; 444 double r; 445 double g; 446 double b; 447 double a; 448 if (endianness == JXL_BIG_ENDIAN) { 449 r = LoadBEFloat(pixels + i); 450 g = gray ? r : LoadBEFloat(pixels + i + 4); 451 b = gray ? r : LoadBEFloat(pixels + i + 8); 452 a = alpha ? LoadBEFloat(pixels + i + num_channels * 4 - 4) : 1.0; 453 } else { 454 r = LoadLEFloat(pixels + i); 455 g = gray ? r : LoadLEFloat(pixels + i + 4); 456 b = gray ? r : LoadLEFloat(pixels + i + 8); 457 a = alpha ? LoadLEFloat(pixels + i + num_channels * 4 - 4) : 1.0; 458 } 459 result[j + 0] = r; 460 result[j + 1] = g; 461 result[j + 2] = b; 462 result[j + 3] = a; 463 } 464 } 465 } else if (format.data_type == JXL_TYPE_FLOAT16) { 466 Check(endianness != JXL_NATIVE_ENDIAN); 467 for (size_t y = 0; y < ysize; ++y) { 468 for (size_t x = 0; x < xsize; ++x) { 469 size_t j = (y * xsize + x) * 4; 470 size_t i = y * stride + x * num_channels * 2; 471 double r; 472 double g; 473 double b; 474 double a; 475 if (endianness == JXL_BIG_ENDIAN) { 476 r = LoadBEFloat16(pixels + i); 477 g = gray ? r : LoadBEFloat16(pixels + i + 2); 478 b = gray ? r : LoadBEFloat16(pixels + i + 4); 479 a = alpha ? LoadBEFloat16(pixels + i + num_channels * 2 - 2) : 1.0; 480 } else { 481 r = LoadLEFloat16(pixels + i); 482 g = gray ? r : LoadLEFloat16(pixels + i + 2); 483 b = gray ? r : LoadLEFloat16(pixels + i + 4); 484 a = alpha ? LoadLEFloat16(pixels + i + num_channels * 2 - 2) : 1.0; 485 } 486 result[j + 0] = r; 487 result[j + 1] = g; 488 result[j + 2] = b; 489 result[j + 3] = a; 490 } 491 } 492 } else { 493 Check(false); // Unsupported type 494 } 495 return result; 496 } 497 498 size_t ComparePixels(const uint8_t* a, const uint8_t* b, size_t xsize, 499 size_t ysize, const JxlPixelFormat& format_a, 500 const JxlPixelFormat& format_b, 501 double threshold_multiplier) { 502 // Convert both images to equal full precision for comparison. 503 std::vector<double> a_full = ConvertToRGBA32(a, xsize, ysize, format_a); 504 std::vector<double> b_full = ConvertToRGBA32(b, xsize, ysize, format_b); 505 bool gray_a = format_a.num_channels < 3; 506 bool gray_b = format_b.num_channels < 3; 507 bool alpha_a = ((format_a.num_channels & 1) == 0); 508 bool alpha_b = ((format_b.num_channels & 1) == 0); 509 size_t bits_a = GetPrecision(format_a.data_type); 510 size_t bits_b = GetPrecision(format_b.data_type); 511 size_t bits = std::min(bits_a, bits_b); 512 // How much distance is allowed in case of pixels with lower bit depths, given 513 // that the double precision float images use range 0-1.0. 514 // E.g. in case of 1-bit this is 0.5 since 0.499 must map to 0 and 0.501 must 515 // map to 1. 516 double precision = 0.5 * threshold_multiplier / ((1ull << bits) - 1ull); 517 if (format_a.data_type == JXL_TYPE_FLOAT16 || 518 format_b.data_type == JXL_TYPE_FLOAT16) { 519 // Lower the precision for float16, because it currently looks like the 520 // scalar and wasm implementations of hwy have 1 less bit of precision 521 // than the x86 implementations. 522 // TODO(lode): Set the required precision back to 11 bits when possible. 523 precision = 0.5 * threshold_multiplier / ((1ull << (bits - 1)) - 1ull); 524 } 525 if (format_b.data_type == JXL_TYPE_UINT8) { 526 // Increase the threshold by the maximum difference introduced by dithering. 527 precision += 63.0 / 128.0; 528 } 529 size_t numdiff = 0; 530 for (size_t y = 0; y < ysize; y++) { 531 for (size_t x = 0; x < xsize; x++) { 532 size_t i = (y * xsize + x) * 4; 533 bool ok = true; 534 if (gray_a || gray_b) { 535 if (!Near(a_full[i + 0], b_full[i + 0], precision)) ok = false; 536 // If the input was grayscale and the output not, then the output must 537 // have all channels equal. 538 if (gray_a && b_full[i + 0] != b_full[i + 1] && 539 b_full[i + 2] != b_full[i + 2]) { 540 ok = false; 541 } 542 } else { 543 if (!Near(a_full[i + 0], b_full[i + 0], precision) || 544 !Near(a_full[i + 1], b_full[i + 1], precision) || 545 !Near(a_full[i + 2], b_full[i + 2], precision)) { 546 ok = false; 547 } 548 } 549 if (alpha_a && alpha_b) { 550 if (!Near(a_full[i + 3], b_full[i + 3], precision)) ok = false; 551 } else { 552 // If the input had no alpha channel, the output should be opaque 553 // after roundtrip. 554 if (alpha_b && !Near(1.0, b_full[i + 3], precision)) ok = false; 555 } 556 if (!ok) numdiff++; 557 } 558 } 559 return numdiff; 560 } 561 562 double DistanceRMS(const uint8_t* a, const uint8_t* b, size_t xsize, 563 size_t ysize, const JxlPixelFormat& format) { 564 // Convert both images to equal full precision for comparison. 565 std::vector<double> a_full = ConvertToRGBA32(a, xsize, ysize, format); 566 std::vector<double> b_full = ConvertToRGBA32(b, xsize, ysize, format); 567 double sum = 0.0; 568 for (size_t y = 0; y < ysize; y++) { 569 double row_sum = 0.0; 570 for (size_t x = 0; x < xsize; x++) { 571 size_t i = (y * xsize + x) * 4; 572 for (size_t c = 0; c < format.num_channels; ++c) { 573 double diff = a_full[i + c] - b_full[i + c]; 574 row_sum += diff * diff; 575 } 576 } 577 sum += row_sum; 578 } 579 sum /= (xsize * ysize); 580 return sqrt(sum); 581 } 582 583 float ButteraugliDistance(const extras::PackedPixelFile& a, 584 const extras::PackedPixelFile& b, ThreadPool* pool) { 585 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 586 CodecInOut io0{memory_manager}; 587 Check(ConvertPackedPixelFileToCodecInOut(a, pool, &io0)); 588 CodecInOut io1{memory_manager}; 589 Check(ConvertPackedPixelFileToCodecInOut(b, pool, &io1)); 590 // TODO(eustas): simplify? 591 return ButteraugliDistance(io0.frames, io1.frames, ButteraugliParams(), 592 *JxlGetDefaultCms(), 593 /*distmap=*/nullptr, pool); 594 } 595 596 float ButteraugliDistance(const ImageBundle& rgb0, const ImageBundle& rgb1, 597 const ButteraugliParams& params, 598 const JxlCmsInterface& cms, ImageF* distmap, 599 ThreadPool* pool, bool ignore_alpha) { 600 JxlButteraugliComparator comparator(params, cms); 601 float distance; 602 Check(ComputeScore(rgb0, rgb1, &comparator, cms, &distance, distmap, pool, 603 ignore_alpha)); 604 return distance; 605 } 606 607 float ButteraugliDistance(const std::vector<ImageBundle>& frames0, 608 const std::vector<ImageBundle>& frames1, 609 const ButteraugliParams& params, 610 const JxlCmsInterface& cms, ImageF* distmap, 611 ThreadPool* pool) { 612 JxlButteraugliComparator comparator(params, cms); 613 Check(frames0.size() == frames1.size()); 614 float max_dist = 0.0f; 615 for (size_t i = 0; i < frames0.size(); ++i) { 616 float frame_score; 617 Check(ComputeScore(frames0[i], frames1[i], &comparator, cms, &frame_score, 618 distmap, pool)); 619 max_dist = std::max(max_dist, frame_score); 620 } 621 return max_dist; 622 } 623 624 float Butteraugli3Norm(const extras::PackedPixelFile& a, 625 const extras::PackedPixelFile& b, ThreadPool* pool) { 626 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 627 CodecInOut io0{memory_manager}; 628 Check(ConvertPackedPixelFileToCodecInOut(a, pool, &io0)); 629 CodecInOut io1{memory_manager}; 630 Check(ConvertPackedPixelFileToCodecInOut(b, pool, &io1)); 631 ButteraugliParams butteraugli_params; 632 ImageF distmap; 633 ButteraugliDistance(io0.frames, io1.frames, butteraugli_params, 634 *JxlGetDefaultCms(), &distmap, pool); 635 return ComputeDistanceP(distmap, butteraugli_params, 3); 636 } 637 638 float ComputeDistance2(const extras::PackedPixelFile& a, 639 const extras::PackedPixelFile& b) { 640 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 641 CodecInOut io0{memory_manager}; 642 Check(ConvertPackedPixelFileToCodecInOut(a, nullptr, &io0)); 643 CodecInOut io1{memory_manager}; 644 Check(ConvertPackedPixelFileToCodecInOut(b, nullptr, &io1)); 645 return ComputeDistance2(io0.Main(), io1.Main(), *JxlGetDefaultCms()); 646 } 647 648 float ComputePSNR(const extras::PackedPixelFile& a, 649 const extras::PackedPixelFile& b) { 650 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 651 CodecInOut io0{memory_manager}; 652 Check(ConvertPackedPixelFileToCodecInOut(a, nullptr, &io0)); 653 CodecInOut io1{memory_manager}; 654 Check(ConvertPackedPixelFileToCodecInOut(b, nullptr, &io1)); 655 return ComputePSNR(io0.Main(), io1.Main(), *JxlGetDefaultCms()); 656 } 657 658 bool SameAlpha(const extras::PackedPixelFile& a, 659 const extras::PackedPixelFile& b) { 660 Check(a.info.xsize == b.info.xsize); 661 Check(a.info.ysize == b.info.ysize); 662 Check(a.info.alpha_bits == b.info.alpha_bits); 663 Check(a.info.alpha_exponent_bits == b.info.alpha_exponent_bits); 664 Check(a.info.alpha_bits > 0); 665 Check(a.frames.size() == b.frames.size()); 666 for (size_t i = 0; i < a.frames.size(); ++i) { 667 const extras::PackedImage& color_a = a.frames[i].color; 668 const extras::PackedImage& color_b = b.frames[i].color; 669 Check(color_a.format.num_channels == color_b.format.num_channels); 670 Check(color_a.format.data_type == color_b.format.data_type); 671 Check(color_a.format.endianness == color_b.format.endianness); 672 Check(color_a.pixels_size == color_b.pixels_size); 673 size_t pwidth = 674 extras::PackedImage::BitsPerChannel(color_a.format.data_type) / 8; 675 size_t num_color = color_a.format.num_channels < 3 ? 1 : 3; 676 const uint8_t* p_a = reinterpret_cast<const uint8_t*>(color_a.pixels()); 677 const uint8_t* p_b = reinterpret_cast<const uint8_t*>(color_b.pixels()); 678 for (size_t y = 0; y < a.info.ysize; ++y) { 679 for (size_t x = 0; x < a.info.xsize; ++x) { 680 size_t idx = 681 ((y * a.info.xsize + x) * color_a.format.num_channels + num_color) * 682 pwidth; 683 if (memcmp(&p_a[idx], &p_b[idx], pwidth) != 0) { 684 return false; 685 } 686 } 687 } 688 } 689 return true; 690 } 691 692 bool SamePixels(const extras::PackedImage& a, const extras::PackedImage& b) { 693 Check(a.xsize == b.xsize); 694 Check(a.ysize == b.ysize); 695 Check(a.format.num_channels == b.format.num_channels); 696 Check(a.format.data_type == b.format.data_type); 697 Check(a.format.endianness == b.format.endianness); 698 Check(a.pixels_size == b.pixels_size); 699 const uint8_t* p_a = reinterpret_cast<const uint8_t*>(a.pixels()); 700 const uint8_t* p_b = reinterpret_cast<const uint8_t*>(b.pixels()); 701 for (size_t y = 0; y < a.ysize; ++y) { 702 for (size_t x = 0; x < a.xsize; ++x) { 703 size_t idx = (y * a.xsize + x) * a.pixel_stride(); 704 if (memcmp(&p_a[idx], &p_b[idx], a.pixel_stride()) != 0) { 705 printf("Mismatch at row %" PRIuS " col %" PRIuS "\n", y, x); 706 printf(" a: "); 707 for (size_t j = 0; j < a.pixel_stride(); ++j) { 708 printf(" %3u", p_a[idx + j]); 709 } 710 printf("\n b: "); 711 for (size_t j = 0; j < a.pixel_stride(); ++j) { 712 printf(" %3u", p_b[idx + j]); 713 } 714 printf("\n"); 715 return false; 716 } 717 } 718 } 719 return true; 720 } 721 722 bool SamePixels(const extras::PackedPixelFile& a, 723 const extras::PackedPixelFile& b) { 724 Check(a.info.xsize == b.info.xsize); 725 Check(a.info.ysize == b.info.ysize); 726 Check(a.info.bits_per_sample == b.info.bits_per_sample); 727 Check(a.info.exponent_bits_per_sample == b.info.exponent_bits_per_sample); 728 Check(a.frames.size() == b.frames.size()); 729 for (size_t i = 0; i < a.frames.size(); ++i) { 730 const auto& frame_a = a.frames[i]; 731 const auto& frame_b = b.frames[i]; 732 if (!SamePixels(frame_a.color, frame_b.color)) { 733 return false; 734 } 735 Check(frame_a.extra_channels.size() == frame_b.extra_channels.size()); 736 for (size_t j = 0; j < frame_a.extra_channels.size(); ++j) { 737 if (!SamePixels(frame_a.extra_channels[i], frame_b.extra_channels[i])) { 738 return false; 739 } 740 } 741 } 742 return true; 743 } 744 745 Status ReadICC(BitReader* JXL_RESTRICT reader, 746 std::vector<uint8_t>* JXL_RESTRICT icc) { 747 JxlMemoryManager* memort_manager = jxl::test::MemoryManager(); 748 icc->clear(); 749 ICCReader icc_reader{memort_manager}; 750 PaddedBytes icc_buffer{memort_manager}; 751 JXL_RETURN_IF_ERROR(icc_reader.Init(reader)); 752 JXL_RETURN_IF_ERROR(icc_reader.Process(reader, &icc_buffer)); 753 Bytes(icc_buffer).AppendTo(*icc); 754 return true; 755 } 756 757 namespace { // For EncodeFile 758 Status PrepareCodecMetadataFromIO(const CompressParams& cparams, 759 const CodecInOut* io, 760 CodecMetadata* metadata) { 761 *metadata = io->metadata; 762 size_t ups = 1; 763 if (cparams.already_downsampled) ups = cparams.resampling; 764 765 JXL_RETURN_IF_ERROR(metadata->size.Set(io->xsize() * ups, io->ysize() * ups)); 766 767 // Keep ICC profile in lossless modes because a reconstructed profile may be 768 // slightly different (quantization). 769 // Also keep ICC in JPEG reconstruction mode as we need byte-exact profiles. 770 if (!cparams.IsLossless() && !io->Main().IsJPEG() && cparams.cms_set) { 771 metadata->m.color_encoding.DecideIfWantICC(cparams.cms); 772 } 773 774 metadata->m.xyb_encoded = 775 cparams.color_transform == ColorTransform::kXYB ? true : false; 776 777 // TODO(firsching): move this EncodeFile to test_utils / re-implement this 778 // using API functions 779 return true; 780 } 781 782 Status EncodePreview(const CompressParams& cparams, ImageBundle& ib, 783 const CodecMetadata* metadata, const JxlCmsInterface& cms, 784 ThreadPool* pool, BitWriter* JXL_RESTRICT writer) { 785 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 786 BitWriter preview_writer{memory_manager}; 787 // TODO(janwas): also support generating preview by downsampling 788 if (ib.HasColor()) { 789 AuxOut aux_out; 790 // TODO(lode): check if we want all extra channels and matching xyb_encoded 791 // for the preview, such that using the main ImageMetadata object for 792 // encoding this frame is warrented. 793 FrameInfo frame_info; 794 frame_info.is_preview = true; 795 JXL_RETURN_IF_ERROR(EncodeFrame(memory_manager, cparams, frame_info, 796 metadata, ib, cms, pool, &preview_writer, 797 &aux_out)); 798 preview_writer.ZeroPadToByte(); 799 } 800 801 if (preview_writer.BitsWritten() != 0) { 802 writer->ZeroPadToByte(); 803 JXL_RETURN_IF_ERROR(writer->AppendByteAligned(preview_writer.GetSpan())); 804 } 805 806 return true; 807 } 808 809 } // namespace 810 811 Status EncodeFile(const CompressParams& params, CodecInOut* io, 812 std::vector<uint8_t>* compressed, ThreadPool* pool) { 813 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 814 compressed->clear(); 815 const JxlCmsInterface& cms = *JxlGetDefaultCms(); 816 JXL_RETURN_IF_ERROR(io->CheckMetadata()); 817 BitWriter writer{memory_manager}; 818 819 CompressParams cparams = params; 820 if (io->Main().color_transform != ColorTransform::kNone) { 821 // Set the color transform to YCbCr or XYB if the original image is such. 822 cparams.color_transform = io->Main().color_transform; 823 } 824 825 JXL_RETURN_IF_ERROR(ParamsPostInit(&cparams)); 826 827 std::unique_ptr<CodecMetadata> metadata = jxl::make_unique<CodecMetadata>(); 828 JXL_RETURN_IF_ERROR(PrepareCodecMetadataFromIO(cparams, io, metadata.get())); 829 JXL_RETURN_IF_ERROR( 830 WriteCodestreamHeaders(metadata.get(), &writer, /*aux_out*/ nullptr)); 831 832 // Only send ICC (at least several hundred bytes) if fields aren't enough. 833 if (metadata->m.color_encoding.WantICC()) { 834 JXL_RETURN_IF_ERROR( 835 WriteICC(Span<const uint8_t>(metadata->m.color_encoding.ICC()), &writer, 836 LayerType::Header, /* aux_out */ nullptr)); 837 } 838 839 if (metadata->m.have_preview) { 840 JXL_RETURN_IF_ERROR(EncodePreview(cparams, io->preview_frame, 841 metadata.get(), cms, pool, &writer)); 842 } 843 844 // Each frame should start on byte boundaries. 845 JXL_RETURN_IF_ERROR( 846 writer.WithMaxBits(8, LayerType::Header, /*aux_out=*/nullptr, [&] { 847 writer.ZeroPadToByte(); 848 return true; 849 })); 850 851 for (size_t i = 0; i < io->frames.size(); i++) { 852 FrameInfo info; 853 info.is_last = i == io->frames.size() - 1; 854 if (io->frames[i].use_for_next_frame) { 855 info.save_as_reference = 1; 856 } 857 JXL_RETURN_IF_ERROR(EncodeFrame(memory_manager, cparams, info, 858 metadata.get(), io->frames[i], cms, pool, 859 &writer, 860 /* aux_out */ nullptr)); 861 } 862 863 PaddedBytes output = std::move(writer).TakeBytes(); 864 Bytes(output).AppendTo(*compressed); 865 return true; 866 } 867 868 extras::JXLCompressParams CompressParamsForLossless() { 869 extras::JXLCompressParams cparams; 870 cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR, 1); 871 cparams.AddOption(JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM, 1); 872 cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, 6); // Weighted 873 cparams.distance = 0.0; 874 return cparams; 875 } 876 877 StatusOr<ImageF> GetImage(const extras::PackedPixelFile& ppf) { 878 JxlMemoryManager* memory_manager = MemoryManager(); 879 ImageF gray; 880 JXL_ENSURE(!ppf.frames.empty()); 881 const extras::PackedImage& image = ppf.frames[0].color; 882 const JxlPixelFormat& format = image.format; 883 JXL_RETURN_IF_ERROR(format.num_channels == 1); 884 const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels()); 885 JXL_TEST_ASSIGN_OR_DIE( 886 gray, ImageF::Create(memory_manager, image.xsize, image.ysize)); 887 JXL_RETURN_IF_ERROR( 888 ConvertFromExternal(pixels, image.pixels_size, image.xsize, image.ysize, 889 ppf.info.bits_per_sample, format, 0, nullptr, &gray)); 890 return gray; 891 } 892 893 StatusOr<Image3F> GetColorImage(const extras::PackedPixelFile& ppf) { 894 JxlMemoryManager* memory_manager = MemoryManager(); 895 Image3F color; 896 JXL_ENSURE(!ppf.frames.empty()); 897 const extras::PackedImage& image = ppf.frames[0].color; 898 const JxlPixelFormat& format = image.format; 899 JXL_RETURN_IF_ERROR(format.num_channels == 3); 900 const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels()); 901 JXL_TEST_ASSIGN_OR_DIE( 902 color, Image3F::Create(memory_manager, image.xsize, image.ysize)); 903 for (size_t c = 0; c < format.num_channels; ++c) { 904 JXL_RETURN_IF_ERROR(ConvertFromExternal( 905 pixels, image.pixels_size, image.xsize, image.ysize, 906 ppf.info.bits_per_sample, format, c, nullptr, &color.Plane(c))); 907 } 908 return color; 909 } 910 911 } // namespace test 912 913 bool operator==(const jxl::Bytes& a, const jxl::Bytes& b) { 914 if (a.size() != b.size()) return false; 915 if (memcmp(a.data(), b.data(), a.size()) != 0) return false; 916 return true; 917 } 918 919 // Allow using EXPECT_EQ on jxl::Bytes 920 bool operator!=(const jxl::Bytes& a, const jxl::Bytes& b) { return !(a == b); } 921 922 } // namespace jxl