diff --git a/backend/Makefile.am b/backend/Makefile.am index ea6edb2af..22d06496b 100644 --- a/backend/Makefile.am +++ b/backend/Makefile.am @@ -494,6 +494,10 @@ libgenesys_la_SOURCES = genesys.cc genesys.h \ genesys_gl646.cc genesys_gl646.h genesys_gl841.cc genesys_gl841.h \ genesys_gl843.cc genesys_gl843.h genesys_gl846.cc genesys_gl846.h \ genesys_gl847.cc genesys_gl847.h genesys_gl124.cc genesys_gl124.h \ + genesys_row_buffer.h \ + genesys_image_buffer.h genesys_image_buffer.cc \ + genesys_image_pipeline.h genesys_image_pipeline.cc \ + genesys_image.h genesys_image.cc \ genesys_motor.h \ genesys_register.h \ genesys_sanei.h genesys_sanei.cc \ diff --git a/backend/genesys_enums.h b/backend/genesys_enums.h index 37225c9b9..6800331ba 100644 --- a/backend/genesys_enums.h +++ b/backend/genesys_enums.h @@ -120,6 +120,20 @@ enum class ColorOrder { RGB, GBR, + BGR, +}; + +enum class PixelFormat +{ + UNKNOWN, + I1, + RGB111, + I8, + RGB888, + BGR888, + I16, + RGB161616, + BGR161616, }; enum Genesys_Model_Type diff --git a/backend/genesys_image.cc b/backend/genesys_image.cc new file mode 100644 index 000000000..0e8e3002b --- /dev/null +++ b/backend/genesys_image.cc @@ -0,0 +1,602 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + 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 "genesys_image.h" + +#include + +struct PixelFormatDesc +{ + PixelFormat format; + unsigned depth; + unsigned channels; + ColorOrder order; +}; + +const PixelFormatDesc s_known_pixel_formats[] = { + { PixelFormat::I1, 1, 1, ColorOrder::RGB }, + { PixelFormat::I8, 8, 1, ColorOrder::RGB }, + { PixelFormat::I16, 16, 1, ColorOrder::RGB }, + { PixelFormat::RGB111, 1, 3, ColorOrder::RGB }, + { PixelFormat::RGB888, 8, 3, ColorOrder::RGB }, + { PixelFormat::RGB161616, 16, 3, ColorOrder::RGB }, + { PixelFormat::BGR888, 8, 3, ColorOrder::BGR }, + { PixelFormat::BGR161616, 16, 3, ColorOrder::BGR }, +}; + + +ColorOrder get_pixel_format_color_order(PixelFormat format) +{ + for (const auto& desc : s_known_pixel_formats) { + if (desc.format == format) + return desc.order; + } + throw SaneException("Unknown pixel format %d", static_cast(format)); +} + + +unsigned get_pixel_format_depth(PixelFormat format) +{ + for (const auto& desc : s_known_pixel_formats) { + if (desc.format == format) + return desc.depth; + } + throw SaneException("Unknown pixel format %d", static_cast(format)); +} + +unsigned get_pixel_channels(PixelFormat format) +{ + for (const auto& desc : s_known_pixel_formats) { + if (desc.format == format) + return desc.channels; + } + throw SaneException("Unknown pixel format %d", static_cast(format)); +} + +std::size_t get_pixel_row_bytes(PixelFormat format, std::size_t width) +{ + std::size_t depth = get_pixel_format_depth(format) * get_pixel_channels(format); + std::size_t total_bits = depth * width; + return total_bits / 8 + ((total_bits % 8 > 0) ? 1 : 0); +} + +std::size_t get_pixels_from_row_bytes(PixelFormat format, std::size_t row_bytes) +{ + std::size_t depth = get_pixel_format_depth(format) * get_pixel_channels(format); + return (row_bytes * 8) / depth; +} + +PixelFormat create_pixel_format(unsigned depth, unsigned channels, ColorOrder order) +{ + for (const auto& desc : s_known_pixel_formats) { + if (desc.depth == depth && desc.channels == channels && desc.order == order) { + return desc.format; + } + } + throw SaneException("Unknown pixel format %d %d %d", depth, channels, + static_cast(order)); +} + +static inline unsigned read_bit(const std::uint8_t* data, std::size_t x) +{ + return (data[x / 8] >> (7 - (x % 8))) & 0x1; +} + +static inline void write_bit(std::uint8_t* data, std::size_t x, unsigned value) +{ + value = (value & 0x1) << (7 - (x % 8)); + std::uint8_t mask = 0x1 << (7 - (x % 8)); + + data[x / 8] = (data[x / 8] & ~mask) | (value & mask); +} + +Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x, PixelFormat format) +{ + switch (format) { + case PixelFormat::I1: { + std::uint16_t val = read_bit(data, x) ? 0xffff : 0x0000; + return Pixel(val, val, val); + } + case PixelFormat::RGB111: { + x *= 3; + std::uint16_t r = read_bit(data, x) ? 0xffff : 0x0000; + std::uint16_t g = read_bit(data, x + 1) ? 0xffff : 0x0000; + std::uint16_t b = read_bit(data, x + 2) ? 0xffff : 0x0000; + return Pixel(r, g, b); + } + case PixelFormat::I8: { + std::uint16_t val = std::uint16_t(data[x]) | (data[x] << 8); + return Pixel(val, val, val); + } + case PixelFormat::I16: { + x *= 2; + std::uint16_t val = std::uint16_t(data[x]) | (data[x + 1] << 8); + return Pixel(val, val, val); + } + case PixelFormat::RGB888: { + x *= 3; + std::uint16_t r = std::uint16_t(data[x]) | (data[x] << 8); + std::uint16_t g = std::uint16_t(data[x + 1]) | (data[x + 1] << 8); + std::uint16_t b = std::uint16_t(data[x + 2]) | (data[x + 2] << 8); + return Pixel(r, g, b); + } + case PixelFormat::BGR888: { + x *= 3; + std::uint16_t b = std::uint16_t(data[x]) | (data[x] << 8); + std::uint16_t g = std::uint16_t(data[x + 1]) | (data[x + 1] << 8); + std::uint16_t r = std::uint16_t(data[x + 2]) | (data[x + 2] << 8); + return Pixel(r, g, b); + } + case PixelFormat::RGB161616: { + x *= 6; + std::uint16_t r = std::uint16_t(data[x]) | (data[x + 1] << 8); + std::uint16_t g = std::uint16_t(data[x + 2]) | (data[x + 3] << 8); + std::uint16_t b = std::uint16_t(data[x + 4]) | (data[x + 5] << 8); + return Pixel(r, g, b); + } + case PixelFormat::BGR161616: { + x *= 6; + std::uint16_t b = std::uint16_t(data[x]) | (data[x + 1] << 8); + std::uint16_t g = std::uint16_t(data[x + 2]) | (data[x + 3] << 8); + std::uint16_t r = std::uint16_t(data[x + 4]) | (data[x + 5] << 8); + return Pixel(r, g, b); + } + default: + throw SaneException("Unknown pixel format %d", static_cast(format)); + } +} + +void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel, PixelFormat format) +{ + switch (format) { + case PixelFormat::I1: + write_bit(data, x, pixel.r & 0x8000 ? 1 : 0); + return; + case PixelFormat::RGB111: { + x *= 3; + write_bit(data, x, pixel.r & 0x8000 ? 1 : 0); + write_bit(data, x + 1,pixel.g & 0x8000 ? 1 : 0); + write_bit(data, x + 2, pixel.b & 0x8000 ? 1 : 0); + return; + } + case PixelFormat::I8: { + float val = (pixel.r >> 8) * 0.3f; + val += (pixel.g >> 8) * 0.59; + val += (pixel.b >> 8) * 0.11; + data[x] = static_cast(val); + return; + } + case PixelFormat::I16: { + x *= 2; + float val = pixel.r * 0.3f; + val += pixel.g * 0.59; + val += pixel.b * 0.11; + std::uint16_t val16 = val; + data[x] = val16 & 0xff; + data[x + 1] = (val16 >> 8) & 0xff; + return; + } + case PixelFormat::RGB888: { + x *= 3; + data[x] = pixel.r >> 8; + data[x + 1] = pixel.g >> 8; + data[x + 2] = pixel.b >> 8; + return; + } + case PixelFormat::BGR888: { + x *= 3; + data[x] = pixel.b >> 8; + data[x + 1] = pixel.g >> 8; + data[x + 2] = pixel.r >> 8; + return; + } + case PixelFormat::RGB161616: { + x *= 6; + data[x] = pixel.r & 0xff; + data[x + 1] = (pixel.r >> 8) & 0xff; + data[x + 2] = pixel.g & 0xff; + data[x + 3] = (pixel.g >> 8) & 0xff; + data[x + 4] = pixel.b & 0xff; + data[x + 5] = (pixel.b >> 8) & 0xff; + return; + } + case PixelFormat::BGR161616: + x *= 6; + data[x] = pixel.b & 0xff; + data[x + 1] = (pixel.b >> 8) & 0xff; + data[x + 2] = pixel.g & 0xff; + data[x + 3] = (pixel.g >> 8) & 0xff; + data[x + 4] = pixel.r & 0xff; + data[x + 5] = (pixel.r >> 8) & 0xff; + return; + default: + throw SaneException("Unknown pixel format %d", static_cast(format)); + } +} + +RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x, PixelFormat format) +{ + switch (format) { + case PixelFormat::I1: + return RawPixel(read_bit(data, x)); + case PixelFormat::RGB111: { + x *= 3; + return RawPixel(read_bit(data, x) << 2 | + (read_bit(data, x + 1) << 1) | + (read_bit(data, x + 2))); + } + case PixelFormat::I8: + return RawPixel(data[x]); + case PixelFormat::I16: { + x *= 2; + return RawPixel(data[x], data[x + 1]); + } + case PixelFormat::RGB888: + case PixelFormat::BGR888: { + x *= 3; + return RawPixel(data[x], data[x + 1], data[x + 2]); + } + case PixelFormat::RGB161616: + case PixelFormat::BGR161616: { + x *= 6; + return RawPixel(data[x], data[x + 1], data[x + 2], + data[x + 3], data[x + 4], data[x + 5]); + } + default: + throw SaneException("Unknown pixel format %d", static_cast(format)); + } +} + +void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel, PixelFormat format) +{ + switch (format) { + case PixelFormat::I1: + write_bit(data, x, pixel.data[0] & 0x1); + return; + case PixelFormat::RGB111: { + x *= 3; + write_bit(data, x, (pixel.data[0] >> 2) & 0x1); + write_bit(data, x + 1, (pixel.data[0] >> 1) & 0x1); + write_bit(data, x + 2, (pixel.data[0]) & 0x1); + return; + } + case PixelFormat::I8: + data[x] = pixel.data[0]; + return; + case PixelFormat::I16: { + x *= 2; + data[x] = pixel.data[0]; + data[x + 1] = pixel.data[1]; + return; + } + case PixelFormat::RGB888: + case PixelFormat::BGR888: { + x *= 3; + data[x] = pixel.data[0]; + data[x + 1] = pixel.data[1]; + data[x + 2] = pixel.data[2]; + return; + } + case PixelFormat::RGB161616: + case PixelFormat::BGR161616: { + x *= 6; + data[x] = pixel.data[0]; + data[x + 1] = pixel.data[1]; + data[x + 2] = pixel.data[2]; + data[x + 3] = pixel.data[3]; + data[x + 4] = pixel.data[4]; + data[x + 5] = pixel.data[5]; + return; + } + default: + throw SaneException("Unknown pixel format %d", static_cast(format)); + } +} + +std::uint16_t get_raw_channel_from_row(const std::uint8_t* data, std::size_t x, unsigned channel, + PixelFormat format) +{ + switch (format) { + case PixelFormat::I1: + return read_bit(data, x); + case PixelFormat::RGB111: + return read_bit(data, x * 3 + channel); + case PixelFormat::I8: + return data[x]; + case PixelFormat::I16: { + x *= 2; + return data[x] | (data[x + 1] << 8); + } + case PixelFormat::RGB888: + case PixelFormat::BGR888: + return data[x * 3 + channel]; + case PixelFormat::RGB161616: + case PixelFormat::BGR161616: + return data[x * 6 + channel * 2] | (data[x * 6 + channel * 2 + 1]) << 8; + default: + throw SaneException("Unknown pixel format %d", static_cast(format)); + } +} + +void set_raw_channel_to_row(std::uint8_t* data, std::size_t x, unsigned channel, + std::uint16_t pixel, PixelFormat format) +{ + switch (format) { + case PixelFormat::I1: + write_bit(data, x, pixel & 0x1); + return; + case PixelFormat::RGB111: { + write_bit(data, x * 3 + channel, pixel & 0x1); + return; + } + case PixelFormat::I8: + data[x] = pixel; + return; + case PixelFormat::I16: { + x *= 2; + data[x] = pixel; + data[x + 1] = pixel >> 8; + return; + } + case PixelFormat::RGB888: + case PixelFormat::BGR888: { + x *= 3; + data[x + channel] = pixel; + return; + } + case PixelFormat::RGB161616: + case PixelFormat::BGR161616: { + x *= 6; + data[x + channel * 2] = pixel; + data[x + channel * 2 + 1] = pixel >> 8; + return; + } + default: + throw SaneException("Unknown pixel format %d", static_cast(format)); + } +} + +template +Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x) +{ + return get_pixel_from_row(data, x, Format); +} + +template +void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel) +{ + set_pixel_to_row(data, x, pixel, Format); +} + +template +RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x) +{ + return get_raw_pixel_from_row(data, x, Format); +} + +template +void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel) +{ + set_raw_pixel_to_row(data, x, pixel, Format); +} + +template +std::uint16_t get_raw_channel_from_row(const std::uint8_t* data, std::size_t x, unsigned channel) +{ + return get_raw_channel_from_row(data, x, channel, Format); +} + +template +void set_raw_channel_to_row(std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel) +{ + set_raw_channel_to_row(data, x, channel, pixel, Format); +} + +template Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x); +template Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x); + +template RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x); +template RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x); + +template std::uint16_t get_raw_channel_from_row( + const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row( + const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row( + const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row( + const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row( + const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row( + const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row( + const std::uint8_t* data, std::size_t x, unsigned channel); +template std::uint16_t get_raw_channel_from_row + (const std::uint8_t* data, std::size_t x, unsigned channel); + +template void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel); +template void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel); + +template void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); +template void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); + +template void set_raw_channel_to_row( + std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row( + std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row( + std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row( + std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row( + std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row( + std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row( + std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); +template void set_raw_channel_to_row( + std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel); + +template +void convert_pixel_row_impl2(const std::uint8_t* in_data, std::uint8_t* out_data, + std::size_t count) +{ + for (std::size_t i = 0; i < count; ++i) { + Pixel pixel = get_pixel_from_row(in_data, i, SrcFormat); + set_pixel_to_row(out_data, i, pixel, DstFormat); + } +} + +template +void convert_pixel_row_impl(const std::uint8_t* in_data, std::uint8_t* out_data, + PixelFormat out_format, std::size_t count) +{ + switch (out_format) { + case PixelFormat::I1: { + convert_pixel_row_impl2(in_data, out_data, count); + return; + } + case PixelFormat::RGB111: { + convert_pixel_row_impl2(in_data, out_data, count); + return; + } + case PixelFormat::I8: { + convert_pixel_row_impl2(in_data, out_data, count); + return; + } + case PixelFormat::RGB888: { + convert_pixel_row_impl2(in_data, out_data, count); + return; + } + case PixelFormat::BGR888: { + convert_pixel_row_impl2(in_data, out_data, count); + return; + } + case PixelFormat::I16: { + convert_pixel_row_impl2(in_data, out_data, count); + return; + } + case PixelFormat::RGB161616: { + convert_pixel_row_impl2(in_data, out_data, count); + return; + } + case PixelFormat::BGR161616: { + convert_pixel_row_impl2(in_data, out_data, count); + return; + } + default: + throw SaneException("Unknown pixel format %d", static_cast(out_format)); + } +} +void convert_pixel_row_format(const std::uint8_t* in_data, PixelFormat in_format, + std::uint8_t* out_data, PixelFormat out_format, std::size_t count) +{ + if (in_format == out_format) { + std::memcpy(out_data, in_data, get_pixel_row_bytes(in_format, count)); + return; + } + + switch (in_format) { + case PixelFormat::I1: { + convert_pixel_row_impl(in_data, out_data, out_format, count); + return; + } + case PixelFormat::RGB111: { + convert_pixel_row_impl(in_data, out_data, out_format, count); + return; + } + case PixelFormat::I8: { + convert_pixel_row_impl(in_data, out_data, out_format, count); + return; + } + case PixelFormat::RGB888: { + convert_pixel_row_impl(in_data, out_data, out_format, count); + return; + } + case PixelFormat::BGR888: { + convert_pixel_row_impl(in_data, out_data, out_format, count); + return; + } + case PixelFormat::I16: { + convert_pixel_row_impl(in_data, out_data, out_format, count); + return; + } + case PixelFormat::RGB161616: { + convert_pixel_row_impl(in_data, out_data, out_format, count); + return; + } + case PixelFormat::BGR161616: { + convert_pixel_row_impl(in_data, out_data, out_format, count); + return; + } + default: + throw SaneException("Unknown pixel format %d", static_cast(in_format)); + } +} diff --git a/backend/genesys_image.h b/backend/genesys_image.h new file mode 100644 index 000000000..9ff012f9e --- /dev/null +++ b/backend/genesys_image.h @@ -0,0 +1,100 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + 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. +*/ + +#ifndef BACKEND_GENESYS_IMAGE_H +#define BACKEND_GENESYS_IMAGE_H + +#include "genesys_enums.h" +#include "genesys_error.h" +#include "genesys_image_buffer.h" + +#include +#include + +ColorOrder get_pixel_format_color_order(PixelFormat format); +unsigned get_pixel_format_depth(PixelFormat format); +unsigned get_pixel_channels(PixelFormat format); +std::size_t get_pixel_row_bytes(PixelFormat format, std::size_t width); + +std::size_t get_pixels_from_row_bytes(PixelFormat format, std::size_t row_bytes); + +PixelFormat create_pixel_format(unsigned depth, unsigned channels, ColorOrder order); + +// retrieves or sets the logical pixel values in 16-bit range. +Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x, PixelFormat format); +void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel, PixelFormat format); + +// retrieves or sets the physical pixel values. The low bytes of the RawPixel are interpreted as +// the retrieved values / values to set +RawPixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x, PixelFormat format); +void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel, PixelFormat format); + +// retrieves or sets the physical value of specific channel of the pixel. The channels are numbered +// in the same order as the pixel is laid out in memory, that is, whichever channel comes first +// has the index 0. E.g. 0-th channel in RGB888 is the red byte, but in BGR888 is the blue byte. +std::uint16_t get_raw_channel_from_row(const std::uint8_t* data, std::size_t x, unsigned channel, + PixelFormat format); +void set_raw_channel_to_row(std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel, + PixelFormat format); + +void convert_pixel_row_format(const std::uint8_t* in_data, PixelFormat in_format, + std::uint8_t* out_data, PixelFormat out_format, std::size_t count); + +template +Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x); +template +void set_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); + +template +Pixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x); +template +void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel); + +template +std::uint16_t get_raw_channel_from_row(const std::uint8_t* data, std::size_t x, unsigned channel); +template +void set_raw_channel_to_row(std::uint8_t* data, std::size_t x, unsigned channel, + std::uint16_t pixel); + + +#endif // ifndef BACKEND_GENESYS_IMAGE_H diff --git a/backend/genesys_image_buffer.cc b/backend/genesys_image_buffer.cc new file mode 100644 index 000000000..d7ce5df45 --- /dev/null +++ b/backend/genesys_image_buffer.cc @@ -0,0 +1,85 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + 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 "genesys_image_buffer.h" +#include "genesys_image.h" + +ImageBuffer::ImageBuffer(std::size_t size, ProducerCallback producer) : + producer_{producer}, + size_{size}, + buffer_offset_{size} +{ + buffer_.resize(size_); +} + +void ImageBuffer::get_data(std::size_t size, std::uint8_t* out_data) +{ + const std::uint8_t* out_data_end = out_data + size; + + auto copy_buffer = [&]() + { + std::size_t bytes_copy = std::min(out_data_end - out_data, available()); + std::memcpy(out_data, buffer_.data() + buffer_offset_, bytes_copy); + out_data += bytes_copy; + buffer_offset_ += bytes_copy; + }; + + // first, read remaining data from buffer + if (available() > 0) { + copy_buffer(); + } + + if (out_data == out_data_end) { + return; + } + + // now the buffer is empty and there's more data to be read + do { + buffer_offset_ = 0; + producer_(size_, buffer_.data()); + + copy_buffer(); + } while(out_data < out_data_end); +} diff --git a/backend/genesys_image_buffer.h b/backend/genesys_image_buffer.h new file mode 100644 index 000000000..1c8f5cfc4 --- /dev/null +++ b/backend/genesys_image_buffer.h @@ -0,0 +1,107 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + 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. +*/ + +#ifndef BACKEND_GENESYS_IMAGE_BUFFER_H +#define BACKEND_GENESYS_IMAGE_BUFFER_H + +#include "genesys_enums.h" +#include "genesys_row_buffer.h" +#include +#include + +struct Pixel +{ + Pixel() = default; + Pixel(std::uint16_t red, std::uint16_t green, std::uint16_t blue) : + r{red}, g{green}, b{blue} {} + + std::uint16_t r = 0; + std::uint16_t g = 0; + std::uint16_t b = 0; + + bool operator==(const Pixel& other) const + { + return r == other.r && g == other.g && b == other.b; + } +}; + +struct RawPixel +{ + RawPixel() = default; + RawPixel(std::uint8_t d0) : data{d0, 0, 0, 0, 0, 0} {} + RawPixel(std::uint8_t d0, std::uint8_t d1) : data{d0, d1, 0, 0, 0, 0} {} + RawPixel(std::uint8_t d0, std::uint8_t d1, std::uint8_t d2) : data{d0, d1, d2, 0, 0, 0} {} + RawPixel(std::uint8_t d0, std::uint8_t d1, std::uint8_t d2, + std::uint8_t d3, std::uint8_t d4, std::uint8_t d5) : data{d0, d1, d2, d3, d4, d5} {} + std::uint8_t data[6] = {}; + + bool operator==(const RawPixel& other) const + { + return std::equal(std::begin(data), std::end(data), + std::begin(other.data), std::end(other.data)); + } +}; + +// This class allows reading from row-based source in smaller or larger chunks of data +class ImageBuffer +{ +public: + using ProducerCallback = std::function; + + ImageBuffer() {} + ImageBuffer(std::size_t size, ProducerCallback producer); + + std::size_t size() const { return size_; } + std::size_t available() const { return size_ - buffer_offset_; } + + void get_data(std::size_t size, std::uint8_t* out_data); + +private: + ProducerCallback producer_; + std::size_t size_ = 0; + + std::size_t buffer_offset_ = 0; + std::vector buffer_; +}; + +#endif // BACKEND_GENESYS_IMAGE_BUFFER_H diff --git a/backend/genesys_image_pipeline.cc b/backend/genesys_image_pipeline.cc new file mode 100644 index 000000000..b53f6153f --- /dev/null +++ b/backend/genesys_image_pipeline.cc @@ -0,0 +1,527 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + 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 "genesys_image_pipeline.h" +#include + +ImagePipelineNode::~ImagePipelineNode() {} + +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} +{ +} + +void 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()); + return; + } + buffer_.get_data(get_row_bytes(), out_data); + curr_row_++; +} + +ImagePipelineNodeArraySource::ImagePipelineNodeArraySource(std::size_t width, std::size_t height, + PixelFormat format, + std::vector data) : + width_{width}, + height_{height}, + format_{format}, + data_{std::move(data)}, + next_row_{0} +{ + auto min_size = get_row_bytes() * height_; + if (data_.size() < min_size) { + throw SaneException("The given array is too small (%zu bytes). Need at least %zu", + data_.size(), min_size); + } +} + +void ImagePipelineNodeArraySource::get_next_row_data(std::uint8_t* out_data) +{ + if (next_row_ >= height_) { + throw SaneException("Trying to access line that is out of bounds"); + } + + std::memcpy(out_data, data_.data() + get_row_bytes() * next_row_, get_row_bytes()); + next_row_++; +} + + +void ImagePipelineNodeFormatConvert::get_next_row_data(std::uint8_t* out_data) +{ + auto src_format = source_.get_format(); + if (src_format == dst_format_) { + source_.get_next_row_data(out_data); + return; + } + + buffer_.clear(); + buffer_.resize(source_.get_row_bytes()); + source_.get_next_row_data(buffer_.data()); + + convert_pixel_row_format(buffer_.data(), src_format, out_data, dst_format_, get_width()); +} + +ImagePipelineNodeDesegment::ImagePipelineNodeDesegment(ImagePipelineNode& source, + std::size_t output_width, + const std::vector& segment_order, + std::size_t segment_size, + std::size_t interleaved_lines, + std::size_t pixels_per_chunk) : + source_(source), + output_width_{output_width}, + segment_order_{segment_order}, + segment_size_{segment_size}, + interleaved_lines_{interleaved_lines}, + pixels_per_chunk_{pixels_per_chunk}, + buffer_{get_row_bytes()} +{ + 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_size, + std::size_t interleaved_lines, + std::size_t pixels_per_chunk) : + source_(source), + output_width_{output_width}, + segment_size_{segment_size}, + 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_size, interleaved_lines, + pixels_per_chunk); + + segment_order_.resize(segment_count); + std::iota(segment_order_.begin(), segment_order_.end(), 0); +} + +void ImagePipelineNodeDesegment::get_next_row_data(uint8_t* out_data) +{ + buffer_.clear(); + for (std::size_t i = 0; i < interleaved_lines_; ++i) { + buffer_.push_back(); + 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); + + // verify that dev->session.output_segment_pixel_group_count == groups_count + // output_width = session.output_segment_pixel_group_count * session.segment_count + // segment_size_ = dev->session.conseq_pixel_dist_bytes + 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_; + // BUG: segment_size_ is specified in bytes, but here we're expected it in pixels + input_offset += segment_size_ * 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); + } + } + } +} + +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_row_bytes(), + interleaved_lines, pixels_per_chunk) +{} + +ImagePipelineNodeMergeMonoLines::ImagePipelineNodeMergeMonoLines(ImagePipelineNode& source, + ColorOrder color_order) : + source_(source), + buffer_(source_.get_row_bytes()) +{ + DBG_HELPER_ARGS(dbg, "color_order %d", static_cast(color_order)); + + output_format_ = get_output_format(source_.get_format(), color_order); +} + +void ImagePipelineNodeMergeMonoLines::get_next_row_data(std::uint8_t* out_data) +{ + buffer_.clear(); + for (unsigned i = 0; i < 3; ++i) { + buffer_.push_back(); + 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_); + } +} + +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(input_format), + static_cast(order)); +} + +ImagePipelineNodeSplitMonoLines::ImagePipelineNodeSplitMonoLines(ImagePipelineNode& source) : + source_(source), + next_channel_{0} +{ + output_format_ = get_output_format(source_.get_format()); +} + +void ImagePipelineNodeSplitMonoLines::get_next_row_data(std::uint8_t* out_data) +{ + if (next_channel_ == 0) { + buffer_.resize(source_.get_row_bytes()); + 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; +} + +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(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(source.get_format())); + } + extra_height_ = *std::max_element(channel_shifts_.begin(), channel_shifts_.end()); +} + +void ImagePipelineNodeComponentShiftLines::get_next_row_data(std::uint8_t* out_data) +{ + if (!buffer_.empty()) { + buffer_.pop_front(); + } + while (buffer_.height() < extra_height_ + 1) { + buffer_.push_back(); + 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); + } +} + +ImagePipelineNodePixelShiftLines::ImagePipelineNodePixelShiftLines( + ImagePipelineNode& source, const std::vector& 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()); +} + +void ImagePipelineNodePixelShiftLines::get_next_row_data(std::uint8_t* out_data) +{ + if (!buffer_.empty()) { + buffer_.pop_front(); + } + while (buffer_.height() < extra_height_ + 1) { + buffer_.push_back(); + source_.get_next_row_data(buffer_.get_back_row_ptr()); + } + + auto format = get_format(); + auto shift_count = pixel_shifts_.size(); + + std::array 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); + } + } +} + +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() {} + +void ImagePipelineNodeExtract::get_next_row_data(std::uint8_t* out_data) +{ + while (current_line_ < offset_y_) { + 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; + } + // now we're sure that the following holds: + // offset_y_ <= current_line_ < offset_y_ + source_.get_height()) + 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_++; +} + +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"); + } +} + +std::vector ImagePipelineStack::get_all_data() +{ + auto row_bytes = get_output_row_bytes(); + auto height = get_output_height(); + + std::vector 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; +} diff --git a/backend/genesys_image_pipeline.h b/backend/genesys_image_pipeline.h new file mode 100644 index 000000000..1a17fbe3a --- /dev/null +++ b/backend/genesys_image_pipeline.h @@ -0,0 +1,395 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + 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. +*/ + +#ifndef BACKEND_GENESYS_IMAGE_PIPELINE_H +#define BACKEND_GENESYS_IMAGE_PIPELINE_H + +#include "genesys_image.h" + +#include +#include +#include + +class ImagePipelineNode +{ +public: + virtual ~ImagePipelineNode(); + + virtual std::size_t get_width() const = 0; + virtual std::size_t get_height() const = 0; + virtual PixelFormat get_format() const = 0; + + std::size_t get_row_bytes() const + { + return get_pixel_row_bytes(get_format(), get_width()); + } + + virtual void get_next_row_data(std::uint8_t* out_data) = 0; +}; + +// A pipeline node that produces data from a callable +class ImagePipelineNodeCallableSource : public ImagePipelineNode +{ +public: + using ProducerCallback = std::function; + + ImagePipelineNodeCallableSource(std::size_t width, std::size_t height, PixelFormat format, + ProducerCallback producer) : + producer_{producer}, + width_{width}, + height_{height}, + format_{format} + {} + + std::size_t get_width() const override { return width_; } + std::size_t get_height() const override { return height_; } + PixelFormat get_format() const override { return format_; } + + void get_next_row_data(std::uint8_t* out_data) override + { + producer_(get_row_bytes(), out_data); + } + +private: + ProducerCallback producer_; + std::size_t width_ = 0; + std::size_t height_ = 0; + PixelFormat format_ = PixelFormat::UNKNOWN; +}; + +// A pipeline node that produces data from a callable requesting fixed-size chunks. +class ImagePipelineNodeBufferedCallableSource : public ImagePipelineNode +{ +public: + using ProducerCallback = std::function; + + ImagePipelineNodeBufferedCallableSource(std::size_t width, std::size_t height, + PixelFormat format, std::size_t input_batch_size, + ProducerCallback producer); + + std::size_t get_width() const override { return width_; } + std::size_t get_height() const override { return height_; } + PixelFormat get_format() const override { return format_; } + + void get_next_row_data(std::uint8_t* out_data) override; + + std::size_t buffer_size() const { return buffer_.size(); } + std::size_t buffer_available() const { return buffer_.available(); } + +private: + ProducerCallback producer_; + std::size_t width_ = 0; + std::size_t height_ = 0; + PixelFormat format_ = PixelFormat::UNKNOWN; + + std::size_t curr_row_ = 0; + + ImageBuffer buffer_; +}; + +// A pipeline node that produces data from the given array. +class ImagePipelineNodeArraySource : public ImagePipelineNode +{ +public: + ImagePipelineNodeArraySource(std::size_t width, std::size_t height, PixelFormat format, + std::vector data); + + std::size_t get_width() const override { return width_; } + std::size_t get_height() const override { return height_; } + PixelFormat get_format() const override { return format_; } + + void get_next_row_data(std::uint8_t* out_data) override; + +private: + std::size_t width_ = 0; + std::size_t height_ = 0; + PixelFormat format_ = PixelFormat::UNKNOWN; + + std::vector data_; + std::size_t next_row_ = 0; +}; + + +// A pipeline node that converts between pixel formats +class ImagePipelineNodeFormatConvert : public ImagePipelineNode +{ +public: + ImagePipelineNodeFormatConvert(ImagePipelineNode& source, PixelFormat dst_format) : + source_(source), + dst_format_{dst_format} + {} + + ~ImagePipelineNodeFormatConvert() override = default; + + std::size_t get_width() const override { return source_.get_width(); } + std::size_t get_height() const override { return source_.get_height(); } + PixelFormat get_format() const override { return dst_format_; } + + void get_next_row_data(std::uint8_t* out_data) override; + +private: + ImagePipelineNode& source_; + PixelFormat dst_format_; + std::vector buffer_; +}; + +// A pipeline node that handles data that comes out of segmented sensors. Note that the width of +// the output data does not necessarily match the input data width, because in many cases almost +// all width of the image needs to be read in order to desegment it. +class ImagePipelineNodeDesegment : public ImagePipelineNode +{ +public: + // segment size is specified in bytes. TODO: switch to pixels + ImagePipelineNodeDesegment(ImagePipelineNode& source, + std::size_t output_width, + const std::vector& segment_order, + std::size_t segment_size, + std::size_t interleaved_lines, + std::size_t pixels_per_chunk); + + // segment size is specified in bytes. TODO: switch to pixels + ImagePipelineNodeDesegment(ImagePipelineNode& source, + std::size_t output_width, + std::size_t segment_count, + std::size_t segment_size, + std::size_t interleaved_lines, + std::size_t pixels_per_chunk); + + ~ImagePipelineNodeDesegment() override = default; + + std::size_t get_width() const override { return output_width_; } + std::size_t get_height() const override { return source_.get_height() / interleaved_lines_; } + PixelFormat get_format() const override { return source_.get_format(); } + + void get_next_row_data(std::uint8_t* out_data) override; + +private: + ImagePipelineNode& source_; + std::size_t output_width_; + std::vector segment_order_; + std::size_t segment_size_ = 0; + std::size_t interleaved_lines_ = 0; + std::size_t pixels_per_chunk_ = 0; + + RowBuffer buffer_; +}; + +// A pipeline node that deinterleaves data on multiple lines +class ImagePipelineNodeDeinterleaveLines : public ImagePipelineNodeDesegment +{ +public: + ImagePipelineNodeDeinterleaveLines(ImagePipelineNode& source, + std::size_t interleaved_lines, + std::size_t pixels_per_chunk); +}; + +// A pipeline node that merges 3 mono lines into a color channel +class ImagePipelineNodeMergeMonoLines : public ImagePipelineNode +{ +public: + ImagePipelineNodeMergeMonoLines(ImagePipelineNode& source, + ColorOrder color_order); + + std::size_t get_width() const override { return source_.get_width(); } + std::size_t get_height() const override { return source_.get_height() / 3; } + PixelFormat get_format() const override { return output_format_; } + + void get_next_row_data(std::uint8_t* out_data) override; + +private: + static PixelFormat get_output_format(PixelFormat input_format, ColorOrder order); + + ImagePipelineNode& source_; + PixelFormat output_format_ = PixelFormat::UNKNOWN; + + RowBuffer buffer_; +}; + +// A pipeline node that splits a color channel into 3 mono lines +class ImagePipelineNodeSplitMonoLines : public ImagePipelineNode +{ +public: + ImagePipelineNodeSplitMonoLines(ImagePipelineNode& source); + + std::size_t get_width() const override { return source_.get_width(); } + std::size_t get_height() const override { return source_.get_height() * 3; } + PixelFormat get_format() const override { return output_format_; } + + void get_next_row_data(std::uint8_t* out_data) override; + +private: + static PixelFormat get_output_format(PixelFormat input_format); + + ImagePipelineNode& source_; + PixelFormat output_format_ = PixelFormat::UNKNOWN; + + std::vector buffer_; + unsigned next_channel_ = 0; +}; + +// A pipeline node that shifts colors across lines by the given offsets +class ImagePipelineNodeComponentShiftLines : public ImagePipelineNode +{ +public: + ImagePipelineNodeComponentShiftLines(ImagePipelineNode& source, + unsigned shift_r, unsigned shift_g, unsigned shift_b); + + std::size_t get_width() const override { return source_.get_width(); } + std::size_t get_height() const override { return source_.get_height() - extra_height_; } + PixelFormat get_format() const override { return source_.get_format(); } + + void get_next_row_data(std::uint8_t* out_data) override; + +private: + ImagePipelineNode& source_; + std::size_t extra_height_ = 0; + + std::array channel_shifts_; + + RowBuffer buffer_; +}; + +// A pipeline node that shifts pixels across lines by the given offsets (performs unstaggering) +class ImagePipelineNodePixelShiftLines : public ImagePipelineNode +{ +public: + constexpr static std::size_t MAX_SHIFTS = 2; + + ImagePipelineNodePixelShiftLines(ImagePipelineNode& source, + const std::vector& shifts); + + std::size_t get_width() const override { return source_.get_width(); } + std::size_t get_height() const override { return source_.get_height() - extra_height_; } + PixelFormat get_format() const override { return source_.get_format(); } + + void get_next_row_data(std::uint8_t* out_data) override; + +private: + ImagePipelineNode& source_; + std::size_t extra_height_ = 0; + + std::vector pixel_shifts_; + + RowBuffer buffer_; +}; + +// A pipeline node that extracts a sub-image from the image. Padding and cropping is done as needed. +// The class can't pad to the left of the image currently, as only positive offsets are accepted. +class ImagePipelineNodeExtract : public ImagePipelineNode +{ +public: + ImagePipelineNodeExtract(ImagePipelineNode& source, + std::size_t offset_x, std::size_t offset_y, + std::size_t width, std::size_t height); + + ~ImagePipelineNodeExtract() override; + + std::size_t get_width() const override { return width_; } + std::size_t get_height() const override { return height_; } + PixelFormat get_format() const override { return source_.get_format(); } + + void get_next_row_data(std::uint8_t* out_data) override; + +private: + ImagePipelineNode& source_; + std::size_t offset_x_ = 0; + std::size_t offset_y_ = 0; + std::size_t width_ = 0; + std::size_t height_ = 0; + + std::size_t current_line_ = 0; + std::vector cached_line_; +}; + +class ImagePipelineStack +{ +public: + ImagePipelineStack() {} + + std::size_t get_input_width() const; + std::size_t get_input_height() const; + PixelFormat get_input_format() const; + std::size_t get_input_row_bytes() const; + + std::size_t get_output_width() const; + std::size_t get_output_height() const; + PixelFormat get_output_format() const; + std::size_t get_output_row_bytes() const; + + void clear() + { + nodes_.clear(); + } + + template + void push_first_node(Args&&... args) + { + if (!nodes_.empty()) { + throw SaneException("Trying to append first node when there are existing nodes"); + } + nodes_.emplace_back(std::unique_ptr(new Node(std::forward(args)...))); + } + + template + void push_node(Args&&... args) + { + ensure_node_exists(); + nodes_.emplace_back(std::unique_ptr(new Node(*nodes_.back(), + std::forward(args)...))); + } + + void get_next_row_data(std::uint8_t* out_data) + { + nodes_.back()->get_next_row_data(out_data); + } + + std::vector get_all_data(); + +private: + void ensure_node_exists() const; + + std::vector> nodes_; +}; + + +#endif // ifndef BACKEND_GENESYS_IMAGE_PIPELINE_H diff --git a/backend/genesys_row_buffer.h b/backend/genesys_row_buffer.h new file mode 100644 index 000000000..5b1a7c72a --- /dev/null +++ b/backend/genesys_row_buffer.h @@ -0,0 +1,210 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + 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. +*/ + +#ifndef BACKEND_GENESYS_LINE_BUFFER_H +#define BACKEND_GENESYS_LINE_BUFFER_H + +#include "genesys_error.h" + +#include +#include +#include +#include + +class RowBuffer +{ +public: + RowBuffer(std::size_t line_bytes) : row_bytes_{line_bytes} {} + RowBuffer(const RowBuffer&) = default; + RowBuffer& operator=(const RowBuffer&) = default; + ~RowBuffer() = default; + + const std::uint8_t* get_row_ptr(std::size_t y) const + { + if (y >= height()) { + throw SaneException("y %zu is out of range", y); + } + return data_.data() + row_bytes_ * get_row_index(y); + } + + std::uint8_t* get_row_ptr(std::size_t y) + { + if (y >= height()) { + throw SaneException("y %zu is out of range", y); + } + return data_.data() + row_bytes_ * get_row_index(y); + } + + const std::uint8_t* get_front_row_ptr() const { return get_row_ptr(0); } + std::uint8_t* get_front_row_ptr() { return get_row_ptr(0); } + const std::uint8_t* get_back_row_ptr() const { return get_row_ptr(height() - 1); } + std::uint8_t* get_back_row_ptr() { return get_row_ptr(height() - 1); } + + bool empty() const { return is_linear_ && first_ == last_; } + + bool full() + { + if (is_linear_) { + return last_ == buffer_end_; + } + return first_ == last_; + } + + bool is_linear() const { return is_linear_; } + + void linearize() + { + if (!is_linear_) { + std::rotate(data_.begin(), data_.begin() + row_bytes_ * first_, data_.end()); + last_ = height(); + first_ = 0; + is_linear_ = true; + } + } + + void pop_front() + { + if (empty()) { + throw SaneException("Trying to pop out of empty() line buffer"); + } + + first_++; + if (first_ == last_) { + first_ = 0; + last_ = 0; + is_linear_ = true; + } else if (first_ == buffer_end_) { + first_ = 0; + is_linear_ = true; + } + } + + void push_front() + { + if (height() + 1 >= height_capacity()) { + ensure_capacity(std::max(1, height() * 2)); + } + + if (first_ == 0) { + is_linear_ = false; + first_ = buffer_end_; + } + first_--; + } + + void pop_back() + { + if (empty()) { + throw SaneException("Trying to pop out of empty() line buffer"); + } + if (last_ == 0) { + last_ = buffer_end_; + is_linear_ = true; + } + last_--; + if (first_ == last_) { + first_ = 0; + last_ = 0; + is_linear_ = true; + } + } + + void push_back() + { + if (height() + 1 >= height_capacity()) { + ensure_capacity(std::max(1, height() * 2)); + } + + if (last_ == buffer_end_) { + is_linear_ = false; + last_ = 0; + } + last_++; + } + + std::size_t row_bytes() const { return row_bytes_; } + + std::size_t height() const + { + if (!is_linear_) { + return last_ + buffer_end_ - first_; + } + return last_ - first_; + } + + std::size_t height_capacity() const { return buffer_end_; } + + void clear() + { + first_ = 0; + last_ = 0; + } + +private: + std::size_t get_row_index(std::size_t index) const + { + if (index >= buffer_end_ - first_) { + return index - (buffer_end_ - first_); + } + return index + first_; + } + + void ensure_capacity(std::size_t capacity) + { + if (capacity < height_capacity()) + return; + linearize(); + data_.resize(capacity * row_bytes_); + buffer_end_ = capacity; + } + +private: + std::size_t row_bytes_ = 0; + std::size_t first_ = 0; + std::size_t last_ = 0; + std::size_t buffer_end_ = 0; + bool is_linear_ = true; + std::vector data_; +}; + +#endif // BACKEND_GENESYS_LINE_BUFFER_H diff --git a/testsuite/backend/genesys/Makefile.am b/testsuite/backend/genesys/Makefile.am index e96a17dff..15b2bb6e2 100644 --- a/testsuite/backend/genesys/Makefile.am +++ b/testsuite/backend/genesys/Makefile.am @@ -19,8 +19,11 @@ TESTS = $(check_PROGRAMS) AM_CPPFLAGS += -I. -I$(srcdir) -I$(top_builddir)/include -I$(top_srcdir)/include $(USB_CFLAGS) \ -DBACKEND_NAME=genesys -genesys_tests_SOURCES = tests.cc tests.h minigtest.cc minigtest.h \ +genesys_tests_SOURCES = tests.cc tests.h minigtest.cc minigtest.h tests_printers.h \ tests_calibration.cc \ + tests_image.cc \ + tests_image_pipeline.cc \ + tests_row_buffer.cc \ tests_sensor.cc genesys_tests_LDADD = $(TEST_LDADD) diff --git a/testsuite/backend/genesys/minigtest.h b/testsuite/backend/genesys/minigtest.h index 752efe160..4bd218237 100644 --- a/testsuite/backend/genesys/minigtest.h +++ b/testsuite/backend/genesys/minigtest.h @@ -38,7 +38,7 @@ inline void print_location(std::ostream& out, const char* function, const char* template void check_equal(const T& t, const U& u, const char* function, const char* path, unsigned line) { - if (t != u) { + if (!(t == u)) { s_num_failures++; std::cerr << "FAILURE at "; print_location(std::cerr, function, path, line); @@ -69,7 +69,7 @@ inline void check_true(bool x, const char* function, const char* path, unsigned while (false) #define ASSERT_TRUE(x) do { check_true(bool(x), __func__, __FILE__, __LINE__); } \ while (false) -#define ASSERT_FALSE(x) do { !check_true(bool(x), __func__, __FILE__, __LINE__); } \ +#define ASSERT_FALSE(x) do { check_true(!bool(x), __func__, __FILE__, __LINE__); } \ while (false) int finish_tests(); diff --git a/testsuite/backend/genesys/tests.cc b/testsuite/backend/genesys/tests.cc index 0e1c17228..5288b9766 100644 --- a/testsuite/backend/genesys/tests.cc +++ b/testsuite/backend/genesys/tests.cc @@ -28,6 +28,9 @@ int main() { test_calibration_parsing(); + test_image(); + test_image_pipeline(); + test_row_buffer(); test_sensor(); return finish_tests(); } diff --git a/testsuite/backend/genesys/tests.h b/testsuite/backend/genesys/tests.h index 9a40dcc97..0db744c38 100644 --- a/testsuite/backend/genesys/tests.h +++ b/testsuite/backend/genesys/tests.h @@ -24,6 +24,9 @@ #define SANE_TESTSUITE_BACKEND_GENESYS_GENESYS_UNIT_TEST_H void test_calibration_parsing(); +void test_image(); +void test_image_pipeline(); +void test_row_buffer(); void test_sensor(); #endif diff --git a/testsuite/backend/genesys/tests_image.cc b/testsuite/backend/genesys/tests_image.cc new file mode 100644 index 000000000..88b827cf7 --- /dev/null +++ b/testsuite/backend/genesys/tests_image.cc @@ -0,0 +1,571 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + 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. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "tests.h" +#include "minigtest.h" +#include "tests_printers.h" + +#include "../../../backend/genesys_image_pipeline.h" +#include + +void test_get_pixel_from_row() +{ + std::vector data = { + 0x12, 0x34, 0x56, 0x67, 0x89, 0xab, + 0xcd, 0xef, 0x21, 0x43, 0x65, 0x87 + }; + ASSERT_EQ(get_pixel_from_row(data.data(), 0, PixelFormat::I1), + Pixel(0, 0, 0)); + ASSERT_EQ(get_pixel_from_row(data.data(), 3, PixelFormat::I1), + Pixel(0xffff, 0xffff, 0xffff)); + ASSERT_EQ(get_pixel_from_row(data.data(), 0, PixelFormat::RGB111), + Pixel(0, 0, 0)); + ASSERT_EQ(get_pixel_from_row(data.data(), 1, PixelFormat::RGB111), + Pixel(0xffff, 0, 0)); + ASSERT_EQ(get_pixel_from_row(data.data(), 2, PixelFormat::RGB111), + Pixel(0xffff, 0, 0)); + ASSERT_EQ(get_pixel_from_row(data.data(), 3, PixelFormat::RGB111), + Pixel(0, 0xffff, 0xffff)); + ASSERT_EQ(get_pixel_from_row(data.data(), 0, PixelFormat::I8), + Pixel(0x1212, 0x1212, 0x1212)); + ASSERT_EQ(get_pixel_from_row(data.data(), 1, PixelFormat::I8), + Pixel(0x3434, 0x3434, 0x3434)); + ASSERT_EQ(get_pixel_from_row(data.data(), 0, PixelFormat::RGB888), + Pixel(0x1212, 0x3434, 0x5656)); + ASSERT_EQ(get_pixel_from_row(data.data(), 1, PixelFormat::RGB888), + Pixel(0x6767, 0x8989, 0xabab)); + ASSERT_EQ(get_pixel_from_row(data.data(), 0, PixelFormat::BGR888), + Pixel(0x5656, 0x3434, 0x1212)); + ASSERT_EQ(get_pixel_from_row(data.data(), 1, PixelFormat::BGR888), + Pixel(0xabab, 0x8989, 0x6767)); + ASSERT_EQ(get_pixel_from_row(data.data(), 0, PixelFormat::I16), + Pixel(0x3412, 0x3412, 0x3412)); + ASSERT_EQ(get_pixel_from_row(data.data(), 1, PixelFormat::I16), + Pixel(0x6756, 0x6756, 0x6756)); + ASSERT_EQ(get_pixel_from_row(data.data(), 0, PixelFormat::RGB161616), + Pixel(0x3412, 0x6756, 0xab89)); + ASSERT_EQ(get_pixel_from_row(data.data(), 1, PixelFormat::RGB161616), + Pixel(0xefcd, 0x4321, 0x8765)); + ASSERT_EQ(get_pixel_from_row(data.data(), 0, PixelFormat::BGR161616), + Pixel(0xab89, 0x6756, 0x3412)); + ASSERT_EQ(get_pixel_from_row(data.data(), 1, PixelFormat::BGR161616), + Pixel(0x8765, 0x4321, 0xefcd)); +} + +void test_set_pixel_to_row() +{ + using Data = std::vector; + Data data; + data.resize(12, 0); + + auto reset = [&]() { std::fill(data.begin(), data.end(), 0); }; + + Pixel pixel; + + pixel = Pixel(0x8000, 0x8000, 0x8000); + set_pixel_to_row(data.data(), 0, pixel, PixelFormat::I1); + ASSERT_EQ(data, Data({0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x8000, 0x8000, 0x8000); + set_pixel_to_row(data.data(), 2, pixel, PixelFormat::I1); + ASSERT_EQ(data, Data({0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x8000, 0x8000, 0x8000); + set_pixel_to_row(data.data(), 8, pixel, PixelFormat::I1); + ASSERT_EQ(data, Data({0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x8000, 0x0000, 0x8000); + set_pixel_to_row(data.data(), 0, pixel, PixelFormat::RGB111); + ASSERT_EQ(data, Data({0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x8000, 0x0000, 0x8000); + set_pixel_to_row(data.data(), 1, pixel, PixelFormat::RGB111); + ASSERT_EQ(data, Data({0x14, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x8000, 0x0000, 0x8000); + set_pixel_to_row(data.data(), 8, pixel, PixelFormat::RGB111); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x1200, 0x1200, 0x1200); + set_pixel_to_row(data.data(), 0, pixel, PixelFormat::I8); + ASSERT_EQ(data, Data({0x12, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x1200, 0x1200, 0x1200); + set_pixel_to_row(data.data(), 2, pixel, PixelFormat::I8); + ASSERT_EQ(data, Data({0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x1200, 0x3400, 0x5600); + set_pixel_to_row(data.data(), 0, pixel, PixelFormat::RGB888); + ASSERT_EQ(data, Data({0x12, 0x34, 0x56, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x1200, 0x3400, 0x5600); + set_pixel_to_row(data.data(), 1, pixel, PixelFormat::RGB888); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x12, 0x34, 0x56, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x1200, 0x3400, 0x5600); + set_pixel_to_row(data.data(), 0, pixel, PixelFormat::BGR888); + ASSERT_EQ(data, Data({0x56, 0x34, 0x12, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x1200, 0x3400, 0x5600); + set_pixel_to_row(data.data(), 1, pixel, PixelFormat::BGR888); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x56, 0x34, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x1234, 0x1234, 0x1234); + set_pixel_to_row(data.data(), 0, pixel, PixelFormat::I16); + ASSERT_EQ(data, Data({0x34, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x1234, 0x1234, 0x1234); + set_pixel_to_row(data.data(), 1, pixel, PixelFormat::I16); + ASSERT_EQ(data, Data({0x00, 0x00, 0x34, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x1234, 0x5678, 0x9abc); + set_pixel_to_row(data.data(), 0, pixel, PixelFormat::RGB161616); + ASSERT_EQ(data, Data({0x34, 0x12, 0x78, 0x56, 0xbc, 0x9a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x1234, 0x5678, 0x9abc); + set_pixel_to_row(data.data(), 1, pixel, PixelFormat::RGB161616); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x12, 0x78, 0x56, 0xbc, 0x9a})); + reset(); + + pixel = Pixel(0x1234, 0x5678, 0x9abc); + set_pixel_to_row(data.data(), 0, pixel, PixelFormat::BGR161616); + ASSERT_EQ(data, Data({0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = Pixel(0x1234, 0x5678, 0x9abc); + set_pixel_to_row(data.data(), 1, pixel, PixelFormat::BGR161616); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12})); + reset(); +} + +void test_get_raw_pixel_from_row() +{ + std::vector data = { + 0x12, 0x34, 0x56, 0x67, 0x89, 0xab, + 0xcd, 0xef, 0x21, 0x43, 0x65, 0x87 + }; + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 0, PixelFormat::I1), + RawPixel(0x0)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 3, PixelFormat::I1), + RawPixel(0x1)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 0, PixelFormat::RGB111), + RawPixel(0)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 1, PixelFormat::RGB111), + RawPixel(0x4)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 2, PixelFormat::RGB111), + RawPixel(0x4)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 3, PixelFormat::RGB111), + RawPixel(0x3)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 0, PixelFormat::I8), + RawPixel(0x12)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 1, PixelFormat::I8), + RawPixel(0x34)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 0, PixelFormat::RGB888), + RawPixel(0x12, 0x34, 0x56)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 1, PixelFormat::RGB888), + RawPixel(0x67, 0x89, 0xab)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 0, PixelFormat::BGR888), + RawPixel(0x12, 0x34, 0x56)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 1, PixelFormat::BGR888), + RawPixel(0x67, 0x89, 0xab)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 0, PixelFormat::I16), + RawPixel(0x12, 0x34)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 1, PixelFormat::I16), + RawPixel(0x56, 0x67)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 0, PixelFormat::RGB161616), + RawPixel(0x12, 0x34, 0x56, 0x67, 0x89, 0xab)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 1, PixelFormat::RGB161616), + RawPixel(0xcd, 0xef, 0x21, 0x43, 0x65, 0x87)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 0, PixelFormat::BGR161616), + RawPixel(0x12, 0x34, 0x56, 0x67, 0x89, 0xab)); + ASSERT_EQ(get_raw_pixel_from_row(data.data(), 1, PixelFormat::BGR161616), + RawPixel(0xcd, 0xef, 0x21, 0x43, 0x65, 0x87)); +} + +void test_set_raw_pixel_to_row() +{ + using Data = std::vector; + Data data; + data.resize(12, 0); + + auto reset = [&]() { std::fill(data.begin(), data.end(), 0); }; + + RawPixel pixel; + + pixel = RawPixel(0x01); + set_raw_pixel_to_row(data.data(), 0, pixel, PixelFormat::I1); + ASSERT_EQ(data, Data({0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x01); + set_raw_pixel_to_row(data.data(), 2, pixel, PixelFormat::I1); + ASSERT_EQ(data, Data({0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x01); + set_raw_pixel_to_row(data.data(), 8, pixel, PixelFormat::I1); + ASSERT_EQ(data, Data({0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x05); + set_raw_pixel_to_row(data.data(), 0, pixel, PixelFormat::RGB111); + ASSERT_EQ(data, Data({0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x05); + set_raw_pixel_to_row(data.data(), 1, pixel, PixelFormat::RGB111); + ASSERT_EQ(data, Data({0x14, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x05); + set_raw_pixel_to_row(data.data(), 8, pixel, PixelFormat::RGB111); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x12); + set_raw_pixel_to_row(data.data(), 0, pixel, PixelFormat::I8); + ASSERT_EQ(data, Data({0x12, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x12); + set_raw_pixel_to_row(data.data(), 2, pixel, PixelFormat::I8); + ASSERT_EQ(data, Data({0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x12, 0x34, 0x56); + set_raw_pixel_to_row(data.data(), 0, pixel, PixelFormat::RGB888); + ASSERT_EQ(data, Data({0x12, 0x34, 0x56, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x12, 0x34, 0x56); + set_raw_pixel_to_row(data.data(), 1, pixel, PixelFormat::RGB888); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x12, 0x34, 0x56, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x12, 0x34, 0x56); + set_raw_pixel_to_row(data.data(), 0, pixel, PixelFormat::BGR888); + ASSERT_EQ(data, Data({0x12, 0x34, 0x56, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x12, 0x34, 0x56); + set_raw_pixel_to_row(data.data(), 1, pixel, PixelFormat::BGR888); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x12, 0x34, 0x56, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x34, 0x12); + set_raw_pixel_to_row(data.data(), 0, pixel, PixelFormat::I16); + ASSERT_EQ(data, Data({0x34, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x34, 0x12); + set_raw_pixel_to_row(data.data(), 1, pixel, PixelFormat::I16); + ASSERT_EQ(data, Data({0x00, 0x00, 0x34, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x34, 0x12, 0x78, 0x56, 0xbc, 0x9a); + set_raw_pixel_to_row(data.data(), 0, pixel, PixelFormat::RGB161616); + ASSERT_EQ(data, Data({0x34, 0x12, 0x78, 0x56, 0xbc, 0x9a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x34, 0x12, 0x78, 0x56, 0xbc, 0x9a); + set_raw_pixel_to_row(data.data(), 1, pixel, PixelFormat::RGB161616); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x12, 0x78, 0x56, 0xbc, 0x9a})); + reset(); + + pixel = RawPixel(0x34, 0x12, 0x78, 0x56, 0xbc, 0x9a); + set_raw_pixel_to_row(data.data(), 0, pixel, PixelFormat::BGR161616); + ASSERT_EQ(data, Data({0x34, 0x12, 0x78, 0x56, 0xbc, 0x9a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + pixel = RawPixel(0x34, 0x12, 0x78, 0x56, 0xbc, 0x9a); + set_raw_pixel_to_row(data.data(), 1, pixel, PixelFormat::BGR161616); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x12, 0x78, 0x56, 0xbc, 0x9a})); + reset(); +} + +void test_get_raw_channel_from_row() +{ + std::vector data = { + 0x12, 0x34, 0x56, 0x67, 0x89, 0xab, + 0xcd, 0xef, 0x21, 0x43, 0x65, 0x87 + }; + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 0, PixelFormat::I1), 0); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 3, 0, PixelFormat::I1), 1); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 0, PixelFormat::RGB111), 0); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 1, PixelFormat::RGB111), 0); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 2, PixelFormat::RGB111), 0); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 0, PixelFormat::RGB111), 1); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 1, PixelFormat::RGB111), 0); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 2, PixelFormat::RGB111), 0); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 2, 0, PixelFormat::RGB111), 1); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 2, 1, PixelFormat::RGB111), 0); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 2, 2, PixelFormat::RGB111), 0); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 3, 0, PixelFormat::RGB111), 0); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 3, 1, PixelFormat::RGB111), 1); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 3, 2, PixelFormat::RGB111), 1); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 0, PixelFormat::I8), 0x12); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 0, PixelFormat::I8), 0x34); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 0, PixelFormat::RGB888), 0x12); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 1, PixelFormat::RGB888), 0x34); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 2, PixelFormat::RGB888), 0x56); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 0, PixelFormat::RGB888), 0x67); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 1, PixelFormat::RGB888), 0x89); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 2, PixelFormat::RGB888), 0xab); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 0, PixelFormat::BGR888), 0x12); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 1, PixelFormat::BGR888), 0x34); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 2, PixelFormat::BGR888), 0x56); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 0, PixelFormat::BGR888), 0x67); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 1, PixelFormat::BGR888), 0x89); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 2, PixelFormat::BGR888), 0xab); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 0, PixelFormat::I16), 0x3412); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 0, PixelFormat::I16), 0x6756); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 0, PixelFormat::RGB161616), 0x3412); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 1, PixelFormat::RGB161616), 0x6756); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 2, PixelFormat::RGB161616), 0xab89); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 0, PixelFormat::RGB161616), 0xefcd); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 1, PixelFormat::RGB161616), 0x4321); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 2, PixelFormat::RGB161616), 0x8765); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 0, PixelFormat::BGR161616), 0x3412); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 1, PixelFormat::BGR161616), 0x6756); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 0, 2, PixelFormat::BGR161616), 0xab89); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 0, PixelFormat::BGR161616), 0xefcd); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 1, PixelFormat::BGR161616), 0x4321); + ASSERT_EQ(get_raw_channel_from_row(data.data(), 1, 2, PixelFormat::BGR161616), 0x8765); +} + +void test_set_raw_channel_to_row() +{ + using Data = std::vector; + Data data; + data.resize(12, 0); + + auto reset = [&]() { std::fill(data.begin(), data.end(), 0); }; + + set_raw_channel_to_row(data.data(), 0, 0, 1, PixelFormat::I1); + ASSERT_EQ(data, Data({0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 2, 0, 1, PixelFormat::I1); + ASSERT_EQ(data, Data({0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 8, 0, 1, PixelFormat::I1); + ASSERT_EQ(data, Data({0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 0, 0, 1, PixelFormat::RGB111); + ASSERT_EQ(data, Data({0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 0, 1, 1, PixelFormat::RGB111); + ASSERT_EQ(data, Data({0x40, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 0, 2, 1, PixelFormat::RGB111); + ASSERT_EQ(data, Data({0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 8, 0, 1, PixelFormat::RGB111); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 0, 0, 0x12, PixelFormat::I8); + ASSERT_EQ(data, Data({0x12, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 2, 0, 0x12, PixelFormat::I8); + ASSERT_EQ(data, Data({0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + for (auto format : { PixelFormat::RGB888, PixelFormat::BGR888 }) { + set_raw_channel_to_row(data.data(), 0, 0, 0x12, format); + ASSERT_EQ(data, Data({0x12, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 0, 1, 0x12, format); + ASSERT_EQ(data, Data({0x00, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 0, 2, 0x12, format); + ASSERT_EQ(data, Data({0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 1, 0, 0x12, format); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 1, 1, 0x12, format); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x00, 0x12, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 1, 2, 0x12, format); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x00, 0x00, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + } + + set_raw_channel_to_row(data.data(), 0, 0, 0x1234, PixelFormat::I16); + ASSERT_EQ(data, Data({0x34, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 1, 0, 0x1234, PixelFormat::I16); + ASSERT_EQ(data, Data({0x00, 0x00, 0x34, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + for (auto format : { PixelFormat::RGB161616, PixelFormat::BGR161616 }) { + set_raw_channel_to_row(data.data(), 0, 0, 0x1234, format); + ASSERT_EQ(data, Data({0x34, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 0, 1, 0x1234, format); + ASSERT_EQ(data, Data({0x00, 0x00, 0x34, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 0, 2, 0x1234, format); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x00, 0x34, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 1, 0, 0x1234, format); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 1, 1, 0x1234, format); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x34, 0x12, 0x00, 0x00})); + reset(); + + set_raw_channel_to_row(data.data(), 1, 2, 0x1234, format); + ASSERT_EQ(data, Data({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x34, 0x12})); + reset(); + } +} + +void test_convert_pixel_row_format() +{ + // The actual work is done in set_channel_to_row and get_channel_from_row, so we don't need + // to test all format combinations. + using Data = std::vector; + + Data in_data = { + 0x12, 0x34, 0x56, + 0x78, 0x98, 0xab, + 0xcd, 0xef, 0x21, + }; + Data out_data; + out_data.resize(in_data.size() * 2); + + convert_pixel_row_format(in_data.data(), PixelFormat::RGB888, + out_data.data(), PixelFormat::BGR161616, 3); + + Data expected_data = { + 0x56, 0x56, 0x34, 0x34, 0x12, 0x12, + 0xab, 0xab, 0x98, 0x98, 0x78, 0x78, + 0x21, 0x21, 0xef, 0xef, 0xcd, 0xcd, + }; + + ASSERT_EQ(out_data, expected_data); +} + +void test_image() +{ + test_get_pixel_from_row(); + test_set_pixel_to_row(); + test_get_raw_pixel_from_row(); + test_set_raw_pixel_to_row(); + test_get_raw_channel_from_row(); + test_set_raw_channel_to_row(); + test_convert_pixel_row_format(); +} diff --git a/testsuite/backend/genesys/tests_image_pipeline.cc b/testsuite/backend/genesys/tests_image_pipeline.cc new file mode 100644 index 000000000..5f8c7e79d --- /dev/null +++ b/testsuite/backend/genesys/tests_image_pipeline.cc @@ -0,0 +1,305 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + 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. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "tests.h" +#include "minigtest.h" +#include "tests_printers.h" + +#include "../../../backend/genesys_image_pipeline.h" + +#include + +void test_node_buffered_callable_source() +{ + using Data = std::vector; + + Data in_data = { + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11 + }; + + std::size_t chunk_size = 3; + std::size_t curr_index = 0; + + auto data_source_cb = [&](std::size_t size, std::uint8_t* out_data) + { + ASSERT_EQ(size, chunk_size); + std::copy(in_data.begin() + curr_index, + in_data.begin() + curr_index + chunk_size, out_data); + curr_index += chunk_size; + }; + + ImagePipelineStack stack; + stack.push_first_node(4, 3, PixelFormat::I8, + chunk_size, data_source_cb); + + Data out_data; + out_data.resize(4); + + ASSERT_EQ(curr_index, 0u); + + stack.get_next_row_data(out_data.data()); + ASSERT_EQ(out_data, Data({0, 1, 2, 3})); + ASSERT_EQ(curr_index, 6u); + + stack.get_next_row_data(out_data.data()); + ASSERT_EQ(out_data, Data({4, 5, 6, 7})); + ASSERT_EQ(curr_index, 9u); + + stack.get_next_row_data(out_data.data()); + ASSERT_EQ(out_data, Data({8, 9, 10, 11})); + ASSERT_EQ(curr_index, 12u); +} + +void test_node_format_convert() +{ + using Data = std::vector; + + Data in_data = { + 0x12, 0x34, 0x56, + 0x78, 0x98, 0xab, + 0xcd, 0xef, 0x21, + }; + + ImagePipelineStack stack; + stack.push_first_node(3, 1, PixelFormat::RGB888, + std::move(in_data)); + stack.push_node(PixelFormat::BGR161616); + + ASSERT_EQ(stack.get_output_width(), 3u); + ASSERT_EQ(stack.get_output_height(), 1u); + ASSERT_EQ(stack.get_output_row_bytes(), 6u * 3); + ASSERT_EQ(stack.get_output_format(), PixelFormat::BGR161616); + + auto out_data = stack.get_all_data(); + + Data expected_data = { + 0x56, 0x56, 0x34, 0x34, 0x12, 0x12, + 0xab, 0xab, 0x98, 0x98, 0x78, 0x78, + 0x21, 0x21, 0xef, 0xef, 0xcd, 0xcd, + }; + + ASSERT_EQ(out_data, expected_data); +} + +void test_node_desegment_1_line() +{ + using Data = std::vector; + + Data in_data = { + 1, 5, 9, 13, 17, + 3, 7, 11, 15, 19, + 2, 6, 10, 14, 18, + 4, 8, 12, 16, 20, + 21, 25, 29, 33, 37, + 23, 27, 31, 35, 39, + 22, 26, 30, 34, 38, + 24, 28, 32, 36, 40, + }; + + ImagePipelineStack stack; + stack.push_first_node(20, 2, PixelFormat::I8, + std::move(in_data)); + stack.push_node(20, std::vector{ 0, 2, 1, 3 }, 5, 1, 1); + + ASSERT_EQ(stack.get_output_width(), 20u); + ASSERT_EQ(stack.get_output_height(), 2u); + ASSERT_EQ(stack.get_output_row_bytes(), 20u); + ASSERT_EQ(stack.get_output_format(), PixelFormat::I8); + + auto out_data = stack.get_all_data(); + + Data expected_data; + expected_data.resize(40, 0); + std::iota(expected_data.begin(), expected_data.end(), 1); // will fill with 1, 2, 3, ..., 40 + + ASSERT_EQ(out_data, expected_data); +} + +void test_node_deinterleave_lines() +{ + using Data = std::vector; + + Data in_data = { + 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, + 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, + }; + + ImagePipelineStack stack; + stack.push_first_node(10, 2, PixelFormat::I8, + std::move(in_data)); + stack.push_node(2, 1); + + ASSERT_EQ(stack.get_output_width(), 20u); + ASSERT_EQ(stack.get_output_height(), 1u); + ASSERT_EQ(stack.get_output_row_bytes(), 20u); + ASSERT_EQ(stack.get_output_format(), PixelFormat::I8); + + auto out_data = stack.get_all_data(); + + Data expected_data; + expected_data.resize(20, 0); + std::iota(expected_data.begin(), expected_data.end(), 1); // will fill with 1, 2, 3, ..., 20 + + ASSERT_EQ(out_data, expected_data); +} + +void test_node_merge_mono_lines() +{ + using Data = std::vector; + + Data in_data = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + }; + + ImagePipelineStack stack; + stack.push_first_node(8, 3, PixelFormat::I8, + std::move(in_data)); + stack.push_node(ColorOrder::RGB); + + ASSERT_EQ(stack.get_output_width(), 8u); + ASSERT_EQ(stack.get_output_height(), 1u); + ASSERT_EQ(stack.get_output_row_bytes(), 24u); + ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888); + + auto out_data = stack.get_all_data(); + + Data expected_data = { + 0x10, 0x20, 0x30, 0x11, 0x21, 0x31, + 0x12, 0x22, 0x32, 0x13, 0x23, 0x33, + 0x14, 0x24, 0x34, 0x15, 0x25, 0x35, + 0x16, 0x26, 0x36, 0x17, 0x27, 0x37, + }; + + ASSERT_EQ(out_data, expected_data); +} + +void test_node_split_mono_lines() +{ + using Data = std::vector; + + Data in_data = { + 0x10, 0x20, 0x30, 0x11, 0x21, 0x31, + 0x12, 0x22, 0x32, 0x13, 0x23, 0x33, + 0x14, 0x24, 0x34, 0x15, 0x25, 0x35, + 0x16, 0x26, 0x36, 0x17, 0x27, 0x37, + }; + + ImagePipelineStack stack; + stack.push_first_node(8, 1, PixelFormat::RGB888, + std::move(in_data)); + stack.push_node(); + + ASSERT_EQ(stack.get_output_width(), 8u); + ASSERT_EQ(stack.get_output_height(), 3u); + ASSERT_EQ(stack.get_output_row_bytes(), 8u); + ASSERT_EQ(stack.get_output_format(), PixelFormat::I8); + + auto out_data = stack.get_all_data(); + + Data expected_data = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + }; + + ASSERT_EQ(out_data, expected_data); +} + +void test_node_component_shift_lines() +{ + using Data = std::vector; + + Data in_data = { + 0x10, 0x20, 0x30, 0x11, 0x21, 0x31, 0x12, 0x22, 0x32, 0x13, 0x23, 0x33, + 0x14, 0x24, 0x34, 0x15, 0x25, 0x35, 0x16, 0x26, 0x36, 0x17, 0x27, 0x37, + 0x18, 0x28, 0x38, 0x19, 0x29, 0x39, 0x1a, 0x2a, 0x3a, 0x1b, 0x2b, 0x3b, + 0x1c, 0x2c, 0x3c, 0x1d, 0x2d, 0x3d, 0x1e, 0x2e, 0x3e, 0x1f, 0x2f, 0x3f, + }; + + ImagePipelineStack stack; + stack.push_first_node(4, 4, PixelFormat::RGB888, + std::move(in_data)); + stack.push_node(0, 1, 2); + + ASSERT_EQ(stack.get_output_width(), 4u); + ASSERT_EQ(stack.get_output_height(), 2u); + ASSERT_EQ(stack.get_output_row_bytes(), 12u); + ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888); + + auto out_data = stack.get_all_data(); + + Data expected_data = { + 0x10, 0x24, 0x38, 0x11, 0x25, 0x39, 0x12, 0x26, 0x3a, 0x13, 0x27, 0x3b, + 0x14, 0x28, 0x3c, 0x15, 0x29, 0x3d, 0x16, 0x2a, 0x3e, 0x17, 0x2b, 0x3f, + }; + + ASSERT_EQ(out_data, expected_data); +} + +void test_node_pixel_shift_lines() +{ + using Data = std::vector; + + Data in_data = { + 0x10, 0x20, 0x30, 0x11, 0x21, 0x31, 0x12, 0x22, 0x32, 0x13, 0x23, 0x33, + 0x14, 0x24, 0x34, 0x15, 0x25, 0x35, 0x16, 0x26, 0x36, 0x17, 0x27, 0x37, + 0x18, 0x28, 0x38, 0x19, 0x29, 0x39, 0x1a, 0x2a, 0x3a, 0x1b, 0x2b, 0x3b, + 0x1c, 0x2c, 0x3c, 0x1d, 0x2d, 0x3d, 0x1e, 0x2e, 0x3e, 0x1f, 0x2f, 0x3f, + }; + + ImagePipelineStack stack; + stack.push_first_node(4, 4, PixelFormat::RGB888, + std::move(in_data)); + stack.push_node(std::vector{0, 2}); + + ASSERT_EQ(stack.get_output_width(), 4u); + ASSERT_EQ(stack.get_output_height(), 2u); + ASSERT_EQ(stack.get_output_row_bytes(), 12u); + ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888); + + auto out_data = stack.get_all_data(); + + Data expected_data = { + 0x10, 0x20, 0x30, 0x19, 0x29, 0x39, 0x12, 0x22, 0x32, 0x1b, 0x2b, 0x3b, + 0x14, 0x24, 0x34, 0x1d, 0x2d, 0x3d, 0x16, 0x26, 0x36, 0x1f, 0x2f, 0x3f, + }; + + ASSERT_EQ(out_data, expected_data); +} + +void test_image_pipeline() +{ + test_node_buffered_callable_source(); + test_node_format_convert(); + test_node_desegment_1_line(); + test_node_deinterleave_lines(); + test_node_merge_mono_lines(); + test_node_split_mono_lines(); + test_node_component_shift_lines(); + test_node_pixel_shift_lines(); +} diff --git a/testsuite/backend/genesys/tests_printers.h b/testsuite/backend/genesys/tests_printers.h new file mode 100644 index 000000000..276e9b290 --- /dev/null +++ b/testsuite/backend/genesys/tests_printers.h @@ -0,0 +1,65 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + 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. +*/ + +#ifndef SANE_TESTSUITE_BACKEND_GENESYS_TESTS_PRINTERS_H +#define SANE_TESTSUITE_BACKEND_GENESYS_TESTS_PRINTERS_H + +#include "../../../backend/genesys_image_buffer.h" +#include +#include +#include + +template +std::ostream& operator<<(std::ostream& str, const std::vector& arg) +{ + str << "{ "; + for (const auto& el : arg) { + str << static_cast(el) << ", "; + } + str << "}\n"; + return str; +} + +inline std::ostream& operator<<(std::ostream& str, const PixelFormat& arg) +{ + str << static_cast(arg); + return str; +} + +inline std::ostream& operator<<(std::ostream& str, const Pixel& arg) +{ + str << "{ " << arg.r << ", " << arg.g << ", " << arg.b << " }"; + return str; +} + +inline std::ostream& operator<<(std::ostream& str, const RawPixel& arg) +{ + auto flags = str.flags(); + str << std::hex; + for (auto el : arg.data) { + str << static_cast(el) << " "; + } + str.flags(flags); + return str; +} + +#endif diff --git a/testsuite/backend/genesys/tests_row_buffer.cc b/testsuite/backend/genesys/tests_row_buffer.cc new file mode 100644 index 000000000..30427d865 --- /dev/null +++ b/testsuite/backend/genesys/tests_row_buffer.cc @@ -0,0 +1,87 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + 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. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "tests.h" +#include "minigtest.h" +#include "tests_printers.h" + +#include "../../../backend/genesys_low.h" + +#include + +void test_row_buffer_push_pop_forward(unsigned size) +{ + RowBuffer buf{1}; + + ASSERT_TRUE(buf.empty()); + for (unsigned i = 0; i < size; i++) { + buf.push_back(); + *buf.get_back_row_ptr() = i; + for (unsigned j = 0; j < i + 1; j++) { + ASSERT_EQ(*buf.get_row_ptr(j), j); + } + } + ASSERT_FALSE(buf.empty()); + + for (unsigned i = 0; i < 10; i++) { + ASSERT_EQ(buf.height(), size); + ASSERT_EQ(static_cast(*buf.get_front_row_ptr()), i); + buf.pop_front(); + ASSERT_EQ(buf.height(), size - 1); + buf.push_back(); + *buf.get_back_row_ptr() = i + size; + } +} + +void test_row_buffer_push_pop_backward(unsigned size) +{ + RowBuffer buf{1}; + + ASSERT_TRUE(buf.empty()); + for (unsigned i = 0; i < size; i++) { + buf.push_front(); + *buf.get_front_row_ptr() = i; + for (unsigned j = 0; j < i + 1; j++) { + ASSERT_EQ(*buf.get_row_ptr(j), i - j); + } + } + ASSERT_FALSE(buf.empty()); + + for (unsigned i = 0; i < 10; i++) { + ASSERT_EQ(buf.height(), size); + ASSERT_EQ(static_cast(*buf.get_back_row_ptr()), i); + buf.pop_back(); + ASSERT_EQ(buf.height(), size - 1); + buf.push_front(); + *buf.get_front_row_ptr() = i + size; + } +} + +void test_row_buffer() +{ + for (unsigned size = 1; size < 5; ++size) { + test_row_buffer_push_pop_forward(size); + test_row_buffer_push_pop_backward(size); + } +} diff --git a/testsuite/backend/genesys/tests_sensor.cc b/testsuite/backend/genesys/tests_sensor.cc index 49f5c2e2f..c2a8fa237 100644 --- a/testsuite/backend/genesys/tests_sensor.cc +++ b/testsuite/backend/genesys/tests_sensor.cc @@ -24,22 +24,12 @@ #include "tests.h" #include "minigtest.h" +#include "tests_printers.h" #include "../../../backend/genesys_low.h" #include -template -std::ostream& operator<<(std::ostream& str, const std::vector& arg) -{ - str << "{ "; - for (const auto& el : arg) { - str << (unsigned) el << ", "; - } - str << "}\n"; - return str; -} - void test_fill_segmented_buffer_depth8() { Genesys_Device dev;