kopia lustrzana https://gitlab.com/sane-project/backends
genesys: Implement library for image manipulation
rodzic
17dc1aee81
commit
7d7a395277
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,602 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
|
||||
As a special exception, the authors of SANE give permission for
|
||||
additional uses of the libraries contained in this release of SANE.
|
||||
|
||||
The exception is that, if you link a SANE library with other files
|
||||
to produce an executable, this does not by itself cause the
|
||||
resulting executable to be covered by the GNU General Public
|
||||
License. Your use of that executable is in no way restricted on
|
||||
account of linking the SANE library code into it.
|
||||
|
||||
This exception does not, however, invalidate any other reasons why
|
||||
the executable file might be covered by the GNU General Public
|
||||
License.
|
||||
|
||||
If you submit changes to SANE to the maintainers to be included in
|
||||
a subsequent release, you agree by submitting the changes that
|
||||
those changes may be distributed with this exception intact.
|
||||
|
||||
If you write modifications of your own for SANE, it is your choice
|
||||
whether to permit this exception to apply to your modifications.
|
||||
If you do not wish that, delete this exception notice.
|
||||
*/
|
||||
|
||||
#define DEBUG_DECLARE_ONLY
|
||||
|
||||
#include "genesys_image.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
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<unsigned>(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<unsigned>(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<unsigned>(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<unsigned>(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<unsigned>(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<uint16_t>(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<unsigned>(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<unsigned>(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<unsigned>(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<unsigned>(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<unsigned>(format));
|
||||
}
|
||||
}
|
||||
|
||||
template<PixelFormat Format>
|
||||
Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x)
|
||||
{
|
||||
return get_pixel_from_row(data, x, Format);
|
||||
}
|
||||
|
||||
template<PixelFormat Format>
|
||||
void set_pixel_to_row(std::uint8_t* data, std::size_t x, Pixel pixel)
|
||||
{
|
||||
set_pixel_to_row(data, x, pixel, Format);
|
||||
}
|
||||
|
||||
template<PixelFormat Format>
|
||||
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<PixelFormat Format>
|
||||
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<PixelFormat Format>
|
||||
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<PixelFormat Format>
|
||||
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<PixelFormat::I1>(const std::uint8_t* data, std::size_t x);
|
||||
template Pixel get_pixel_from_row<PixelFormat::RGB111>(const std::uint8_t* data, std::size_t x);
|
||||
template Pixel get_pixel_from_row<PixelFormat::I8>(const std::uint8_t* data, std::size_t x);
|
||||
template Pixel get_pixel_from_row<PixelFormat::RGB888>(const std::uint8_t* data, std::size_t x);
|
||||
template Pixel get_pixel_from_row<PixelFormat::BGR888>(const std::uint8_t* data, std::size_t x);
|
||||
template Pixel get_pixel_from_row<PixelFormat::I16>(const std::uint8_t* data, std::size_t x);
|
||||
template Pixel get_pixel_from_row<PixelFormat::RGB161616>(const std::uint8_t* data, std::size_t x);
|
||||
template Pixel get_pixel_from_row<PixelFormat::BGR161616>(const std::uint8_t* data, std::size_t x);
|
||||
|
||||
template RawPixel get_raw_pixel_from_row<PixelFormat::I1>(const std::uint8_t* data, std::size_t x);
|
||||
template RawPixel get_raw_pixel_from_row<PixelFormat::RGB111>(const std::uint8_t* data, std::size_t x);
|
||||
template RawPixel get_raw_pixel_from_row<PixelFormat::I8>(const std::uint8_t* data, std::size_t x);
|
||||
template RawPixel get_raw_pixel_from_row<PixelFormat::RGB888>(const std::uint8_t* data, std::size_t x);
|
||||
template RawPixel get_raw_pixel_from_row<PixelFormat::BGR888>(const std::uint8_t* data, std::size_t x);
|
||||
template RawPixel get_raw_pixel_from_row<PixelFormat::I16>(const std::uint8_t* data, std::size_t x);
|
||||
template RawPixel get_raw_pixel_from_row<PixelFormat::RGB161616>(const std::uint8_t* data, std::size_t x);
|
||||
template RawPixel get_raw_pixel_from_row<PixelFormat::BGR161616>(const std::uint8_t* data, std::size_t x);
|
||||
|
||||
template std::uint16_t get_raw_channel_from_row<PixelFormat::I1>(
|
||||
const std::uint8_t* data, std::size_t x, unsigned channel);
|
||||
template std::uint16_t get_raw_channel_from_row<PixelFormat::RGB111>(
|
||||
const std::uint8_t* data, std::size_t x, unsigned channel);
|
||||
template std::uint16_t get_raw_channel_from_row<PixelFormat::I8>(
|
||||
const std::uint8_t* data, std::size_t x, unsigned channel);
|
||||
template std::uint16_t get_raw_channel_from_row<PixelFormat::RGB888>(
|
||||
const std::uint8_t* data, std::size_t x, unsigned channel);
|
||||
template std::uint16_t get_raw_channel_from_row<PixelFormat::BGR888>(
|
||||
const std::uint8_t* data, std::size_t x, unsigned channel);
|
||||
template std::uint16_t get_raw_channel_from_row<PixelFormat::I16>(
|
||||
const std::uint8_t* data, std::size_t x, unsigned channel);
|
||||
template std::uint16_t get_raw_channel_from_row<PixelFormat::RGB161616>(
|
||||
const std::uint8_t* data, std::size_t x, unsigned channel);
|
||||
template std::uint16_t get_raw_channel_from_row<PixelFormat::BGR161616>
|
||||
(const std::uint8_t* data, std::size_t x, unsigned channel);
|
||||
|
||||
template void set_pixel_to_row<PixelFormat::I1>(std::uint8_t* data, std::size_t x, Pixel pixel);
|
||||
template void set_pixel_to_row<PixelFormat::RGB111>(std::uint8_t* data, std::size_t x, Pixel pixel);
|
||||
template void set_pixel_to_row<PixelFormat::I8>(std::uint8_t* data, std::size_t x, Pixel pixel);
|
||||
template void set_pixel_to_row<PixelFormat::RGB888>(std::uint8_t* data, std::size_t x, Pixel pixel);
|
||||
template void set_pixel_to_row<PixelFormat::BGR888>(std::uint8_t* data, std::size_t x, Pixel pixel);
|
||||
template void set_pixel_to_row<PixelFormat::I16>(std::uint8_t* data, std::size_t x, Pixel pixel);
|
||||
template void set_pixel_to_row<PixelFormat::RGB161616>(std::uint8_t* data, std::size_t x, Pixel pixel);
|
||||
template void set_pixel_to_row<PixelFormat::BGR161616>(std::uint8_t* data, std::size_t x, Pixel pixel);
|
||||
|
||||
template void set_raw_pixel_to_row<PixelFormat::I1>(std::uint8_t* data, std::size_t x, RawPixel pixel);
|
||||
template void set_raw_pixel_to_row<PixelFormat::RGB111>(std::uint8_t* data, std::size_t x, RawPixel pixel);
|
||||
template void set_raw_pixel_to_row<PixelFormat::I8>(std::uint8_t* data, std::size_t x, RawPixel pixel);
|
||||
template void set_raw_pixel_to_row<PixelFormat::RGB888>(std::uint8_t* data, std::size_t x, RawPixel pixel);
|
||||
template void set_raw_pixel_to_row<PixelFormat::BGR888>(std::uint8_t* data, std::size_t x, RawPixel pixel);
|
||||
template void set_raw_pixel_to_row<PixelFormat::I16>(std::uint8_t* data, std::size_t x, RawPixel pixel);
|
||||
template void set_raw_pixel_to_row<PixelFormat::RGB161616>(std::uint8_t* data, std::size_t x, RawPixel pixel);
|
||||
template void set_raw_pixel_to_row<PixelFormat::BGR161616>(std::uint8_t* data, std::size_t x, RawPixel pixel);
|
||||
|
||||
template void set_raw_channel_to_row<PixelFormat::I1>(
|
||||
std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel);
|
||||
template void set_raw_channel_to_row<PixelFormat::RGB111>(
|
||||
std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel);
|
||||
template void set_raw_channel_to_row<PixelFormat::I8>(
|
||||
std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel);
|
||||
template void set_raw_channel_to_row<PixelFormat::RGB888>(
|
||||
std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel);
|
||||
template void set_raw_channel_to_row<PixelFormat::BGR888>(
|
||||
std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel);
|
||||
template void set_raw_channel_to_row<PixelFormat::I16>(
|
||||
std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel);
|
||||
template void set_raw_channel_to_row<PixelFormat::RGB161616>(
|
||||
std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel);
|
||||
template void set_raw_channel_to_row<PixelFormat::BGR161616>(
|
||||
std::uint8_t* data, std::size_t x, unsigned channel, std::uint16_t pixel);
|
||||
|
||||
template<PixelFormat SrcFormat, PixelFormat DstFormat>
|
||||
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<PixelFormat SrcFormat>
|
||||
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<SrcFormat, PixelFormat::I1>(in_data, out_data, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::RGB111: {
|
||||
convert_pixel_row_impl2<SrcFormat, PixelFormat::RGB111>(in_data, out_data, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::I8: {
|
||||
convert_pixel_row_impl2<SrcFormat, PixelFormat::I8>(in_data, out_data, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::RGB888: {
|
||||
convert_pixel_row_impl2<SrcFormat, PixelFormat::RGB888>(in_data, out_data, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::BGR888: {
|
||||
convert_pixel_row_impl2<SrcFormat, PixelFormat::BGR888>(in_data, out_data, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::I16: {
|
||||
convert_pixel_row_impl2<SrcFormat, PixelFormat::I16>(in_data, out_data, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::RGB161616: {
|
||||
convert_pixel_row_impl2<SrcFormat, PixelFormat::RGB161616>(in_data, out_data, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::BGR161616: {
|
||||
convert_pixel_row_impl2<SrcFormat, PixelFormat::BGR161616>(in_data, out_data, count);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw SaneException("Unknown pixel format %d", static_cast<unsigned>(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<PixelFormat::I1>(in_data, out_data, out_format, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::RGB111: {
|
||||
convert_pixel_row_impl<PixelFormat::RGB111>(in_data, out_data, out_format, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::I8: {
|
||||
convert_pixel_row_impl<PixelFormat::I8>(in_data, out_data, out_format, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::RGB888: {
|
||||
convert_pixel_row_impl<PixelFormat::RGB888>(in_data, out_data, out_format, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::BGR888: {
|
||||
convert_pixel_row_impl<PixelFormat::BGR888>(in_data, out_data, out_format, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::I16: {
|
||||
convert_pixel_row_impl<PixelFormat::I16>(in_data, out_data, out_format, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::RGB161616: {
|
||||
convert_pixel_row_impl<PixelFormat::RGB161616>(in_data, out_data, out_format, count);
|
||||
return;
|
||||
}
|
||||
case PixelFormat::BGR161616: {
|
||||
convert_pixel_row_impl<PixelFormat::BGR161616>(in_data, out_data, out_format, count);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw SaneException("Unknown pixel format %d", static_cast<unsigned>(in_format));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
|
||||
As a special exception, the authors of SANE give permission for
|
||||
additional uses of the libraries contained in this release of SANE.
|
||||
|
||||
The exception is that, if you link a SANE library with other files
|
||||
to produce an executable, this does not by itself cause the
|
||||
resulting executable to be covered by the GNU General Public
|
||||
License. Your use of that executable is in no way restricted on
|
||||
account of linking the SANE library code into it.
|
||||
|
||||
This exception does not, however, invalidate any other reasons why
|
||||
the executable file might be covered by the GNU General Public
|
||||
License.
|
||||
|
||||
If you submit changes to SANE to the maintainers to be included in
|
||||
a subsequent release, you agree by submitting the changes that
|
||||
those changes may be distributed with this exception intact.
|
||||
|
||||
If you write modifications of your own for SANE, it is your choice
|
||||
whether to permit this exception to apply to your modifications.
|
||||
If you do not wish that, delete this exception notice.
|
||||
*/
|
||||
|
||||
#ifndef BACKEND_GENESYS_IMAGE_H
|
||||
#define BACKEND_GENESYS_IMAGE_H
|
||||
|
||||
#include "genesys_enums.h"
|
||||
#include "genesys_error.h"
|
||||
#include "genesys_image_buffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
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<PixelFormat Format>
|
||||
Pixel get_pixel_from_row(const std::uint8_t* data, std::size_t x);
|
||||
template<PixelFormat Format>
|
||||
void set_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel);
|
||||
|
||||
template<PixelFormat Format>
|
||||
Pixel get_raw_pixel_from_row(const std::uint8_t* data, std::size_t x);
|
||||
template<PixelFormat Format>
|
||||
void set_raw_pixel_to_row(std::uint8_t* data, std::size_t x, RawPixel pixel);
|
||||
|
||||
template<PixelFormat Format>
|
||||
std::uint16_t get_raw_channel_from_row(const std::uint8_t* data, std::size_t x, unsigned channel);
|
||||
template<PixelFormat Format>
|
||||
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
|
|
@ -0,0 +1,85 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
|
||||
As a special exception, the authors of SANE give permission for
|
||||
additional uses of the libraries contained in this release of SANE.
|
||||
|
||||
The exception is that, if you link a SANE library with other files
|
||||
to produce an executable, this does not by itself cause the
|
||||
resulting executable to be covered by the GNU General Public
|
||||
License. Your use of that executable is in no way restricted on
|
||||
account of linking the SANE library code into it.
|
||||
|
||||
This exception does not, however, invalidate any other reasons why
|
||||
the executable file might be covered by the GNU General Public
|
||||
License.
|
||||
|
||||
If you submit changes to SANE to the maintainers to be included in
|
||||
a subsequent release, you agree by submitting the changes that
|
||||
those changes may be distributed with this exception intact.
|
||||
|
||||
If you write modifications of your own for SANE, it is your choice
|
||||
whether to permit this exception to apply to your modifications.
|
||||
If you do not wish that, delete this exception notice.
|
||||
*/
|
||||
|
||||
#define DEBUG_DECLARE_ONLY
|
||||
|
||||
#include "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<std::size_t>(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);
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
|
||||
As a special exception, the authors of SANE give permission for
|
||||
additional uses of the libraries contained in this release of SANE.
|
||||
|
||||
The exception is that, if you link a SANE library with other files
|
||||
to produce an executable, this does not by itself cause the
|
||||
resulting executable to be covered by the GNU General Public
|
||||
License. Your use of that executable is in no way restricted on
|
||||
account of linking the SANE library code into it.
|
||||
|
||||
This exception does not, however, invalidate any other reasons why
|
||||
the executable file might be covered by the GNU General Public
|
||||
License.
|
||||
|
||||
If you submit changes to SANE to the maintainers to be included in
|
||||
a subsequent release, you agree by submitting the changes that
|
||||
those changes may be distributed with this exception intact.
|
||||
|
||||
If you write modifications of your own for SANE, it is your choice
|
||||
whether to permit this exception to apply to your modifications.
|
||||
If you do not wish that, delete this exception notice.
|
||||
*/
|
||||
|
||||
#ifndef BACKEND_GENESYS_IMAGE_BUFFER_H
|
||||
#define BACKEND_GENESYS_IMAGE_BUFFER_H
|
||||
|
||||
#include "genesys_enums.h"
|
||||
#include "genesys_row_buffer.h"
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
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<void(std::size_t size, std::uint8_t* out_data)>;
|
||||
|
||||
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<std::uint8_t> buffer_;
|
||||
};
|
||||
|
||||
#endif // BACKEND_GENESYS_IMAGE_BUFFER_H
|
|
@ -0,0 +1,527 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
|
||||
As a special exception, the authors of SANE give permission for
|
||||
additional uses of the libraries contained in this release of SANE.
|
||||
|
||||
The exception is that, if you link a SANE library with other files
|
||||
to produce an executable, this does not by itself cause the
|
||||
resulting executable to be covered by the GNU General Public
|
||||
License. Your use of that executable is in no way restricted on
|
||||
account of linking the SANE library code into it.
|
||||
|
||||
This exception does not, however, invalidate any other reasons why
|
||||
the executable file might be covered by the GNU General Public
|
||||
License.
|
||||
|
||||
If you submit changes to SANE to the maintainers to be included in
|
||||
a subsequent release, you agree by submitting the changes that
|
||||
those changes may be distributed with this exception intact.
|
||||
|
||||
If you write modifications of your own for SANE, it is your choice
|
||||
whether to permit this exception to apply to your modifications.
|
||||
If you do not wish that, delete this exception notice.
|
||||
*/
|
||||
|
||||
#define DEBUG_DECLARE_ONLY
|
||||
|
||||
#include "genesys_image_pipeline.h"
|
||||
#include <numeric>
|
||||
|
||||
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<std::uint8_t> 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<unsigned>& 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<unsigned>(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<unsigned>(input_format),
|
||||
static_cast<unsigned>(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<unsigned>(input_format));
|
||||
}
|
||||
|
||||
ImagePipelineNodeComponentShiftLines::ImagePipelineNodeComponentShiftLines(
|
||||
ImagePipelineNode& source, unsigned shift_r, unsigned shift_g, unsigned shift_b) :
|
||||
source_(source),
|
||||
buffer_{source.get_row_bytes()}
|
||||
{
|
||||
DBG_HELPER_ARGS(dbg, "shifts={%d, %d, %d}", shift_r, shift_g, shift_b);
|
||||
|
||||
switch (source.get_format()) {
|
||||
case PixelFormat::RGB111:
|
||||
case PixelFormat::RGB888:
|
||||
case PixelFormat::RGB161616: {
|
||||
channel_shifts_ = { shift_r, shift_g, shift_b };
|
||||
break;
|
||||
}
|
||||
case PixelFormat::BGR888:
|
||||
case PixelFormat::BGR161616: {
|
||||
channel_shifts_ = { shift_b, shift_g, shift_r };
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw SaneException("Unsupported input format %d",
|
||||
static_cast<unsigned>(source.get_format()));
|
||||
}
|
||||
extra_height_ = *std::max_element(channel_shifts_.begin(), channel_shifts_.end());
|
||||
}
|
||||
|
||||
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<std::size_t>& shifts) :
|
||||
source_(source),
|
||||
pixel_shifts_{shifts},
|
||||
buffer_{get_row_bytes()}
|
||||
{
|
||||
DBG_HELPER(dbg);
|
||||
DBG(DBG_proc, "%s: shifts={", __func__);
|
||||
for (auto el : pixel_shifts_) {
|
||||
DBG(DBG_proc, " %zu", el);
|
||||
}
|
||||
DBG(DBG_proc, " }\n");
|
||||
|
||||
if (pixel_shifts_.size() > MAX_SHIFTS) {
|
||||
throw SaneException("Unsupported number of shift configurations %zu", pixel_shifts_.size());
|
||||
}
|
||||
|
||||
extra_height_ = *std::max_element(pixel_shifts_.begin(), pixel_shifts_.end());
|
||||
}
|
||||
|
||||
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<std::uint8_t*, MAX_SHIFTS> rows;
|
||||
|
||||
for (std::size_t irow = 0; irow < shift_count; ++irow) {
|
||||
rows[irow] = buffer_.get_row_ptr(pixel_shifts_[irow]);
|
||||
}
|
||||
|
||||
for (std::size_t x = 0, width = get_width(); x < width;) {
|
||||
for (std::size_t irow = 0; irow < shift_count && x < width; irow++, x++) {
|
||||
RawPixel pixel = get_raw_pixel_from_row(rows[irow], x, format);
|
||||
set_raw_pixel_to_row(out_data, x, pixel, format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<std::uint8_t> ImagePipelineStack::get_all_data()
|
||||
{
|
||||
auto row_bytes = get_output_row_bytes();
|
||||
auto height = get_output_height();
|
||||
|
||||
std::vector<std::uint8_t> ret;
|
||||
ret.resize(row_bytes * height);
|
||||
|
||||
for (std::size_t i = 0; i < height; ++i) {
|
||||
get_next_row_data(ret.data() + row_bytes * i);
|
||||
}
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,395 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
|
||||
As a special exception, the authors of SANE give permission for
|
||||
additional uses of the libraries contained in this release of SANE.
|
||||
|
||||
The exception is that, if you link a SANE library with other files
|
||||
to produce an executable, this does not by itself cause the
|
||||
resulting executable to be covered by the GNU General Public
|
||||
License. Your use of that executable is in no way restricted on
|
||||
account of linking the SANE library code into it.
|
||||
|
||||
This exception does not, however, invalidate any other reasons why
|
||||
the executable file might be covered by the GNU General Public
|
||||
License.
|
||||
|
||||
If you submit changes to SANE to the maintainers to be included in
|
||||
a subsequent release, you agree by submitting the changes that
|
||||
those changes may be distributed with this exception intact.
|
||||
|
||||
If you write modifications of your own for SANE, it is your choice
|
||||
whether to permit this exception to apply to your modifications.
|
||||
If you do not wish that, delete this exception notice.
|
||||
*/
|
||||
|
||||
#ifndef BACKEND_GENESYS_IMAGE_PIPELINE_H
|
||||
#define BACKEND_GENESYS_IMAGE_PIPELINE_H
|
||||
|
||||
#include "genesys_image.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
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<void(std::size_t size, std::uint8_t* out_data)>;
|
||||
|
||||
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<void(std::size_t size, std::uint8_t* out_data)>;
|
||||
|
||||
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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> 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<unsigned>& 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<unsigned> 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<std::uint8_t> 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<unsigned, 3> 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<std::size_t>& 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<std::size_t> 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<uint8_t> 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<class Node, class... Args>
|
||||
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<Node>(new Node(std::forward<Args>(args)...)));
|
||||
}
|
||||
|
||||
template<class Node, class... Args>
|
||||
void push_node(Args&&... args)
|
||||
{
|
||||
ensure_node_exists();
|
||||
nodes_.emplace_back(std::unique_ptr<Node>(new Node(*nodes_.back(),
|
||||
std::forward<Args>(args)...)));
|
||||
}
|
||||
|
||||
void get_next_row_data(std::uint8_t* out_data)
|
||||
{
|
||||
nodes_.back()->get_next_row_data(out_data);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> get_all_data();
|
||||
|
||||
private:
|
||||
void ensure_node_exists() const;
|
||||
|
||||
std::vector<std::unique_ptr<ImagePipelineNode>> nodes_;
|
||||
};
|
||||
|
||||
|
||||
#endif // ifndef BACKEND_GENESYS_IMAGE_PIPELINE_H
|
|
@ -0,0 +1,210 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
|
||||
As a special exception, the authors of SANE give permission for
|
||||
additional uses of the libraries contained in this release of SANE.
|
||||
|
||||
The exception is that, if you link a SANE library with other files
|
||||
to produce an executable, this does not by itself cause the
|
||||
resulting executable to be covered by the GNU General Public
|
||||
License. Your use of that executable is in no way restricted on
|
||||
account of linking the SANE library code into it.
|
||||
|
||||
This exception does not, however, invalidate any other reasons why
|
||||
the executable file might be covered by the GNU General Public
|
||||
License.
|
||||
|
||||
If you submit changes to SANE to the maintainers to be included in
|
||||
a subsequent release, you agree by submitting the changes that
|
||||
those changes may be distributed with this exception intact.
|
||||
|
||||
If you write modifications of your own for SANE, it is your choice
|
||||
whether to permit this exception to apply to your modifications.
|
||||
If you do not wish that, delete this exception notice.
|
||||
*/
|
||||
|
||||
#ifndef BACKEND_GENESYS_LINE_BUFFER_H
|
||||
#define BACKEND_GENESYS_LINE_BUFFER_H
|
||||
|
||||
#include "genesys_error.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
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<std::size_t>(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<std::size_t>(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<std::uint8_t> data_;
|
||||
};
|
||||
|
||||
#endif // BACKEND_GENESYS_LINE_BUFFER_H
|
|
@ -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)
|
||||
|
|
|
@ -38,7 +38,7 @@ inline void print_location(std::ostream& out, const char* function, const char*
|
|||
template<class T, class U>
|
||||
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();
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
int main()
|
||||
{
|
||||
test_calibration_parsing();
|
||||
test_image();
|
||||
test_image_pipeline();
|
||||
test_row_buffer();
|
||||
test_sensor();
|
||||
return finish_tests();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,571 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#define DEBUG_DECLARE_ONLY
|
||||
|
||||
#include "tests.h"
|
||||
#include "minigtest.h"
|
||||
#include "tests_printers.h"
|
||||
|
||||
#include "../../../backend/genesys_image_pipeline.h"
|
||||
#include <vector>
|
||||
|
||||
void test_get_pixel_from_row()
|
||||
{
|
||||
std::vector<std::uint8_t> 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<std::uint8_t>;
|
||||
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<std::uint8_t> 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<std::uint8_t>;
|
||||
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<std::uint8_t> 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<std::uint8_t>;
|
||||
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<std::uint8_t>;
|
||||
|
||||
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();
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#define DEBUG_DECLARE_ONLY
|
||||
|
||||
#include "tests.h"
|
||||
#include "minigtest.h"
|
||||
#include "tests_printers.h"
|
||||
|
||||
#include "../../../backend/genesys_image_pipeline.h"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
void test_node_buffered_callable_source()
|
||||
{
|
||||
using Data = std::vector<std::uint8_t>;
|
||||
|
||||
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<ImagePipelineNodeBufferedCallableSource>(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<std::uint8_t>;
|
||||
|
||||
Data in_data = {
|
||||
0x12, 0x34, 0x56,
|
||||
0x78, 0x98, 0xab,
|
||||
0xcd, 0xef, 0x21,
|
||||
};
|
||||
|
||||
ImagePipelineStack stack;
|
||||
stack.push_first_node<ImagePipelineNodeArraySource>(3, 1, PixelFormat::RGB888,
|
||||
std::move(in_data));
|
||||
stack.push_node<ImagePipelineNodeFormatConvert>(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<std::uint8_t>;
|
||||
|
||||
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<ImagePipelineNodeArraySource>(20, 2, PixelFormat::I8,
|
||||
std::move(in_data));
|
||||
stack.push_node<ImagePipelineNodeDesegment>(20, std::vector<unsigned>{ 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<std::uint8_t>;
|
||||
|
||||
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<ImagePipelineNodeArraySource>(10, 2, PixelFormat::I8,
|
||||
std::move(in_data));
|
||||
stack.push_node<ImagePipelineNodeDeinterleaveLines>(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<std::uint8_t>;
|
||||
|
||||
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<ImagePipelineNodeArraySource>(8, 3, PixelFormat::I8,
|
||||
std::move(in_data));
|
||||
stack.push_node<ImagePipelineNodeMergeMonoLines>(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<std::uint8_t>;
|
||||
|
||||
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<ImagePipelineNodeArraySource>(8, 1, PixelFormat::RGB888,
|
||||
std::move(in_data));
|
||||
stack.push_node<ImagePipelineNodeSplitMonoLines>();
|
||||
|
||||
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<std::uint8_t>;
|
||||
|
||||
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<ImagePipelineNodeArraySource>(4, 4, PixelFormat::RGB888,
|
||||
std::move(in_data));
|
||||
stack.push_node<ImagePipelineNodeComponentShiftLines>(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<std::uint8_t>;
|
||||
|
||||
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<ImagePipelineNodeArraySource>(4, 4, PixelFormat::RGB888,
|
||||
std::move(in_data));
|
||||
stack.push_node<ImagePipelineNodePixelShiftLines>(std::vector<std::size_t>{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();
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef SANE_TESTSUITE_BACKEND_GENESYS_TESTS_PRINTERS_H
|
||||
#define SANE_TESTSUITE_BACKEND_GENESYS_TESTS_PRINTERS_H
|
||||
|
||||
#include "../../../backend/genesys_image_buffer.h"
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
|
||||
template<class T>
|
||||
std::ostream& operator<<(std::ostream& str, const std::vector<T>& arg)
|
||||
{
|
||||
str << "{ ";
|
||||
for (const auto& el : arg) {
|
||||
str << static_cast<unsigned>(el) << ", ";
|
||||
}
|
||||
str << "}\n";
|
||||
return str;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& str, const PixelFormat& arg)
|
||||
{
|
||||
str << static_cast<unsigned>(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<unsigned>(el) << " ";
|
||||
}
|
||||
str.flags(flags);
|
||||
return str;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,87 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#define DEBUG_DECLARE_ONLY
|
||||
|
||||
#include "tests.h"
|
||||
#include "minigtest.h"
|
||||
#include "tests_printers.h"
|
||||
|
||||
#include "../../../backend/genesys_low.h"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
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<unsigned>(*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<unsigned>(*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);
|
||||
}
|
||||
}
|
|
@ -24,22 +24,12 @@
|
|||
|
||||
#include "tests.h"
|
||||
#include "minigtest.h"
|
||||
#include "tests_printers.h"
|
||||
|
||||
#include "../../../backend/genesys_low.h"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
template<class T>
|
||||
std::ostream& operator<<(std::ostream& str, const std::vector<T>& arg)
|
||||
{
|
||||
str << "{ ";
|
||||
for (const auto& el : arg) {
|
||||
str << (unsigned) el << ", ";
|
||||
}
|
||||
str << "}\n";
|
||||
return str;
|
||||
}
|
||||
|
||||
void test_fill_segmented_buffer_depth8()
|
||||
{
|
||||
Genesys_Device dev;
|
||||
|
|
Ładowanie…
Reference in New Issue