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