diff options
Diffstat (limited to 'backend/genesys/image_pipeline.cpp')
-rw-r--r-- | backend/genesys/image_pipeline.cpp | 839 |
1 files changed, 839 insertions, 0 deletions
diff --git a/backend/genesys/image_pipeline.cpp b/backend/genesys/image_pipeline.cpp new file mode 100644 index 0000000..c01b7f4 --- /dev/null +++ b/backend/genesys/image_pipeline.cpp @@ -0,0 +1,839 @@ +/* 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 |