render_pipeline_test.cc (19080B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include "lib/jxl/render_pipeline/render_pipeline.h" 7 8 #include <jxl/cms.h> 9 10 #include <algorithm> 11 #include <cctype> 12 #include <cstdint> 13 #include <cstdio> 14 #include <ostream> 15 #include <sstream> 16 #include <string> 17 #include <utility> 18 #include <vector> 19 20 #include "lib/extras/codec.h" 21 #include "lib/jxl/base/common.h" 22 #include "lib/jxl/base/compiler_specific.h" 23 #include "lib/jxl/base/override.h" 24 #include "lib/jxl/base/span.h" 25 #include "lib/jxl/base/status.h" 26 #include "lib/jxl/chroma_from_luma.h" 27 #include "lib/jxl/color_encoding_internal.h" 28 #include "lib/jxl/common.h" // JXL_HIGH_PRECISION, JPEGXL_ENABLE_TRANSCODE_JPEG 29 #include "lib/jxl/dec_bit_reader.h" 30 #include "lib/jxl/dec_cache.h" 31 #include "lib/jxl/dec_frame.h" 32 #include "lib/jxl/enc_params.h" 33 #include "lib/jxl/fake_parallel_runner_testonly.h" 34 #include "lib/jxl/fields.h" 35 #include "lib/jxl/frame_dimensions.h" 36 #include "lib/jxl/frame_header.h" 37 #include "lib/jxl/headers.h" 38 #include "lib/jxl/image.h" 39 #include "lib/jxl/image_metadata.h" 40 #include "lib/jxl/image_ops.h" 41 #include "lib/jxl/image_test_utils.h" 42 #include "lib/jxl/jpeg/enc_jpeg_data.h" 43 #include "lib/jxl/render_pipeline/test_render_pipeline_stages.h" 44 #include "lib/jxl/splines.h" 45 #include "lib/jxl/test_utils.h" 46 #include "lib/jxl/testing.h" 47 48 namespace jxl { 49 namespace { 50 51 Status DecodeFile(const Span<const uint8_t> file, bool use_slow_pipeline, 52 CodecInOut* io, ThreadPool* pool) { 53 Status ret = true; 54 { 55 BitReader reader(file); 56 BitReaderScopedCloser reader_closer(&reader, &ret); 57 JXL_RETURN_IF_ERROR(reader.ReadFixedBits<16>() == 0x0AFF); 58 JXL_RETURN_IF_ERROR(ReadSizeHeader(&reader, &io->metadata.size)); 59 JXL_RETURN_IF_ERROR(ReadImageMetadata(&reader, &io->metadata.m)); 60 io->metadata.transform_data.nonserialized_xyb_encoded = 61 io->metadata.m.xyb_encoded; 62 JXL_RETURN_IF_ERROR(Bundle::Read(&reader, &io->metadata.transform_data)); 63 if (io->metadata.m.color_encoding.WantICC()) { 64 std::vector<uint8_t> icc; 65 JXL_RETURN_IF_ERROR(test::ReadICC(&reader, &icc)); 66 JXL_RETURN_IF_ERROR(io->metadata.m.color_encoding.SetICC( 67 std::move(icc), JxlGetDefaultCms())); 68 } 69 PassesDecoderState dec_state; 70 JXL_RETURN_IF_ERROR( 71 dec_state.output_encoding_info.SetFromMetadata(io->metadata)); 72 JXL_RETURN_IF_ERROR(reader.JumpToByteBoundary()); 73 io->frames.clear(); 74 FrameHeader frame_header(&io->metadata); 75 do { 76 io->frames.emplace_back(&io->metadata.m); 77 // Skip frames that are not displayed. 78 do { 79 size_t frame_start = reader.TotalBitsConsumed() / kBitsPerByte; 80 size_t size_left = file.size() - frame_start; 81 JXL_RETURN_IF_ERROR(DecodeFrame(&dec_state, pool, 82 file.data() + frame_start, size_left, 83 &frame_header, &io->frames.back(), 84 io->metadata, use_slow_pipeline)); 85 reader.SkipBits(io->frames.back().decoded_bytes() * kBitsPerByte); 86 } while (frame_header.frame_type != FrameType::kRegularFrame && 87 frame_header.frame_type != FrameType::kSkipProgressive); 88 } while (!frame_header.is_last); 89 90 if (io->frames.empty()) return JXL_FAILURE("Not enough data."); 91 92 if (reader.TotalBitsConsumed() != file.size() * kBitsPerByte) { 93 return JXL_FAILURE("Reader position not at EOF."); 94 } 95 if (!reader.AllReadsWithinBounds()) { 96 return JXL_FAILURE("Reader out of bounds read."); 97 } 98 io->CheckMetadata(); 99 // reader is closed here. 100 } 101 return ret; 102 } 103 104 TEST(RenderPipelineTest, Build) { 105 RenderPipeline::Builder builder(/*num_c=*/1); 106 builder.AddStage(jxl::make_unique<UpsampleXSlowStage>()); 107 builder.AddStage(jxl::make_unique<UpsampleYSlowStage>()); 108 builder.AddStage(jxl::make_unique<Check0FinalStage>()); 109 builder.UseSimpleImplementation(); 110 FrameDimensions frame_dimensions; 111 frame_dimensions.Set(/*xsize=*/1024, /*ysize=*/1024, /*group_size_shift=*/0, 112 /*max_hshift=*/0, /*max_vshift=*/0, 113 /*modular_mode=*/false, /*upsampling=*/1); 114 std::move(builder).Finalize(frame_dimensions).value(); 115 } 116 117 TEST(RenderPipelineTest, CallAllGroups) { 118 RenderPipeline::Builder builder(/*num_c=*/1); 119 builder.AddStage(jxl::make_unique<UpsampleXSlowStage>()); 120 builder.AddStage(jxl::make_unique<UpsampleYSlowStage>()); 121 builder.AddStage(jxl::make_unique<Check0FinalStage>()); 122 builder.UseSimpleImplementation(); 123 FrameDimensions frame_dimensions; 124 frame_dimensions.Set(/*xsize=*/1024, /*ysize=*/1024, /*group_size_shift=*/0, 125 /*max_hshift=*/0, /*max_vshift=*/0, 126 /*modular_mode=*/false, /*upsampling=*/1); 127 auto pipeline = std::move(builder).Finalize(frame_dimensions).value(); 128 ASSERT_TRUE(pipeline->PrepareForThreads(1, /*use_group_ids=*/false)); 129 130 for (size_t i = 0; i < frame_dimensions.num_groups; i++) { 131 auto input_buffers = pipeline->GetInputBuffers(i, 0); 132 FillPlane(0.0f, input_buffers.GetBuffer(0).first, 133 input_buffers.GetBuffer(0).second); 134 JXL_CHECK(input_buffers.Done()); 135 } 136 137 EXPECT_EQ(pipeline->PassesWithAllInput(), 1); 138 } 139 140 TEST(RenderPipelineTest, BuildFast) { 141 RenderPipeline::Builder builder(/*num_c=*/1); 142 builder.AddStage(jxl::make_unique<UpsampleXSlowStage>()); 143 builder.AddStage(jxl::make_unique<UpsampleYSlowStage>()); 144 builder.AddStage(jxl::make_unique<Check0FinalStage>()); 145 FrameDimensions frame_dimensions; 146 frame_dimensions.Set(/*xsize=*/1024, /*ysize=*/1024, /*group_size_shift=*/0, 147 /*max_hshift=*/0, /*max_vshift=*/0, 148 /*modular_mode=*/false, /*upsampling=*/1); 149 std::move(builder).Finalize(frame_dimensions).value(); 150 } 151 152 TEST(RenderPipelineTest, CallAllGroupsFast) { 153 RenderPipeline::Builder builder(/*num_c=*/1); 154 builder.AddStage(jxl::make_unique<UpsampleXSlowStage>()); 155 builder.AddStage(jxl::make_unique<UpsampleYSlowStage>()); 156 builder.AddStage(jxl::make_unique<Check0FinalStage>()); 157 builder.UseSimpleImplementation(); 158 FrameDimensions frame_dimensions; 159 frame_dimensions.Set(/*xsize=*/1024, /*ysize=*/1024, /*group_size_shift=*/0, 160 /*max_hshift=*/0, /*max_vshift=*/0, 161 /*modular_mode=*/false, /*upsampling=*/1); 162 auto pipeline = std::move(builder).Finalize(frame_dimensions).value(); 163 ASSERT_TRUE(pipeline->PrepareForThreads(1, /*use_group_ids=*/false)); 164 165 for (size_t i = 0; i < frame_dimensions.num_groups; i++) { 166 auto input_buffers = pipeline->GetInputBuffers(i, 0); 167 FillPlane(0.0f, input_buffers.GetBuffer(0).first, 168 input_buffers.GetBuffer(0).second); 169 JXL_CHECK(input_buffers.Done()); 170 } 171 172 EXPECT_EQ(pipeline->PassesWithAllInput(), 1); 173 } 174 175 struct RenderPipelineTestInputSettings { 176 // Input image. 177 std::string input_path; 178 size_t xsize, ysize; 179 bool jpeg_transcode = false; 180 // Encoding settings. 181 CompressParams cparams; 182 // Short name for the encoder settings. 183 std::string cparams_descr; 184 185 bool add_spot_color = false; 186 187 Splines splines; 188 }; 189 190 class RenderPipelineTestParam 191 : public ::testing::TestWithParam<RenderPipelineTestInputSettings> {}; 192 193 TEST_P(RenderPipelineTestParam, PipelineTest) { 194 RenderPipelineTestInputSettings config = GetParam(); 195 196 // Use a parallel runner that randomly shuffles tasks to detect possible 197 // border handling bugs. 198 FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8); 199 ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); 200 const std::vector<uint8_t> orig = jxl::test::ReadTestData(config.input_path); 201 202 CodecInOut io; 203 if (config.jpeg_transcode) { 204 ASSERT_TRUE(jpeg::DecodeImageJPG(Bytes(orig), &io)); 205 } else { 206 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool)); 207 } 208 io.ShrinkTo(config.xsize, config.ysize); 209 210 if (config.add_spot_color) { 211 JXL_ASSIGN_OR_DIE(ImageF spot, ImageF::Create(config.xsize, config.ysize)); 212 jxl::ZeroFillImage(&spot); 213 214 for (size_t y = 0; y < config.ysize; y++) { 215 float* JXL_RESTRICT row = spot.Row(y); 216 for (size_t x = 0; x < config.xsize; x++) { 217 row[x] = ((x ^ y) & 255) * (1.f / 255.f); 218 } 219 } 220 ExtraChannelInfo info; 221 info.bit_depth.bits_per_sample = 8; 222 info.dim_shift = 0; 223 info.type = jxl::ExtraChannel::kSpotColor; 224 info.spot_color[0] = 0.5f; 225 info.spot_color[1] = 0.2f; 226 info.spot_color[2] = 1.f; 227 info.spot_color[3] = 0.5f; 228 229 io.metadata.m.extra_channel_info.push_back(info); 230 std::vector<ImageF> ec; 231 ec.push_back(std::move(spot)); 232 io.frames[0].SetExtraChannels(std::move(ec)); 233 } 234 235 std::vector<uint8_t> compressed; 236 237 config.cparams.custom_splines = config.splines; 238 ASSERT_TRUE(test::EncodeFile(config.cparams, &io, &compressed, &pool)); 239 240 CodecInOut io_default; 241 ASSERT_TRUE(DecodeFile(Bytes(compressed), 242 /*use_slow_pipeline=*/false, &io_default, &pool)); 243 CodecInOut io_slow_pipeline; 244 ASSERT_TRUE(DecodeFile(Bytes(compressed), 245 /*use_slow_pipeline=*/true, &io_slow_pipeline, &pool)); 246 247 ASSERT_EQ(io_default.frames.size(), io_slow_pipeline.frames.size()); 248 for (size_t i = 0; i < io_default.frames.size(); i++) { 249 #if JXL_HIGH_PRECISION 250 constexpr float kMaxError = 5e-5; 251 #else 252 constexpr float kMaxError = 5e-4; 253 #endif 254 Image3F def = std::move(*io_default.frames[i].color()); 255 Image3F pip = std::move(*io_slow_pipeline.frames[i].color()); 256 JXL_ASSERT_OK(VerifyRelativeError(pip, def, kMaxError, kMaxError, _)); 257 for (size_t ec = 0; ec < io_default.frames[i].extra_channels().size(); 258 ec++) { 259 JXL_ASSERT_OK(VerifyRelativeError( 260 io_slow_pipeline.frames[i].extra_channels()[ec], 261 io_default.frames[i].extra_channels()[ec], kMaxError, kMaxError, _)); 262 } 263 } 264 } 265 266 Splines CreateTestSplines() { 267 const ColorCorrelationMap cmap; 268 std::vector<Spline::Point> control_points{{9, 54}, {118, 159}, {97, 3}, 269 {10, 40}, {150, 25}, {120, 300}}; 270 const Spline spline{ 271 control_points, 272 /*color_dct=*/ 273 {{0.03125f, 0.00625f, 0.003125f}, {1.f, 0.321875f}, {1.f, 0.24375f}}, 274 /*sigma_dct=*/{0.3125f, 0.f, 0.f, 0.0625f}}; 275 std::vector<Spline> spline_data = {spline}; 276 std::vector<QuantizedSpline> quantized_splines; 277 std::vector<Spline::Point> starting_points; 278 for (const Spline& spline : spline_data) { 279 quantized_splines.emplace_back(spline, /*quantization_adjustment=*/0, 280 cmap.YtoXRatio(0), cmap.YtoBRatio(0)); 281 starting_points.push_back(spline.control_points.front()); 282 } 283 return Splines(/*quantization_adjustment=*/0, std::move(quantized_splines), 284 std::move(starting_points)); 285 } 286 287 std::vector<RenderPipelineTestInputSettings> GeneratePipelineTests() { 288 std::vector<RenderPipelineTestInputSettings> all_tests; 289 290 std::pair<size_t, size_t> sizes[] = { 291 {3, 8}, {128, 128}, {256, 256}, {258, 258}, {533, 401}, {777, 777}, 292 }; 293 294 for (auto size : sizes) { 295 RenderPipelineTestInputSettings settings; 296 settings.input_path = "jxl/flower/flower.png"; 297 settings.xsize = size.first; 298 settings.ysize = size.second; 299 300 // Base settings. 301 settings.cparams.butteraugli_distance = 1.0; 302 settings.cparams.patches = Override::kOff; 303 settings.cparams.dots = Override::kOff; 304 settings.cparams.gaborish = Override::kOff; 305 settings.cparams.epf = 0; 306 settings.cparams.color_transform = ColorTransform::kXYB; 307 308 { 309 auto s = settings; 310 s.cparams_descr = "NoGabNoEpfNoPatches"; 311 all_tests.push_back(s); 312 } 313 314 { 315 auto s = settings; 316 s.cparams.color_transform = ColorTransform::kNone; 317 s.cparams_descr = "NoGabNoEpfNoPatchesNoXYB"; 318 all_tests.push_back(s); 319 } 320 321 { 322 auto s = settings; 323 s.cparams.gaborish = Override::kOn; 324 s.cparams_descr = "GabNoEpfNoPatches"; 325 all_tests.push_back(s); 326 } 327 328 { 329 auto s = settings; 330 s.cparams.epf = 1; 331 s.cparams_descr = "NoGabEpf1NoPatches"; 332 all_tests.push_back(s); 333 } 334 335 { 336 auto s = settings; 337 s.cparams.epf = 2; 338 s.cparams_descr = "NoGabEpf2NoPatches"; 339 all_tests.push_back(s); 340 } 341 342 { 343 auto s = settings; 344 s.cparams.epf = 3; 345 s.cparams_descr = "NoGabEpf3NoPatches"; 346 all_tests.push_back(s); 347 } 348 349 { 350 auto s = settings; 351 s.cparams.gaborish = Override::kOn; 352 s.cparams.epf = 3; 353 s.cparams_descr = "GabEpf3NoPatches"; 354 all_tests.push_back(s); 355 } 356 357 { 358 auto s = settings; 359 s.cparams_descr = "Splines"; 360 s.splines = CreateTestSplines(); 361 all_tests.push_back(s); 362 } 363 364 for (size_t ups : {2, 4, 8}) { 365 { 366 auto s = settings; 367 s.cparams.resampling = ups; 368 s.cparams_descr = "Ups" + std::to_string(ups); 369 all_tests.push_back(s); 370 } 371 { 372 auto s = settings; 373 s.cparams.resampling = ups; 374 s.cparams.epf = 1; 375 s.cparams_descr = "Ups" + std::to_string(ups) + "EPF1"; 376 all_tests.push_back(s); 377 } 378 { 379 auto s = settings; 380 s.cparams.resampling = ups; 381 s.cparams.gaborish = Override::kOn; 382 s.cparams.epf = 1; 383 s.cparams_descr = "Ups" + std::to_string(ups) + "GabEPF1"; 384 all_tests.push_back(s); 385 } 386 } 387 388 { 389 auto s = settings; 390 s.cparams_descr = "Noise"; 391 s.cparams.photon_noise_iso = 3200; 392 all_tests.push_back(s); 393 } 394 395 { 396 auto s = settings; 397 s.cparams_descr = "NoiseUps"; 398 s.cparams.photon_noise_iso = 3200; 399 s.cparams.resampling = 2; 400 all_tests.push_back(s); 401 } 402 403 { 404 auto s = settings; 405 s.cparams_descr = "ModularLossless"; 406 s.cparams.modular_mode = true; 407 s.cparams.butteraugli_distance = 0; 408 all_tests.push_back(s); 409 } 410 411 { 412 auto s = settings; 413 s.cparams_descr = "ProgressiveDC"; 414 s.cparams.progressive_dc = 1; 415 all_tests.push_back(s); 416 } 417 418 { 419 auto s = settings; 420 s.cparams_descr = "ModularLossy"; 421 s.cparams.modular_mode = true; 422 s.cparams.butteraugli_distance = 1.f; 423 all_tests.push_back(s); 424 } 425 426 { 427 auto s = settings; 428 s.input_path = "jxl/flower/flower_alpha.png"; 429 s.cparams_descr = "AlphaVarDCT"; 430 all_tests.push_back(s); 431 } 432 433 { 434 auto s = settings; 435 s.input_path = "jxl/flower/flower_alpha.png"; 436 s.cparams_descr = "AlphaVarDCTUpsamplingEPF"; 437 s.cparams.epf = 1; 438 s.cparams.ec_resampling = 2; 439 all_tests.push_back(s); 440 } 441 442 { 443 auto s = settings; 444 s.cparams.modular_mode = true; 445 s.cparams.butteraugli_distance = 0; 446 s.input_path = "jxl/flower/flower_alpha.png"; 447 s.cparams_descr = "AlphaLossless"; 448 all_tests.push_back(s); 449 } 450 451 { 452 auto s = settings; 453 s.input_path = "jxl/flower/flower_alpha.png"; 454 s.cparams_descr = "AlphaDownsample"; 455 s.cparams.ec_resampling = 2; 456 all_tests.push_back(s); 457 } 458 459 { 460 auto s = settings; 461 s.cparams_descr = "SpotColor"; 462 s.add_spot_color = true; 463 all_tests.push_back(s); 464 } 465 } 466 467 #if JPEGXL_ENABLE_TRANSCODE_JPEG 468 for (const char* input : {"jxl/flower/flower.png.im_q85_444.jpg", 469 "jxl/flower/flower.png.im_q85_420.jpg", 470 "jxl/flower/flower.png.im_q85_422.jpg", 471 "jxl/flower/flower.png.im_q85_440.jpg"}) { 472 RenderPipelineTestInputSettings settings; 473 settings.input_path = input; 474 settings.jpeg_transcode = true; 475 settings.xsize = 2268; 476 settings.ysize = 1512; 477 settings.cparams_descr = "Default"; 478 all_tests.push_back(settings); 479 } 480 481 #endif 482 483 { 484 RenderPipelineTestInputSettings settings; 485 settings.input_path = "jxl/grayscale_patches.png"; 486 settings.xsize = 1011; 487 settings.ysize = 277; 488 settings.cparams_descr = "Patches"; 489 all_tests.push_back(settings); 490 } 491 492 { 493 RenderPipelineTestInputSettings settings; 494 settings.input_path = "jxl/grayscale_patches.png"; 495 settings.xsize = 1011; 496 settings.ysize = 277; 497 settings.cparams.photon_noise_iso = 1000; 498 settings.cparams_descr = "PatchesAndNoise"; 499 all_tests.push_back(settings); 500 } 501 502 { 503 RenderPipelineTestInputSettings settings; 504 settings.input_path = "jxl/grayscale_patches.png"; 505 settings.xsize = 1011; 506 settings.ysize = 277; 507 settings.cparams.resampling = 2; 508 settings.cparams_descr = "PatchesAndUps2"; 509 all_tests.push_back(settings); 510 } 511 512 return all_tests; 513 } 514 515 std::ostream& operator<<(std::ostream& os, 516 const RenderPipelineTestInputSettings& c) { 517 std::string filename; 518 size_t pos = c.input_path.find_last_of('/'); 519 if (pos == std::string::npos) { 520 filename = c.input_path; 521 } else { 522 filename = c.input_path.substr(pos + 1); 523 } 524 std::replace_if( 525 filename.begin(), filename.end(), [](char c) { return isalnum(c) == 0; }, 526 '_'); 527 os << filename << "_" << (c.jpeg_transcode ? "JPEG_" : "") << c.xsize << "x" 528 << c.ysize << "_" << c.cparams_descr; 529 return os; 530 } 531 532 std::string PipelineTestDescription( 533 const testing::TestParamInfo<RenderPipelineTestParam::ParamType>& info) { 534 std::stringstream name; 535 name << info.param; 536 return name.str(); 537 } 538 539 JXL_GTEST_INSTANTIATE_TEST_SUITE_P(RenderPipelineTest, RenderPipelineTestParam, 540 testing::ValuesIn(GeneratePipelineTests()), 541 PipelineTestDescription); 542 543 TEST(RenderPipelineDecodingTest, Animation) { 544 FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8); 545 ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); 546 547 std::vector<uint8_t> compressed = 548 jxl::test::ReadTestData("jxl/blending/cropped_traffic_light.jxl"); 549 550 CodecInOut io_default; 551 ASSERT_TRUE(DecodeFile(Bytes(compressed), 552 /*use_slow_pipeline=*/false, &io_default, &pool)); 553 CodecInOut io_slow_pipeline; 554 ASSERT_TRUE(DecodeFile(Bytes(compressed), 555 /*use_slow_pipeline=*/true, &io_slow_pipeline, &pool)); 556 557 ASSERT_EQ(io_default.frames.size(), io_slow_pipeline.frames.size()); 558 for (size_t i = 0; i < io_default.frames.size(); i++) { 559 #if JXL_HIGH_PRECISION 560 constexpr float kMaxError = 1e-5; 561 #else 562 constexpr float kMaxError = 1e-4; 563 #endif 564 565 Image3F fast_pipeline = std::move(*io_default.frames[i].color()); 566 Image3F slow_pipeline = std::move(*io_slow_pipeline.frames[i].color()); 567 JXL_ASSERT_OK(VerifyRelativeError(slow_pipeline, fast_pipeline, kMaxError, 568 kMaxError, _)) 569 for (size_t ec = 0; ec < io_default.frames[i].extra_channels().size(); 570 ec++) { 571 JXL_ASSERT_OK(VerifyRelativeError( 572 io_slow_pipeline.frames[i].extra_channels()[ec], 573 io_default.frames[i].extra_channels()[ec], kMaxError, kMaxError, _)); 574 } 575 } 576 } 577 578 } // namespace 579 } // namespace jxl