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