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