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