genesys: Implement library for image manipulation

merge-requests/174/head
Povilas Kanapickas 2019-09-13 10:38:04 +03:00
rodzic 17dc1aee81
commit 7d7a395277
18 zmienionych plików z 3085 dodań i 14 usunięć

Wyświetl plik

@ -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 \

Wyświetl plik

@ -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

Wyświetl plik

@ -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));
}
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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();

Wyświetl plik

@ -28,6 +28,9 @@
int main()
{
test_calibration_parsing();
test_image();
test_image_pipeline();
test_row_buffer();
test_sensor();
return finish_tests();
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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;