jxl_test.cc (63690B)
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/extras/dec/jxl.h" 7 8 #include <jxl/cms.h> 9 #include <jxl/color_encoding.h> 10 #include <jxl/encode.h> 11 #include <jxl/types.h> 12 13 #include <algorithm> 14 #include <cstddef> 15 #include <cstdint> 16 #include <cstdio> 17 #include <cstring> 18 #include <future> 19 #include <ostream> 20 #include <string> 21 #include <tuple> 22 #include <vector> 23 24 #include "lib/extras/codec.h" 25 #include "lib/extras/dec/decode.h" 26 #include "lib/extras/enc/encode.h" 27 #include "lib/extras/enc/jxl.h" 28 #include "lib/extras/packed_image.h" 29 #include "lib/jxl/alpha.h" 30 #include "lib/jxl/base/data_parallel.h" 31 #include "lib/jxl/base/span.h" 32 #include "lib/jxl/base/status.h" 33 #include "lib/jxl/codec_in_out.h" 34 #include "lib/jxl/color_encoding_internal.h" 35 #include "lib/jxl/common.h" // JXL_HIGH_PRECISION 36 #include "lib/jxl/enc_params.h" 37 #include "lib/jxl/fake_parallel_runner_testonly.h" 38 #include "lib/jxl/image.h" 39 #include "lib/jxl/image_bundle.h" 40 #include "lib/jxl/image_metadata.h" 41 #include "lib/jxl/jpeg/enc_jpeg_data.h" 42 #include "lib/jxl/test_image.h" 43 #include "lib/jxl/test_utils.h" 44 #include "lib/jxl/testing.h" 45 46 namespace jxl { 47 48 struct AuxOut; 49 50 namespace { 51 using extras::JXLCompressParams; 52 using extras::JXLDecompressParams; 53 using extras::PackedPixelFile; 54 using test::ButteraugliDistance; 55 using test::ComputeDistance2; 56 using test::ReadTestData; 57 using test::Roundtrip; 58 using test::TestImage; 59 using test::ThreadPoolForTests; 60 61 #define JXL_TEST_NL 0 // Disabled in code 62 63 TEST(JxlTest, RoundtripSinglePixel) { 64 TestImage t; 65 t.SetDimensions(1, 1).AddFrame().ZeroFill(); 66 PackedPixelFile ppf_out; 67 EXPECT_EQ(Roundtrip(t.ppf(), {}, {}, nullptr, &ppf_out), 55); 68 } 69 70 TEST(JxlTest, RoundtripSinglePixelWithAlpha) { 71 TestImage t; 72 t.SetDimensions(1, 1).SetChannels(4).AddFrame().ZeroFill(); 73 PackedPixelFile ppf_out; 74 EXPECT_EQ(Roundtrip(t.ppf(), {}, {}, nullptr, &ppf_out), 58); 75 } 76 77 // Changing serialized signature causes Decode to fail. 78 #ifndef JXL_CRASH_ON_ERROR 79 TEST(JxlTest, RoundtripMarker) { 80 TestImage t; 81 t.SetDimensions(1, 1).AddFrame().ZeroFill(); 82 for (size_t i = 0; i < 2; ++i) { 83 std::vector<uint8_t> compressed; 84 EXPECT_TRUE(extras::EncodeImageJXL({}, t.ppf(), /*jpeg_bytes=*/nullptr, 85 &compressed)); 86 compressed[i] ^= 0xFF; 87 PackedPixelFile ppf_out; 88 EXPECT_FALSE(extras::DecodeImageJXL(compressed.data(), compressed.size(), 89 {}, /* decoded_bytes */ nullptr, 90 &ppf_out)); 91 } 92 } 93 #endif 94 95 TEST(JxlTest, RoundtripTinyFast) { 96 ThreadPool* pool = nullptr; 97 const std::vector<uint8_t> orig = 98 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 99 TestImage t; 100 t.DecodeFromBytes(orig).ClearMetadata().SetDimensions(32, 32); 101 102 JXLCompressParams cparams; 103 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); 104 cparams.distance = 4.0f; 105 106 PackedPixelFile ppf_out; 107 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 181, 15); 108 } 109 110 TEST(JxlTest, RoundtripSmallD1) { 111 ThreadPool* pool = nullptr; 112 const std::vector<uint8_t> orig = 113 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 114 TestImage t; 115 t.DecodeFromBytes(orig).ClearMetadata(); 116 size_t xsize = t.ppf().info.xsize / 8; 117 size_t ysize = t.ppf().info.ysize / 8; 118 t.SetDimensions(xsize, ysize); 119 120 { 121 PackedPixelFile ppf_out; 122 EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 916, 40); 123 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.888)); 124 } 125 126 // With a lower intensity target than the default, the bitrate should be 127 // smaller. 128 t.ppf().info.intensity_target = 100.0f; 129 130 { 131 PackedPixelFile ppf_out; 132 EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 745, 20); 133 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.3)); 134 EXPECT_EQ(ppf_out.info.intensity_target, t.ppf().info.intensity_target); 135 } 136 } 137 TEST(JxlTest, RoundtripResample2) { 138 ThreadPool* pool = nullptr; 139 const std::vector<uint8_t> orig = 140 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 141 TestImage t; 142 t.DecodeFromBytes(orig).ClearMetadata(); 143 144 JXLCompressParams cparams; 145 cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2); 146 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 3); // kFalcon 147 148 PackedPixelFile ppf_out; 149 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 17300, 500); 150 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(90)); 151 } 152 153 TEST(JxlTest, RoundtripResample2Slow) { 154 ThreadPool* pool = nullptr; 155 const std::vector<uint8_t> orig = 156 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 157 TestImage t; 158 t.DecodeFromBytes(orig).ClearMetadata(); 159 160 JXLCompressParams cparams; 161 cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2); 162 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 9); // kTortoise 163 cparams.distance = 10.0; 164 165 PackedPixelFile ppf_out; 166 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 3888, 200); 167 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(250)); 168 } 169 170 TEST(JxlTest, RoundtripResample2MT) { 171 ThreadPoolForTests pool(4); 172 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 173 // image has to be large enough to have multiple groups after downsampling 174 TestImage t; 175 t.DecodeFromBytes(orig).ClearMetadata(); 176 177 JXLCompressParams cparams; 178 cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2); 179 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 3); // kFalcon 180 181 PackedPixelFile ppf_out; 182 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 203300, 2000); 183 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(340)); 184 } 185 186 // Roundtrip the image using a parallel runner that executes single-threaded but 187 // in random order. 188 TEST(JxlTest, RoundtripOutOfOrderProcessing) { 189 FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8); 190 ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); 191 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 192 TestImage t; 193 t.DecodeFromBytes(orig).ClearMetadata(); 194 // Image size is selected so that the block border needed is larger than the 195 // amount of pixels available on the next block. 196 t.SetDimensions(513, 515); 197 198 JXLCompressParams cparams; 199 // Force epf so we end up needing a lot of border. 200 cparams.AddOption(JXL_ENC_FRAME_SETTING_EPF, 3); 201 202 PackedPixelFile ppf_out; 203 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 27444, 400); 204 EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), 1.35); 205 } 206 207 TEST(JxlTest, RoundtripOutOfOrderProcessingBorder) { 208 FakeParallelRunner fake_pool(/*order_seed=*/47, /*num_threads=*/8); 209 ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); 210 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 211 TestImage t; 212 t.DecodeFromBytes(orig).ClearMetadata(); 213 // Image size is selected so that the block border needed is larger than the 214 // amount of pixels available on the next block. 215 t.SetDimensions(513, 515); 216 217 JXLCompressParams cparams; 218 // Force epf so we end up needing a lot of border. 219 cparams.AddOption(JXL_ENC_FRAME_SETTING_EPF, 3); 220 cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2); 221 222 PackedPixelFile ppf_out; 223 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 10065, 200); 224 EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), 2.9); 225 } 226 227 TEST(JxlTest, RoundtripResample4) { 228 ThreadPool* pool = nullptr; 229 const std::vector<uint8_t> orig = 230 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 231 TestImage t; 232 t.DecodeFromBytes(orig).ClearMetadata(); 233 234 JXLCompressParams cparams; 235 cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 4); 236 237 PackedPixelFile ppf_out; 238 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 5758, 100); 239 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(22)); 240 } 241 242 TEST(JxlTest, RoundtripResample8) { 243 ThreadPool* pool = nullptr; 244 const std::vector<uint8_t> orig = 245 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 246 TestImage t; 247 t.DecodeFromBytes(orig).ClearMetadata(); 248 249 JXLCompressParams cparams; 250 cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 8); 251 252 PackedPixelFile ppf_out; 253 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 2036, 50); 254 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(50)); 255 } 256 257 TEST(JxlTest, RoundtripUnalignedD2) { 258 ThreadPool* pool = nullptr; 259 const std::vector<uint8_t> orig = 260 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 261 TestImage t; 262 t.DecodeFromBytes(orig).ClearMetadata(); 263 size_t xsize = t.ppf().info.xsize / 12; 264 size_t ysize = t.ppf().info.ysize / 7; 265 t.SetDimensions(xsize, ysize); 266 267 JXLCompressParams cparams; 268 cparams.distance = 2.0; 269 270 PackedPixelFile ppf_out; 271 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 506, 30); 272 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.72)); 273 } 274 275 TEST(JxlTest, RoundtripMultiGroup) { 276 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 277 TestImage t; 278 t.DecodeFromBytes(orig).ClearMetadata().SetDimensions(600, 1024); 279 280 auto test = [&](jxl::SpeedTier speed_tier, float target_distance, 281 size_t expected_size, float expected_distance) { 282 ThreadPoolForTests pool(4); 283 JXLCompressParams cparams; 284 int64_t effort = 10 - static_cast<int>(speed_tier); 285 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, effort); 286 cparams.distance = target_distance; 287 288 PackedPixelFile ppf_out; 289 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), expected_size, 290 700); 291 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), 292 IsSlightlyBelow(expected_distance)); 293 }; 294 295 auto run_kitten = std::async(std::launch::async, test, SpeedTier::kKitten, 296 1.0f, 63624u, 8.5); 297 auto run_wombat = std::async(std::launch::async, test, SpeedTier::kWombat, 298 2.0f, 39620u, 15.5); 299 } 300 301 TEST(JxlTest, RoundtripRGBToGrayscale) { 302 ThreadPoolForTests pool(4); 303 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 304 CodecInOut io; 305 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool)); 306 io.ShrinkTo(600, 1024); 307 308 CompressParams cparams; 309 cparams.butteraugli_distance = 1.0f; 310 cparams.speed_tier = SpeedTier::kFalcon; 311 312 JXLDecompressParams dparams; 313 dparams.color_space = "Gra_D65_Rel_SRG"; 314 315 CodecInOut io2; 316 EXPECT_FALSE(io.Main().IsGray()); 317 size_t compressed_size; 318 JXL_EXPECT_OK( 319 Roundtrip(&io, cparams, dparams, &io2, _, &compressed_size, &pool)); 320 EXPECT_LE(compressed_size, 65000u); 321 EXPECT_TRUE(io2.Main().IsGray()); 322 323 // Convert original to grayscale here, because TransformTo refuses to 324 // convert between grayscale and RGB. 325 ColorEncoding srgb_lin = ColorEncoding::LinearSRGB(/*is_gray=*/false); 326 ASSERT_TRUE(io.frames[0].TransformTo(srgb_lin, *JxlGetDefaultCms())); 327 Image3F* color = io.Main().color(); 328 for (size_t y = 0; y < color->ysize(); ++y) { 329 float* row_r = color->PlaneRow(0, y); 330 float* row_g = color->PlaneRow(1, y); 331 float* row_b = color->PlaneRow(2, y); 332 for (size_t x = 0; x < color->xsize(); ++x) { 333 float luma = 0.2126 * row_r[x] + 0.7152 * row_g[x] + 0.0722 * row_b[x]; 334 row_r[x] = row_g[x] = row_b[x] = luma; 335 } 336 } 337 ColorEncoding srgb_gamma = ColorEncoding::SRGB(/*is_gray=*/false); 338 ASSERT_TRUE(io.frames[0].TransformTo(srgb_gamma, *JxlGetDefaultCms())); 339 io.metadata.m.color_encoding = io2.Main().c_current(); 340 io.Main().OverrideProfile(io2.Main().c_current()); 341 EXPECT_THAT(ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), 342 *JxlGetDefaultCms(), 343 /*distmap=*/nullptr, &pool), 344 IsSlightlyBelow(1.4)); 345 } 346 347 TEST(JxlTest, RoundtripLargeFast) { 348 ThreadPoolForTests pool(8); 349 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 350 TestImage t; 351 t.DecodeFromBytes(orig).ClearMetadata(); 352 353 JXLCompressParams cparams; 354 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel 355 356 PackedPixelFile ppf_out; 357 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 503000, 12000); 358 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(78)); 359 } 360 361 TEST(JxlTest, JXL_X86_64_TEST(RoundtripLargeEmptyModular)) { 362 ThreadPoolForTests pool(8); 363 TestImage t; 364 t.SetDimensions(8192, 8192).SetDataType(JXL_TYPE_UINT8).SetChannels(1); 365 TestImage::Frame frame = t.AddFrame(); 366 frame.ZeroFill(); 367 for (size_t y = 0; y < 513; y += 7) { 368 for (size_t x = 0; x < 513; x += 7) { 369 frame.SetValue(y, x, 0, 0.88); 370 } 371 } 372 373 JXLCompressParams cparams; 374 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 1); 375 cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR, 1); 376 cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1); 377 cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, 2); 378 cparams.AddOption(JXL_ENC_FRAME_SETTING_DECODING_SPEED, 2); 379 380 PackedPixelFile ppf_out; 381 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 110846, 10000); 382 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(0.7)); 383 } 384 385 TEST(JxlTest, RoundtripOutputColorSpace) { 386 ThreadPoolForTests pool(8); 387 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 388 TestImage t; 389 t.DecodeFromBytes(orig).ClearMetadata(); 390 391 JXLCompressParams cparams; 392 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel 393 394 JXLDecompressParams dparams; 395 dparams.color_space = "RGB_D65_DCI_Rel_709"; 396 PackedPixelFile ppf_out; 397 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out), 503000, 398 12000); 399 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(78)); 400 } 401 402 TEST(JxlTest, RoundtripDotsForceEpf) { 403 ThreadPoolForTests pool(8); 404 const std::vector<uint8_t> orig = 405 ReadTestData("external/wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); 406 TestImage t; 407 t.DecodeFromBytes(orig).ClearMetadata(); 408 409 JXLCompressParams cparams; 410 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel 411 cparams.AddOption(JXL_ENC_FRAME_SETTING_EPF, 2); 412 cparams.AddOption(JXL_ENC_FRAME_SETTING_DOTS, 1); 413 414 PackedPixelFile ppf_out; 415 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 41355, 300); 416 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(18)); 417 } 418 419 // Checks for differing size/distance in two consecutive runs of distance 2, 420 // which involves additional processing including adaptive reconstruction. 421 // Failing this may be a sign of race conditions or invalid memory accesses. 422 TEST(JxlTest, RoundtripD2Consistent) { 423 ThreadPoolForTests pool(8); 424 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 425 TestImage t; 426 t.DecodeFromBytes(orig).ClearMetadata(); 427 428 JXLCompressParams cparams; 429 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel 430 cparams.distance = 2.0; 431 432 // Try each xsize mod kBlockDim to verify right border handling. 433 for (size_t xsize = 48; xsize > 40; --xsize) { 434 t.SetDimensions(xsize, 15); 435 436 PackedPixelFile ppf2; 437 const size_t size2 = Roundtrip(t.ppf(), cparams, {}, &pool, &ppf2); 438 439 PackedPixelFile ppf3; 440 const size_t size3 = Roundtrip(t.ppf(), cparams, {}, &pool, &ppf3); 441 442 // Exact same compressed size. 443 EXPECT_EQ(size2, size3); 444 445 // Exact same distance. 446 const float dist2 = ComputeDistance2(t.ppf(), ppf2); 447 const float dist3 = ComputeDistance2(t.ppf(), ppf3); 448 EXPECT_EQ(dist2, dist3); 449 } 450 } 451 452 // Same as above, but for full image, testing multiple groups. 453 TEST(JxlTest, RoundtripLargeConsistent) { 454 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 455 TestImage t; 456 t.DecodeFromBytes(orig).ClearMetadata(); 457 458 JXLCompressParams cparams; 459 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel 460 cparams.distance = 2.0; 461 462 auto roundtrip_and_compare = [&]() { 463 ThreadPoolForTests pool(8); 464 PackedPixelFile ppf2; 465 size_t size = Roundtrip(t.ppf(), cparams, {}, &pool, &ppf2); 466 double dist = ComputeDistance2(t.ppf(), ppf2); 467 return std::tuple<size_t, double>(size, dist); 468 }; 469 470 // Try each xsize mod kBlockDim to verify right border handling. 471 auto future2 = std::async(std::launch::async, roundtrip_and_compare); 472 auto future3 = std::async(std::launch::async, roundtrip_and_compare); 473 474 const auto result2 = future2.get(); 475 const auto result3 = future3.get(); 476 477 // Exact same compressed size. 478 EXPECT_EQ(std::get<0>(result2), std::get<0>(result3)); 479 480 // Exact same distance. 481 EXPECT_EQ(std::get<1>(result2), std::get<1>(result3)); 482 } 483 484 TEST(JxlTest, RoundtripSmallNL) { 485 ThreadPool* pool = nullptr; 486 const std::vector<uint8_t> orig = 487 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 488 TestImage t; 489 t.DecodeFromBytes(orig).ClearMetadata(); 490 size_t xsize = t.ppf().info.xsize / 8; 491 size_t ysize = t.ppf().info.ysize / 8; 492 t.SetDimensions(xsize, ysize); 493 494 PackedPixelFile ppf_out; 495 EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 916, 45); 496 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.82)); 497 } 498 499 TEST(JxlTest, RoundtripNoGaborishNoAR) { 500 ThreadPool* pool = nullptr; 501 const std::vector<uint8_t> orig = 502 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 503 TestImage t; 504 t.DecodeFromBytes(orig).ClearMetadata(); 505 506 JXLCompressParams cparams; 507 cparams.AddOption(JXL_ENC_FRAME_SETTING_EPF, 0); 508 cparams.AddOption(JXL_ENC_FRAME_SETTING_GABORISH, 0); 509 510 PackedPixelFile ppf_out; 511 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 41142, 400); 512 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.8)); 513 } 514 515 TEST(JxlTest, RoundtripSmallNoGaborish) { 516 ThreadPool* pool = nullptr; 517 const std::vector<uint8_t> orig = 518 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 519 TestImage t; 520 t.DecodeFromBytes(orig).ClearMetadata(); 521 size_t xsize = t.ppf().info.xsize / 8; 522 size_t ysize = t.ppf().info.ysize / 8; 523 t.SetDimensions(xsize, ysize); 524 525 JXLCompressParams cparams; 526 cparams.AddOption(JXL_ENC_FRAME_SETTING_GABORISH, 0); 527 528 PackedPixelFile ppf_out; 529 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 1006, 20); 530 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.1)); 531 } 532 533 TEST(JxlTest, RoundtripSmallPatchesAlpha) { 534 ThreadPool* pool = nullptr; 535 TestImage t; 536 t.SetDimensions(256, 256).SetChannels(4); 537 t.SetColorEncoding("RGB_D65_SRG_Rel_Lin"); 538 TestImage::Frame frame = t.AddFrame(); 539 frame.ZeroFill(); 540 // This pattern should be picked up by the patch detection heuristics. 541 for (size_t y = 0; y < t.ppf().info.ysize; ++y) { 542 for (size_t x = 0; x < t.ppf().info.xsize; ++x) { 543 if (x % 4 == 0 && (y / 32) % 4 == 0) { 544 frame.SetValue(y, x, 1, 127.0f / 255.0f); 545 } 546 frame.SetValue(y, x, 3, 1.0f); 547 } 548 } 549 550 JXLCompressParams cparams; 551 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel 552 cparams.distance = 0.1f; 553 554 PackedPixelFile ppf_out; 555 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 597, 100); 556 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.018f)); 557 } 558 559 TEST(JxlTest, RoundtripSmallPatches) { 560 ThreadPool* pool = nullptr; 561 TestImage t; 562 t.SetDimensions(256, 256); 563 t.SetColorEncoding("RGB_D65_SRG_Rel_Lin"); 564 TestImage::Frame frame = t.AddFrame(); 565 frame.ZeroFill(); 566 // This pattern should be picked up by the patch detection heuristics. 567 for (size_t y = 0; y < t.ppf().info.ysize; ++y) { 568 for (size_t x = 0; x < t.ppf().info.xsize; ++x) { 569 if (x % 4 == 0 && (y / 32) % 4 == 0) { 570 frame.SetValue(y, x, 1, 127.0f / 255.0f); 571 } 572 } 573 } 574 575 JXLCompressParams cparams; 576 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel 577 cparams.distance = 0.1f; 578 579 PackedPixelFile ppf_out; 580 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 486, 100); 581 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.018f)); 582 } 583 584 // TODO(szabadka) Add encoder and decoder API functions that accept frame 585 // buffers in arbitrary unsigned and floating point formats, and then roundtrip 586 // test the lossless codepath to make sure the exact binary representations 587 // are preserved. 588 #if JXL_FALSE 589 TEST(JxlTest, RoundtripImageBundleOriginalBits) { 590 // Image does not matter, only io.metadata.m and io2.metadata.m are tested. 591 JXL_ASSIGN_OR_DIE(Image3F image, Image3F::Create(1, 1)); 592 ZeroFillImage(&image); 593 CodecInOut io; 594 io.metadata.m.color_encoding = ColorEncoding::LinearSRGB(); 595 io.SetFromImage(std::move(image), ColorEncoding::LinearSRGB()); 596 597 CompressParams cparams; 598 599 // Test unsigned integers from 1 to 32 bits 600 for (uint32_t bit_depth = 1; bit_depth <= 32; bit_depth++) { 601 if (bit_depth == 32) { 602 // TODO(lode): allow testing 32, however the code below ends up in 603 // enc_modular which does not support 32. We only want to test the header 604 // encoding though, so try without modular. 605 break; 606 } 607 608 io.metadata.m.SetUintSamples(bit_depth); 609 CodecInOut io2; 610 JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _)); 611 612 EXPECT_EQ(bit_depth, io2.metadata.m.bit_depth.bits_per_sample); 613 EXPECT_FALSE(io2.metadata.m.bit_depth.floating_point_sample); 614 EXPECT_EQ(0u, io2.metadata.m.bit_depth.exponent_bits_per_sample); 615 EXPECT_EQ(0u, io2.metadata.m.GetAlphaBits()); 616 } 617 618 // Test various existing and non-existing floating point formats 619 for (uint32_t bit_depth = 8; bit_depth <= 32; bit_depth++) { 620 if (bit_depth != 32) { 621 // TODO(user): test other float types once they work 622 break; 623 } 624 625 uint32_t exponent_bit_depth; 626 if (bit_depth < 10) { 627 exponent_bit_depth = 2; 628 } else if (bit_depth < 12) { 629 exponent_bit_depth = 3; 630 } else if (bit_depth < 16) { 631 exponent_bit_depth = 4; 632 } else if (bit_depth < 20) { 633 exponent_bit_depth = 5; 634 } else if (bit_depth < 24) { 635 exponent_bit_depth = 6; 636 } else if (bit_depth < 28) { 637 exponent_bit_depth = 7; 638 } else { 639 exponent_bit_depth = 8; 640 } 641 642 io.metadata.m.bit_depth.bits_per_sample = bit_depth; 643 io.metadata.m.bit_depth.floating_point_sample = true; 644 io.metadata.m.bit_depth.exponent_bits_per_sample = exponent_bit_depth; 645 646 CodecInOut io2; 647 JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2)); 648 649 EXPECT_EQ(bit_depth, io2.metadata.m.bit_depth.bits_per_sample); 650 EXPECT_TRUE(io2.metadata.m.bit_depth.floating_point_sample); 651 EXPECT_EQ(exponent_bit_depth, 652 io2.metadata.m.bit_depth.exponent_bits_per_sample); 653 EXPECT_EQ(0u, io2.metadata.m.GetAlphaBits()); 654 } 655 } 656 #endif 657 658 TEST(JxlTest, RoundtripGrayscale) { 659 const std::vector<uint8_t> orig = ReadTestData( 660 "external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); 661 CodecInOut io; 662 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); 663 ASSERT_NE(io.xsize(), 0u); 664 io.ShrinkTo(128, 128); 665 EXPECT_TRUE(io.Main().IsGray()); 666 EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample); 667 EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample); 668 EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample); 669 EXPECT_TRUE(io.metadata.m.color_encoding.Tf().IsSRGB()); 670 671 { 672 CompressParams cparams; 673 cparams.butteraugli_distance = 1.0; 674 675 std::vector<uint8_t> compressed; 676 EXPECT_TRUE(test::EncodeFile(cparams, &io, &compressed)); 677 CodecInOut io2; 678 EXPECT_TRUE(test::DecodeFile({}, Bytes(compressed), &io2)); 679 EXPECT_TRUE(io2.Main().IsGray()); 680 681 EXPECT_LE(compressed.size(), 7000u); 682 EXPECT_THAT(ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), 683 *JxlGetDefaultCms(), 684 /*distmap=*/nullptr), 685 IsSlightlyBelow(1.6)); 686 } 687 688 // Test with larger butteraugli distance and other settings enabled so 689 // different jxl codepaths trigger. 690 { 691 CompressParams cparams; 692 cparams.butteraugli_distance = 8.0; 693 694 std::vector<uint8_t> compressed; 695 EXPECT_TRUE(test::EncodeFile(cparams, &io, &compressed)); 696 CodecInOut io2; 697 EXPECT_TRUE(test::DecodeFile({}, Bytes(compressed), &io2)); 698 EXPECT_TRUE(io2.Main().IsGray()); 699 700 EXPECT_LE(compressed.size(), 1300u); 701 EXPECT_THAT(ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), 702 *JxlGetDefaultCms(), 703 /*distmap=*/nullptr), 704 IsSlightlyBelow(6.7)); 705 } 706 707 { 708 CompressParams cparams; 709 cparams.butteraugli_distance = 1.0; 710 711 std::vector<uint8_t> compressed; 712 EXPECT_TRUE(test::EncodeFile(cparams, &io, &compressed)); 713 714 CodecInOut io2; 715 JXLDecompressParams dparams; 716 dparams.color_space = "RGB_D65_SRG_Rel_SRG"; 717 EXPECT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &io2)); 718 EXPECT_FALSE(io2.Main().IsGray()); 719 720 EXPECT_LE(compressed.size(), 7000u); 721 EXPECT_THAT(ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), 722 *JxlGetDefaultCms(), 723 /*distmap=*/nullptr), 724 IsSlightlyBelow(1.6)); 725 } 726 } 727 728 TEST(JxlTest, RoundtripAlpha) { 729 const std::vector<uint8_t> orig = 730 ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); 731 CodecInOut io; 732 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); 733 734 ASSERT_NE(io.xsize(), 0u); 735 ASSERT_TRUE(io.metadata.m.HasAlpha()); 736 ASSERT_TRUE(io.Main().HasAlpha()); 737 io.ShrinkTo(300, 300); 738 739 CompressParams cparams; 740 cparams.butteraugli_distance = 1.0; 741 742 EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample); 743 EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample); 744 EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample); 745 EXPECT_TRUE(io.metadata.m.color_encoding.Tf().IsSRGB()); 746 std::vector<uint8_t> compressed; 747 EXPECT_TRUE(test::EncodeFile(cparams, &io, &compressed)); 748 749 EXPECT_LE(compressed.size(), 20000u); 750 751 for (bool use_image_callback : {false, true}) { 752 for (bool unpremul_alpha : {false, true}) { 753 CodecInOut io2; 754 JXLDecompressParams dparams; 755 dparams.use_image_callback = use_image_callback; 756 dparams.unpremultiply_alpha = unpremul_alpha; 757 EXPECT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &io2)); 758 EXPECT_THAT(ButteraugliDistance(io.frames, io2.frames, 759 ButteraugliParams(), *JxlGetDefaultCms(), 760 /*distmap=*/nullptr), 761 IsSlightlyBelow(1.15)); 762 } 763 } 764 } 765 766 namespace { 767 // Performs "PremultiplyAlpha" for each ImageBundle (preview/frames). 768 bool PremultiplyAlpha(CodecInOut& io) { 769 const auto doPremultiplyAlpha = [](ImageBundle& bundle) { 770 if (!bundle.HasAlpha()) return; 771 if (!bundle.HasColor()) return; 772 auto* color = bundle.color(); 773 const auto* alpha = bundle.alpha(); 774 JXL_CHECK(color->ysize() == alpha->ysize()); 775 JXL_CHECK(color->xsize() == alpha->xsize()); 776 for (size_t y = 0; y < color->ysize(); y++) { 777 ::jxl::PremultiplyAlpha(color->PlaneRow(0, y), color->PlaneRow(1, y), 778 color->PlaneRow(2, y), alpha->Row(y), 779 color->xsize()); 780 } 781 }; 782 ExtraChannelInfo* eci = io.metadata.m.Find(ExtraChannel::kAlpha); 783 if (eci == nullptr || eci->alpha_associated) return false; 784 if (io.metadata.m.have_preview) { 785 doPremultiplyAlpha(io.preview_frame); 786 } 787 for (ImageBundle& ib : io.frames) { 788 doPremultiplyAlpha(ib); 789 } 790 eci->alpha_associated = true; 791 return true; 792 } 793 794 bool UnpremultiplyAlpha(CodecInOut& io) { 795 const auto doUnpremultiplyAlpha = [](ImageBundle& bundle) { 796 if (!bundle.HasAlpha()) return; 797 if (!bundle.HasColor()) return; 798 auto* color = bundle.color(); 799 const auto* alpha = bundle.alpha(); 800 JXL_CHECK(color->ysize() == alpha->ysize()); 801 JXL_CHECK(color->xsize() == alpha->xsize()); 802 for (size_t y = 0; y < color->ysize(); y++) { 803 ::jxl::UnpremultiplyAlpha(color->PlaneRow(0, y), color->PlaneRow(1, y), 804 color->PlaneRow(2, y), alpha->Row(y), 805 color->xsize()); 806 } 807 }; 808 ExtraChannelInfo* eci = io.metadata.m.Find(ExtraChannel::kAlpha); 809 if (eci == nullptr || !eci->alpha_associated) return false; 810 if (io.metadata.m.have_preview) { 811 doUnpremultiplyAlpha(io.preview_frame); 812 } 813 for (ImageBundle& ib : io.frames) { 814 doUnpremultiplyAlpha(ib); 815 } 816 eci->alpha_associated = false; 817 return true; 818 } 819 } // namespace 820 821 TEST(JxlTest, RoundtripAlphaPremultiplied) { 822 const std::vector<uint8_t> orig = 823 ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); 824 CodecInOut io; 825 CodecInOut io_nopremul; 826 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); 827 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_nopremul)); 828 829 ASSERT_NE(io.xsize(), 0u); 830 ASSERT_TRUE(io.metadata.m.HasAlpha()); 831 ASSERT_TRUE(io.Main().HasAlpha()); 832 io.ShrinkTo(300, 300); 833 io_nopremul.ShrinkTo(300, 300); 834 835 CompressParams cparams; 836 cparams.butteraugli_distance = 1.0; 837 cparams.SetCms(*JxlGetDefaultCms()); 838 839 EXPECT_FALSE(io.Main().AlphaIsPremultiplied()); 840 EXPECT_TRUE(PremultiplyAlpha(io)); 841 EXPECT_TRUE(io.Main().AlphaIsPremultiplied()); 842 843 EXPECT_FALSE(io_nopremul.Main().AlphaIsPremultiplied()); 844 845 std::vector<uint8_t> compressed; 846 EXPECT_TRUE(test::EncodeFile(cparams, &io, &compressed)); 847 EXPECT_LE(compressed.size(), 18000u); 848 849 for (bool use_image_callback : {false, true}) { 850 for (bool unpremul_alpha : {false, true}) { 851 for (bool use_uint8 : {false, true}) { 852 printf( 853 "Testing premultiplied alpha using %s %s requesting " 854 "%spremultiplied output.\n", 855 use_uint8 ? "uint8" : "float", 856 use_image_callback ? "image callback" : "image_buffer", 857 unpremul_alpha ? "un" : ""); 858 CodecInOut io2; 859 JXLDecompressParams dparams; 860 dparams.use_image_callback = use_image_callback; 861 dparams.unpremultiply_alpha = unpremul_alpha; 862 if (use_uint8) { 863 dparams.accepted_formats = { 864 {4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}}; 865 } 866 EXPECT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &io2)); 867 868 EXPECT_EQ(unpremul_alpha, !io2.Main().AlphaIsPremultiplied()); 869 if (!unpremul_alpha) { 870 EXPECT_THAT( 871 ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), 872 *JxlGetDefaultCms(), 873 /*distmap=*/nullptr), 874 IsSlightlyBelow(1.111)); 875 EXPECT_TRUE(UnpremultiplyAlpha(io2)); 876 EXPECT_FALSE(io2.Main().AlphaIsPremultiplied()); 877 } 878 EXPECT_THAT( 879 ButteraugliDistance(io_nopremul.frames, io2.frames, 880 ButteraugliParams(), *JxlGetDefaultCms(), 881 /*distmap=*/nullptr), 882 IsSlightlyBelow(1.0)); 883 } 884 } 885 } 886 } 887 888 TEST(JxlTest, RoundtripAlphaResampling) { 889 ThreadPool* pool = nullptr; 890 const std::vector<uint8_t> orig = 891 ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); 892 TestImage t; 893 t.DecodeFromBytes(orig).ClearMetadata(); 894 ASSERT_NE(t.ppf().info.xsize, 0); 895 ASSERT_TRUE(t.ppf().info.alpha_bits > 0); 896 897 JXLCompressParams cparams; 898 cparams.alpha_distance = 1.0; 899 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 5); // kHare 900 cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2); 901 cparams.AddOption(JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING, 2); 902 903 PackedPixelFile ppf_out; 904 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 13507, 130); 905 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(5.2)); 906 } 907 908 TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) { 909 ThreadPool* pool = nullptr; 910 const std::vector<uint8_t> orig = 911 ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); 912 TestImage t; 913 t.DecodeFromBytes(orig).ClearMetadata(); 914 ASSERT_NE(t.ppf().info.xsize, 0); 915 ASSERT_TRUE(t.ppf().info.alpha_bits > 0); 916 917 JXLCompressParams cparams; 918 cparams.alpha_distance = 1.0; 919 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 3); // kFalcon 920 cparams.AddOption(JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING, 2); 921 922 PackedPixelFile ppf_out; 923 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 32000, 1000); 924 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.52)); 925 } 926 927 TEST(JxlTest, RoundtripAlphaNonMultipleOf8) { 928 ThreadPool* pool = nullptr; 929 const std::vector<uint8_t> orig = 930 ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); 931 TestImage t; 932 t.DecodeFromBytes(orig).ClearMetadata().SetDimensions(12, 12); 933 ASSERT_NE(t.ppf().info.xsize, 0); 934 ASSERT_TRUE(t.ppf().info.alpha_bits > 0); 935 EXPECT_EQ(t.ppf().frames[0].color.format.data_type, JXL_TYPE_UINT8); 936 937 PackedPixelFile ppf_out; 938 EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 107, 10); 939 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.006)); 940 } 941 942 TEST(JxlTest, RoundtripAlpha16) { 943 ThreadPoolForTests pool(4); 944 // The image is wider than 512 pixels to ensure multiple groups are tested. 945 size_t xsize = 1200; 946 size_t ysize = 160; 947 TestImage t; 948 t.SetDimensions(xsize, ysize).SetChannels(4).SetAllBitDepths(16); 949 TestImage::Frame frame = t.AddFrame(); 950 // Generate 16-bit pattern that uses various colors and alpha values. 951 const float mul = 1.0f / 65535; 952 for (size_t y = 0; y < ysize; y++) { 953 for (size_t x = 0; x < xsize; x++) { 954 uint16_t r = y * 65535 / ysize; 955 uint16_t g = x * 65535 / xsize; 956 uint16_t b = (y + x) * 65535 / (xsize + ysize); 957 frame.SetValue(y, x, 0, r * mul); 958 frame.SetValue(y, x, 1, g * mul); 959 frame.SetValue(y, x, 2, b * mul); 960 frame.SetValue(y, x, 3, g * mul); 961 } 962 } 963 964 ASSERT_NE(t.ppf().info.xsize, 0); 965 ASSERT_EQ(t.ppf().info.alpha_bits, 16); 966 967 JXLCompressParams cparams; 968 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 6); // kWombat 969 cparams.distance = 0.5; 970 cparams.alpha_distance = 0.5; 971 972 PackedPixelFile ppf_out; 973 // TODO(szabadka) Investigate big size difference on i686 974 // This still keeps happening (2023-04-18). 975 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 3666, 120); 976 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.65)); 977 } 978 979 namespace { 980 JXLCompressParams CompressParamsForLossless() { 981 JXLCompressParams cparams; 982 cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR, 1); 983 cparams.AddOption(JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM, 1); 984 cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, 6); // Weighted 985 cparams.distance = 0; 986 return cparams; 987 } 988 } // namespace 989 990 TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8)) { 991 ThreadPoolForTests pool(8); 992 const std::vector<uint8_t> orig = 993 ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); 994 TestImage t; 995 t.DecodeFromBytes(orig).ClearMetadata(); 996 997 JXLCompressParams cparams = CompressParamsForLossless(); 998 JXLDecompressParams dparams; 999 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1000 1001 PackedPixelFile ppf_out; 1002 EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out), 223058); 1003 EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); 1004 } 1005 1006 TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8ThunderGradient)) { 1007 ThreadPoolForTests pool(8); 1008 const std::vector<uint8_t> orig = 1009 ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); 1010 TestImage t; 1011 t.DecodeFromBytes(orig).ClearMetadata(); 1012 1013 JXLCompressParams cparams = CompressParamsForLossless(); 1014 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 2); // kThunder 1015 cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, 5); // Gradient 1016 JXLDecompressParams dparams; 1017 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1018 1019 PackedPixelFile ppf_out; 1020 EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out), 261684); 1021 EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); 1022 } 1023 1024 TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8LightningGradient)) { 1025 ThreadPoolForTests pool(8); 1026 const std::vector<uint8_t> orig = 1027 ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); 1028 TestImage t; 1029 t.DecodeFromBytes(orig).ClearMetadata(); 1030 1031 JXLCompressParams cparams = CompressParamsForLossless(); 1032 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 1); // kLightning 1033 JXLDecompressParams dparams; 1034 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1035 1036 PackedPixelFile ppf_out; 1037 // Lax comparison because different SIMD will cause different compression. 1038 EXPECT_THAT(Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out), 1039 IsSlightlyBelow(286848u)); 1040 EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); 1041 } 1042 1043 TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8Falcon)) { 1044 ThreadPoolForTests pool(8); 1045 const std::vector<uint8_t> orig = 1046 ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); 1047 TestImage t; 1048 t.DecodeFromBytes(orig).ClearMetadata(); 1049 1050 JXLCompressParams cparams = CompressParamsForLossless(); 1051 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 3); // kFalcon 1052 JXLDecompressParams dparams; 1053 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1054 1055 PackedPixelFile ppf_out; 1056 EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out), 230766); 1057 EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); 1058 } 1059 1060 TEST(JxlTest, RoundtripLossless8Alpha) { 1061 ThreadPool* pool = nullptr; 1062 const std::vector<uint8_t> orig = 1063 ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); 1064 TestImage t; 1065 t.DecodeFromBytes(orig).ClearMetadata(); 1066 ASSERT_EQ(t.ppf().info.alpha_bits, 8); 1067 EXPECT_EQ(t.ppf().frames[0].color.format.data_type, JXL_TYPE_UINT8); 1068 1069 JXLCompressParams cparams = CompressParamsForLossless(); 1070 1071 JXLDecompressParams dparams; 1072 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1073 1074 PackedPixelFile ppf_out; 1075 EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out), 251470); 1076 EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); 1077 EXPECT_EQ(ppf_out.info.alpha_bits, 8); 1078 EXPECT_TRUE(test::SameAlpha(t.ppf(), ppf_out)); 1079 } 1080 1081 TEST(JxlTest, RoundtripLossless16Alpha) { 1082 ThreadPool* pool = nullptr; 1083 size_t xsize = 1200; 1084 size_t ysize = 160; 1085 TestImage t; 1086 t.SetDimensions(xsize, ysize).SetChannels(4).SetAllBitDepths(16); 1087 TestImage::Frame frame = t.AddFrame(); 1088 // Generate 16-bit pattern that uses various colors and alpha values. 1089 const float mul = 1.0f / 65535; 1090 for (size_t y = 0; y < ysize; y++) { 1091 for (size_t x = 0; x < xsize; x++) { 1092 uint16_t r = y * 65535 / ysize; 1093 uint16_t g = x * 65535 / xsize + 37; 1094 uint16_t b = (y + x) * 65535 / (xsize + ysize); 1095 frame.SetValue(y, x, 0, r * mul); 1096 frame.SetValue(y, x, 1, g * mul); 1097 frame.SetValue(y, x, 2, b * mul); 1098 frame.SetValue(y, x, 3, g * mul); 1099 } 1100 } 1101 ASSERT_EQ(t.ppf().info.bits_per_sample, 16); 1102 ASSERT_EQ(t.ppf().info.alpha_bits, 16); 1103 1104 JXLCompressParams cparams = CompressParamsForLossless(); 1105 1106 JXLDecompressParams dparams; 1107 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1108 1109 PackedPixelFile ppf_out; 1110 // TODO(szabadka) Investigate big size difference on i686 1111 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out), 4665, 100); 1112 EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); 1113 EXPECT_EQ(ppf_out.info.alpha_bits, 16); 1114 EXPECT_TRUE(test::SameAlpha(t.ppf(), ppf_out)); 1115 } 1116 1117 TEST(JxlTest, RoundtripLossless16AlphaNotMisdetectedAs8Bit) { 1118 ThreadPool* pool = nullptr; 1119 size_t xsize = 128; 1120 size_t ysize = 128; 1121 TestImage t; 1122 t.SetDimensions(xsize, ysize).SetChannels(4).SetAllBitDepths(16); 1123 TestImage::Frame frame = t.AddFrame(); 1124 // All 16-bit values, both color and alpha, of this image are below 64. 1125 // This allows testing if a code path wrongly concludes it's an 8-bit instead 1126 // of 16-bit image (or even 6-bit). 1127 const float mul = 1.0f / 65535; 1128 for (size_t y = 0; y < ysize; y++) { 1129 for (size_t x = 0; x < xsize; x++) { 1130 uint16_t r = y * 64 / ysize; 1131 uint16_t g = x * 64 / xsize + 37; 1132 uint16_t b = (y + x) * 64 / (xsize + ysize); 1133 frame.SetValue(y, x, 0, r * mul); 1134 frame.SetValue(y, x, 1, g * mul); 1135 frame.SetValue(y, x, 2, b * mul); 1136 frame.SetValue(y, x, 3, g * mul); 1137 } 1138 } 1139 ASSERT_EQ(t.ppf().info.bits_per_sample, 16); 1140 ASSERT_EQ(t.ppf().info.alpha_bits, 16); 1141 1142 JXLCompressParams cparams = CompressParamsForLossless(); 1143 1144 JXLDecompressParams dparams; 1145 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1146 1147 PackedPixelFile ppf_out; 1148 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out), 280, 50); 1149 EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); 1150 EXPECT_EQ(ppf_out.info.bits_per_sample, 16); 1151 EXPECT_EQ(ppf_out.info.alpha_bits, 16); 1152 EXPECT_TRUE(test::SameAlpha(t.ppf(), ppf_out)); 1153 } 1154 1155 TEST(JxlTest, RoundtripDots) { 1156 ThreadPool* pool = nullptr; 1157 const std::vector<uint8_t> orig = 1158 ReadTestData("external/wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); 1159 TestImage t; 1160 t.DecodeFromBytes(orig).ClearMetadata(); 1161 ASSERT_NE(t.ppf().info.xsize, 0); 1162 EXPECT_EQ(t.ppf().info.bits_per_sample, 8); 1163 EXPECT_EQ(t.ppf().color_encoding.transfer_function, 1164 JXL_TRANSFER_FUNCTION_SRGB); 1165 1166 JXLCompressParams cparams; 1167 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSkirrel 1168 cparams.AddOption(JXL_ENC_FRAME_SETTING_DOTS, 1); 1169 cparams.distance = 0.04; 1170 1171 PackedPixelFile ppf_out; 1172 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 280333, 4000); 1173 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.35)); 1174 } 1175 1176 TEST(JxlTest, RoundtripNoise) { 1177 ThreadPool* pool = nullptr; 1178 const std::vector<uint8_t> orig = 1179 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 1180 TestImage t; 1181 t.DecodeFromBytes(orig).ClearMetadata(); 1182 ASSERT_NE(t.ppf().info.xsize, 0); 1183 EXPECT_EQ(t.ppf().info.bits_per_sample, 8); 1184 EXPECT_EQ(t.ppf().color_encoding.transfer_function, 1185 JXL_TRANSFER_FUNCTION_SRGB); 1186 1187 JXLCompressParams cparams; 1188 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSkirrel 1189 cparams.AddOption(JXL_ENC_FRAME_SETTING_NOISE, 1); 1190 1191 PackedPixelFile ppf_out; 1192 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 41009, 750); 1193 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.42)); 1194 } 1195 1196 TEST(JxlTest, RoundtripLossless8Gray) { 1197 ThreadPool* pool = nullptr; 1198 const std::vector<uint8_t> orig = ReadTestData( 1199 "external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); 1200 TestImage t; 1201 t.SetColorEncoding("Gra_D65_Rel_SRG").DecodeFromBytes(orig).ClearMetadata(); 1202 EXPECT_EQ(t.ppf().color_encoding.color_space, JXL_COLOR_SPACE_GRAY); 1203 EXPECT_EQ(t.ppf().info.bits_per_sample, 8); 1204 1205 JXLCompressParams cparams = CompressParamsForLossless(); 1206 1207 JXLDecompressParams dparams; 1208 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1209 1210 PackedPixelFile ppf_out; 1211 EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out), 92185); 1212 EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); 1213 EXPECT_EQ(ppf_out.color_encoding.color_space, JXL_COLOR_SPACE_GRAY); 1214 EXPECT_EQ(ppf_out.info.bits_per_sample, 8); 1215 } 1216 1217 TEST(JxlTest, RoundtripAnimation) { 1218 if (!jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) { 1219 fprintf(stderr, "Skipping test because of missing GIF decoder.\n"); 1220 return; 1221 } 1222 ThreadPool* pool = nullptr; 1223 const std::vector<uint8_t> orig = ReadTestData("jxl/traffic_light.gif"); 1224 TestImage t; 1225 t.DecodeFromBytes(orig).ClearMetadata(); 1226 EXPECT_EQ(4, t.ppf().frames.size()); 1227 1228 JXLDecompressParams dparams; 1229 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1230 1231 PackedPixelFile ppf_out; 1232 EXPECT_THAT(Roundtrip(t.ppf(), {}, dparams, pool, &ppf_out), 1233 IsSlightlyBelow(3350)); 1234 1235 t.CoalesceGIFAnimationWithAlpha(); 1236 ASSERT_EQ(ppf_out.frames.size(), t.ppf().frames.size()); 1237 static constexpr double kMaxButteraugli = 1238 #if JXL_HIGH_PRECISION 1239 1.55; 1240 #else 1241 1.75; 1242 #endif 1243 EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), kMaxButteraugli); 1244 } 1245 1246 TEST(JxlTest, RoundtripLosslessAnimation) { 1247 if (!jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) { 1248 fprintf(stderr, "Skipping test because of missing GIF decoder.\n"); 1249 return; 1250 } 1251 ThreadPool* pool = nullptr; 1252 const std::vector<uint8_t> orig = ReadTestData("jxl/traffic_light.gif"); 1253 TestImage t; 1254 t.DecodeFromBytes(orig).ClearMetadata(); 1255 EXPECT_EQ(4, t.ppf().frames.size()); 1256 1257 JXLCompressParams cparams = CompressParamsForLossless(); 1258 1259 JXLDecompressParams dparams; 1260 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1261 1262 PackedPixelFile ppf_out; 1263 EXPECT_THAT(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out), 1264 IsSlightlyBelow(958)); 1265 1266 t.CoalesceGIFAnimationWithAlpha(); 1267 ASSERT_EQ(ppf_out.frames.size(), t.ppf().frames.size()); 1268 EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), 5e-4); 1269 } 1270 1271 TEST(JxlTest, RoundtripAnimationPatches) { 1272 if (!jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) { 1273 fprintf(stderr, "Skipping test because of missing GIF decoder.\n"); 1274 return; 1275 } 1276 ThreadPool* pool = nullptr; 1277 const std::vector<uint8_t> orig = ReadTestData("jxl/animation_patches.gif"); 1278 1279 TestImage t; 1280 t.DecodeFromBytes(orig).ClearMetadata(); 1281 ASSERT_EQ(2u, t.ppf().frames.size()); 1282 1283 JXLCompressParams cparams; 1284 cparams.AddOption(JXL_ENC_FRAME_SETTING_PATCHES, 1); 1285 1286 JXLDecompressParams dparams; 1287 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1288 1289 PackedPixelFile ppf_out; 1290 // 40k with no patches, 27k with patch frames encoded multiple times. 1291 EXPECT_THAT(Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out), 1292 IsSlightlyBelow(19300)); 1293 EXPECT_EQ(ppf_out.frames.size(), t.ppf().frames.size()); 1294 // >10 with broken patches; not all patches are detected on borders. 1295 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.9)); 1296 } 1297 1298 size_t RoundtripJpeg(const std::vector<uint8_t>& jpeg_in, ThreadPool* pool) { 1299 std::vector<uint8_t> compressed; 1300 EXPECT_TRUE(extras::EncodeImageJXL({}, extras::PackedPixelFile(), &jpeg_in, 1301 &compressed)); 1302 1303 jxl::JXLDecompressParams dparams; 1304 test::DefaultAcceptedFormats(dparams); 1305 test::SetThreadParallelRunner(dparams, pool); 1306 std::vector<uint8_t> out; 1307 jxl::PackedPixelFile ppf; 1308 EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams, 1309 nullptr, &ppf, &out)); 1310 EXPECT_EQ(out.size(), jpeg_in.size()); 1311 size_t failures = 0; 1312 for (size_t i = 0; i < std::min(out.size(), jpeg_in.size()); i++) { 1313 if (out[i] != jpeg_in[i]) { 1314 EXPECT_EQ(out[i], jpeg_in[i]) 1315 << "byte mismatch " << i << " " << out[i] << " != " << jpeg_in[i]; 1316 if (++failures > 4) { 1317 return compressed.size(); 1318 } 1319 } 1320 } 1321 return compressed.size(); 1322 } 1323 1324 void RoundtripJpegToPixels(const std::vector<uint8_t>& jpeg_in, 1325 JXLDecompressParams dparams, ThreadPool* pool, 1326 PackedPixelFile* ppf_out) { 1327 std::vector<uint8_t> jpeg_bytes(jpeg_in.data(), 1328 jpeg_in.data() + jpeg_in.size()); 1329 std::vector<uint8_t> compressed; 1330 EXPECT_TRUE(extras::EncodeImageJXL({}, extras::PackedPixelFile(), &jpeg_bytes, 1331 &compressed)); 1332 1333 test::DefaultAcceptedFormats(dparams); 1334 test::SetThreadParallelRunner(dparams, pool); 1335 EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams, 1336 nullptr, ppf_out, nullptr)); 1337 } 1338 1339 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444)) { 1340 ThreadPoolForTests pool(8); 1341 const std::vector<uint8_t> orig = 1342 ReadTestData("jxl/flower/flower.png.im_q85_444.jpg"); 1343 // JPEG size is 696,659 bytes. 1344 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 568891u, 20); 1345 } 1346 1347 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels)) { 1348 TEST_LIBJPEG_SUPPORT(); 1349 ThreadPoolForTests pool(8); 1350 const std::vector<uint8_t> orig = 1351 ReadTestData("jxl/flower/flower.png.im_q85_444.jpg"); 1352 TestImage t; 1353 t.DecodeFromBytes(orig); 1354 1355 PackedPixelFile ppf_out; 1356 RoundtripJpegToPixels(orig, {}, &pool, &ppf_out); 1357 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(12)); 1358 } 1359 1360 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420)) { 1361 TEST_LIBJPEG_SUPPORT(); 1362 ThreadPoolForTests pool(8); 1363 const std::vector<uint8_t> orig = 1364 ReadTestData("jxl/flower/flower.png.im_q85_420.jpg"); 1365 TestImage t; 1366 t.DecodeFromBytes(orig); 1367 1368 PackedPixelFile ppf_out; 1369 RoundtripJpegToPixels(orig, {}, &pool, &ppf_out); 1370 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(11)); 1371 } 1372 1373 TEST(JxlTest, 1374 JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420EarlyFlush)) { 1375 TEST_LIBJPEG_SUPPORT(); 1376 ThreadPoolForTests pool(8); 1377 const std::vector<uint8_t> orig = 1378 ReadTestData("jxl/flower/flower.png.im_q85_420.jpg"); 1379 TestImage t; 1380 t.DecodeFromBytes(orig); 1381 1382 JXLDecompressParams dparams; 1383 dparams.max_downsampling = 8; 1384 1385 PackedPixelFile ppf_out; 1386 RoundtripJpegToPixels(orig, dparams, &pool, &ppf_out); 1387 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(4410)); 1388 } 1389 1390 TEST(JxlTest, 1391 JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420Mul16)) { 1392 TEST_LIBJPEG_SUPPORT(); 1393 ThreadPoolForTests pool(8); 1394 const std::vector<uint8_t> orig = 1395 ReadTestData("jxl/flower/flower_cropped.jpg"); 1396 TestImage t; 1397 t.DecodeFromBytes(orig); 1398 1399 PackedPixelFile ppf_out; 1400 RoundtripJpegToPixels(orig, {}, &pool, &ppf_out); 1401 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(4)); 1402 } 1403 1404 TEST(JxlTest, 1405 JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels_asymmetric)) { 1406 TEST_LIBJPEG_SUPPORT(); 1407 ThreadPoolForTests pool(8); 1408 const std::vector<uint8_t> orig = 1409 ReadTestData("jxl/flower/flower.png.im_q85_asymmetric.jpg"); 1410 TestImage t; 1411 t.DecodeFromBytes(orig); 1412 1413 PackedPixelFile ppf_out; 1414 RoundtripJpegToPixels(orig, {}, &pool, &ppf_out); 1415 EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(10)); 1416 } 1417 1418 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionGray)) { 1419 ThreadPoolForTests pool(8); 1420 const std::vector<uint8_t> orig = 1421 ReadTestData("jxl/flower/flower.png.im_q85_gray.jpg"); 1422 // JPEG size is 456,528 bytes. 1423 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 387496u, 200); 1424 } 1425 1426 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420)) { 1427 ThreadPoolForTests pool(8); 1428 const std::vector<uint8_t> orig = 1429 ReadTestData("jxl/flower/flower.png.im_q85_420.jpg"); 1430 // JPEG size is 546,797 bytes. 1431 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 455510u, 20); 1432 } 1433 1434 TEST(JxlTest, 1435 JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_luma_subsample)) { 1436 ThreadPoolForTests pool(8); 1437 const std::vector<uint8_t> orig = 1438 ReadTestData("jxl/flower/flower.png.im_q85_luma_subsample.jpg"); 1439 // JPEG size is 400,724 bytes. 1440 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 325310u, 20); 1441 } 1442 1443 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444_12)) { 1444 // 444 JPEG that has an interesting sampling-factor (1x2, 1x2, 1x2). 1445 ThreadPoolForTests pool(8); 1446 const std::vector<uint8_t> orig = 1447 ReadTestData("jxl/flower/flower.png.im_q85_444_1x2.jpg"); 1448 // JPEG size is 703,874 bytes. 1449 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 569630u, 20); 1450 } 1451 1452 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression422)) { 1453 ThreadPoolForTests pool(8); 1454 const std::vector<uint8_t> orig = 1455 ReadTestData("jxl/flower/flower.png.im_q85_422.jpg"); 1456 // JPEG size is 522,057 bytes. 1457 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 499236u, 20); 1458 } 1459 1460 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression440)) { 1461 ThreadPoolForTests pool(8); 1462 const std::vector<uint8_t> orig = 1463 ReadTestData("jxl/flower/flower.png.im_q85_440.jpg"); 1464 // JPEG size is 603,623 bytes. 1465 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 501101u, 20); 1466 } 1467 1468 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_asymmetric)) { 1469 // 2x vertical downsample of one chroma channel, 2x horizontal downsample of 1470 // the other. 1471 ThreadPoolForTests pool(8); 1472 const std::vector<uint8_t> orig = 1473 ReadTestData("jxl/flower/flower.png.im_q85_asymmetric.jpg"); 1474 // JPEG size is 604,601 bytes. 1475 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 500548u, 20); 1476 } 1477 1478 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420Progr)) { 1479 ThreadPoolForTests pool(8); 1480 const std::vector<uint8_t> orig = 1481 ReadTestData("jxl/flower/flower.png.im_q85_420_progr.jpg"); 1482 // JPEG size is 522,057 bytes. 1483 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 455454u, 20); 1484 } 1485 1486 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionMetadata)) { 1487 ThreadPoolForTests pool(8); 1488 const std::vector<uint8_t> orig = 1489 ReadTestData("jxl/jpeg_reconstruction/1x1_exif_xmp.jpg"); 1490 // JPEG size is 4290 bytes 1491 // 1370 on 386, so higher margin. 1492 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 1334u, 100); 1493 } 1494 1495 TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionRestarts)) { 1496 ThreadPoolForTests pool(8); 1497 const std::vector<uint8_t> orig = 1498 ReadTestData("jxl/jpeg_reconstruction/bicycles_restarts.jpg"); 1499 // JPEG size is 87478 bytes 1500 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 76054u, 30); 1501 } 1502 1503 TEST(JxlTest, 1504 JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionOrientationICC)) { 1505 ThreadPoolForTests pool(8); 1506 const std::vector<uint8_t> orig = 1507 ReadTestData("jxl/jpeg_reconstruction/sideways_bench.jpg"); 1508 // JPEG size is 15252 bytes 1509 EXPECT_NEAR(RoundtripJpeg(orig, &pool), 12000u, 470); 1510 // TODO(jon): investigate why 'Cross-compiling i686-linux-gnu' produces a 1511 // larger result 1512 } 1513 1514 TEST(JxlTest, RoundtripProgressive) { 1515 ThreadPoolForTests pool(4); 1516 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 1517 TestImage t; 1518 t.DecodeFromBytes(orig).ClearMetadata().SetDimensions(600, 1024); 1519 1520 JXLCompressParams cparams; 1521 cparams.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 1); 1522 cparams.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, 1); 1523 cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1); 1524 1525 PackedPixelFile ppf_out; 1526 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 70544, 750); 1527 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.4)); 1528 } 1529 1530 TEST(JxlTest, RoundtripProgressiveLevel2Slow) { 1531 ThreadPoolForTests pool(8); 1532 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 1533 TestImage t; 1534 t.DecodeFromBytes(orig).ClearMetadata().SetDimensions(600, 1024); 1535 1536 JXLCompressParams cparams; 1537 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 9); // kTortoise 1538 cparams.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 2); 1539 cparams.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, 1); 1540 cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1); 1541 1542 PackedPixelFile ppf_out; 1543 EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 76666, 1000); 1544 EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.17)); 1545 } 1546 1547 TEST(JxlTest, RoundtripUnsignedCustomBitdepthLossless) { 1548 ThreadPool* pool = nullptr; 1549 for (uint32_t num_channels = 1; num_channels < 6; ++num_channels) { 1550 for (JxlEndianness endianness : {JXL_LITTLE_ENDIAN, JXL_BIG_ENDIAN}) { 1551 for (uint32_t bitdepth = 3; bitdepth <= 16; ++bitdepth) { 1552 if (bitdepth <= 8 && endianness == JXL_BIG_ENDIAN) continue; 1553 printf("Testing %u channel unsigned %u bit %s endian lossless.\n", 1554 num_channels, bitdepth, 1555 endianness == JXL_LITTLE_ENDIAN ? "little" : "big"); 1556 TestImage t; 1557 t.SetDimensions(256, 256).SetChannels(num_channels); 1558 t.SetAllBitDepths(bitdepth).SetEndianness(endianness); 1559 TestImage::Frame frame = t.AddFrame(); 1560 frame.RandomFill(); 1561 1562 JXLCompressParams cparams = CompressParamsForLossless(); 1563 cparams.input_bitdepth.type = JXL_BIT_DEPTH_FROM_CODESTREAM; 1564 1565 JXLDecompressParams dparams; 1566 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1567 dparams.output_bitdepth.type = JXL_BIT_DEPTH_FROM_CODESTREAM; 1568 1569 PackedPixelFile ppf_out; 1570 Roundtrip(t.ppf(), cparams, dparams, pool, &ppf_out); 1571 1572 ASSERT_TRUE(test::SamePixels(t.ppf(), ppf_out)); 1573 } 1574 } 1575 } 1576 } 1577 1578 TEST(JxlTest, LosslessPNMRoundtrip) { 1579 static const char* kChannels[] = {"", "g", "ga", "rgb", "rgba"}; 1580 static const char* kExtension[] = {"", ".pgm", ".pam", ".ppm", ".pam"}; 1581 for (size_t bit_depth = 1; bit_depth <= 16; ++bit_depth) { 1582 for (size_t channels = 1; channels <= 4; ++channels) { 1583 if (bit_depth == 1 && (channels == 2 || channels == 4)) continue; 1584 std::string extension(kExtension[channels]); 1585 std::string filename = "jxl/flower/flower_small." + 1586 std::string(kChannels[channels]) + ".depth" + 1587 std::to_string(bit_depth) + extension; 1588 const std::vector<uint8_t> orig = ReadTestData(filename); 1589 test::TestImage t; 1590 if (channels < 3) t.SetColorEncoding("Gra_D65_Rel_SRG"); 1591 t.DecodeFromBytes(orig); 1592 1593 JXLCompressParams cparams = CompressParamsForLossless(); 1594 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 1); // kLightning 1595 cparams.input_bitdepth.type = JXL_BIT_DEPTH_FROM_CODESTREAM; 1596 1597 JXLDecompressParams dparams; 1598 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1599 dparams.output_bitdepth.type = JXL_BIT_DEPTH_FROM_CODESTREAM; 1600 1601 PackedPixelFile ppf_out; 1602 Roundtrip(t.ppf(), cparams, dparams, nullptr, &ppf_out); 1603 1604 extras::EncodedImage encoded; 1605 auto encoder = extras::Encoder::FromExtension(extension); 1606 ASSERT_TRUE(encoder.get()); 1607 ASSERT_TRUE(encoder->Encode(ppf_out, &encoded, nullptr)); 1608 ASSERT_EQ(encoded.bitstreams.size(), 1); 1609 ASSERT_EQ(orig.size(), encoded.bitstreams[0].size()); 1610 EXPECT_EQ(0, 1611 memcmp(orig.data(), encoded.bitstreams[0].data(), orig.size())); 1612 } 1613 } 1614 } 1615 1616 class JxlTest : public ::testing::TestWithParam<const char*> {}; 1617 1618 TEST_P(JxlTest, LosslessSmallFewColors) { 1619 ThreadPoolForTests pool(8); 1620 const std::vector<uint8_t> orig = ReadTestData(GetParam()); 1621 TestImage t; 1622 t.DecodeFromBytes(orig).ClearMetadata(); 1623 1624 JXLCompressParams cparams; 1625 cparams.distance = 0; 1626 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 1); 1627 JXLDecompressParams dparams; 1628 dparams.accepted_formats.push_back(t.ppf().frames[0].color.format); 1629 1630 PackedPixelFile ppf_out; 1631 Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out); 1632 EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); 1633 } 1634 1635 JXL_GTEST_INSTANTIATE_TEST_SUITE_P( 1636 ImageTests, JxlTest, 1637 ::testing::Values("jxl/blending/cropped_traffic_light_frame-0.png", 1638 "palette/358colors.png")); 1639 1640 struct StreamingTestParam { 1641 size_t xsize; 1642 size_t ysize; 1643 bool is_grey; 1644 bool has_alpha; 1645 int effort; 1646 bool progressive; 1647 1648 size_t num_channels() const { 1649 return (is_grey ? 1 : 3) + (has_alpha ? 1 : 0); 1650 } 1651 1652 float max_psnr() const { return is_grey ? 90 : 50; } 1653 1654 static std::vector<StreamingTestParam> All() { 1655 std::vector<StreamingTestParam> params; 1656 for (int e : {1, 3, 4, 7}) { 1657 for (bool g : {false, true}) { 1658 params.push_back(StreamingTestParam{357, 517, g, false, e, false}); 1659 params.push_back(StreamingTestParam{2247, 2357, g, false, e, false}); 1660 } 1661 } 1662 params.push_back(StreamingTestParam{2247, 2357, false, false, 1, true}); 1663 params.push_back(StreamingTestParam{2247, 2157, false, false, 5, false}); 1664 params.push_back(StreamingTestParam{2247, 2157, false, true, 5, false}); 1665 return params; 1666 } 1667 }; 1668 1669 std::ostream& operator<<(std::ostream& out, StreamingTestParam p) { 1670 out << (p.is_grey ? "Grey" : "RGB"); 1671 out << p.xsize << "x" << p.ysize; 1672 out << "e" << p.effort; 1673 if (p.progressive) { 1674 out << "Progressive"; 1675 } 1676 return out; 1677 } 1678 1679 class JxlStreamingTest : public ::testing::TestWithParam<StreamingTestParam> {}; 1680 1681 TEST_P(JxlStreamingTest, Roundtrip) { 1682 const StreamingTestParam& p = GetParam(); 1683 1684 jxl::test::TestImage image; 1685 image.SetDimensions(p.xsize, p.ysize) 1686 .SetDataType(JXL_TYPE_UINT8) 1687 .SetChannels(p.num_channels()) 1688 .SetAllBitDepths(8); 1689 image.AddFrame().RandomFill(); 1690 JXLCompressParams cparams; 1691 cparams.distance = 0.1; 1692 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, p.effort); 1693 cparams.AddOption(JXL_ENC_FRAME_SETTING_BUFFERING, 3); 1694 if (p.progressive) { 1695 cparams.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, 1); 1696 } 1697 1698 ThreadPoolForTests pool(8); 1699 PackedPixelFile ppf_out; 1700 Roundtrip(image.ppf(), cparams, {}, &pool, &ppf_out); 1701 EXPECT_GT(jxl::test::ComputePSNR(image.ppf(), ppf_out), p.max_psnr()); 1702 } 1703 1704 JXL_GTEST_INSTANTIATE_TEST_SUITE_P( 1705 JxlStreamingTest, JxlStreamingTest, 1706 testing::ValuesIn(StreamingTestParam::All())); 1707 1708 struct StreamingEncodingTestParam { 1709 std::string file; 1710 int effort; 1711 float distance; 1712 int group_size; 1713 float palette_percent; 1714 1715 static std::vector<StreamingEncodingTestParam> All() { 1716 std::vector<StreamingEncodingTestParam> params; 1717 for (const auto* file : 1718 {"jxl/flower/flower.png", "jxl/flower/flower_alpha.png"}) { 1719 for (int effort : {1, 3, 5, 6}) { 1720 if (effort != 1) { 1721 params.push_back( 1722 StreamingEncodingTestParam{file, effort, 1.0, 1, -1}); 1723 params.push_back( 1724 StreamingEncodingTestParam{file, effort, 4.0, 1, -1}); 1725 } 1726 for (auto group_size : {-1, 0}) { 1727 for (float palette_percent : {-1, 50, 100}) { 1728 params.push_back(StreamingEncodingTestParam{ 1729 file, effort, 0.0, group_size, palette_percent}); 1730 } 1731 } 1732 } 1733 } 1734 return params; 1735 } 1736 }; 1737 1738 std::ostream& operator<<(std::ostream& out, 1739 const StreamingEncodingTestParam& p) { 1740 out << p.file << "-"; 1741 out << "e" << p.effort; 1742 if (p.distance == 0) { 1743 out << "Lossless"; 1744 out << "G" << p.group_size << "P" << p.palette_percent; 1745 } else { 1746 out << "D" << p.distance; 1747 } 1748 return out; 1749 } 1750 1751 class JxlStreamingEncodingTest 1752 : public ::testing::TestWithParam<StreamingEncodingTestParam> {}; 1753 1754 // This is broken on mingw32, so we only enable it for x86_64 now. 1755 TEST_P(JxlStreamingEncodingTest, JXL_X86_64_TEST(StreamingSamePixels)) { 1756 const auto param = GetParam(); 1757 1758 const std::vector<uint8_t> orig = ReadTestData(param.file); 1759 jxl::test::TestImage image; 1760 image.DecodeFromBytes(orig); 1761 1762 JXLCompressParams cparams; 1763 cparams.distance = param.distance; 1764 cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, param.effort); 1765 cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, param.group_size); 1766 cparams.AddFloatOption(JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 1767 param.palette_percent); 1768 cparams.AddOption(JXL_ENC_FRAME_SETTING_USE_FULL_IMAGE_HEURISTICS, 0); 1769 1770 ThreadPoolForTests pool(8); 1771 PackedPixelFile ppf_out; 1772 Roundtrip(image.ppf(), cparams, {}, &pool, &ppf_out); 1773 1774 cparams.AddOption(JXL_ENC_FRAME_SETTING_BUFFERING, 3); 1775 PackedPixelFile ppf_out_streaming; 1776 Roundtrip(image.ppf(), cparams, {}, &pool, &ppf_out_streaming); 1777 1778 EXPECT_TRUE(jxl::test::SamePixels(ppf_out, ppf_out_streaming)); 1779 } 1780 1781 JXL_GTEST_INSTANTIATE_TEST_SUITE_P( 1782 JxlStreamingTest, JxlStreamingEncodingTest, 1783 testing::ValuesIn(StreamingEncodingTestParam::All())); 1784 1785 } // namespace 1786 } // namespace jxl