libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

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