kopia lustrzana https://gitlab.com/sane-project/backends
				
				
				
			
		
			
				
	
	
		
			840 wiersze
		
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
			
		
		
	
	
			840 wiersze
		
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
/* sane - Scanner Access Now Easy.
 | 
						|
 | 
						|
   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
 | 
						|
 | 
						|
   This file is part of the SANE package.
 | 
						|
 | 
						|
   This program is free software; you can redistribute it and/or
 | 
						|
   modify it under the terms of the GNU General Public License as
 | 
						|
   published by the Free Software Foundation; either version 2 of the
 | 
						|
   License, or (at your option) any later version.
 | 
						|
 | 
						|
   This program is distributed in the hope that it will be useful, but
 | 
						|
   WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
   General Public License for more details.
 | 
						|
 | 
						|
   You should have received a copy of the GNU General Public License
 | 
						|
   along with this program; if not, write to the Free Software
 | 
						|
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 | 
						|
   MA 02111-1307, USA.
 | 
						|
 | 
						|
   As a special exception, the authors of SANE give permission for
 | 
						|
   additional uses of the libraries contained in this release of SANE.
 | 
						|
 | 
						|
   The exception is that, if you link a SANE library with other files
 | 
						|
   to produce an executable, this does not by itself cause the
 | 
						|
   resulting executable to be covered by the GNU General Public
 | 
						|
   License.  Your use of that executable is in no way restricted on
 | 
						|
   account of linking the SANE library code into it.
 | 
						|
 | 
						|
   This exception does not, however, invalidate any other reasons why
 | 
						|
   the executable file might be covered by the GNU General Public
 | 
						|
   License.
 | 
						|
 | 
						|
   If you submit changes to SANE to the maintainers to be included in
 | 
						|
   a subsequent release, you agree by submitting the changes that
 | 
						|
   those changes may be distributed with this exception intact.
 | 
						|
 | 
						|
   If you write modifications of your own for SANE, it is your choice
 | 
						|
   whether to permit this exception to apply to your modifications.
 | 
						|
   If you do not wish that, delete this exception notice.
 | 
						|
*/
 | 
						|
 | 
						|
#define DEBUG_DECLARE_ONLY
 | 
						|
 | 
						|
#include "image_pipeline.h"
 | 
						|
#include "image.h"
 | 
						|
#include "low.h"
 | 
						|
#include <cmath>
 | 
						|
#include <numeric>
 | 
						|
 | 
						|
namespace genesys {
 | 
						|
 | 
						|
ImagePipelineNode::~ImagePipelineNode() {}
 | 
						|
 | 
						|
std::size_t ImagePipelineNodeBytesSource::consume_remaining_bytes(std::size_t bytes)
 | 
						|
{
 | 
						|
    if (bytes > remaining_bytes_) {
 | 
						|
        bytes = remaining_bytes_;
 | 
						|
    }
 | 
						|
    remaining_bytes_ -= bytes;
 | 
						|
    return bytes;
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeCallableSource::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    bool got_data = producer_(get_row_bytes(), out_data);
 | 
						|
    if (!got_data)
 | 
						|
        eof_ = true;
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeBufferedCallableSource::ImagePipelineNodeBufferedCallableSource(
 | 
						|
        std::size_t width, std::size_t height, PixelFormat format, std::size_t input_batch_size,
 | 
						|
        ProducerCallback producer) :
 | 
						|
    width_{width},
 | 
						|
    height_{height},
 | 
						|
    format_{format},
 | 
						|
    buffer_{input_batch_size, producer}
 | 
						|
{
 | 
						|
    set_remaining_bytes(height_ * get_row_bytes());
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeBufferedCallableSource::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    if (curr_row_ >= get_height()) {
 | 
						|
        DBG(DBG_warn, "%s: reading out of bounds. Row %zu, height: %zu\n", __func__,
 | 
						|
            curr_row_, get_height());
 | 
						|
        eof_ = true;
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    bool got_data = true;
 | 
						|
 | 
						|
    auto row_bytes = get_row_bytes();
 | 
						|
    auto bytes_to_ask = consume_remaining_bytes(row_bytes);
 | 
						|
    if (bytes_to_ask < row_bytes) {
 | 
						|
        got_data = false;
 | 
						|
    }
 | 
						|
 | 
						|
    got_data &= buffer_.get_data(bytes_to_ask, out_data);
 | 
						|
    curr_row_++;
 | 
						|
    if (!got_data) {
 | 
						|
        eof_ = true;
 | 
						|
    }
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
ImagePipelineNodeBufferedGenesysUsb::ImagePipelineNodeBufferedGenesysUsb(
 | 
						|
        std::size_t width, std::size_t height, PixelFormat format, std::size_t total_size,
 | 
						|
        const FakeBufferModel& buffer_model, ProducerCallback producer) :
 | 
						|
    width_{width},
 | 
						|
    height_{height},
 | 
						|
    format_{format},
 | 
						|
    buffer_{total_size, buffer_model, producer}
 | 
						|
{
 | 
						|
    set_remaining_bytes(total_size);
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeBufferedGenesysUsb::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    if (remaining_bytes() != buffer_.remaining_size() + buffer_.available()) {
 | 
						|
        buffer_.set_remaining_size(remaining_bytes() - buffer_.available());
 | 
						|
    }
 | 
						|
    bool got_data = true;
 | 
						|
 | 
						|
    std::size_t row_bytes = get_row_bytes();
 | 
						|
    std::size_t ask_bytes = consume_remaining_bytes(row_bytes);
 | 
						|
    if (ask_bytes < row_bytes) {
 | 
						|
        got_data = false;
 | 
						|
    }
 | 
						|
    got_data &= buffer_.get_data(ask_bytes, out_data);
 | 
						|
    if (!got_data) {
 | 
						|
        eof_ = true;
 | 
						|
    }
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeArraySource::ImagePipelineNodeArraySource(std::size_t width, std::size_t height,
 | 
						|
                                                           PixelFormat format,
 | 
						|
                                                           std::vector<std::uint8_t> data) :
 | 
						|
    width_{width},
 | 
						|
    height_{height},
 | 
						|
    format_{format},
 | 
						|
    data_{std::move(data)},
 | 
						|
    next_row_{0}
 | 
						|
{
 | 
						|
    auto size = get_row_bytes() * height_;
 | 
						|
    if (data_.size() < size) {
 | 
						|
        throw SaneException("The given array is too small (%zu bytes). Need at least %zu",
 | 
						|
                            data_.size(), size);
 | 
						|
    }
 | 
						|
    set_remaining_bytes(size);
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeArraySource::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    if (next_row_ >= height_) {
 | 
						|
        eof_ = true;
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    bool got_data = true;
 | 
						|
 | 
						|
    auto row_bytes = get_row_bytes();
 | 
						|
    auto bytes_to_ask = consume_remaining_bytes(row_bytes);
 | 
						|
    if (bytes_to_ask < row_bytes) {
 | 
						|
        got_data = false;
 | 
						|
    }
 | 
						|
 | 
						|
    std::memcpy(out_data, data_.data() + get_row_bytes() * next_row_, bytes_to_ask);
 | 
						|
    next_row_++;
 | 
						|
 | 
						|
    if (!got_data) {
 | 
						|
        eof_ = true;
 | 
						|
    }
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
ImagePipelineNodeImageSource::ImagePipelineNodeImageSource(const Image& source) :
 | 
						|
    source_{source}
 | 
						|
{}
 | 
						|
 | 
						|
bool ImagePipelineNodeImageSource::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    if (next_row_ >= get_height()) {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
    std::memcpy(out_data, source_.get_row_ptr(next_row_), get_row_bytes());
 | 
						|
    next_row_++;
 | 
						|
    return true;
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeFormatConvert::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    auto src_format = source_.get_format();
 | 
						|
    if (src_format == dst_format_) {
 | 
						|
        return source_.get_next_row_data(out_data);
 | 
						|
    }
 | 
						|
 | 
						|
    buffer_.clear();
 | 
						|
    buffer_.resize(source_.get_row_bytes());
 | 
						|
    bool got_data = source_.get_next_row_data(buffer_.data());
 | 
						|
 | 
						|
    convert_pixel_row_format(buffer_.data(), src_format, out_data, dst_format_, get_width());
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeDesegment::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) :
 | 
						|
    source_(source),
 | 
						|
    output_width_{output_width},
 | 
						|
    segment_order_{segment_order},
 | 
						|
    segment_pixels_{segment_pixels},
 | 
						|
    interleaved_lines_{interleaved_lines},
 | 
						|
    pixels_per_chunk_{pixels_per_chunk},
 | 
						|
    buffer_{source_.get_row_bytes()}
 | 
						|
{
 | 
						|
    DBG_HELPER_ARGS(dbg, "segment_count=%zu, segment_size=%zu, interleaved_lines=%zu, "
 | 
						|
                         "pixels_per_shunk=%zu", segment_order.size(), segment_pixels,
 | 
						|
                    interleaved_lines, pixels_per_chunk);
 | 
						|
 | 
						|
    if (source_.get_height() % interleaved_lines_ > 0) {
 | 
						|
        throw SaneException("Height is not a multiple of the number of lines to interelave %zu/%zu",
 | 
						|
                            source_.get_height(), interleaved_lines_);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeDesegment::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) :
 | 
						|
    source_(source),
 | 
						|
    output_width_{output_width},
 | 
						|
    segment_pixels_{segment_pixels},
 | 
						|
    interleaved_lines_{interleaved_lines},
 | 
						|
    pixels_per_chunk_{pixels_per_chunk},
 | 
						|
    buffer_{source_.get_row_bytes()}
 | 
						|
{
 | 
						|
    DBG_HELPER_ARGS(dbg, "segment_count=%zu, segment_size=%zu, interleaved_lines=%zu, "
 | 
						|
                    "pixels_per_shunk=%zu", segment_count, segment_pixels, interleaved_lines,
 | 
						|
                    pixels_per_chunk);
 | 
						|
 | 
						|
    segment_order_.resize(segment_count);
 | 
						|
    std::iota(segment_order_.begin(), segment_order_.end(), 0);
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeDesegment::get_next_row_data(uint8_t* out_data)
 | 
						|
{
 | 
						|
    bool got_data = true;
 | 
						|
 | 
						|
    buffer_.clear();
 | 
						|
    for (std::size_t i = 0; i < interleaved_lines_; ++i) {
 | 
						|
        buffer_.push_back();
 | 
						|
        got_data &= source_.get_next_row_data(buffer_.get_row_ptr(i));
 | 
						|
    }
 | 
						|
    if (!buffer_.is_linear()) {
 | 
						|
        throw SaneException("Buffer is not linear");
 | 
						|
    }
 | 
						|
 | 
						|
    auto format = get_format();
 | 
						|
    auto segment_count = segment_order_.size();
 | 
						|
 | 
						|
    const std::uint8_t* in_data = buffer_.get_row_ptr(0);
 | 
						|
 | 
						|
    std::size_t groups_count = output_width_ / (segment_order_.size() * pixels_per_chunk_);
 | 
						|
 | 
						|
    for (std::size_t igroup = 0; igroup < groups_count; ++igroup) {
 | 
						|
        for (std::size_t isegment = 0; isegment < segment_count; ++isegment) {
 | 
						|
            auto input_offset = igroup * pixels_per_chunk_;
 | 
						|
            input_offset += segment_pixels_ * segment_order_[isegment];
 | 
						|
            auto output_offset = (igroup * segment_count + isegment) * pixels_per_chunk_;
 | 
						|
 | 
						|
            for (std::size_t ipixel = 0; ipixel < pixels_per_chunk_; ++ipixel) {
 | 
						|
                auto pixel = get_raw_pixel_from_row(in_data, input_offset + ipixel, format);
 | 
						|
                set_raw_pixel_to_row(out_data, output_offset + ipixel, pixel, format);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeDeinterleaveLines::ImagePipelineNodeDeinterleaveLines(
 | 
						|
        ImagePipelineNode& source, std::size_t interleaved_lines, std::size_t pixels_per_chunk) :
 | 
						|
    ImagePipelineNodeDesegment(source, source.get_width() * interleaved_lines,
 | 
						|
                               interleaved_lines, source.get_width(),
 | 
						|
                               interleaved_lines, pixels_per_chunk)
 | 
						|
{}
 | 
						|
 | 
						|
ImagePipelineNodeSwap16BitEndian::ImagePipelineNodeSwap16BitEndian(ImagePipelineNode& source) :
 | 
						|
    source_(source),
 | 
						|
    needs_swapping_{false}
 | 
						|
{
 | 
						|
    if (get_pixel_format_depth(source_.get_format()) == 16) {
 | 
						|
        needs_swapping_ = true;
 | 
						|
    } else {
 | 
						|
        DBG(DBG_info, "%s: this pipeline node does nothing for non 16-bit formats", __func__);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeSwap16BitEndian::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    bool got_data = source_.get_next_row_data(out_data);
 | 
						|
    if (needs_swapping_) {
 | 
						|
        std::size_t pixels = get_row_bytes() / 2;
 | 
						|
        for (std::size_t i = 0; i < pixels; ++i) {
 | 
						|
            std::swap(*out_data, *(out_data + 1));
 | 
						|
            out_data += 2;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeMergeMonoLines::ImagePipelineNodeMergeMonoLines(ImagePipelineNode& source,
 | 
						|
                                                                 ColorOrder color_order) :
 | 
						|
    source_(source),
 | 
						|
    buffer_(source_.get_row_bytes())
 | 
						|
{
 | 
						|
    DBG_HELPER_ARGS(dbg, "color_order %d", static_cast<unsigned>(color_order));
 | 
						|
 | 
						|
    output_format_ = get_output_format(source_.get_format(), color_order);
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeMergeMonoLines::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    bool got_data = true;
 | 
						|
 | 
						|
    buffer_.clear();
 | 
						|
    for (unsigned i = 0; i < 3; ++i) {
 | 
						|
        buffer_.push_back();
 | 
						|
        got_data &= source_.get_next_row_data(buffer_.get_row_ptr(i));
 | 
						|
    }
 | 
						|
 | 
						|
    const auto* row0 = buffer_.get_row_ptr(0);
 | 
						|
    const auto* row1 = buffer_.get_row_ptr(1);
 | 
						|
    const auto* row2 = buffer_.get_row_ptr(2);
 | 
						|
 | 
						|
    auto format = source_.get_format();
 | 
						|
 | 
						|
    for (std::size_t x = 0, width = get_width(); x < width; ++x) {
 | 
						|
        std::uint16_t ch0 = get_raw_channel_from_row(row0, x, 0, format);
 | 
						|
        std::uint16_t ch1 = get_raw_channel_from_row(row1, x, 0, format);
 | 
						|
        std::uint16_t ch2 = get_raw_channel_from_row(row2, x, 0, format);
 | 
						|
        set_raw_channel_to_row(out_data, x, 0, ch0, output_format_);
 | 
						|
        set_raw_channel_to_row(out_data, x, 1, ch1, output_format_);
 | 
						|
        set_raw_channel_to_row(out_data, x, 2, ch2, output_format_);
 | 
						|
    }
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
PixelFormat ImagePipelineNodeMergeMonoLines::get_output_format(PixelFormat input_format,
 | 
						|
                                                               ColorOrder order)
 | 
						|
{
 | 
						|
    switch (input_format) {
 | 
						|
        case PixelFormat::I1: {
 | 
						|
            if (order == ColorOrder::RGB) {
 | 
						|
                return PixelFormat::RGB111;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case PixelFormat::I8: {
 | 
						|
            if (order == ColorOrder::RGB) {
 | 
						|
                return PixelFormat::RGB888;
 | 
						|
            }
 | 
						|
            if (order == ColorOrder::BGR) {
 | 
						|
                return PixelFormat::BGR888;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case PixelFormat::I16: {
 | 
						|
            if (order == ColorOrder::RGB) {
 | 
						|
                return PixelFormat::RGB161616;
 | 
						|
            }
 | 
						|
            if (order == ColorOrder::BGR) {
 | 
						|
                return PixelFormat::BGR161616;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        default: break;
 | 
						|
    }
 | 
						|
    throw SaneException("Unsupported format combidation %d %d",
 | 
						|
                        static_cast<unsigned>(input_format),
 | 
						|
                        static_cast<unsigned>(order));
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeSplitMonoLines::ImagePipelineNodeSplitMonoLines(ImagePipelineNode& source) :
 | 
						|
    source_(source),
 | 
						|
    next_channel_{0}
 | 
						|
{
 | 
						|
    output_format_ = get_output_format(source_.get_format());
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeSplitMonoLines::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    bool got_data = true;
 | 
						|
 | 
						|
    if (next_channel_ == 0) {
 | 
						|
        buffer_.resize(source_.get_row_bytes());
 | 
						|
        got_data &= source_.get_next_row_data(buffer_.data());
 | 
						|
    }
 | 
						|
 | 
						|
    const auto* row = buffer_.data();
 | 
						|
    auto format = source_.get_format();
 | 
						|
 | 
						|
    for (std::size_t x = 0, width = get_width(); x < width; ++x) {
 | 
						|
        std::uint16_t ch = get_raw_channel_from_row(row, x, next_channel_, format);
 | 
						|
        set_raw_channel_to_row(out_data, x, 0, ch, output_format_);
 | 
						|
    }
 | 
						|
    next_channel_ = (next_channel_ + 1) % 3;
 | 
						|
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
PixelFormat ImagePipelineNodeSplitMonoLines::get_output_format(PixelFormat input_format)
 | 
						|
{
 | 
						|
    switch (input_format) {
 | 
						|
        case PixelFormat::RGB111: return PixelFormat::I1;
 | 
						|
        case PixelFormat::RGB888:
 | 
						|
        case PixelFormat::BGR888: return PixelFormat::I8;
 | 
						|
        case PixelFormat::RGB161616:
 | 
						|
        case PixelFormat::BGR161616: return PixelFormat::I16;
 | 
						|
        default: break;
 | 
						|
    }
 | 
						|
    throw SaneException("Unsupported input format %d", static_cast<unsigned>(input_format));
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeComponentShiftLines::ImagePipelineNodeComponentShiftLines(
 | 
						|
        ImagePipelineNode& source, unsigned shift_r, unsigned shift_g, unsigned shift_b) :
 | 
						|
    source_(source),
 | 
						|
    buffer_{source.get_row_bytes()}
 | 
						|
{
 | 
						|
    DBG_HELPER_ARGS(dbg, "shifts={%d, %d, %d}", shift_r, shift_g, shift_b);
 | 
						|
 | 
						|
    switch (source.get_format()) {
 | 
						|
        case PixelFormat::RGB111:
 | 
						|
        case PixelFormat::RGB888:
 | 
						|
        case PixelFormat::RGB161616: {
 | 
						|
            channel_shifts_ = { shift_r, shift_g, shift_b };
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case PixelFormat::BGR888:
 | 
						|
        case PixelFormat::BGR161616: {
 | 
						|
            channel_shifts_ = { shift_b, shift_g, shift_r };
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        default:
 | 
						|
            throw SaneException("Unsupported input format %d",
 | 
						|
                                static_cast<unsigned>(source.get_format()));
 | 
						|
    }
 | 
						|
    extra_height_ = *std::max_element(channel_shifts_.begin(), channel_shifts_.end());
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeComponentShiftLines::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    bool got_data = true;
 | 
						|
 | 
						|
    if (!buffer_.empty()) {
 | 
						|
        buffer_.pop_front();
 | 
						|
    }
 | 
						|
    while (buffer_.height() < extra_height_ + 1) {
 | 
						|
        buffer_.push_back();
 | 
						|
        got_data &= source_.get_next_row_data(buffer_.get_back_row_ptr());
 | 
						|
    }
 | 
						|
 | 
						|
    auto format = get_format();
 | 
						|
    const auto* row0 = buffer_.get_row_ptr(channel_shifts_[0]);
 | 
						|
    const auto* row1 = buffer_.get_row_ptr(channel_shifts_[1]);
 | 
						|
    const auto* row2 = buffer_.get_row_ptr(channel_shifts_[2]);
 | 
						|
 | 
						|
    for (std::size_t x = 0, width = get_width(); x < width; ++x) {
 | 
						|
        std::uint16_t ch0 = get_raw_channel_from_row(row0, x, 0, format);
 | 
						|
        std::uint16_t ch1 = get_raw_channel_from_row(row1, x, 1, format);
 | 
						|
        std::uint16_t ch2 = get_raw_channel_from_row(row2, x, 2, format);
 | 
						|
        set_raw_channel_to_row(out_data, x, 0, ch0, format);
 | 
						|
        set_raw_channel_to_row(out_data, x, 1, ch1, format);
 | 
						|
        set_raw_channel_to_row(out_data, x, 2, ch2, format);
 | 
						|
    }
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodePixelShiftLines::ImagePipelineNodePixelShiftLines(
 | 
						|
        ImagePipelineNode& source, const std::vector<std::size_t>& shifts) :
 | 
						|
    source_(source),
 | 
						|
    pixel_shifts_{shifts},
 | 
						|
    buffer_{get_row_bytes()}
 | 
						|
{
 | 
						|
    DBG_HELPER(dbg);
 | 
						|
    DBG(DBG_proc, "%s: shifts={", __func__);
 | 
						|
    for (auto el : pixel_shifts_) {
 | 
						|
        DBG(DBG_proc, " %zu", el);
 | 
						|
    }
 | 
						|
    DBG(DBG_proc, " }\n");
 | 
						|
 | 
						|
    if (pixel_shifts_.size() > MAX_SHIFTS) {
 | 
						|
        throw SaneException("Unsupported number of shift configurations %zu", pixel_shifts_.size());
 | 
						|
    }
 | 
						|
 | 
						|
    extra_height_ = *std::max_element(pixel_shifts_.begin(), pixel_shifts_.end());
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodePixelShiftLines::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    bool got_data = true;
 | 
						|
 | 
						|
    if (!buffer_.empty()) {
 | 
						|
        buffer_.pop_front();
 | 
						|
    }
 | 
						|
    while (buffer_.height() < extra_height_ + 1) {
 | 
						|
        buffer_.push_back();
 | 
						|
        got_data &= source_.get_next_row_data(buffer_.get_back_row_ptr());
 | 
						|
    }
 | 
						|
 | 
						|
    auto format = get_format();
 | 
						|
    auto shift_count = pixel_shifts_.size();
 | 
						|
 | 
						|
    std::array<std::uint8_t*, MAX_SHIFTS> rows;
 | 
						|
 | 
						|
    for (std::size_t irow = 0; irow < shift_count; ++irow) {
 | 
						|
        rows[irow] = buffer_.get_row_ptr(pixel_shifts_[irow]);
 | 
						|
    }
 | 
						|
 | 
						|
    for (std::size_t x = 0, width = get_width(); x < width;) {
 | 
						|
        for (std::size_t irow = 0; irow < shift_count && x < width; irow++, x++) {
 | 
						|
            RawPixel pixel = get_raw_pixel_from_row(rows[irow], x, format);
 | 
						|
            set_raw_pixel_to_row(out_data, x, pixel, format);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeExtract::ImagePipelineNodeExtract(ImagePipelineNode& source,
 | 
						|
                                                   std::size_t offset_x, std::size_t offset_y,
 | 
						|
                                                   std::size_t width, std::size_t height) :
 | 
						|
    source_(source),
 | 
						|
    offset_x_{offset_x},
 | 
						|
    offset_y_{offset_y},
 | 
						|
    width_{width},
 | 
						|
    height_{height}
 | 
						|
{
 | 
						|
    cached_line_.resize(source_.get_row_bytes());
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeExtract::~ImagePipelineNodeExtract() {}
 | 
						|
 | 
						|
ImagePipelineNodeScaleRows::ImagePipelineNodeScaleRows(ImagePipelineNode& source,
 | 
						|
                                                       std::size_t width) :
 | 
						|
    source_(source),
 | 
						|
    width_{width}
 | 
						|
{
 | 
						|
    cached_line_.resize(source_.get_row_bytes());
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeScaleRows::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    auto src_width = source_.get_width();
 | 
						|
    auto dst_width = width_;
 | 
						|
 | 
						|
    bool got_data = source_.get_next_row_data(cached_line_.data());
 | 
						|
 | 
						|
    const auto* src_data = cached_line_.data();
 | 
						|
    auto format = get_format();
 | 
						|
    auto channels = get_pixel_channels(format);
 | 
						|
 | 
						|
    if (src_width > dst_width) {
 | 
						|
        // average
 | 
						|
        std::uint32_t counter = src_width / 2;
 | 
						|
        unsigned src_x = 0;
 | 
						|
        for (unsigned dst_x = 0; dst_x < dst_width; dst_x++) {
 | 
						|
            unsigned avg[3] = {0, 0, 0};
 | 
						|
            unsigned count = 0;
 | 
						|
            while (counter < src_width && src_x < src_width) {
 | 
						|
                counter += dst_width;
 | 
						|
 | 
						|
                for (unsigned c = 0; c < channels; c++) {
 | 
						|
                    avg[c] += get_raw_channel_from_row(src_data, src_x, c, format);
 | 
						|
                }
 | 
						|
 | 
						|
                src_x++;
 | 
						|
                count++;
 | 
						|
            }
 | 
						|
            counter -= src_width;
 | 
						|
 | 
						|
            for (unsigned c = 0; c < channels; c++) {
 | 
						|
                set_raw_channel_to_row(out_data, dst_x, c, avg[c] / count, format);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        // interpolate and copy pixels
 | 
						|
        std::uint32_t counter = dst_width / 2;
 | 
						|
        unsigned dst_x = 0;
 | 
						|
 | 
						|
        for (unsigned src_x = 0; src_x < src_width; src_x++) {
 | 
						|
            unsigned avg[3] = {0, 0, 0};
 | 
						|
            for (unsigned c = 0; c < channels; c++) {
 | 
						|
                avg[c] += get_raw_channel_from_row(src_data, src_x, c, format);
 | 
						|
            }
 | 
						|
            while ((counter < dst_width || src_x + 1 == src_width) && dst_x < dst_width) {
 | 
						|
                counter += src_width;
 | 
						|
 | 
						|
                for (unsigned c = 0; c < channels; c++) {
 | 
						|
                    set_raw_channel_to_row(out_data, dst_x, c, avg[c], format);
 | 
						|
                }
 | 
						|
                dst_x++;
 | 
						|
            }
 | 
						|
            counter -= dst_width;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeExtract::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    bool got_data = true;
 | 
						|
 | 
						|
    while (current_line_ < offset_y_) {
 | 
						|
        got_data &= source_.get_next_row_data(cached_line_.data());
 | 
						|
        current_line_++;
 | 
						|
    }
 | 
						|
    if (current_line_ >= offset_y_ + source_.get_height()) {
 | 
						|
        std::fill(out_data, out_data + get_row_bytes(), 0);
 | 
						|
        current_line_++;
 | 
						|
        return got_data;
 | 
						|
    }
 | 
						|
    // now we're sure that the following holds:
 | 
						|
    // offset_y_ <= current_line_ < offset_y_ + source_.get_height())
 | 
						|
    got_data &= source_.get_next_row_data(cached_line_.data());
 | 
						|
 | 
						|
    auto format = get_format();
 | 
						|
    auto x_src_width = source_.get_width() > offset_x_ ? source_.get_width() - offset_x_ : 0;
 | 
						|
    x_src_width = std::min(x_src_width, width_);
 | 
						|
    auto x_pad_after = width_ > x_src_width ? width_ - x_src_width : 0;
 | 
						|
 | 
						|
    if (get_pixel_format_depth(format) < 8) {
 | 
						|
        // we need to copy pixels one-by-one as there's no per-bit addressing
 | 
						|
        for (std::size_t i = 0; i < x_src_width; ++i) {
 | 
						|
            auto pixel = get_raw_pixel_from_row(cached_line_.data(), i + offset_x_, format);
 | 
						|
            set_raw_pixel_to_row(out_data, i, pixel, format);
 | 
						|
        }
 | 
						|
        for (std::size_t i = 0; i < x_pad_after; ++i) {
 | 
						|
            set_raw_pixel_to_row(out_data, i + x_src_width, RawPixel{}, format);
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        std::size_t bpp = get_pixel_format_depth(format) / 8;
 | 
						|
        if (x_src_width > 0) {
 | 
						|
            std::memcpy(out_data, cached_line_.data() + offset_x_ * bpp,
 | 
						|
                        x_src_width * bpp);
 | 
						|
        }
 | 
						|
        if (x_pad_after > 0) {
 | 
						|
            std::fill(out_data + x_src_width * bpp,
 | 
						|
                      out_data + (x_src_width + x_pad_after) * bpp, 0);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    current_line_++;
 | 
						|
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeCalibrate::ImagePipelineNodeCalibrate(ImagePipelineNode& source,
 | 
						|
                                                       const std::vector<std::uint16_t>& bottom,
 | 
						|
                                                       const std::vector<std::uint16_t>& top) :
 | 
						|
    source_{source}
 | 
						|
{
 | 
						|
    auto size = std::min(bottom.size(), top.size());
 | 
						|
    offset_.reserve(size);
 | 
						|
    multiplier_.reserve(size);
 | 
						|
 | 
						|
    for (std::size_t i = 0; i < size; ++i) {
 | 
						|
        offset_.push_back(bottom[i] / 65535.0f);
 | 
						|
        multiplier_.push_back(65535.0f / (top[i] - bottom[i]));
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeCalibrate::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    bool ret = source_.get_next_row_data(out_data);
 | 
						|
 | 
						|
    auto format = get_format();
 | 
						|
    auto depth = get_pixel_format_depth(format);
 | 
						|
    std::size_t max_value = 1;
 | 
						|
    switch (depth) {
 | 
						|
        case 8: max_value = 255; break;
 | 
						|
        case 16: max_value = 65535; break;
 | 
						|
        default:
 | 
						|
            throw SaneException("Unsupported depth for calibration %d", depth);
 | 
						|
    }
 | 
						|
    unsigned channels = get_pixel_channels(format);
 | 
						|
 | 
						|
    std::size_t max_calib_i = offset_.size();
 | 
						|
    std::size_t curr_calib_i = 0;
 | 
						|
 | 
						|
    for (std::size_t x = 0, width = get_width(); x < width && curr_calib_i < max_calib_i; ++x) {
 | 
						|
        for (unsigned ch = 0; ch < channels && curr_calib_i < max_calib_i; ++ch) {
 | 
						|
            std::int32_t value = get_raw_channel_from_row(out_data, x, ch, format);
 | 
						|
 | 
						|
            float value_f = static_cast<float>(value) / max_value;
 | 
						|
            value_f = (value_f - offset_[curr_calib_i]) * multiplier_[curr_calib_i];
 | 
						|
            value_f = std::round(value_f * max_value);
 | 
						|
            value = clamp<std::int32_t>(static_cast<std::int32_t>(value_f), 0, max_value);
 | 
						|
            set_raw_channel_to_row(out_data, x, ch, value, format);
 | 
						|
 | 
						|
            curr_calib_i++;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
ImagePipelineNodeDebug::ImagePipelineNodeDebug(ImagePipelineNode& source,
 | 
						|
                                               const std::string& path) :
 | 
						|
    source_(source),
 | 
						|
    path_{path},
 | 
						|
    buffer_{source_.get_row_bytes()}
 | 
						|
{}
 | 
						|
 | 
						|
ImagePipelineNodeDebug::~ImagePipelineNodeDebug()
 | 
						|
{
 | 
						|
    catch_all_exceptions(__func__, [&]()
 | 
						|
    {
 | 
						|
        if (buffer_.empty())
 | 
						|
            return;
 | 
						|
 | 
						|
        auto format = get_format();
 | 
						|
        buffer_.linearize();
 | 
						|
        sanei_genesys_write_pnm_file(path_.c_str(), buffer_.get_front_row_ptr(),
 | 
						|
                                     get_pixel_format_depth(format),
 | 
						|
                                     get_pixel_channels(format),
 | 
						|
                                     get_width(), buffer_.height());
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
bool ImagePipelineNodeDebug::get_next_row_data(std::uint8_t* out_data)
 | 
						|
{
 | 
						|
    buffer_.push_back();
 | 
						|
    bool got_data = source_.get_next_row_data(out_data);
 | 
						|
    std::memcpy(buffer_.get_back_row_ptr(), out_data, get_row_bytes());
 | 
						|
    return got_data;
 | 
						|
}
 | 
						|
 | 
						|
std::size_t ImagePipelineStack::get_input_width() const
 | 
						|
{
 | 
						|
    ensure_node_exists();
 | 
						|
    return nodes_.front()->get_width();
 | 
						|
}
 | 
						|
 | 
						|
std::size_t ImagePipelineStack::get_input_height() const
 | 
						|
{
 | 
						|
    ensure_node_exists();
 | 
						|
    return nodes_.front()->get_height();
 | 
						|
}
 | 
						|
 | 
						|
PixelFormat ImagePipelineStack::get_input_format() const
 | 
						|
{
 | 
						|
    ensure_node_exists();
 | 
						|
    return nodes_.front()->get_format();
 | 
						|
}
 | 
						|
 | 
						|
std::size_t ImagePipelineStack::get_input_row_bytes() const
 | 
						|
{
 | 
						|
    ensure_node_exists();
 | 
						|
    return nodes_.front()->get_row_bytes();
 | 
						|
}
 | 
						|
 | 
						|
std::size_t ImagePipelineStack::get_output_width() const
 | 
						|
{
 | 
						|
    ensure_node_exists();
 | 
						|
    return nodes_.back()->get_width();
 | 
						|
}
 | 
						|
 | 
						|
std::size_t ImagePipelineStack::get_output_height() const
 | 
						|
{
 | 
						|
    ensure_node_exists();
 | 
						|
    return nodes_.back()->get_height();
 | 
						|
}
 | 
						|
 | 
						|
PixelFormat ImagePipelineStack::get_output_format() const
 | 
						|
{
 | 
						|
    ensure_node_exists();
 | 
						|
    return nodes_.back()->get_format();
 | 
						|
}
 | 
						|
 | 
						|
std::size_t ImagePipelineStack::get_output_row_bytes() const
 | 
						|
{
 | 
						|
    ensure_node_exists();
 | 
						|
    return nodes_.back()->get_row_bytes();
 | 
						|
}
 | 
						|
 | 
						|
void ImagePipelineStack::ensure_node_exists() const
 | 
						|
{
 | 
						|
    if (nodes_.empty()) {
 | 
						|
        throw SaneException("The pipeline does not contain any nodes");
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void ImagePipelineStack::clear()
 | 
						|
{
 | 
						|
    // we need to destroy the nodes back to front, so that the destructors still have valid
 | 
						|
    // references to sources
 | 
						|
    for (auto it = nodes_.rbegin(); it != nodes_.rend(); ++it) {
 | 
						|
        it->reset();
 | 
						|
    }
 | 
						|
    nodes_.clear();
 | 
						|
}
 | 
						|
 | 
						|
std::vector<std::uint8_t> ImagePipelineStack::get_all_data()
 | 
						|
{
 | 
						|
    auto row_bytes = get_output_row_bytes();
 | 
						|
    auto height = get_output_height();
 | 
						|
 | 
						|
    std::vector<std::uint8_t> ret;
 | 
						|
    ret.resize(row_bytes * height);
 | 
						|
 | 
						|
    for (std::size_t i = 0; i < height; ++i) {
 | 
						|
        get_next_row_data(ret.data() + row_bytes * i);
 | 
						|
    }
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
Image ImagePipelineStack::get_image()
 | 
						|
{
 | 
						|
    auto height = get_output_height();
 | 
						|
 | 
						|
    Image ret;
 | 
						|
    ret.resize(get_output_width(), height, get_output_format());
 | 
						|
 | 
						|
    for (std::size_t i = 0; i < height; ++i) {
 | 
						|
        get_next_row_data(ret.get_row_ptr(i));
 | 
						|
    }
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace genesys
 |