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 "image_pipeline.h"
24 #include "image.h"
25 #include "low.h"
26 #include <cmath>
27 #include <numeric>
28 
29 namespace genesys {
30 
~ImagePipelineNode()31 ImagePipelineNode::~ImagePipelineNode() {}
32 
get_next_row_data(std::uint8_t* out_data)33 bool ImagePipelineNodeCallableSource::get_next_row_data(std::uint8_t* out_data)
34 {
35     bool got_data = producer_(get_row_bytes(), out_data);
36     if (!got_data)
37         eof_ = true;
38     return got_data;
39 }
40 
ImagePipelineNodeBufferedCallableSource( std::size_t width, std::size_t height, PixelFormat format, std::size_t input_batch_size, ProducerCallback producer)41 ImagePipelineNodeBufferedCallableSource::ImagePipelineNodeBufferedCallableSource(
42         std::size_t width, std::size_t height, PixelFormat format, std::size_t input_batch_size,
43         ProducerCallback producer) :
44     width_{width},
45     height_{height},
46     format_{format},
47     buffer_{input_batch_size, producer}
48 {
49     buffer_.set_remaining_size(height_ * get_row_bytes());
50 }
51 
get_next_row_data(std::uint8_t* out_data)52 bool ImagePipelineNodeBufferedCallableSource::get_next_row_data(std::uint8_t* out_data)
53 {
54     if (curr_row_ >= get_height()) {
55         DBG(DBG_warn, "%s: reading out of bounds. Row %zu, height: %zu\n", __func__,
56             curr_row_, get_height());
57         eof_ = true;
58         return false;
59     }
60 
61     bool got_data = true;
62 
63     got_data &= buffer_.get_data(get_row_bytes(), out_data);
64     curr_row_++;
65     if (!got_data) {
66         eof_ = true;
67     }
68     return got_data;
69 }
70 
ImagePipelineNodeArraySource(std::size_t width, std::size_t height, PixelFormat format, std::vector<std::uint8_t> data)71 ImagePipelineNodeArraySource::ImagePipelineNodeArraySource(std::size_t width, std::size_t height,
72                                                            PixelFormat format,
73                                                            std::vector<std::uint8_t> data) :
74     width_{width},
75     height_{height},
76     format_{format},
77     data_{std::move(data)},
78     next_row_{0}
79 {
80     auto size = get_row_bytes() * height_;
81     if (data_.size() < size) {
82         throw SaneException("The given array is too small (%zu bytes). Need at least %zu",
83                             data_.size(), size);
84     }
85 }
86 
get_next_row_data(std::uint8_t* out_data)87 bool ImagePipelineNodeArraySource::get_next_row_data(std::uint8_t* out_data)
88 {
89     if (next_row_ >= height_) {
90         eof_ = true;
91         return false;
92     }
93 
94     auto row_bytes = get_row_bytes();
95     std::memcpy(out_data, data_.data() + row_bytes * next_row_, row_bytes);
96     next_row_++;
97 
98     return true;
99 }
100 
101 
ImagePipelineNodeImageSource(const Image& source)102 ImagePipelineNodeImageSource::ImagePipelineNodeImageSource(const Image& source) :
103     source_{source}
104 {}
105 
get_next_row_data(std::uint8_t* out_data)106 bool ImagePipelineNodeImageSource::get_next_row_data(std::uint8_t* out_data)
107 {
108     if (next_row_ >= get_height()) {
109         return false;
110     }
111     std::memcpy(out_data, source_.get_row_ptr(next_row_), get_row_bytes());
112     next_row_++;
113     return true;
114 }
115 
get_next_row_data(std::uint8_t* out_data)116 bool ImagePipelineNodeFormatConvert::get_next_row_data(std::uint8_t* out_data)
117 {
118     auto src_format = source_.get_format();
119     if (src_format == dst_format_) {
120         return source_.get_next_row_data(out_data);
121     }
122 
123     buffer_.clear();
124     buffer_.resize(source_.get_row_bytes());
125     bool got_data = source_.get_next_row_data(buffer_.data());
126 
127     convert_pixel_row_format(buffer_.data(), src_format, out_data, dst_format_, get_width());
128     return got_data;
129 }
130 
ImagePipelineNodeDesegment(ImagePipelineNode& source, std::size_t output_width, const std::vector<unsigned>& segment_order, std::size_t segment_pixels, std::size_t interleaved_lines, std::size_t pixels_per_chunk)131 ImagePipelineNodeDesegment::ImagePipelineNodeDesegment(ImagePipelineNode& source,
132                                                        std::size_t output_width,
133                                                        const std::vector<unsigned>& segment_order,
134                                                        std::size_t segment_pixels,
135                                                        std::size_t interleaved_lines,
136                                                        std::size_t pixels_per_chunk) :
137     source_(source),
138     output_width_{output_width},
139     segment_order_{segment_order},
140     segment_pixels_{segment_pixels},
141     interleaved_lines_{interleaved_lines},
142     pixels_per_chunk_{pixels_per_chunk},
143     buffer_{source_.get_row_bytes()}
144 {
145     DBG_HELPER_ARGS(dbg, "segment_count=%zu, segment_size=%zu, interleaved_lines=%zu, "
146                          "pixels_per_shunk=%zu", segment_order.size(), segment_pixels,
147                     interleaved_lines, pixels_per_chunk);
148 
149     if (source_.get_height() % interleaved_lines_ > 0) {
150         throw SaneException("Height is not a multiple of the number of lines to interelave %zu/%zu",
151                             source_.get_height(), interleaved_lines_);
152     }
153 }
154 
ImagePipelineNodeDesegment(ImagePipelineNode& source, std::size_t output_width, std::size_t segment_count, std::size_t segment_pixels, std::size_t interleaved_lines, std::size_t pixels_per_chunk)155 ImagePipelineNodeDesegment::ImagePipelineNodeDesegment(ImagePipelineNode& source,
156                                                        std::size_t output_width,
157                                                        std::size_t segment_count,
158                                                        std::size_t segment_pixels,
159                                                        std::size_t interleaved_lines,
160                                                        std::size_t pixels_per_chunk) :
161     source_(source),
162     output_width_{output_width},
163     segment_pixels_{segment_pixels},
164     interleaved_lines_{interleaved_lines},
165     pixels_per_chunk_{pixels_per_chunk},
166     buffer_{source_.get_row_bytes()}
167 {
168     DBG_HELPER_ARGS(dbg, "segment_count=%zu, segment_size=%zu, interleaved_lines=%zu, "
169                     "pixels_per_shunk=%zu", segment_count, segment_pixels, interleaved_lines,
170                     pixels_per_chunk);
171 
172     segment_order_.resize(segment_count);
173     std::iota(segment_order_.begin(), segment_order_.end(), 0);
174 }
175 
get_next_row_data(std::uint8_t* out_data)176 bool ImagePipelineNodeDesegment::get_next_row_data(std::uint8_t* out_data)
177 {
178     bool got_data = true;
179 
180     buffer_.clear();
181     for (std::size_t i = 0; i < interleaved_lines_; ++i) {
182         buffer_.push_back();
183         got_data &= source_.get_next_row_data(buffer_.get_row_ptr(i));
184     }
185     if (!buffer_.is_linear()) {
186         throw SaneException("Buffer is not linear");
187     }
188 
189     auto format = get_format();
190     auto segment_count = segment_order_.size();
191 
192     const std::uint8_t* in_data = buffer_.get_row_ptr(0);
193 
194     std::size_t groups_count = output_width_ / (segment_order_.size() * pixels_per_chunk_);
195 
196     for (std::size_t igroup = 0; igroup < groups_count; ++igroup) {
197         for (std::size_t isegment = 0; isegment < segment_count; ++isegment) {
198             auto input_offset = igroup * pixels_per_chunk_;
199             input_offset += segment_pixels_ * segment_order_[isegment];
200             auto output_offset = (igroup * segment_count + isegment) * pixels_per_chunk_;
201 
202             for (std::size_t ipixel = 0; ipixel < pixels_per_chunk_; ++ipixel) {
203                 auto pixel = get_raw_pixel_from_row(in_data, input_offset + ipixel, format);
204                 set_raw_pixel_to_row(out_data, output_offset + ipixel, pixel, format);
205             }
206         }
207     }
208     return got_data;
209 }
210 
ImagePipelineNodeDeinterleaveLines( ImagePipelineNode& source, std::size_t interleaved_lines, std::size_t pixels_per_chunk)211 ImagePipelineNodeDeinterleaveLines::ImagePipelineNodeDeinterleaveLines(
212         ImagePipelineNode& source, std::size_t interleaved_lines, std::size_t pixels_per_chunk) :
213     ImagePipelineNodeDesegment(source, source.get_width() * interleaved_lines,
214                                interleaved_lines, source.get_width(),
215                                interleaved_lines, pixels_per_chunk)
216 {}
217 
ImagePipelineNodeSwap16BitEndian(ImagePipelineNode& source)218 ImagePipelineNodeSwap16BitEndian::ImagePipelineNodeSwap16BitEndian(ImagePipelineNode& source) :
219     source_(source),
220     needs_swapping_{false}
221 {
222     if (get_pixel_format_depth(source_.get_format()) == 16) {
223         needs_swapping_ = true;
224     } else {
225         DBG(DBG_info, "%s: this pipeline node does nothing for non 16-bit formats", __func__);
226     }
227 }
228 
get_next_row_data(std::uint8_t* out_data)229 bool ImagePipelineNodeSwap16BitEndian::get_next_row_data(std::uint8_t* out_data)
230 {
231     bool got_data = source_.get_next_row_data(out_data);
232     if (needs_swapping_) {
233         std::size_t pixels = get_row_bytes() / 2;
234         for (std::size_t i = 0; i < pixels; ++i) {
235             std::swap(*out_data, *(out_data + 1));
236             out_data += 2;
237         }
238     }
239     return got_data;
240 }
241 
ImagePipelineNodeInvert(ImagePipelineNode& source)242 ImagePipelineNodeInvert::ImagePipelineNodeInvert(ImagePipelineNode& source) :
243     source_(source)
244 {
245 }
246 
get_next_row_data(std::uint8_t* out_data)247 bool ImagePipelineNodeInvert::get_next_row_data(std::uint8_t* out_data)
248 {
249     bool got_data = source_.get_next_row_data(out_data);
250     auto num_values = get_width() * get_pixel_channels(source_.get_format());
251     auto depth = get_pixel_format_depth(source_.get_format());
252 
253     switch (depth) {
254         case 16: {
255             auto* data = reinterpret_cast<std::uint16_t*>(out_data);
256             for (std::size_t i = 0; i < num_values; ++i) {
257                 *data = 0xffff - *data;
258                 data++;
259             }
260             break;
261         }
262         case 8: {
263             auto* data = out_data;
264             for (std::size_t i = 0; i < num_values; ++i) {
265                 *data = 0xff - *data;
266                 data++;
267             }
268             break;
269         }
270         case 1: {
271             auto* data = out_data;
272             auto num_bytes = (num_values + 7) / 8;
273             for (std::size_t i = 0; i < num_bytes; ++i) {
274                 *data = ~*data;
275                 data++;
276             }
277             break;
278         }
279         default:
280             throw SaneException("Unsupported pixel depth");
281     }
282 
283     return got_data;
284 }
285 
ImagePipelineNodeMergeMonoLinesToColor( ImagePipelineNode& source, ColorOrder color_order)286 ImagePipelineNodeMergeMonoLinesToColor::ImagePipelineNodeMergeMonoLinesToColor(
287         ImagePipelineNode& source, ColorOrder color_order) :
288     source_(source),
289     buffer_(source_.get_row_bytes())
290 {
291     DBG_HELPER_ARGS(dbg, "color_order %d", static_cast<unsigned>(color_order));
292 
293     output_format_ = get_output_format(source_.get_format(), color_order);
294 }
295 
get_next_row_data(std::uint8_t* out_data)296 bool ImagePipelineNodeMergeMonoLinesToColor::get_next_row_data(std::uint8_t* out_data)
297 {
298     bool got_data = true;
299 
300     buffer_.clear();
301     for (unsigned i = 0; i < 3; ++i) {
302         buffer_.push_back();
303         got_data &= source_.get_next_row_data(buffer_.get_row_ptr(i));
304     }
305 
306     const auto* row0 = buffer_.get_row_ptr(0);
307     const auto* row1 = buffer_.get_row_ptr(1);
308     const auto* row2 = buffer_.get_row_ptr(2);
309 
310     auto format = source_.get_format();
311 
312     for (std::size_t x = 0, width = get_width(); x < width; ++x) {
313         std::uint16_t ch0 = get_raw_channel_from_row(row0, x, 0, format);
314         std::uint16_t ch1 = get_raw_channel_from_row(row1, x, 0, format);
315         std::uint16_t ch2 = get_raw_channel_from_row(row2, x, 0, format);
316         set_raw_channel_to_row(out_data, x, 0, ch0, output_format_);
317         set_raw_channel_to_row(out_data, x, 1, ch1, output_format_);
318         set_raw_channel_to_row(out_data, x, 2, ch2, output_format_);
319     }
320     return got_data;
321 }
322 
get_output_format(PixelFormat input_format, ColorOrder order)323 PixelFormat ImagePipelineNodeMergeMonoLinesToColor::get_output_format(PixelFormat input_format,
324                                                                       ColorOrder order)
325 {
326     switch (input_format) {
327         case PixelFormat::I1: {
328             if (order == ColorOrder::RGB) {
329                 return PixelFormat::RGB111;
330             }
331             break;
332         }
333         case PixelFormat::I8: {
334             if (order == ColorOrder::RGB) {
335                 return PixelFormat::RGB888;
336             }
337             if (order == ColorOrder::BGR) {
338                 return PixelFormat::BGR888;
339             }
340             break;
341         }
342         case PixelFormat::I16: {
343             if (order == ColorOrder::RGB) {
344                 return PixelFormat::RGB161616;
345             }
346             if (order == ColorOrder::BGR) {
347                 return PixelFormat::BGR161616;
348             }
349             break;
350         }
351         default: break;
352     }
353     throw SaneException("Unsupported format combidation %d %d",
354                         static_cast<unsigned>(input_format),
355                         static_cast<unsigned>(order));
356 }
357 
ImagePipelineNodeSplitMonoLines(ImagePipelineNode& source)358 ImagePipelineNodeSplitMonoLines::ImagePipelineNodeSplitMonoLines(ImagePipelineNode& source) :
359     source_(source),
360     next_channel_{0}
361 {
362     output_format_ = get_output_format(source_.get_format());
363 }
364 
get_next_row_data(std::uint8_t* out_data)365 bool ImagePipelineNodeSplitMonoLines::get_next_row_data(std::uint8_t* out_data)
366 {
367     bool got_data = true;
368 
369     if (next_channel_ == 0) {
370         buffer_.resize(source_.get_row_bytes());
371         got_data &= source_.get_next_row_data(buffer_.data());
372     }
373 
374     const auto* row = buffer_.data();
375     auto format = source_.get_format();
376 
377     for (std::size_t x = 0, width = get_width(); x < width; ++x) {
378         std::uint16_t ch = get_raw_channel_from_row(row, x, next_channel_, format);
379         set_raw_channel_to_row(out_data, x, 0, ch, output_format_);
380     }
381     next_channel_ = (next_channel_ + 1) % 3;
382 
383     return got_data;
384 }
385 
get_output_format(PixelFormat input_format)386 PixelFormat ImagePipelineNodeSplitMonoLines::get_output_format(PixelFormat input_format)
387 {
388     switch (input_format) {
389         case PixelFormat::RGB111: return PixelFormat::I1;
390         case PixelFormat::RGB888:
391         case PixelFormat::BGR888: return PixelFormat::I8;
392         case PixelFormat::RGB161616:
393         case PixelFormat::BGR161616: return PixelFormat::I16;
394         default: break;
395     }
396     throw SaneException("Unsupported input format %d", static_cast<unsigned>(input_format));
397 }
398 
399 
ImagePipelineNodeMergeColorToGray(ImagePipelineNode& source)400 ImagePipelineNodeMergeColorToGray::ImagePipelineNodeMergeColorToGray(ImagePipelineNode& source) :
401     source_(source)
402 {
403 
404     output_format_ = get_output_format(source_.get_format());
405     float red_mult = 0.2125f;
406     float green_mult = 0.7154f;
407     float blue_mult = 0.0721f;
408 
409     switch (get_pixel_format_color_order(source_.get_format())) {
410         case ColorOrder::RGB: {
411             ch0_mult_ = red_mult;
412             ch1_mult_ = green_mult;
413             ch2_mult_ = blue_mult;
414             break;
415         }
416         case ColorOrder::BGR: {
417             ch0_mult_ = blue_mult;
418             ch1_mult_ = green_mult;
419             ch2_mult_ = red_mult;
420             break;
421         }
422         case ColorOrder::GBR: {
423             ch0_mult_ = green_mult;
424             ch1_mult_ = blue_mult;
425             ch2_mult_ = red_mult;
426             break;
427         }
428         default:
429             throw SaneException("Unknown color order");
430     }
431     temp_buffer_.resize(source_.get_row_bytes());
432 }
433 
get_next_row_data(std::uint8_t* out_data)434 bool ImagePipelineNodeMergeColorToGray::get_next_row_data(std::uint8_t* out_data)
435 {
436     auto* src_data = temp_buffer_.data();
437 
438     bool got_data = source_.get_next_row_data(src_data);
439 
440     auto src_format = source_.get_format();
441 
442     for (std::size_t x = 0, width = get_width(); x < width; ++x) {
443         std::uint16_t ch0 = get_raw_channel_from_row(src_data, x, 0, src_format);
444         std::uint16_t ch1 = get_raw_channel_from_row(src_data, x, 1, src_format);
445         std::uint16_t ch2 = get_raw_channel_from_row(src_data, x, 2, src_format);
446         float mono = ch0 * ch0_mult_ + ch1 * ch1_mult_ + ch2 * ch2_mult_;
447         set_raw_channel_to_row(out_data, x, 0, static_cast<std::uint16_t>(mono), output_format_);
448     }
449     return got_data;
450 }
451 
get_output_format(PixelFormat input_format)452 PixelFormat ImagePipelineNodeMergeColorToGray::get_output_format(PixelFormat input_format)
453 {
454     switch (input_format) {
455         case PixelFormat::RGB111:
456             return PixelFormat::I1;
457         case PixelFormat::RGB888:
458         case PixelFormat::BGR888:
459             return PixelFormat::I8;
460         case PixelFormat::RGB161616:
461         case PixelFormat::BGR161616:
462             return PixelFormat::I16;
463         default: break;
464     }
465     throw SaneException("Unsupported format %d", static_cast<unsigned>(input_format));
466 }
467 
ImagePipelineNodeComponentShiftLines( ImagePipelineNode& source, unsigned shift_r, unsigned shift_g, unsigned shift_b)468 ImagePipelineNodeComponentShiftLines::ImagePipelineNodeComponentShiftLines(
469         ImagePipelineNode& source, unsigned shift_r, unsigned shift_g, unsigned shift_b) :
470     source_(source),
471     buffer_{source.get_row_bytes()}
472 {
473     DBG_HELPER_ARGS(dbg, "shifts={%d, %d, %d}", shift_r, shift_g, shift_b);
474 
475     switch (source.get_format()) {
476         case PixelFormat::RGB111:
477         case PixelFormat::RGB888:
478         case PixelFormat::RGB161616: {
479             channel_shifts_ = { shift_r, shift_g, shift_b };
480             break;
481         }
482         case PixelFormat::BGR888:
483         case PixelFormat::BGR161616: {
484             channel_shifts_ = { shift_b, shift_g, shift_r };
485             break;
486         }
487         default:
488             throw SaneException("Unsupported input format %d",
489                                 static_cast<unsigned>(source.get_format()));
490     }
491     extra_height_ = *std::max_element(channel_shifts_.begin(), channel_shifts_.end());
492     height_ = source_.get_height();
493     if (extra_height_ > height_) {
494         height_ = 0;
495     } else {
496         height_ -= extra_height_;
497     }
498 }
499 
get_next_row_data(std::uint8_t* out_data)500 bool ImagePipelineNodeComponentShiftLines::get_next_row_data(std::uint8_t* out_data)
501 {
502     bool got_data = true;
503 
504     if (!buffer_.empty()) {
505         buffer_.pop_front();
506     }
507     while (buffer_.height() < extra_height_ + 1) {
508         buffer_.push_back();
509         got_data &= source_.get_next_row_data(buffer_.get_back_row_ptr());
510     }
511 
512     auto format = get_format();
513     const auto* row0 = buffer_.get_row_ptr(channel_shifts_[0]);
514     const auto* row1 = buffer_.get_row_ptr(channel_shifts_[1]);
515     const auto* row2 = buffer_.get_row_ptr(channel_shifts_[2]);
516 
517     for (std::size_t x = 0, width = get_width(); x < width; ++x) {
518         std::uint16_t ch0 = get_raw_channel_from_row(row0, x, 0, format);
519         std::uint16_t ch1 = get_raw_channel_from_row(row1, x, 1, format);
520         std::uint16_t ch2 = get_raw_channel_from_row(row2, x, 2, format);
521         set_raw_channel_to_row(out_data, x, 0, ch0, format);
522         set_raw_channel_to_row(out_data, x, 1, ch1, format);
523         set_raw_channel_to_row(out_data, x, 2, ch2, format);
524     }
525     return got_data;
526 }
527 
ImagePipelineNodePixelShiftLines( ImagePipelineNode& source, const std::vector<std::size_t>& shifts)528 ImagePipelineNodePixelShiftLines::ImagePipelineNodePixelShiftLines(
529         ImagePipelineNode& source, const std::vector<std::size_t>& shifts) :
530     source_(source),
531     pixel_shifts_{shifts},
532     buffer_{get_row_bytes()}
533 {
534     extra_height_ = *std::max_element(pixel_shifts_.begin(), pixel_shifts_.end());
535     height_ = source_.get_height();
536     if (extra_height_ > height_) {
537         height_ = 0;
538     } else {
539         height_ -= extra_height_;
540     }
541 }
542 
get_next_row_data(std::uint8_t* out_data)543 bool ImagePipelineNodePixelShiftLines::get_next_row_data(std::uint8_t* out_data)
544 {
545     bool got_data = true;
546 
547     if (!buffer_.empty()) {
548         buffer_.pop_front();
549     }
550     while (buffer_.height() < extra_height_ + 1) {
551         buffer_.push_back();
552         got_data &= source_.get_next_row_data(buffer_.get_back_row_ptr());
553     }
554 
555     auto format = get_format();
556     auto shift_count = pixel_shifts_.size();
557 
558     std::vector<std::uint8_t*> rows;
559     rows.resize(shift_count, nullptr);
560 
561     for (std::size_t irow = 0; irow < shift_count; ++irow) {
562         rows[irow] = buffer_.get_row_ptr(pixel_shifts_[irow]);
563     }
564 
565     for (std::size_t x = 0, width = get_width(); x < width;) {
566         for (std::size_t irow = 0; irow < shift_count && x < width; irow++, x++) {
567             RawPixel pixel = get_raw_pixel_from_row(rows[irow], x, format);
568             set_raw_pixel_to_row(out_data, x, pixel, format);
569         }
570     }
571     return got_data;
572 }
573 
ImagePipelineNodePixelShiftColumns( ImagePipelineNode& source, const std::vector<std::size_t>& shifts)574 ImagePipelineNodePixelShiftColumns::ImagePipelineNodePixelShiftColumns(
575         ImagePipelineNode& source, const std::vector<std::size_t>& shifts) :
576     source_(source),
577     pixel_shifts_{shifts}
578 {
579     width_ = source_.get_width();
580     extra_width_ = compute_pixel_shift_extra_width(width_, pixel_shifts_);
581     if (extra_width_ > width_) {
582         width_ = 0;
583     } else {
584         width_ -= extra_width_;
585     }
586     temp_buffer_.resize(source_.get_row_bytes());
587 }
588 
get_next_row_data(std::uint8_t* out_data)589 bool ImagePipelineNodePixelShiftColumns::get_next_row_data(std::uint8_t* out_data)
590 {
591     if (width_ == 0) {
592         throw SaneException("Attempt to read zero-width line");
593     }
594     bool got_data = source_.get_next_row_data(temp_buffer_.data());
595 
596     auto format = get_format();
597     auto shift_count = pixel_shifts_.size();
598 
599     for (std::size_t x = 0, width = get_width(); x < width; x += shift_count) {
600         for (std::size_t ishift = 0; ishift < shift_count && x + ishift < width; ishift++) {
601             RawPixel pixel = get_raw_pixel_from_row(temp_buffer_.data(), x + pixel_shifts_[ishift],
602                                                     format);
603             set_raw_pixel_to_row(out_data, x + ishift, pixel, format);
604         }
605     }
606     return got_data;
607 }
608 
609 
compute_pixel_shift_extra_width(std::size_t source_width, const std::vector<std::size_t>& shifts)610 std::size_t compute_pixel_shift_extra_width(std::size_t source_width,
611                                             const std::vector<std::size_t>& shifts)
612 {
613     // we iterate across pixel shifts and find the pixel that needs the maximum shift according to
614     // source_width.
615     int group_size = shifts.size();
616     int non_filled_group = source_width % shifts.size();
617     int extra_width = 0;
618 
619     for (int i = 0; i < group_size; ++i) {
620         int shift_groups = shifts[i] / group_size;
621         int shift_rem = shifts[i] % group_size;
622 
623         if (shift_rem < non_filled_group) {
624             shift_groups--;
625         }
626         extra_width = std::max(extra_width, shift_groups * group_size + non_filled_group - i);
627     }
628     return extra_width;
629 }
630 
ImagePipelineNodeExtract(ImagePipelineNode& source, std::size_t offset_x, std::size_t offset_y, std::size_t width, std::size_t height)631 ImagePipelineNodeExtract::ImagePipelineNodeExtract(ImagePipelineNode& source,
632                                                    std::size_t offset_x, std::size_t offset_y,
633                                                    std::size_t width, std::size_t height) :
634     source_(source),
635     offset_x_{offset_x},
636     offset_y_{offset_y},
637     width_{width},
638     height_{height}
639 {
640     cached_line_.resize(source_.get_row_bytes());
641 }
642 
~ImagePipelineNodeExtract()643 ImagePipelineNodeExtract::~ImagePipelineNodeExtract() {}
644 
ImagePipelineNodeScaleRows(ImagePipelineNode& source, std::size_t width)645 ImagePipelineNodeScaleRows::ImagePipelineNodeScaleRows(ImagePipelineNode& source,
646                                                        std::size_t width) :
647     source_(source),
648     width_{width}
649 {
650     cached_line_.resize(source_.get_row_bytes());
651 }
652 
get_next_row_data(std::uint8_t* out_data)653 bool ImagePipelineNodeScaleRows::get_next_row_data(std::uint8_t* out_data)
654 {
655     auto src_width = source_.get_width();
656     auto dst_width = width_;
657 
658     bool got_data = source_.get_next_row_data(cached_line_.data());
659 
660     const auto* src_data = cached_line_.data();
661     auto format = get_format();
662     auto channels = get_pixel_channels(format);
663 
664     if (src_width > dst_width) {
665         // average
666         std::uint32_t counter = src_width / 2;
667         unsigned src_x = 0;
668         for (unsigned dst_x = 0; dst_x < dst_width; dst_x++) {
669             unsigned avg[3] = {0, 0, 0};
670             unsigned count = 0;
671             while (counter < src_width && src_x < src_width) {
672                 counter += dst_width;
673 
674                 for (unsigned c = 0; c < channels; c++) {
675                     avg[c] += get_raw_channel_from_row(src_data, src_x, c, format);
676                 }
677 
678                 src_x++;
679                 count++;
680             }
681             counter -= src_width;
682 
683             for (unsigned c = 0; c < channels; c++) {
684                 set_raw_channel_to_row(out_data, dst_x, c, avg[c] / count, format);
685             }
686         }
687     } else {
688         // interpolate and copy pixels
689         std::uint32_t counter = dst_width / 2;
690         unsigned dst_x = 0;
691 
692         for (unsigned src_x = 0; src_x < src_width; src_x++) {
693             unsigned avg[3] = {0, 0, 0};
694             for (unsigned c = 0; c < channels; c++) {
695                 avg[c] += get_raw_channel_from_row(src_data, src_x, c, format);
696             }
697             while ((counter < dst_width || src_x + 1 == src_width) && dst_x < dst_width) {
698                 counter += src_width;
699 
700                 for (unsigned c = 0; c < channels; c++) {
701                     set_raw_channel_to_row(out_data, dst_x, c, avg[c], format);
702                 }
703                 dst_x++;
704             }
705             counter -= dst_width;
706         }
707     }
708     return got_data;
709 }
710 
get_next_row_data(std::uint8_t* out_data)711 bool ImagePipelineNodeExtract::get_next_row_data(std::uint8_t* out_data)
712 {
713     bool got_data = true;
714 
715     while (current_line_ < offset_y_) {
716         got_data &= source_.get_next_row_data(cached_line_.data());
717         current_line_++;
718     }
719     if (current_line_ >= offset_y_ + source_.get_height()) {
720         std::fill(out_data, out_data + get_row_bytes(), 0);
721         current_line_++;
722         return got_data;
723     }
724     // now we're sure that the following holds:
725     // offset_y_ <= current_line_ < offset_y_ + source_.get_height())
726     got_data &= source_.get_next_row_data(cached_line_.data());
727 
728     auto format = get_format();
729     auto x_src_width = source_.get_width() > offset_x_ ? source_.get_width() - offset_x_ : 0;
730     x_src_width = std::min(x_src_width, width_);
731     auto x_pad_after = width_ > x_src_width ? width_ - x_src_width : 0;
732 
733     if (get_pixel_format_depth(format) < 8) {
734         // we need to copy pixels one-by-one as there's no per-bit addressing
735         for (std::size_t i = 0; i < x_src_width; ++i) {
736             auto pixel = get_raw_pixel_from_row(cached_line_.data(), i + offset_x_, format);
737             set_raw_pixel_to_row(out_data, i, pixel, format);
738         }
739         for (std::size_t i = 0; i < x_pad_after; ++i) {
740             set_raw_pixel_to_row(out_data, i + x_src_width, RawPixel{}, format);
741         }
742     } else {
743         std::size_t bpp = get_pixel_format_depth(format) / 8;
744         if (x_src_width > 0) {
745             std::memcpy(out_data, cached_line_.data() + offset_x_ * bpp,
746                         x_src_width * bpp);
747         }
748         if (x_pad_after > 0) {
749             std::fill(out_data + x_src_width * bpp,
750                       out_data + (x_src_width + x_pad_after) * bpp, 0);
751         }
752     }
753 
754     current_line_++;
755 
756     return got_data;
757 }
758 
ImagePipelineNodeCalibrate(ImagePipelineNode& source, const std::vector<std::uint16_t>& bottom, const std::vector<std::uint16_t>& top, std::size_t x_start)759 ImagePipelineNodeCalibrate::ImagePipelineNodeCalibrate(ImagePipelineNode& source,
760                                                        const std::vector<std::uint16_t>& bottom,
761                                                        const std::vector<std::uint16_t>& top,
762                                                        std::size_t x_start) :
763     source_(source)
764 {
765     std::size_t size = 0;
766     if (bottom.size() >= x_start && top.size() >= x_start) {
767         size = std::min(bottom.size() - x_start, top.size() - x_start);
768     }
769 
770     offset_.reserve(size);
771     multiplier_.reserve(size);
772 
773     for (std::size_t i = 0; i < size; ++i) {
774         offset_.push_back(bottom[i + x_start] / 65535.0f);
775         multiplier_.push_back(65535.0f / (top[i + x_start] - bottom[i + x_start]));
776     }
777 }
778 
get_next_row_data(std::uint8_t* out_data)779 bool ImagePipelineNodeCalibrate::get_next_row_data(std::uint8_t* out_data)
780 {
781     bool ret = source_.get_next_row_data(out_data);
782 
783     auto format = get_format();
784     auto depth = get_pixel_format_depth(format);
785     std::size_t max_value = 1;
786     switch (depth) {
787         case 8: max_value = 255; break;
788         case 16: max_value = 65535; break;
789         default:
790             throw SaneException("Unsupported depth for calibration %d", depth);
791     }
792     unsigned channels = get_pixel_channels(format);
793 
794     std::size_t max_calib_i = offset_.size();
795     std::size_t curr_calib_i = 0;
796 
797     for (std::size_t x = 0, width = get_width(); x < width && curr_calib_i < max_calib_i; ++x) {
798         for (unsigned ch = 0; ch < channels && curr_calib_i < max_calib_i; ++ch) {
799             std::int32_t value = get_raw_channel_from_row(out_data, x, ch, format);
800 
801             float value_f = static_cast<float>(value) / max_value;
802             value_f = (value_f - offset_[curr_calib_i]) * multiplier_[curr_calib_i];
803             value_f = std::round(value_f * max_value);
804             value = clamp<std::int32_t>(static_cast<std::int32_t>(value_f), 0, max_value);
805             set_raw_channel_to_row(out_data, x, ch, value, format);
806 
807             curr_calib_i++;
808         }
809     }
810     return ret;
811 }
812 
ImagePipelineNodeDebug(ImagePipelineNode& source, const std::string& path)813 ImagePipelineNodeDebug::ImagePipelineNodeDebug(ImagePipelineNode& source,
814                                                const std::string& path) :
815     source_(source),
816     path_{path},
817     buffer_{source_.get_row_bytes()}
818 {}
819 
~ImagePipelineNodeDebug()820 ImagePipelineNodeDebug::~ImagePipelineNodeDebug()
821 {
822     catch_all_exceptions(__func__, [&]()
823     {
824         if (buffer_.empty())
825             return;
826 
827         auto format = get_format();
828         buffer_.linearize();
829         write_tiff_file(path_, buffer_.get_front_row_ptr(), get_pixel_format_depth(format),
830                         get_pixel_channels(format), get_width(), buffer_.height());
831     });
832 }
833 
get_next_row_data(std::uint8_t* out_data)834 bool ImagePipelineNodeDebug::get_next_row_data(std::uint8_t* out_data)
835 {
836     buffer_.push_back();
837     bool got_data = source_.get_next_row_data(out_data);
838     std::memcpy(buffer_.get_back_row_ptr(), out_data, get_row_bytes());
839     return got_data;
840 }
841 
get_input_width() const842 std::size_t ImagePipelineStack::get_input_width() const
843 {
844     ensure_node_exists();
845     return nodes_.front()->get_width();
846 }
847 
get_input_height() const848 std::size_t ImagePipelineStack::get_input_height() const
849 {
850     ensure_node_exists();
851     return nodes_.front()->get_height();
852 }
853 
get_input_format() const854 PixelFormat ImagePipelineStack::get_input_format() const
855 {
856     ensure_node_exists();
857     return nodes_.front()->get_format();
858 }
859 
get_input_row_bytes() const860 std::size_t ImagePipelineStack::get_input_row_bytes() const
861 {
862     ensure_node_exists();
863     return nodes_.front()->get_row_bytes();
864 }
865 
get_output_width() const866 std::size_t ImagePipelineStack::get_output_width() const
867 {
868     ensure_node_exists();
869     return nodes_.back()->get_width();
870 }
871 
get_output_height() const872 std::size_t ImagePipelineStack::get_output_height() const
873 {
874     ensure_node_exists();
875     return nodes_.back()->get_height();
876 }
877 
get_output_format() const878 PixelFormat ImagePipelineStack::get_output_format() const
879 {
880     ensure_node_exists();
881     return nodes_.back()->get_format();
882 }
883 
get_output_row_bytes() const884 std::size_t ImagePipelineStack::get_output_row_bytes() const
885 {
886     ensure_node_exists();
887     return nodes_.back()->get_row_bytes();
888 }
889 
ensure_node_exists() const890 void ImagePipelineStack::ensure_node_exists() const
891 {
892     if (nodes_.empty()) {
893         throw SaneException("The pipeline does not contain any nodes");
894     }
895 }
896 
clear()897 void ImagePipelineStack::clear()
898 {
899     // we need to destroy the nodes back to front, so that the destructors still have valid
900     // references to sources
901     for (auto it = nodes_.rbegin(); it != nodes_.rend(); ++it) {
902         it->reset();
903     }
904     nodes_.clear();
905 }
906 
get_all_data()907 std::vector<std::uint8_t> ImagePipelineStack::get_all_data()
908 {
909     auto row_bytes = get_output_row_bytes();
910     auto height = get_output_height();
911 
912     std::vector<std::uint8_t> ret;
913     ret.resize(row_bytes * height);
914 
915     for (std::size_t i = 0; i < height; ++i) {
916         get_next_row_data(ret.data() + row_bytes * i);
917     }
918     return ret;
919 }
920 
get_image()921 Image ImagePipelineStack::get_image()
922 {
923     auto height = get_output_height();
924 
925     Image ret;
926     ret.resize(get_output_width(), height, get_output_format());
927 
928     for (std::size_t i = 0; i < height; ++i) {
929         get_next_row_data(ret.get_row_ptr(i));
930     }
931     return ret;
932 }
933 
934 } // namespace genesys
935