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