1/* sane - Scanner Access Now Easy.
2
3   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
4
5   This file is part of the SANE package.
6
7   This program is free software; you can redistribute it and/or
8   modify it under the terms of the GNU General Public License as
9   published by the Free Software Foundation; either version 2 of the
10   License, or (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program.  If not, see <https://www.gnu.org/licenses/>.
19*/
20
21#define DEBUG_DECLARE_ONLY
22
23#include "tests.h"
24#include "tests_printers.h"
25#include "minigtest.h"
26
27#include "../../../backend/genesys/image_pipeline.h"
28
29#include <numeric>
30
31namespace genesys {
32
33
34void test_image_buffer_exact_reads()
35{
36    std::vector<std::size_t> requests;
37
38    auto on_read = [&](std::size_t x, std::uint8_t* data)
39    {
40        (void) data;
41        requests.push_back(x);
42        return true;
43    };
44
45    ImageBuffer buffer{1000, on_read};
46    buffer.set_remaining_size(2500);
47
48    std::vector<std::uint8_t> dummy;
49    dummy.resize(1000);
50
51    ASSERT_TRUE(buffer.get_data(1000, dummy.data()));
52    ASSERT_TRUE(buffer.get_data(1000, dummy.data()));
53    ASSERT_TRUE(buffer.get_data(500, dummy.data()));
54
55    std::vector<std::size_t> expected = {
56        1000, 1000, 500
57    };
58    ASSERT_EQ(requests, expected);
59}
60
61void test_image_buffer_smaller_reads()
62{
63    std::vector<std::size_t> requests;
64
65    auto on_read = [&](std::size_t x, std::uint8_t* data)
66    {
67        (void) data;
68        requests.push_back(x);
69        return true;
70    };
71
72    ImageBuffer buffer{1000, on_read};
73    buffer.set_remaining_size(2500);
74
75    std::vector<std::uint8_t> dummy;
76    dummy.resize(700);
77
78    ASSERT_TRUE(buffer.get_data(600, dummy.data()));
79    ASSERT_TRUE(buffer.get_data(600, dummy.data()));
80    ASSERT_TRUE(buffer.get_data(600, dummy.data()));
81    ASSERT_TRUE(buffer.get_data(700, dummy.data()));
82
83    std::vector<std::size_t> expected = {
84        1000, 1000, 500
85    };
86    ASSERT_EQ(requests, expected);
87}
88
89void test_image_buffer_larger_reads()
90{
91    std::vector<std::size_t> requests;
92
93    auto on_read = [&](std::size_t x, std::uint8_t* data)
94    {
95        (void) data;
96        requests.push_back(x);
97        return true;
98    };
99
100    ImageBuffer buffer{1000, on_read};
101    buffer.set_remaining_size(2500);
102
103    std::vector<std::uint8_t> dummy;
104    dummy.resize(2500);
105
106    ASSERT_TRUE(buffer.get_data(2500, dummy.data()));
107
108    std::vector<std::size_t> expected = {
109        1000, 1000, 500
110    };
111    ASSERT_EQ(requests, expected);
112}
113
114void test_image_buffer_uncapped_remaining_bytes()
115{
116    std::vector<std::size_t> requests;
117    unsigned request_count = 0;
118    auto on_read = [&](std::size_t x, std::uint8_t* data)
119    {
120        (void) data;
121        requests.push_back(x);
122        request_count++;
123        return request_count < 4;
124    };
125
126    ImageBuffer buffer{1000, on_read};
127
128    std::vector<std::uint8_t> dummy;
129    dummy.resize(3000);
130
131    ASSERT_TRUE(buffer.get_data(3000, dummy.data()));
132    ASSERT_FALSE(buffer.get_data(3000, dummy.data()));
133
134    std::vector<std::size_t> expected = {
135        1000, 1000, 1000, 1000
136    };
137    ASSERT_EQ(requests, expected);
138}
139
140void test_image_buffer_capped_remaining_bytes()
141{
142    std::vector<std::size_t> requests;
143
144    auto on_read = [&](std::size_t x, std::uint8_t* data)
145    {
146        (void) data;
147        requests.push_back(x);
148        return true;
149    };
150
151    ImageBuffer buffer{1000, on_read};
152    buffer.set_remaining_size(10000);
153    buffer.set_last_read_multiple(16);
154
155    std::vector<std::uint8_t> dummy;
156    dummy.resize(2000);
157
158    ASSERT_TRUE(buffer.get_data(2000, dummy.data()));
159    ASSERT_TRUE(buffer.get_data(2000, dummy.data()));
160    buffer.set_remaining_size(100);
161    ASSERT_FALSE(buffer.get_data(200, dummy.data()));
162
163    std::vector<std::size_t> expected = {
164        // note that the sizes are rounded-up to 16 bytes
165        1000, 1000, 1000, 1000, 112
166    };
167    ASSERT_EQ(requests, expected);
168}
169
170void test_node_buffered_callable_source()
171{
172    using Data = std::vector<std::uint8_t>;
173
174    Data in_data = {
175        0, 1, 2, 3,
176        4, 5, 6, 7,
177        8, 9, 10, 11
178    };
179
180    std::size_t chunk_size = 3;
181    std::size_t curr_index = 0;
182
183    auto data_source_cb = [&](std::size_t size, std::uint8_t* out_data)
184    {
185        ASSERT_EQ(size, chunk_size);
186        std::copy(in_data.begin() + curr_index,
187                  in_data.begin() + curr_index + chunk_size, out_data);
188        curr_index += chunk_size;
189        return true;
190    };
191
192    ImagePipelineStack stack;
193    stack.push_first_node<ImagePipelineNodeBufferedCallableSource>(4, 3, PixelFormat::I8,
194                                                                   chunk_size, data_source_cb);
195
196    Data out_data;
197    out_data.resize(4);
198
199    ASSERT_EQ(curr_index, 0u);
200
201    ASSERT_TRUE(stack.get_next_row_data(out_data.data()));
202    ASSERT_EQ(out_data, Data({0, 1, 2, 3}));
203    ASSERT_EQ(curr_index, 6u);
204
205    ASSERT_TRUE(stack.get_next_row_data(out_data.data()));
206    ASSERT_EQ(out_data, Data({4, 5, 6, 7}));
207    ASSERT_EQ(curr_index, 9u);
208
209    ASSERT_TRUE(stack.get_next_row_data(out_data.data()));
210    ASSERT_EQ(out_data, Data({8, 9, 10, 11}));
211    ASSERT_EQ(curr_index, 12u);
212}
213
214void test_node_format_convert()
215{
216    using Data = std::vector<std::uint8_t>;
217
218    Data in_data = {
219        0x12, 0x34, 0x56,
220        0x78, 0x98, 0xab,
221        0xcd, 0xef, 0x21,
222    };
223
224    ImagePipelineStack stack;
225    stack.push_first_node<ImagePipelineNodeArraySource>(3, 1, PixelFormat::RGB888,
226                                                        std::move(in_data));
227    stack.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::BGR161616);
228
229    ASSERT_EQ(stack.get_output_width(), 3u);
230    ASSERT_EQ(stack.get_output_height(), 1u);
231    ASSERT_EQ(stack.get_output_row_bytes(), 6u * 3);
232    ASSERT_EQ(stack.get_output_format(), PixelFormat::BGR161616);
233
234    auto out_data = stack.get_all_data();
235
236    Data expected_data = {
237        0x56, 0x56, 0x34, 0x34, 0x12, 0x12,
238        0xab, 0xab, 0x98, 0x98, 0x78, 0x78,
239        0x21, 0x21, 0xef, 0xef, 0xcd, 0xcd,
240    };
241
242    ASSERT_EQ(out_data, expected_data);
243}
244
245void test_node_desegment_1_line()
246{
247    using Data = std::vector<std::uint8_t>;
248
249    Data in_data = {
250         1,  5,  9, 13, 17,
251         3,  7, 11, 15, 19,
252         2,  6, 10, 14, 18,
253         4,  8, 12, 16, 20,
254        21, 25, 29, 33, 37,
255        23, 27, 31, 35, 39,
256        22, 26, 30, 34, 38,
257        24, 28, 32, 36, 40,
258    };
259
260    ImagePipelineStack stack;
261    stack.push_first_node<ImagePipelineNodeArraySource>(20, 2, PixelFormat::I8,
262                                                        std::move(in_data));
263    stack.push_node<ImagePipelineNodeDesegment>(20, std::vector<unsigned>{ 0, 2, 1, 3 }, 5, 1, 1);
264
265    ASSERT_EQ(stack.get_output_width(), 20u);
266    ASSERT_EQ(stack.get_output_height(), 2u);
267    ASSERT_EQ(stack.get_output_row_bytes(), 20u);
268    ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
269
270    auto out_data = stack.get_all_data();
271
272    Data expected_data;
273    expected_data.resize(40, 0);
274    std::iota(expected_data.begin(), expected_data.end(), 1); // will fill with 1, 2, 3, ..., 40
275
276    ASSERT_EQ(out_data, expected_data);
277}
278
279void test_node_deinterleave_lines_i8()
280{
281    using Data = std::vector<std::uint8_t>;
282
283    Data in_data = {
284        1, 3, 5, 7,  9, 11, 13, 15, 17, 19,
285        2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
286    };
287
288    ImagePipelineStack stack;
289    stack.push_first_node<ImagePipelineNodeArraySource>(10, 2, PixelFormat::I8,
290                                                        std::move(in_data));
291    stack.push_node<ImagePipelineNodeDeinterleaveLines>(2, 1);
292
293    ASSERT_EQ(stack.get_output_width(), 20u);
294    ASSERT_EQ(stack.get_output_height(), 1u);
295    ASSERT_EQ(stack.get_output_row_bytes(), 20u);
296    ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
297
298    auto out_data = stack.get_all_data();
299
300    Data expected_data;
301    expected_data.resize(20, 0);
302    std::iota(expected_data.begin(), expected_data.end(), 1); // will fill with 1, 2, 3, ..., 20
303
304    ASSERT_EQ(out_data, expected_data);
305}
306
307void test_node_deinterleave_lines_rgb888()
308{
309    using Data = std::vector<std::uint8_t>;
310
311    Data in_data = {
312        1, 2, 3,  7,  8,  9, 13, 14, 15, 19, 20, 21,
313        4, 5, 6, 10, 11, 12, 16, 17, 18, 22, 23, 24,
314    };
315
316    ImagePipelineStack stack;
317    stack.push_first_node<ImagePipelineNodeArraySource>(4, 2, PixelFormat::RGB888,
318                                                        std::move(in_data));
319    stack.push_node<ImagePipelineNodeDeinterleaveLines>(2, 1);
320
321    ASSERT_EQ(stack.get_output_width(), 8u);
322    ASSERT_EQ(stack.get_output_height(), 1u);
323    ASSERT_EQ(stack.get_output_row_bytes(), 24u);
324    ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888);
325
326    auto out_data = stack.get_all_data();
327
328    Data expected_data;
329    expected_data.resize(24, 0);
330    std::iota(expected_data.begin(), expected_data.end(), 1); // will fill with 1, 2, 3, ..., 20
331
332    ASSERT_EQ(out_data, expected_data);
333}
334
335void test_node_swap_16bit_endian()
336{
337    using Data = std::vector<std::uint8_t>;
338
339    Data in_data = {
340        0x10, 0x20, 0x30, 0x11, 0x21, 0x31,
341        0x12, 0x22, 0x32, 0x13, 0x23, 0x33,
342        0x14, 0x24, 0x34, 0x15, 0x25, 0x35,
343        0x16, 0x26, 0x36, 0x17, 0x27, 0x37,
344    };
345
346    ImagePipelineStack stack;
347    stack.push_first_node<ImagePipelineNodeArraySource>(4, 1, PixelFormat::RGB161616,
348                                                        std::move(in_data));
349    stack.push_node<ImagePipelineNodeSwap16BitEndian>();
350
351    ASSERT_EQ(stack.get_output_width(), 4u);
352    ASSERT_EQ(stack.get_output_height(), 1u);
353    ASSERT_EQ(stack.get_output_row_bytes(), 24u);
354    ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB161616);
355
356    auto out_data = stack.get_all_data();
357
358    Data expected_data = {
359        0x20, 0x10, 0x11, 0x30, 0x31, 0x21,
360        0x22, 0x12, 0x13, 0x32, 0x33, 0x23,
361        0x24, 0x14, 0x15, 0x34, 0x35, 0x25,
362        0x26, 0x16, 0x17, 0x36, 0x37, 0x27,
363    };
364
365    ASSERT_EQ(out_data, expected_data);
366}
367
368void test_node_invert_16_bits()
369{
370    using Data16 = std::vector<std::uint16_t>;
371    using Data = std::vector<std::uint8_t>;
372
373    Data16 in_data = {
374        0x1020, 0x3011, 0x2131,
375        0x1222, 0x3213, 0x2333,
376        0x1424, 0x3415, 0x2525,
377        0x1626, 0x3617, 0x2737,
378    };
379
380    Data in_data_8bit;
381    in_data_8bit.resize(in_data.size() * 2);
382    std::memcpy(in_data_8bit.data(), in_data.data(), in_data_8bit.size());
383
384    ImagePipelineStack stack;
385    stack.push_first_node<ImagePipelineNodeArraySource>(4, 1, PixelFormat::RGB161616,
386                                                        std::move(in_data_8bit));
387    stack.push_node<ImagePipelineNodeInvert>();
388
389    ASSERT_EQ(stack.get_output_width(), 4u);
390    ASSERT_EQ(stack.get_output_height(), 1u);
391    ASSERT_EQ(stack.get_output_row_bytes(), 24u);
392    ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB161616);
393
394    auto out_data_8bit = stack.get_all_data();
395    Data16 out_data;
396    out_data.resize(out_data_8bit.size() / 2);
397    std::memcpy(out_data.data(), out_data_8bit.data(), out_data_8bit.size());
398
399    Data16 expected_data = {
400        0xefdf, 0xcfee, 0xdece,
401        0xeddd, 0xcdec, 0xdccc,
402        0xebdb, 0xcbea, 0xdada,
403        0xe9d9, 0xc9e8, 0xd8c8,
404    };
405
406    ASSERT_EQ(out_data, expected_data);
407}
408
409void test_node_invert_8_bits()
410{
411    using Data = std::vector<std::uint8_t>;
412
413    Data in_data = {
414        0x10, 0x20, 0x30, 0x11, 0x21, 0x31,
415        0x12, 0x22, 0x32, 0x13, 0x23, 0x33,
416        0x14, 0x24, 0x34, 0x15, 0x25, 0x35,
417        0x16, 0x26, 0x36, 0x17, 0x27, 0x37,
418    };
419
420    ImagePipelineStack stack;
421    stack.push_first_node<ImagePipelineNodeArraySource>(8, 1, PixelFormat::RGB888,
422                                                        std::move(in_data));
423    stack.push_node<ImagePipelineNodeInvert>();
424
425    ASSERT_EQ(stack.get_output_width(), 8u);
426    ASSERT_EQ(stack.get_output_height(), 1u);
427    ASSERT_EQ(stack.get_output_row_bytes(), 24u);
428    ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888);
429
430    auto out_data = stack.get_all_data();
431
432    Data expected_data = {
433        0xef, 0xdf, 0xcf, 0xee, 0xde, 0xce,
434        0xed, 0xdd, 0xcd, 0xec, 0xdc, 0xcc,
435        0xeb, 0xdb, 0xcb, 0xea, 0xda, 0xca,
436        0xe9, 0xd9, 0xc9, 0xe8, 0xd8, 0xc8,
437    };
438
439    ASSERT_EQ(out_data, expected_data);
440}
441
442void test_node_invert_1_bits()
443{
444    using Data = std::vector<std::uint8_t>;
445
446    Data in_data = {
447        0x10, 0x20, 0x30, 0x11, 0x21, 0x31,
448        0x16, 0x26, 0x36, 0x17, 0x27, 0x37,
449    };
450
451    ImagePipelineStack stack;
452    stack.push_first_node<ImagePipelineNodeArraySource>(32, 1, PixelFormat::RGB111,
453                                                        std::move(in_data));
454    stack.push_node<ImagePipelineNodeInvert>();
455
456    ASSERT_EQ(stack.get_output_width(), 32u);
457    ASSERT_EQ(stack.get_output_height(), 1u);
458    ASSERT_EQ(stack.get_output_row_bytes(), 12u);
459    ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB111);
460
461    auto out_data = stack.get_all_data();
462
463    Data expected_data = {
464        0xef, 0xdf, 0xcf, 0xee, 0xde, 0xce,
465        0xe9, 0xd9, 0xc9, 0xe8, 0xd8, 0xc8,
466    };
467
468    ASSERT_EQ(out_data, expected_data);
469}
470
471void test_node_merge_mono_lines_to_color()
472{
473    using Data = std::vector<std::uint8_t>;
474
475    Data in_data = {
476        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
477        0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
478        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
479    };
480
481    ImagePipelineStack stack;
482    stack.push_first_node<ImagePipelineNodeArraySource>(8, 3, PixelFormat::I8,
483                                                        std::move(in_data));
484    stack.push_node<ImagePipelineNodeMergeMonoLinesToColor>(ColorOrder::RGB);
485
486    ASSERT_EQ(stack.get_output_width(), 8u);
487    ASSERT_EQ(stack.get_output_height(), 1u);
488    ASSERT_EQ(stack.get_output_row_bytes(), 24u);
489    ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888);
490
491    auto out_data = stack.get_all_data();
492
493    Data expected_data = {
494        0x10, 0x20, 0x30, 0x11, 0x21, 0x31,
495        0x12, 0x22, 0x32, 0x13, 0x23, 0x33,
496        0x14, 0x24, 0x34, 0x15, 0x25, 0x35,
497        0x16, 0x26, 0x36, 0x17, 0x27, 0x37,
498    };
499
500    ASSERT_EQ(out_data, expected_data);
501}
502
503void test_node_merge_color_to_gray()
504{
505    using Data = std::vector<std::uint8_t>;
506
507    Data in_data = {
508        0x10, 0x20, 0x30, 0x11, 0x21, 0x31,
509        0x12, 0x22, 0x32, 0x13, 0x23, 0x33,
510        0x14, 0x24, 0x34, 0x15, 0x25, 0x35,
511        0x16, 0x26, 0x36, 0x17, 0x27, 0x37,
512    };
513
514    ImagePipelineStack stack;
515    stack.push_first_node<ImagePipelineNodeArraySource>(8, 1, PixelFormat::RGB888,
516                                                        std::move(in_data));
517    stack.push_node<ImagePipelineNodeMergeColorToGray>();
518
519    ASSERT_EQ(stack.get_output_width(), 8u);
520    ASSERT_EQ(stack.get_output_height(), 1u);
521    ASSERT_EQ(stack.get_output_row_bytes(), 8u);
522    ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
523
524    auto out_data = stack.get_all_data();
525
526    Data expected_data = {
527        0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24
528    };
529
530    ASSERT_EQ(out_data, expected_data);
531}
532
533
534void test_node_split_mono_lines()
535{
536    using Data = std::vector<std::uint8_t>;
537
538    Data in_data = {
539        0x10, 0x20, 0x30, 0x11, 0x21, 0x31,
540        0x12, 0x22, 0x32, 0x13, 0x23, 0x33,
541        0x14, 0x24, 0x34, 0x15, 0x25, 0x35,
542        0x16, 0x26, 0x36, 0x17, 0x27, 0x37,
543    };
544
545    ImagePipelineStack stack;
546    stack.push_first_node<ImagePipelineNodeArraySource>(8, 1, PixelFormat::RGB888,
547                                                        std::move(in_data));
548    stack.push_node<ImagePipelineNodeSplitMonoLines>();
549
550    ASSERT_EQ(stack.get_output_width(), 8u);
551    ASSERT_EQ(stack.get_output_height(), 3u);
552    ASSERT_EQ(stack.get_output_row_bytes(), 8u);
553    ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
554
555    auto out_data = stack.get_all_data();
556
557    Data expected_data = {
558        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
559        0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
560        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
561    };
562
563    ASSERT_EQ(out_data, expected_data);
564}
565
566void test_node_component_shift_lines()
567{
568    using Data = std::vector<std::uint8_t>;
569
570    Data in_data = {
571        0x10, 0x20, 0x30, 0x11, 0x21, 0x31, 0x12, 0x22, 0x32, 0x13, 0x23, 0x33,
572        0x14, 0x24, 0x34, 0x15, 0x25, 0x35, 0x16, 0x26, 0x36, 0x17, 0x27, 0x37,
573        0x18, 0x28, 0x38, 0x19, 0x29, 0x39, 0x1a, 0x2a, 0x3a, 0x1b, 0x2b, 0x3b,
574        0x1c, 0x2c, 0x3c, 0x1d, 0x2d, 0x3d, 0x1e, 0x2e, 0x3e, 0x1f, 0x2f, 0x3f,
575    };
576
577    ImagePipelineStack stack;
578    stack.push_first_node<ImagePipelineNodeArraySource>(4, 4, PixelFormat::RGB888,
579                                                        std::move(in_data));
580    stack.push_node<ImagePipelineNodeComponentShiftLines>(0, 1, 2);
581
582    ASSERT_EQ(stack.get_output_width(), 4u);
583    ASSERT_EQ(stack.get_output_height(), 2u);
584    ASSERT_EQ(stack.get_output_row_bytes(), 12u);
585    ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888);
586
587    auto out_data = stack.get_all_data();
588
589    Data expected_data = {
590        0x10, 0x24, 0x38, 0x11, 0x25, 0x39, 0x12, 0x26, 0x3a, 0x13, 0x27, 0x3b,
591        0x14, 0x28, 0x3c, 0x15, 0x29, 0x3d, 0x16, 0x2a, 0x3e, 0x17, 0x2b, 0x3f,
592    };
593
594    ASSERT_EQ(out_data, expected_data);
595}
596
597void test_node_pixel_shift_lines_2lines()
598{
599    using Data = std::vector<std::uint8_t>;
600
601    Data in_data = {
602        0x10, 0x20, 0x30, 0x11, 0x21, 0x31, 0x12, 0x22, 0x32, 0x13, 0x23, 0x33,
603        0x14, 0x24, 0x34, 0x15, 0x25, 0x35, 0x16, 0x26, 0x36, 0x17, 0x27, 0x37,
604        0x18, 0x28, 0x38, 0x19, 0x29, 0x39, 0x1a, 0x2a, 0x3a, 0x1b, 0x2b, 0x3b,
605        0x1c, 0x2c, 0x3c, 0x1d, 0x2d, 0x3d, 0x1e, 0x2e, 0x3e, 0x1f, 0x2f, 0x3f,
606    };
607
608    ImagePipelineStack stack;
609    stack.push_first_node<ImagePipelineNodeArraySource>(4, 4, PixelFormat::RGB888,
610                                                        std::move(in_data));
611    stack.push_node<ImagePipelineNodePixelShiftLines>(std::vector<std::size_t>{0, 2});
612
613    ASSERT_EQ(stack.get_output_width(), 4u);
614    ASSERT_EQ(stack.get_output_height(), 2u);
615    ASSERT_EQ(stack.get_output_row_bytes(), 12u);
616    ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888);
617
618    auto out_data = stack.get_all_data();
619
620    Data expected_data = {
621        0x10, 0x20, 0x30, 0x19, 0x29, 0x39, 0x12, 0x22, 0x32, 0x1b, 0x2b, 0x3b,
622        0x14, 0x24, 0x34, 0x1d, 0x2d, 0x3d, 0x16, 0x26, 0x36, 0x1f, 0x2f, 0x3f,
623    };
624
625    ASSERT_EQ(out_data, expected_data);
626}
627
628void test_node_pixel_shift_lines_4lines()
629{
630    using Data = std::vector<std::uint8_t>;
631
632    Data in_data = {
633        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
634        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
635        0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
636        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
637        0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
638        0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b,
639        0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
640        0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b,
641        0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b,
642    };
643
644    ImagePipelineStack stack;
645    stack.push_first_node<ImagePipelineNodeArraySource>(12, 9, PixelFormat::I8,
646                                                        std::move(in_data));
647    stack.push_node<ImagePipelineNodePixelShiftLines>(std::vector<std::size_t>{0, 2, 1, 3});
648
649    ASSERT_EQ(stack.get_output_width(), 12u);
650    ASSERT_EQ(stack.get_output_height(), 6u);
651    ASSERT_EQ(stack.get_output_row_bytes(), 12u);
652    ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
653
654    auto out_data = stack.get_all_data();
655
656    Data expected_data = {
657        0x00, 0x21, 0x12, 0x33, 0x04, 0x25, 0x16, 0x37, 0x08, 0x29, 0x1a, 0x3b,
658        0x10, 0x31, 0x22, 0x43, 0x14, 0x35, 0x26, 0x47, 0x18, 0x39, 0x2a, 0x4b,
659        0x20, 0x41, 0x32, 0x53, 0x24, 0x45, 0x36, 0x57, 0x28, 0x49, 0x3a, 0x5b,
660        0x30, 0x51, 0x42, 0x63, 0x34, 0x55, 0x46, 0x67, 0x38, 0x59, 0x4a, 0x6b,
661        0x40, 0x61, 0x52, 0x73, 0x44, 0x65, 0x56, 0x77, 0x48, 0x69, 0x5a, 0x7b,
662        0x50, 0x71, 0x62, 0x83, 0x54, 0x75, 0x66, 0x87, 0x58, 0x79, 0x6a, 0x8b,
663    };
664
665    ASSERT_EQ(out_data, expected_data);
666}
667
668void test_node_pixel_shift_columns_compute_max_width()
669{
670    ASSERT_EQ(compute_pixel_shift_extra_width(12, {0, 1, 2, 3}), 0u);
671    ASSERT_EQ(compute_pixel_shift_extra_width(13, {0, 1, 2, 3}), 0u);
672    ASSERT_EQ(compute_pixel_shift_extra_width(14, {0, 1, 2, 3}), 0u);
673    ASSERT_EQ(compute_pixel_shift_extra_width(15, {0, 1, 2, 3}), 0u);
674    ASSERT_EQ(compute_pixel_shift_extra_width(16, {0, 1, 2, 3}), 0u);
675    ASSERT_EQ(compute_pixel_shift_extra_width(17, {0, 1, 2, 3}), 0u);
676    ASSERT_EQ(compute_pixel_shift_extra_width(18, {0, 1, 2, 3}), 0u);
677    ASSERT_EQ(compute_pixel_shift_extra_width(19, {0, 1, 2, 3}), 0u);
678
679    ASSERT_EQ(compute_pixel_shift_extra_width(12, {1, 1, 2, 3}), 0u);
680    ASSERT_EQ(compute_pixel_shift_extra_width(13, {1, 1, 2, 3}), 1u);
681    ASSERT_EQ(compute_pixel_shift_extra_width(14, {1, 1, 2, 3}), 0u);
682    ASSERT_EQ(compute_pixel_shift_extra_width(15, {1, 1, 2, 3}), 0u);
683    ASSERT_EQ(compute_pixel_shift_extra_width(16, {1, 1, 2, 3}), 0u);
684    ASSERT_EQ(compute_pixel_shift_extra_width(17, {1, 1, 2, 3}), 1u);
685    ASSERT_EQ(compute_pixel_shift_extra_width(18, {1, 1, 2, 3}), 0u);
686    ASSERT_EQ(compute_pixel_shift_extra_width(19, {1, 1, 2, 3}), 0u);
687
688    ASSERT_EQ(compute_pixel_shift_extra_width(12, {2, 1, 2, 3}), 0u);
689    ASSERT_EQ(compute_pixel_shift_extra_width(13, {2, 1, 2, 3}), 1u);
690    ASSERT_EQ(compute_pixel_shift_extra_width(14, {2, 1, 2, 3}), 2u);
691    ASSERT_EQ(compute_pixel_shift_extra_width(15, {2, 1, 2, 3}), 0u);
692    ASSERT_EQ(compute_pixel_shift_extra_width(16, {2, 1, 2, 3}), 0u);
693    ASSERT_EQ(compute_pixel_shift_extra_width(17, {2, 1, 2, 3}), 1u);
694    ASSERT_EQ(compute_pixel_shift_extra_width(18, {2, 1, 2, 3}), 2u);
695    ASSERT_EQ(compute_pixel_shift_extra_width(19, {2, 1, 2, 3}), 0u);
696
697    ASSERT_EQ(compute_pixel_shift_extra_width(12, {3, 1, 2, 3}), 0u);
698    ASSERT_EQ(compute_pixel_shift_extra_width(13, {3, 1, 2, 3}), 1u);
699    ASSERT_EQ(compute_pixel_shift_extra_width(14, {3, 1, 2, 3}), 2u);
700    ASSERT_EQ(compute_pixel_shift_extra_width(15, {3, 1, 2, 3}), 3u);
701    ASSERT_EQ(compute_pixel_shift_extra_width(16, {3, 1, 2, 3}), 0u);
702    ASSERT_EQ(compute_pixel_shift_extra_width(17, {3, 1, 2, 3}), 1u);
703    ASSERT_EQ(compute_pixel_shift_extra_width(18, {3, 1, 2, 3}), 2u);
704    ASSERT_EQ(compute_pixel_shift_extra_width(19, {3, 1, 2, 3}), 3u);
705
706    ASSERT_EQ(compute_pixel_shift_extra_width(12, {7, 1, 2, 3}), 4u);
707    ASSERT_EQ(compute_pixel_shift_extra_width(13, {7, 1, 2, 3}), 5u);
708    ASSERT_EQ(compute_pixel_shift_extra_width(14, {7, 1, 2, 3}), 6u);
709    ASSERT_EQ(compute_pixel_shift_extra_width(15, {7, 1, 2, 3}), 7u);
710    ASSERT_EQ(compute_pixel_shift_extra_width(16, {7, 1, 2, 3}), 4u);
711    ASSERT_EQ(compute_pixel_shift_extra_width(17, {7, 1, 2, 3}), 5u);
712    ASSERT_EQ(compute_pixel_shift_extra_width(18, {7, 1, 2, 3}), 6u);
713    ASSERT_EQ(compute_pixel_shift_extra_width(19, {7, 1, 2, 3}), 7u);
714
715    ASSERT_EQ(compute_pixel_shift_extra_width(12, {0, 1, 3, 3}), 0u);
716    ASSERT_EQ(compute_pixel_shift_extra_width(13, {0, 1, 3, 3}), 0u);
717    ASSERT_EQ(compute_pixel_shift_extra_width(14, {0, 1, 3, 3}), 0u);
718    ASSERT_EQ(compute_pixel_shift_extra_width(15, {0, 1, 3, 3}), 1u);
719    ASSERT_EQ(compute_pixel_shift_extra_width(16, {0, 1, 3, 3}), 0u);
720    ASSERT_EQ(compute_pixel_shift_extra_width(17, {0, 1, 3, 3}), 0u);
721    ASSERT_EQ(compute_pixel_shift_extra_width(18, {0, 1, 3, 3}), 0u);
722    ASSERT_EQ(compute_pixel_shift_extra_width(19, {0, 1, 3, 3}), 1u);
723
724    ASSERT_EQ(compute_pixel_shift_extra_width(12, {0, 1, 4, 3}), 2u);
725    ASSERT_EQ(compute_pixel_shift_extra_width(13, {0, 1, 4, 3}), 0u);
726    ASSERT_EQ(compute_pixel_shift_extra_width(14, {0, 1, 4, 3}), 0u);
727    ASSERT_EQ(compute_pixel_shift_extra_width(15, {0, 1, 4, 3}), 1u);
728    ASSERT_EQ(compute_pixel_shift_extra_width(16, {0, 1, 4, 3}), 2u);
729    ASSERT_EQ(compute_pixel_shift_extra_width(17, {0, 1, 4, 3}), 0u);
730    ASSERT_EQ(compute_pixel_shift_extra_width(18, {0, 1, 4, 3}), 0u);
731    ASSERT_EQ(compute_pixel_shift_extra_width(19, {0, 1, 4, 3}), 1u);
732
733    ASSERT_EQ(compute_pixel_shift_extra_width(12, {0, 1, 5, 3}), 2u);
734    ASSERT_EQ(compute_pixel_shift_extra_width(13, {0, 1, 5, 3}), 3u);
735    ASSERT_EQ(compute_pixel_shift_extra_width(14, {0, 1, 5, 3}), 0u);
736    ASSERT_EQ(compute_pixel_shift_extra_width(15, {0, 1, 5, 3}), 1u);
737    ASSERT_EQ(compute_pixel_shift_extra_width(16, {0, 1, 5, 3}), 2u);
738    ASSERT_EQ(compute_pixel_shift_extra_width(17, {0, 1, 5, 3}), 3u);
739    ASSERT_EQ(compute_pixel_shift_extra_width(18, {0, 1, 5, 3}), 0u);
740    ASSERT_EQ(compute_pixel_shift_extra_width(19, {0, 1, 5, 3}), 1u);
741
742    ASSERT_EQ(compute_pixel_shift_extra_width(12, {0, 1, 9, 3}), 6u);
743    ASSERT_EQ(compute_pixel_shift_extra_width(13, {0, 1, 9, 3}), 7u);
744    ASSERT_EQ(compute_pixel_shift_extra_width(14, {0, 1, 9, 3}), 4u);
745    ASSERT_EQ(compute_pixel_shift_extra_width(15, {0, 1, 9, 3}), 5u);
746    ASSERT_EQ(compute_pixel_shift_extra_width(16, {0, 1, 9, 3}), 6u);
747    ASSERT_EQ(compute_pixel_shift_extra_width(17, {0, 1, 9, 3}), 7u);
748    ASSERT_EQ(compute_pixel_shift_extra_width(18, {0, 1, 9, 3}), 4u);
749    ASSERT_EQ(compute_pixel_shift_extra_width(19, {0, 1, 9, 3}), 5u);
750}
751
752void test_node_pixel_shift_columns_no_switch()
753{
754    using Data = std::vector<std::uint8_t>;
755
756    Data in_data = {
757        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
758        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
759    };
760
761    ImagePipelineStack stack;
762    stack.push_first_node<ImagePipelineNodeArraySource>(12, 2, PixelFormat::I8, in_data);
763    stack.push_node<ImagePipelineNodePixelShiftColumns>(std::vector<std::size_t>{0, 1, 2, 3});
764
765    ASSERT_EQ(stack.get_output_width(), 12u);
766    ASSERT_EQ(stack.get_output_height(), 2u);
767    ASSERT_EQ(stack.get_output_row_bytes(), 12u);
768    ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
769
770    auto out_data = stack.get_all_data();
771
772    ASSERT_EQ(out_data, in_data);
773}
774
775void test_node_pixel_shift_columns_group_switch_pixel_multiple()
776{
777    using Data = std::vector<std::uint8_t>;
778
779    Data in_data = {
780        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
781        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
782    };
783
784    ImagePipelineStack stack;
785    stack.push_first_node<ImagePipelineNodeArraySource>(12, 2, PixelFormat::I8, in_data);
786    stack.push_node<ImagePipelineNodePixelShiftColumns>(std::vector<std::size_t>{3, 1, 2, 0});
787
788    ASSERT_EQ(stack.get_output_width(), 12u);
789    ASSERT_EQ(stack.get_output_height(), 2u);
790    ASSERT_EQ(stack.get_output_row_bytes(), 12u);
791    ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
792
793    auto out_data = stack.get_all_data();
794
795    Data expected_data = {
796        0x03, 0x01, 0x02, 0x00, 0x07, 0x05, 0x06, 0x04, 0x0b, 0x09, 0x0a, 0x08,
797        0x13, 0x11, 0x12, 0x10, 0x17, 0x15, 0x16, 0x14, 0x1b, 0x19, 0x1a, 0x18,
798    };
799    ASSERT_EQ(out_data, expected_data);
800}
801
802void test_node_pixel_shift_columns_group_switch_pixel_not_multiple()
803{
804    using Data = std::vector<std::uint8_t>;
805
806    Data in_data = {
807        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
808        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
809    };
810
811    ImagePipelineStack stack;
812    stack.push_first_node<ImagePipelineNodeArraySource>(13, 2, PixelFormat::I8, in_data);
813    stack.push_node<ImagePipelineNodePixelShiftColumns>(std::vector<std::size_t>{3, 1, 2, 0});
814
815    ASSERT_EQ(stack.get_output_width(), 12u);
816    ASSERT_EQ(stack.get_output_height(), 2u);
817    ASSERT_EQ(stack.get_output_row_bytes(), 12u);
818    ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
819
820    auto out_data = stack.get_all_data();
821
822    Data expected_data = {
823        0x03, 0x01, 0x02, 0x00, 0x07, 0x05, 0x06, 0x04, 0x0b, 0x09, 0x0a, 0x08,
824        0x13, 0x11, 0x12, 0x10, 0x17, 0x15, 0x16, 0x14, 0x1b, 0x19, 0x1a, 0x18,
825    };
826    ASSERT_EQ(out_data, expected_data);
827}
828
829void test_node_pixel_shift_columns_group_switch_pixel_large_offsets_multiple()
830{
831    using Data = std::vector<std::uint8_t>;
832
833    Data in_data = {
834        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
835        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
836    };
837
838    ImagePipelineStack stack;
839    stack.push_first_node<ImagePipelineNodeArraySource>(12, 2, PixelFormat::I8, in_data);
840    stack.push_node<ImagePipelineNodePixelShiftColumns>(std::vector<std::size_t>{7, 1, 5, 0});
841
842    ASSERT_EQ(stack.get_output_width(), 8u);
843    ASSERT_EQ(stack.get_output_height(), 2u);
844    ASSERT_EQ(stack.get_output_row_bytes(), 8u);
845    ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
846
847    auto out_data = stack.get_all_data();
848
849    Data expected_data = {
850        0x07, 0x01, 0x05, 0x00, 0x0b, 0x05, 0x09, 0x04,
851        0x17, 0x11, 0x15, 0x10, 0x1b, 0x15, 0x19, 0x14,
852    };
853    ASSERT_EQ(out_data, expected_data);
854}
855
856void test_node_pixel_shift_columns_group_switch_pixel_large_offsets_not_multiple()
857{
858    using Data = std::vector<std::uint8_t>;
859
860    Data in_data = {
861        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
862        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
863    };
864
865    ImagePipelineStack stack;
866    stack.push_first_node<ImagePipelineNodeArraySource>(13, 2, PixelFormat::I8, in_data);
867    stack.push_node<ImagePipelineNodePixelShiftColumns>(std::vector<std::size_t>{7, 1, 5, 0});
868
869    ASSERT_EQ(stack.get_output_width(), 8u);
870    ASSERT_EQ(stack.get_output_height(), 2u);
871    ASSERT_EQ(stack.get_output_row_bytes(), 8u);
872    ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
873
874    auto out_data = stack.get_all_data();
875
876    Data expected_data = {
877        0x07, 0x01, 0x05, 0x00, 0x0b, 0x05, 0x09, 0x04,
878        0x17, 0x11, 0x15, 0x10, 0x1b, 0x15, 0x19, 0x14,
879    };
880    ASSERT_EQ(out_data, expected_data);
881}
882
883void test_node_calibrate_8bit()
884{
885    using Data = std::vector<std::uint8_t>;
886
887    Data in_data = {
888        0x20, 0x38, 0x38
889    };
890
891    std::vector<std::uint16_t> bottom = {
892        0x1000, 0x2000, 0x3000
893    };
894
895    std::vector<std::uint16_t> top = {
896        0x3000, 0x4000, 0x5000
897    };
898
899    ImagePipelineStack stack;
900    stack.push_first_node<ImagePipelineNodeArraySource>(1, 1, PixelFormat::RGB888,
901                                                        std::move(in_data));
902    stack.push_node<ImagePipelineNodeCalibrate>(bottom, top, 0);
903
904    ASSERT_EQ(stack.get_output_width(), 1u);
905    ASSERT_EQ(stack.get_output_height(), 1u);
906    ASSERT_EQ(stack.get_output_row_bytes(), 3u);
907    ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888);
908
909    auto out_data = stack.get_all_data();
910
911    Data expected_data = {
912        // note that we don't handle rounding properly in the implementation
913        0x80, 0xc1, 0x41
914    };
915
916    ASSERT_EQ(out_data, expected_data);
917}
918
919void test_node_calibrate_16bit()
920{
921    using Data = std::vector<std::uint8_t>;
922
923    Data in_data = {
924        0x00, 0x20, 0x00, 0x38, 0x00, 0x38
925    };
926
927    std::vector<std::uint16_t> bottom = {
928        0x1000, 0x2000, 0x3000
929    };
930
931    std::vector<std::uint16_t> top = {
932        0x3000, 0x4000, 0x5000
933    };
934
935    ImagePipelineStack stack;
936    stack.push_first_node<ImagePipelineNodeArraySource>(1, 1, PixelFormat::RGB161616,
937                                                        std::move(in_data));
938    stack.push_node<ImagePipelineNodeCalibrate>(bottom, top, 0);
939
940    ASSERT_EQ(stack.get_output_width(), 1u);
941    ASSERT_EQ(stack.get_output_height(), 1u);
942    ASSERT_EQ(stack.get_output_row_bytes(), 6u);
943    ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB161616);
944
945    auto out_data = stack.get_all_data();
946
947    Data expected_data = {
948        // note that we don't handle rounding properly in the implementation
949        0x00, 0x80, 0xff, 0xbf, 0x00, 0x40
950    };
951
952    ASSERT_EQ(out_data, expected_data);
953}
954
955void test_image_pipeline()
956{
957    test_image_buffer_exact_reads();
958    test_image_buffer_smaller_reads();
959    test_image_buffer_larger_reads();
960    test_image_buffer_uncapped_remaining_bytes();
961    test_image_buffer_capped_remaining_bytes();
962    test_node_buffered_callable_source();
963    test_node_format_convert();
964    test_node_desegment_1_line();
965    test_node_deinterleave_lines_i8();
966    test_node_deinterleave_lines_rgb888();
967    test_node_swap_16bit_endian();
968    test_node_invert_16_bits();
969    test_node_invert_8_bits();
970    test_node_invert_1_bits();
971    test_node_merge_mono_lines_to_color();
972    test_node_merge_color_to_gray();
973    test_node_split_mono_lines();
974    test_node_component_shift_lines();
975    test_node_pixel_shift_columns_no_switch();
976    test_node_pixel_shift_columns_group_switch_pixel_multiple();
977    test_node_pixel_shift_columns_group_switch_pixel_not_multiple();
978    test_node_pixel_shift_columns_group_switch_pixel_large_offsets_multiple();
979    test_node_pixel_shift_columns_group_switch_pixel_large_offsets_not_multiple();
980    test_node_pixel_shift_lines_2lines();
981    test_node_pixel_shift_lines_4lines();
982    test_node_pixel_shift_columns_compute_max_width();
983    test_node_calibrate_8bit();
984    test_node_calibrate_16bit();
985}
986
987} // namespace genesys
988