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