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 <stdint.h>
9 #include <stdio.h>
10 
11 #include <algorithm>
12 #include <utility>
13 #include <vector>
14 
15 #include "gtest/gtest.h"
16 #include "lib/extras/codec.h"
17 #include "lib/jxl/enc_params.h"
18 #include "lib/jxl/fake_parallel_runner_testonly.h"
19 #include "lib/jxl/image_test_utils.h"
20 #include "lib/jxl/jpeg/enc_jpeg_data.h"
21 #include "lib/jxl/render_pipeline/test_render_pipeline_stages.h"
22 #include "lib/jxl/test_utils.h"
23 #include "lib/jxl/testdata.h"
24 
25 namespace jxl {
26 namespace {
27 
TEST(RenderPipelineTest,Build)28 TEST(RenderPipelineTest, Build) {
29   RenderPipeline::Builder builder(/*num_c=*/1);
30   builder.AddStage(jxl::make_unique<UpsampleXSlowStage>());
31   builder.AddStage(jxl::make_unique<UpsampleYSlowStage>());
32   builder.AddStage(jxl::make_unique<Check0FinalStage>());
33   builder.UseSimpleImplementation();
34   FrameDimensions frame_dimensions;
35   frame_dimensions.Set(/*xsize=*/1024, /*ysize=*/1024, /*group_size_shift=*/0,
36                        /*max_hshift=*/0, /*max_vshift=*/0,
37                        /*modular_mode=*/false, /*upsampling=*/1);
38   std::move(builder).Finalize(frame_dimensions);
39 }
40 
TEST(RenderPipelineTest,CallAllGroups)41 TEST(RenderPipelineTest, CallAllGroups) {
42   RenderPipeline::Builder builder(/*num_c=*/1);
43   builder.AddStage(jxl::make_unique<UpsampleXSlowStage>());
44   builder.AddStage(jxl::make_unique<UpsampleYSlowStage>());
45   builder.AddStage(jxl::make_unique<Check0FinalStage>());
46   builder.UseSimpleImplementation();
47   FrameDimensions frame_dimensions;
48   frame_dimensions.Set(/*xsize=*/1024, /*ysize=*/1024, /*group_size_shift=*/0,
49                        /*max_hshift=*/0, /*max_vshift=*/0,
50                        /*modular_mode=*/false, /*upsampling=*/1);
51   auto pipeline = std::move(builder).Finalize(frame_dimensions);
52   pipeline->PrepareForThreads(1);
53 
54   for (size_t i = 0; i < frame_dimensions.num_groups; i++) {
55     auto input_buffers = pipeline->GetInputBuffers(i, 0);
56     FillPlane(0.0f, input_buffers.GetBuffer(0).first,
57               input_buffers.GetBuffer(0).second);
58     input_buffers.Done();
59   }
60 
61   EXPECT_TRUE(pipeline->PassesWithAllInput() == 1);
62 }
63 
64 struct RenderPipelineTestInputSettings {
65   // Input image.
66   std::string input_path;
67   size_t xsize, ysize;
68   bool jpeg_transcode = false;
69   // Encoding settings.
70   CompressParams cparams;
71   // Short name for the encoder settings.
72   std::string cparams_descr;
73 
74   bool add_spot_color = false;
75 
76   Splines splines;
77 };
78 
79 class RenderPipelineTestParam
80     : public ::testing::TestWithParam<RenderPipelineTestInputSettings> {};
81 
TEST_P(RenderPipelineTestParam,PipelineTest)82 TEST_P(RenderPipelineTestParam, PipelineTest) {
83   RenderPipelineTestInputSettings config = GetParam();
84 
85   // Use a parallel runner that randomly shuffles tasks to detect possible
86   // border handling bugs.
87   FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8);
88   ThreadPool pool(&JxlFakeParallelRunner, &fake_pool);
89   const PaddedBytes orig = ReadTestData(config.input_path);
90 
91   CodecInOut io;
92   if (config.jpeg_transcode) {
93     ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io));
94   } else {
95     ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
96   }
97   io.ShrinkTo(config.xsize, config.ysize);
98 
99   if (config.add_spot_color) {
100     jxl::ImageF spot(config.xsize, config.ysize);
101     jxl::ZeroFillImage(&spot);
102 
103     for (size_t y = 0; y < config.ysize; y++) {
104       float* JXL_RESTRICT row = spot.Row(y);
105       for (size_t x = 0; x < config.xsize; x++) {
106         row[x] = ((x ^ y) & 255) * (1.f / 255.f);
107       }
108     }
109     ExtraChannelInfo info;
110     info.bit_depth.bits_per_sample = 8;
111     info.dim_shift = 0;
112     info.type = jxl::ExtraChannel::kSpotColor;
113     info.spot_color[0] = 0.5f;
114     info.spot_color[1] = 0.2f;
115     info.spot_color[2] = 1.f;
116     info.spot_color[3] = 0.5f;
117 
118     io.metadata.m.extra_channel_info.push_back(info);
119     std::vector<jxl::ImageF> ec;
120     ec.push_back(std::move(spot));
121     io.frames[0].SetExtraChannels(std::move(ec));
122   }
123 
124   PaddedBytes compressed;
125 
126   PassesEncoderState enc_state;
127   enc_state.shared.image_features.splines = config.splines;
128   ASSERT_TRUE(EncodeFile(config.cparams, &io, &enc_state, &compressed,
129                          GetJxlCms(), /*aux_out=*/nullptr, &pool));
130 
131   DecompressParams dparams;
132 
133   dparams.render_spotcolors = true;
134 
135   CodecInOut io_default;
136   ASSERT_TRUE(DecodeFile(dparams, compressed, &io_default, &pool));
137   CodecInOut io_slow_pipeline;
138   dparams.use_slow_render_pipeline = true;
139   ASSERT_TRUE(DecodeFile(dparams, compressed, &io_slow_pipeline, &pool));
140 
141   ASSERT_EQ(io_default.frames.size(), io_slow_pipeline.frames.size());
142   for (size_t i = 0; i < io_default.frames.size(); i++) {
143 #if JXL_HIGH_PRECISION
144     constexpr float kMaxError = 1e-5;
145 #else
146     constexpr float kMaxError = 1e-4;
147 #endif
148     VerifyRelativeError(*io_default.frames[i].color(),
149                         *io_slow_pipeline.frames[i].color(), kMaxError,
150                         kMaxError);
151     for (size_t ec = 0; ec < io_default.frames[i].extra_channels().size();
152          ec++) {
153       VerifyRelativeError(io_default.frames[i].extra_channels()[ec],
154                           io_slow_pipeline.frames[i].extra_channels()[ec],
155                           kMaxError, kMaxError);
156     }
157   }
158 }
159 
CreateTestSplines()160 Splines CreateTestSplines() {
161   const ColorCorrelationMap cmap;
162   std::vector<Spline::Point> control_points{{9, 54},  {118, 159}, {97, 3},
163                                             {10, 40}, {150, 25},  {120, 300}};
164   const Spline spline{
165       control_points,
166       /*color_dct=*/
167       {{0.03125f, 0.00625f, 0.003125f}, {1.f, 0.321875f}, {1.f, 0.24375f}},
168       /*sigma_dct=*/{0.3125f, 0.f, 0.f, 0.0625f}};
169   std::vector<Spline> spline_data = {spline};
170   std::vector<QuantizedSpline> quantized_splines;
171   std::vector<Spline::Point> starting_points;
172   for (const Spline& spline : spline_data) {
173     quantized_splines.emplace_back(spline, /*quantization_adjustment=*/0,
174                                    cmap.YtoXRatio(0), cmap.YtoBRatio(0));
175     starting_points.push_back(spline.control_points.front());
176   }
177   return Splines(/*quantization_adjustment=*/0, std::move(quantized_splines),
178                  std::move(starting_points));
179 }
180 
GeneratePipelineTests()181 std::vector<RenderPipelineTestInputSettings> GeneratePipelineTests() {
182   std::vector<RenderPipelineTestInputSettings> all_tests;
183 
184   std::pair<size_t, size_t> sizes[] = {
185       {128, 128}, {256, 256}, {258, 258}, {533, 401}, {777, 777},
186   };
187 
188   for (auto size : sizes) {
189     RenderPipelineTestInputSettings settings;
190     settings.input_path = "imagecompression.info/flower_foveon.png";
191     settings.xsize = size.first;
192     settings.ysize = size.second;
193 
194     // Base settings.
195     settings.cparams.butteraugli_distance = 1.0;
196     settings.cparams.patches = Override::kOff;
197     settings.cparams.dots = Override::kOff;
198     settings.cparams.gaborish = Override::kOff;
199     settings.cparams.epf = 0;
200     settings.cparams.color_transform = ColorTransform::kXYB;
201 
202     {
203       auto s = settings;
204       s.cparams_descr = "NoGabNoEpfNoPatches";
205       all_tests.push_back(s);
206     }
207 
208     {
209       auto s = settings;
210       s.cparams.color_transform = ColorTransform::kNone;
211       s.cparams_descr = "NoGabNoEpfNoPatchesNoXYB";
212       all_tests.push_back(s);
213     }
214 
215     {
216       auto s = settings;
217       s.cparams.gaborish = Override::kOn;
218       s.cparams_descr = "GabNoEpfNoPatches";
219       all_tests.push_back(s);
220     }
221 
222     {
223       auto s = settings;
224       s.cparams.epf = 1;
225       s.cparams_descr = "NoGabEpf1NoPatches";
226       all_tests.push_back(s);
227     }
228 
229     {
230       auto s = settings;
231       s.cparams.epf = 2;
232       s.cparams_descr = "NoGabEpf2NoPatches";
233       all_tests.push_back(s);
234     }
235 
236     {
237       auto s = settings;
238       s.cparams.epf = 3;
239       s.cparams_descr = "NoGabEpf3NoPatches";
240       all_tests.push_back(s);
241     }
242 
243     {
244       auto s = settings;
245       s.cparams.gaborish = Override::kOn;
246       s.cparams.epf = 3;
247       s.cparams_descr = "GabEpf3NoPatches";
248       all_tests.push_back(s);
249     }
250 
251     {
252       auto s = settings;
253       s.cparams_descr = "Splines";
254       s.splines = CreateTestSplines();
255       all_tests.push_back(s);
256     }
257 
258     for (size_t ups : {2, 4, 8}) {
259       {
260         auto s = settings;
261         s.cparams.resampling = ups;
262         s.cparams_descr = "Ups" + std::to_string(ups);
263         all_tests.push_back(s);
264       }
265       {
266         auto s = settings;
267         s.cparams.resampling = ups;
268         s.cparams.epf = 1;
269         s.cparams_descr = "Ups" + std::to_string(ups) + "EPF1";
270         all_tests.push_back(s);
271       }
272       {
273         auto s = settings;
274         s.cparams.resampling = ups;
275         s.cparams.gaborish = Override::kOn;
276         s.cparams.epf = 1;
277         s.cparams_descr = "Ups" + std::to_string(ups) + "GabEPF1";
278         all_tests.push_back(s);
279       }
280     }
281 
282     {
283       auto s = settings;
284       s.cparams_descr = "Noise";
285       s.cparams.photon_noise_iso = 3200;
286       all_tests.push_back(s);
287     }
288 
289     {
290       auto s = settings;
291       s.cparams_descr = "NoiseUps";
292       s.cparams.photon_noise_iso = 3200;
293       s.cparams.resampling = 2;
294       all_tests.push_back(s);
295     }
296 
297     {
298       auto s = settings;
299       s.cparams_descr = "ModularLossless";
300       s.cparams.modular_mode = true;
301       s.cparams.butteraugli_distance = 0;
302       all_tests.push_back(s);
303     }
304 
305     {
306       auto s = settings;
307       s.cparams_descr = "ProgressiveDC";
308       s.cparams.progressive_dc = 1;
309       all_tests.push_back(s);
310     }
311 
312     {
313       auto s = settings;
314       s.cparams_descr = "ModularLossy";
315       s.cparams.modular_mode = true;
316       s.cparams.quality_pair = {90, 90};
317       all_tests.push_back(s);
318     }
319 
320     {
321       auto s = settings;
322       s.input_path = "wide-gamut-tests/R2020-sRGB-blue.png";
323       s.cparams_descr = "AlphaVarDCT";
324       all_tests.push_back(s);
325     }
326 
327     {
328       auto s = settings;
329       s.input_path = "wide-gamut-tests/R2020-sRGB-blue.png";
330       s.cparams_descr = "AlphaVarDCTUpsamplingEPF";
331       s.cparams.epf = 1;
332       s.cparams.ec_resampling = 2;
333       all_tests.push_back(s);
334     }
335 
336     {
337       auto s = settings;
338       s.cparams.modular_mode = true;
339       s.cparams.butteraugli_distance = 0;
340       s.input_path = "wide-gamut-tests/R2020-sRGB-blue.png";
341       s.cparams_descr = "AlphaLossless";
342       all_tests.push_back(s);
343     }
344 
345     {
346       auto s = settings;
347       s.input_path = "wide-gamut-tests/R2020-sRGB-blue.png";
348       s.cparams_descr = "AlphaDownsample";
349       s.cparams.ec_resampling = 2;
350       all_tests.push_back(s);
351     }
352 
353     {
354       auto s = settings;
355       s.cparams_descr = "SpotColor";
356       s.add_spot_color = true;
357       all_tests.push_back(s);
358     }
359   }
360 
361 #if JPEGXL_ENABLE_TRANSCODE_JPEG
362   for (const char* input :
363        {"imagecompression.info/flower_foveon.png.im_q85_444.jpg",
364         "imagecompression.info/flower_foveon.png.im_q85_420.jpg",
365         "imagecompression.info/flower_foveon.png.im_q85_422.jpg",
366         "imagecompression.info/flower_foveon.png.im_q85_440.jpg"}) {
367     RenderPipelineTestInputSettings settings;
368     settings.input_path = input;
369     settings.jpeg_transcode = true;
370     settings.xsize = 2268;
371     settings.ysize = 1512;
372     settings.cparams_descr = "Default";
373     all_tests.push_back(settings);
374   }
375 
376 #endif
377 
378   {
379     RenderPipelineTestInputSettings settings;
380     settings.input_path = "jxl/grayscale_patches.png";
381     settings.xsize = 1011;
382     settings.ysize = 277;
383     settings.cparams_descr = "Patches";
384     all_tests.push_back(settings);
385   }
386 
387   return all_tests;
388 }
389 
operator <<(std::ostream & os,const RenderPipelineTestInputSettings & c)390 std::ostream& operator<<(std::ostream& os,
391                          const RenderPipelineTestInputSettings& c) {
392   std::string filename;
393   size_t pos = c.input_path.find_last_of('/');
394   if (pos == std::string::npos) {
395     filename = c.input_path;
396   } else {
397     filename = c.input_path.substr(pos + 1);
398   }
399   std::replace_if(
400       filename.begin(), filename.end(), [](char c) { return !isalnum(c); },
401       '_');
402   os << filename << "_" << (c.jpeg_transcode ? "JPEG_" : "") << c.xsize << "x"
403      << c.ysize << "_" << c.cparams_descr;
404   return os;
405 }
406 
PipelineTestDescription(const testing::TestParamInfo<RenderPipelineTestParam::ParamType> & info)407 std::string PipelineTestDescription(
408     const testing::TestParamInfo<RenderPipelineTestParam::ParamType>& info) {
409   std::stringstream name;
410   name << info.param;
411   return name.str();
412 }
413 
414 JXL_GTEST_INSTANTIATE_TEST_SUITE_P(RenderPipelineTest, RenderPipelineTestParam,
415                                    testing::ValuesIn(GeneratePipelineTests()),
416                                    PipelineTestDescription);
417 
418 }  // namespace
419 }  // namespace jxl
420