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