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
29namespace genesys {
30
31ImagePipelineNode::~ImagePipelineNode() {}
32
33bool 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
41ImagePipelineNodeBufferedCallableSource::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
52bool 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
71ImagePipelineNodeArraySource::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
87bool 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
102ImagePipelineNodeImageSource::ImagePipelineNodeImageSource(const Image& source) :
103    source_{source}
104{}
105
106bool 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
116bool 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
131ImagePipelineNodeDesegment::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
155ImagePipelineNodeDesegment::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
176bool 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
211ImagePipelineNodeDeinterleaveLines::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
218ImagePipelineNodeSwap16BitEndian::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
229bool 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
242ImagePipelineNodeInvert::ImagePipelineNodeInvert(ImagePipelineNode& source) :
243    source_(source)
244{
245}
246
247bool 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
286ImagePipelineNodeMergeMonoLinesToColor::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
296bool 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
323PixelFormat 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
358ImagePipelineNodeSplitMonoLines::ImagePipelineNodeSplitMonoLines(ImagePipelineNode& source) :
359    source_(source),
360    next_channel_{0}
361{
362    output_format_ = get_output_format(source_.get_format());
363}
364
365bool 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
386PixelFormat 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
400ImagePipelineNodeMergeColorToGray::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
434bool 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
452PixelFormat 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
468ImagePipelineNodeComponentShiftLines::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
500bool 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
528ImagePipelineNodePixelShiftLines::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
543bool 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
574ImagePipelineNodePixelShiftColumns::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
589bool 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
610std::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
631ImagePipelineNodeExtract::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
643ImagePipelineNodeExtract::~ImagePipelineNodeExtract() {}
644
645ImagePipelineNodeScaleRows::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
653bool 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
711bool 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
759ImagePipelineNodeCalibrate::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
779bool 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
813ImagePipelineNodeDebug::ImagePipelineNodeDebug(ImagePipelineNode& source,
814                                               const std::string& path) :
815    source_(source),
816    path_{path},
817    buffer_{source_.get_row_bytes()}
818{}
819
820ImagePipelineNodeDebug::~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
834bool 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
842std::size_t ImagePipelineStack::get_input_width() const
843{
844    ensure_node_exists();
845    return nodes_.front()->get_width();
846}
847
848std::size_t ImagePipelineStack::get_input_height() const
849{
850    ensure_node_exists();
851    return nodes_.front()->get_height();
852}
853
854PixelFormat ImagePipelineStack::get_input_format() const
855{
856    ensure_node_exists();
857    return nodes_.front()->get_format();
858}
859
860std::size_t ImagePipelineStack::get_input_row_bytes() const
861{
862    ensure_node_exists();
863    return nodes_.front()->get_row_bytes();
864}
865
866std::size_t ImagePipelineStack::get_output_width() const
867{
868    ensure_node_exists();
869    return nodes_.back()->get_width();
870}
871
872std::size_t ImagePipelineStack::get_output_height() const
873{
874    ensure_node_exists();
875    return nodes_.back()->get_height();
876}
877
878PixelFormat ImagePipelineStack::get_output_format() const
879{
880    ensure_node_exists();
881    return nodes_.back()->get_format();
882}
883
884std::size_t ImagePipelineStack::get_output_row_bytes() const
885{
886    ensure_node_exists();
887    return nodes_.back()->get_row_bytes();
888}
889
890void ImagePipelineStack::ensure_node_exists() const
891{
892    if (nodes_.empty()) {
893        throw SaneException("The pipeline does not contain any nodes");
894    }
895}
896
897void 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
907std::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
921Image 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