tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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