passes_test.cc (15466B)
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 <jxl/cms.h> 7 #include <jxl/memory_manager.h> 8 #include <jxl/types.h> 9 10 #include <cstddef> 11 #include <cstdint> 12 #include <future> 13 #include <string> 14 #include <utility> 15 #include <vector> 16 17 #include "lib/extras/codec.h" 18 #include "lib/extras/dec/jxl.h" 19 #include "lib/jxl/base/data_parallel.h" 20 #include "lib/jxl/base/override.h" 21 #include "lib/jxl/base/rect.h" 22 #include "lib/jxl/base/span.h" 23 #include "lib/jxl/base/status.h" 24 #include "lib/jxl/common.h" 25 #include "lib/jxl/enc_params.h" 26 #include "lib/jxl/image.h" 27 #include "lib/jxl/image_bundle.h" 28 #include "lib/jxl/image_ops.h" 29 #include "lib/jxl/test_memory_manager.h" 30 #include "lib/jxl/test_utils.h" 31 #include "lib/jxl/testing.h" 32 33 namespace jxl { 34 35 using ::jxl::test::ButteraugliDistance; 36 using ::jxl::test::ReadTestData; 37 using ::jxl::test::Roundtrip; 38 using ::jxl::test::ThreadPoolForTests; 39 40 namespace { 41 42 TEST(PassesTest, RoundtripSmallPasses) { 43 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 44 const std::vector<uint8_t> orig = 45 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 46 CodecInOut io{memory_manager}; 47 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); 48 ASSERT_TRUE(io.ShrinkTo(io.xsize() / 8, io.ysize() / 8)); 49 50 CompressParams cparams; 51 cparams.butteraugli_distance = 1.0; 52 cparams.progressive_mode = Override::kOn; 53 cparams.SetCms(*JxlGetDefaultCms()); 54 55 CodecInOut io2{memory_manager}; 56 JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _)); 57 EXPECT_SLIGHTLY_BELOW( 58 ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), 59 *JxlGetDefaultCms(), 60 /*distmap=*/nullptr), 61 1.0); 62 } 63 64 TEST(PassesTest, RoundtripUnalignedPasses) { 65 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 66 const std::vector<uint8_t> orig = 67 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 68 CodecInOut io{memory_manager}; 69 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); 70 ASSERT_TRUE(io.ShrinkTo(io.xsize() / 12, io.ysize() / 7)); 71 72 CompressParams cparams; 73 cparams.butteraugli_distance = 2.0; 74 cparams.progressive_mode = Override::kOn; 75 cparams.SetCms(*JxlGetDefaultCms()); 76 77 CodecInOut io2{memory_manager}; 78 JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _)); 79 EXPECT_SLIGHTLY_BELOW( 80 ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), 81 *JxlGetDefaultCms(), 82 /*distmap=*/nullptr), 83 1.72); 84 } 85 86 TEST(PassesTest, RoundtripMultiGroupPasses) { 87 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 88 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 89 CodecInOut io{memory_manager}; 90 { 91 ThreadPoolForTests pool(4); 92 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get())); 93 } 94 ASSERT_TRUE(io.ShrinkTo(600, 1024)); // partial X, full Y group 95 96 auto test = [&](float target_distance, float threshold) { 97 ThreadPoolForTests pool(4); 98 CompressParams cparams; 99 cparams.butteraugli_distance = target_distance; 100 cparams.progressive_mode = Override::kOn; 101 cparams.SetCms(*JxlGetDefaultCms()); 102 CodecInOut io2{memory_manager}; 103 JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _, 104 /* compressed_size */ nullptr, pool.get())); 105 EXPECT_SLIGHTLY_BELOW( 106 ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), 107 *JxlGetDefaultCms(), 108 /*distmap=*/nullptr, pool.get()), 109 target_distance + threshold); 110 }; 111 112 auto run1 = std::async(std::launch::async, test, 1.0f, 0.25f); 113 auto run2 = std::async(std::launch::async, test, 2.0f, 0.0f); 114 } 115 116 TEST(PassesTest, RoundtripLargeFastPasses) { 117 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 118 ThreadPoolForTests pool(8); 119 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 120 CodecInOut io{memory_manager}; 121 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get())); 122 123 CompressParams cparams; 124 cparams.speed_tier = SpeedTier::kSquirrel; 125 cparams.progressive_mode = Override::kOn; 126 cparams.SetCms(*JxlGetDefaultCms()); 127 128 CodecInOut io2{memory_manager}; 129 JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _, 130 /* compressed_size */ nullptr, pool.get())); 131 } 132 133 // Checks for differing size/distance in two consecutive runs of distance 2, 134 // which involves additional processing including adaptive reconstruction. 135 // Failing this may be a sign of race conditions or invalid memory accesses. 136 TEST(PassesTest, RoundtripProgressiveConsistent) { 137 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 138 ThreadPoolForTests pool(8); 139 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 140 CodecInOut io{memory_manager}; 141 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get())); 142 143 CompressParams cparams; 144 cparams.speed_tier = SpeedTier::kSquirrel; 145 cparams.progressive_mode = Override::kOn; 146 cparams.butteraugli_distance = 2.0; 147 cparams.SetCms(*JxlGetDefaultCms()); 148 149 // Try each xsize mod kBlockDim to verify right border handling. 150 for (size_t xsize = 48; xsize > 40; --xsize) { 151 ASSERT_TRUE(io.ShrinkTo(xsize, 15)); 152 153 CodecInOut io2{memory_manager}; 154 size_t size2; 155 JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _, &size2, pool.get())); 156 157 CodecInOut io3{memory_manager}; 158 size_t size3; 159 JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io3, _, &size3, pool.get())); 160 161 // Exact same compressed size. 162 EXPECT_EQ(size2, size3); 163 164 // Exact same distance. 165 const float dist2 = ButteraugliDistance( 166 io.frames, io2.frames, ButteraugliParams(), *JxlGetDefaultCms(), 167 /*distmap=*/nullptr, pool.get()); 168 const float dist3 = ButteraugliDistance( 169 io.frames, io3.frames, ButteraugliParams(), *JxlGetDefaultCms(), 170 /*distmap=*/nullptr, pool.get()); 171 EXPECT_EQ(dist2, dist3); 172 } 173 } 174 175 TEST(PassesTest, AllDownsampleFeasible) { 176 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 177 ThreadPoolForTests pool(8); 178 const std::vector<uint8_t> orig = 179 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 180 CodecInOut io{memory_manager}; 181 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get())); 182 183 std::vector<uint8_t> compressed; 184 185 CompressParams cparams; 186 cparams.speed_tier = SpeedTier::kSquirrel; 187 cparams.progressive_mode = Override::kOn; 188 cparams.butteraugli_distance = 1.0; 189 ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, pool.get())); 190 191 EXPECT_LE(compressed.size(), 240000u); 192 float target_butteraugli[9] = {}; 193 target_butteraugli[1] = 2.5f; 194 target_butteraugli[2] = 16.0f; 195 target_butteraugli[4] = 20.0f; 196 target_butteraugli[8] = 80.0f; 197 198 // The default progressive encoding scheme should make all these downsampling 199 // factors achievable. 200 // TODO(veluca): re-enable downsampling 16. 201 std::vector<size_t> downsamplings = {1, 2, 4, 8}; //, 16}; 202 203 auto check = [&](const uint32_t task, size_t /* thread */) -> Status { 204 const size_t downsampling = downsamplings[task]; 205 extras::JXLDecompressParams dparams; 206 dparams.max_downsampling = downsampling; 207 CodecInOut output{memory_manager}; 208 JXL_RETURN_IF_ERROR(test::DecodeFile(dparams, Bytes(compressed), &output)); 209 EXPECT_EQ(output.xsize(), io.xsize()) << "downsampling = " << downsampling; 210 EXPECT_EQ(output.ysize(), io.ysize()) << "downsampling = " << downsampling; 211 EXPECT_LE(ButteraugliDistance(io.frames, output.frames, ButteraugliParams(), 212 *JxlGetDefaultCms(), 213 /*distmap=*/nullptr, nullptr), 214 target_butteraugli[downsampling]) 215 << "downsampling: " << downsampling; 216 return true; 217 }; 218 EXPECT_TRUE(RunOnPool(pool.get(), 0, downsamplings.size(), ThreadPool::NoInit, 219 check, "TestDownsampling")); 220 } 221 222 TEST(PassesTest, AllDownsampleFeasibleQProgressive) { 223 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 224 ThreadPoolForTests pool(8); 225 const std::vector<uint8_t> orig = 226 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 227 CodecInOut io{memory_manager}; 228 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get())); 229 230 std::vector<uint8_t> compressed; 231 232 CompressParams cparams; 233 cparams.speed_tier = SpeedTier::kSquirrel; 234 cparams.qprogressive_mode = Override::kOn; 235 cparams.butteraugli_distance = 1.0; 236 ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, pool.get())); 237 238 EXPECT_LE(compressed.size(), 220000u); 239 240 float target_butteraugli[9] = {}; 241 target_butteraugli[1] = 3.0f; 242 target_butteraugli[2] = 6.0f; 243 target_butteraugli[4] = 10.0f; 244 target_butteraugli[8] = 80.0f; 245 246 // The default progressive encoding scheme should make all these downsampling 247 // factors achievable. 248 std::vector<size_t> downsamplings = {1, 2, 4, 8}; 249 250 auto check = [&](const uint32_t task, size_t /* thread */) -> Status { 251 const size_t downsampling = downsamplings[task]; 252 extras::JXLDecompressParams dparams; 253 dparams.max_downsampling = downsampling; 254 CodecInOut output{memory_manager}; 255 JXL_RETURN_IF_ERROR(test::DecodeFile(dparams, Bytes(compressed), &output)); 256 EXPECT_EQ(output.xsize(), io.xsize()) << "downsampling = " << downsampling; 257 EXPECT_EQ(output.ysize(), io.ysize()) << "downsampling = " << downsampling; 258 EXPECT_LE(ButteraugliDistance(io.frames, output.frames, ButteraugliParams(), 259 *JxlGetDefaultCms(), 260 /*distmap=*/nullptr), 261 target_butteraugli[downsampling]) 262 << "downsampling: " << downsampling; 263 return true; 264 }; 265 EXPECT_TRUE(RunOnPool(pool.get(), 0, downsamplings.size(), ThreadPool::NoInit, 266 check, "TestQProgressive")); 267 } 268 269 TEST(PassesTest, ProgressiveDownsample2DegradesCorrectlyGrayscale) { 270 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 271 ThreadPoolForTests pool(8); 272 const std::vector<uint8_t> orig = ReadTestData( 273 "external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); 274 CodecInOut io_orig{memory_manager}; 275 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_orig, pool.get())); 276 Rect rect(0, 0, io_orig.xsize(), 128); 277 // need 2 DC groups for the DC frame to actually be progressive. 278 JXL_TEST_ASSIGN_OR_DIE(Image3F large, 279 Image3F::Create(memory_manager, 4242, rect.ysize())); 280 ZeroFillImage(&large); 281 ASSERT_TRUE(CopyImageTo(rect, *io_orig.Main().color(), rect, &large)); 282 CodecInOut io{memory_manager}; 283 io.metadata = io_orig.metadata; 284 ASSERT_TRUE(io.SetFromImage(std::move(large), io_orig.Main().c_current())); 285 286 std::vector<uint8_t> compressed; 287 288 CompressParams cparams; 289 cparams.speed_tier = SpeedTier::kSquirrel; 290 cparams.progressive_dc = 1; 291 cparams.responsive = JXL_TRUE; 292 cparams.qprogressive_mode = Override::kOn; 293 cparams.butteraugli_distance = 1.0; 294 ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, pool.get())); 295 296 EXPECT_LE(compressed.size(), 10000u); 297 298 extras::JXLDecompressParams dparams; 299 dparams.max_downsampling = 1; 300 CodecInOut output{memory_manager}; 301 ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output)); 302 303 dparams.max_downsampling = 2; 304 CodecInOut output_d2{memory_manager}; 305 ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output_d2)); 306 307 // 0 if reading all the passes, ~15 if skipping the 8x pass. 308 float butteraugli_distance_down2_full = ButteraugliDistance( 309 output.frames, output_d2.frames, ButteraugliParams(), *JxlGetDefaultCms(), 310 /*distmap=*/nullptr); 311 312 EXPECT_LE(butteraugli_distance_down2_full, 3.2f); 313 EXPECT_GE(butteraugli_distance_down2_full, 1.0f); 314 } 315 316 TEST(PassesTest, ProgressiveDownsample2DegradesCorrectly) { 317 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 318 ThreadPoolForTests pool(8); 319 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 320 CodecInOut io_orig{memory_manager}; 321 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_orig, pool.get())); 322 Rect rect(0, 0, io_orig.xsize(), 128); 323 // need 2 DC groups for the DC frame to actually be progressive. 324 JXL_TEST_ASSIGN_OR_DIE(Image3F large, 325 Image3F::Create(memory_manager, 4242, rect.ysize())); 326 ZeroFillImage(&large); 327 ASSERT_TRUE(CopyImageTo(rect, *io_orig.Main().color(), rect, &large)); 328 CodecInOut io{memory_manager}; 329 ASSERT_TRUE(io.SetFromImage(std::move(large), io_orig.Main().c_current())); 330 331 std::vector<uint8_t> compressed; 332 333 CompressParams cparams; 334 cparams.speed_tier = SpeedTier::kSquirrel; 335 cparams.progressive_dc = 1; 336 cparams.responsive = JXL_TRUE; 337 cparams.qprogressive_mode = Override::kOn; 338 cparams.butteraugli_distance = 1.0; 339 ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, pool.get())); 340 341 EXPECT_LE(compressed.size(), 220000u); 342 343 extras::JXLDecompressParams dparams; 344 dparams.max_downsampling = 1; 345 CodecInOut output{memory_manager}; 346 ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output)); 347 348 dparams.max_downsampling = 2; 349 CodecInOut output_d2{memory_manager}; 350 ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output_d2)); 351 352 // 0 if reading all the passes, ~15 if skipping the 8x pass. 353 float butteraugli_distance_down2_full = ButteraugliDistance( 354 output.frames, output_d2.frames, ButteraugliParams(), *JxlGetDefaultCms(), 355 /*distmap=*/nullptr); 356 357 EXPECT_LE(butteraugli_distance_down2_full, 3.0f); 358 EXPECT_GE(butteraugli_distance_down2_full, 1.0f); 359 } 360 361 TEST(PassesTest, NonProgressiveDCImage) { 362 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 363 ThreadPoolForTests pool(8); 364 const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png"); 365 CodecInOut io{memory_manager}; 366 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get())); 367 368 std::vector<uint8_t> compressed; 369 370 CompressParams cparams; 371 cparams.speed_tier = SpeedTier::kSquirrel; 372 cparams.progressive_mode = Override::kOff; 373 cparams.butteraugli_distance = 2.0; 374 ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, pool.get())); 375 376 // Even in non-progressive mode, it should be possible to return a DC-only 377 // image. 378 extras::JXLDecompressParams dparams; 379 dparams.max_downsampling = 100; 380 CodecInOut output{memory_manager}; 381 ASSERT_TRUE( 382 test::DecodeFile(dparams, Bytes(compressed), &output, pool.get())); 383 EXPECT_EQ(output.xsize(), io.xsize()); 384 EXPECT_EQ(output.ysize(), io.ysize()); 385 } 386 387 TEST(PassesTest, RoundtripSmallNoGaborishPasses) { 388 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 389 const std::vector<uint8_t> orig = 390 ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); 391 CodecInOut io{memory_manager}; 392 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); 393 ASSERT_TRUE(io.ShrinkTo(io.xsize() / 8, io.ysize() / 8)); 394 395 CompressParams cparams; 396 cparams.gaborish = Override::kOff; 397 cparams.butteraugli_distance = 1.0; 398 cparams.progressive_mode = Override::kOn; 399 cparams.SetCms(*JxlGetDefaultCms()); 400 401 CodecInOut io2{memory_manager}; 402 JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _)); 403 EXPECT_SLIGHTLY_BELOW( 404 ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), 405 *JxlGetDefaultCms(), 406 /*distmap=*/nullptr), 407 1.0); 408 } 409 410 } // namespace 411 } // namespace jxl