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