summaryrefslogtreecommitdiff
path: root/testsuite/backend
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2020-03-30 21:30:45 +0200
committerJörg Frings-Fürst <debian@jff-webhosting.net>2020-03-30 21:30:45 +0200
commitee770c2346eb37e0dcb8b6cf3eaacf3d8efd6bbc (patch)
tree58f05092be1a17a939e861f8cadcda1b6ca2ecef /testsuite/backend
parent0da9e21872802cfc6e975b1ebaf9efb9e5934d84 (diff)
parentfef76e17ed4c607ea73b81279f9ef1d7121be900 (diff)
Merge branch 'release/experimental/1.0.29-1_experimental1'experimental/1.0.29-1_experimental1
Diffstat (limited to 'testsuite/backend')
-rw-r--r--testsuite/backend/Makefile.am7
-rw-r--r--testsuite/backend/genesys/Makefile.am35
-rw-r--r--testsuite/backend/genesys/minigtest.cpp37
-rw-r--r--testsuite/backend/genesys/minigtest.h111
-rw-r--r--testsuite/backend/genesys/session_config_test.cpp503
-rw-r--r--testsuite/backend/genesys/tests.cpp37
-rw-r--r--testsuite/backend/genesys/tests.h37
-rw-r--r--testsuite/backend/genesys/tests_calibration.cpp135
-rw-r--r--testsuite/backend/genesys/tests_image.cpp576
-rw-r--r--testsuite/backend/genesys/tests_image_pipeline.cpp519
-rw-r--r--testsuite/backend/genesys/tests_motor.cpp365
-rw-r--r--testsuite/backend/genesys/tests_printers.h62
-rw-r--r--testsuite/backend/genesys/tests_row_buffer.cpp91
-rw-r--r--testsuite/backend/genesys/tests_utilities.cpp110
14 files changed, 2625 insertions, 0 deletions
diff --git a/testsuite/backend/Makefile.am b/testsuite/backend/Makefile.am
new file mode 100644
index 0000000..1f7d30e
--- /dev/null
+++ b/testsuite/backend/Makefile.am
@@ -0,0 +1,7 @@
+## Makefile.am -- an automake template for Makefile.in file
+## Copyright (C) 2019 Sane Developers.
+##
+## This file is part of the "Sane" build infra-structure. See
+## included LICENSE file for license information.
+
+SUBDIRS = genesys
diff --git a/testsuite/backend/genesys/Makefile.am b/testsuite/backend/genesys/Makefile.am
new file mode 100644
index 0000000..818a523
--- /dev/null
+++ b/testsuite/backend/genesys/Makefile.am
@@ -0,0 +1,35 @@
+## Makefile.am -- an automake template for Makefile.in file
+## Copyright (C) 2019 Sane Developers.
+##
+## This file is part of the "Sane" build infra-structure. See
+## included LICENSE file for license information.
+
+TEST_LDADD = \
+ ../../../sanei/libsanei.la \
+ ../../../sanei/sanei_usb.lo \
+ ../../../sanei/sanei_magic.lo \
+ ../../../lib/liblib.la \
+ ../../../backend/libgenesys.la \
+ ../../../backend/sane_strstatus.lo \
+ $(MATH_LIB) $(USB_LIBS) $(XML_LIBS) $(PTHREAD_LIBS)
+
+check_PROGRAMS = genesys_unit_tests genesys_session_config_tests
+TESTS = genesys_unit_tests
+
+AM_CPPFLAGS += -I. -I$(srcdir) -I$(top_builddir)/include -I$(top_srcdir)/include $(USB_CFLAGS) \
+ -DBACKEND_NAME=genesys -DTESTSUITE_BACKEND_GENESYS_SRCDIR=$(srcdir)
+
+genesys_unit_tests_SOURCES = tests.cpp tests.h \
+ minigtest.cpp minigtest.h tests_printers.h \
+ tests_calibration.cpp \
+ tests_image.cpp \
+ tests_image_pipeline.cpp \
+ tests_motor.cpp \
+ tests_row_buffer.cpp \
+ tests_utilities.cpp
+
+genesys_unit_tests_LDADD = $(TEST_LDADD)
+
+genesys_session_config_tests_SOURCES = session_config_test.cpp
+
+genesys_session_config_tests_LDADD = $(TEST_LDADD)
diff --git a/testsuite/backend/genesys/minigtest.cpp b/testsuite/backend/genesys/minigtest.cpp
new file mode 100644
index 0000000..8afb62a
--- /dev/null
+++ b/testsuite/backend/genesys/minigtest.cpp
@@ -0,0 +1,37 @@
+/* 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.
+*/
+
+#include "minigtest.h"
+
+#define DEBUG_DECLARE_ONLY
+
+size_t s_num_successes = 0;
+size_t s_num_failures = 0;
+
+int finish_tests()
+{
+ std::cerr << "Finished tests. Sucessses: " << s_num_successes
+ << " failures: " << s_num_failures << "\n";
+ if (s_num_failures > 0)
+ return 1;
+ return 0;
+}
diff --git a/testsuite/backend/genesys/minigtest.h b/testsuite/backend/genesys/minigtest.h
new file mode 100644
index 0000000..9a38e77
--- /dev/null
+++ b/testsuite/backend/genesys/minigtest.h
@@ -0,0 +1,111 @@
+/* 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_MINIGTEST_H
+#define SANE_TESTSUITE_BACKEND_GENESYS_MINIGTEST_H
+
+#include <cstdlib>
+#include <iostream>
+
+extern size_t s_num_successes;
+extern size_t s_num_failures;
+
+inline void print_location(std::ostream& out, const char* function, const char* path,
+ unsigned line)
+{
+ out << path << ":" << line << " in " << function;
+}
+
+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)) {
+ s_num_failures++;
+ std::cerr << "FAILURE at ";
+ print_location(std::cerr, function, path, line);
+ std::cerr << " :\n" << t << " != " << u << "\n\n";
+ } else {
+ s_num_successes++;
+ std::cerr << "SUCCESS at ";
+ print_location(std::cerr, function, path, line);
+ std::cerr << "\n";
+ }
+}
+
+inline void check_true(bool x, const char* function, const char* path, unsigned line)
+{
+ if (x) {
+ s_num_successes++;
+ std::cerr << "SUCCESS at ";
+ } else {
+ s_num_failures++;
+ std::cerr << "FAILURE at ";
+ }
+ print_location(std::cerr, function, path, line);
+ std::cerr << "\n";
+}
+
+inline void check_raises_success(const char* function, const char* path, unsigned line)
+{
+ s_num_successes++;
+ std::cerr << "SUCCESS at ";
+ print_location(std::cerr, function, path, line);
+ std::cerr << "\n";
+}
+
+inline void check_raises_did_not_raise(const char* function, const char* path, unsigned line)
+{
+ s_num_failures++;
+ std::cerr << "FAILURE at ";
+ print_location(std::cerr, function, path, line);
+ std::cerr << " : did not raise exception\n";
+
+}
+
+inline void check_raises_raised_unexpected(const char* function, const char* path, unsigned line)
+{
+ s_num_failures++;
+ std::cerr << "FAILURE at ";
+ print_location(std::cerr, function, path, line);
+ std::cerr << " : unexpected exception raised\n";
+}
+
+#define ASSERT_EQ(x, y) do { check_equal((x), (y), __func__, __FILE__, __LINE__); } \
+ 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__); } \
+ while (false)
+
+#define ASSERT_RAISES(x, T) \
+ do { try { \
+ x; \
+ check_raises_did_not_raise(__func__, __FILE__, __LINE__); \
+ } catch (const T&) { \
+ check_raises_success(__func__, __FILE__, __LINE__); \
+ } catch (...) { \
+ check_raises_raised_unexpected(__func__, __FILE__, __LINE__); \
+ } } while (false)
+
+int finish_tests();
+
+#endif
diff --git a/testsuite/backend/genesys/session_config_test.cpp b/testsuite/backend/genesys/session_config_test.cpp
new file mode 100644
index 0000000..72043bb
--- /dev/null
+++ b/testsuite/backend/genesys/session_config_test.cpp
@@ -0,0 +1,503 @@
+/* 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 "../../../backend/genesys/device.h"
+#include "../../../backend/genesys/enums.h"
+#include "../../../backend/genesys/error.h"
+#include "../../../backend/genesys/low.h"
+#include "../../../backend/genesys/genesys.h"
+#include "../../../backend/genesys/test_settings.h"
+#include "../../../backend/genesys/test_scanner_interface.h"
+#include "../../../backend/genesys/utilities.h"
+#include "../../../include/sane/saneopts.h"
+#include "sys/stat.h"
+#include <cstdio>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+
+#define XSTR(s) STR(s)
+#define STR(s) #s
+#define CURR_SRCDIR XSTR(TESTSUITE_BACKEND_GENESYS_SRCDIR)
+
+struct TestConfig
+{
+ std::uint16_t vendor_id = 0;
+ std::uint16_t product_id = 0;
+ std::string model_name;
+ genesys::ScanMethod method = genesys::ScanMethod::FLATBED;
+ genesys::ScanColorMode color_mode = genesys::ScanColorMode::COLOR_SINGLE_PASS;
+ unsigned depth = 0;
+ unsigned resolution = 0;
+
+ std::string name() const
+ {
+ std::stringstream out;
+ out << "capture_" << model_name
+ << '_' << method
+ << '_' << color_mode
+ << "_depth" << depth
+ << "_dpi" << resolution;
+ return out.str();
+ }
+
+};
+
+class SaneOptions
+{
+public:
+ void fetch(SANE_Handle handle)
+ {
+ handle_ = handle;
+ options_.resize(1);
+ options_[0] = fetch_option(0);
+
+ if (std::strcmp(options_[0].name, SANE_NAME_NUM_OPTIONS) != 0 ||
+ options_[0].type != SANE_TYPE_INT)
+ {
+ throw std::runtime_error("Expected option number option");
+ }
+ int option_count = 0;
+ TIE(sane_control_option(handle, 0, SANE_ACTION_GET_VALUE, &option_count, nullptr));
+
+ options_.resize(option_count);
+ for (int i = 0; i < option_count; ++i) {
+ options_[i] = fetch_option(i);
+ }
+ }
+
+ void close()
+ {
+ handle_ = nullptr;
+ }
+
+ bool get_value_bool(const std::string& name) const
+ {
+ auto i = find_option(name, SANE_TYPE_BOOL);
+ int value = 0;
+ TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
+ return value;
+ }
+
+ void set_value_bool(const std::string& name, bool value)
+ {
+ auto i = find_option(name, SANE_TYPE_BOOL);
+ int value_int = value;
+ TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr));
+ }
+
+ bool get_value_button(const std::string& name) const
+ {
+ auto i = find_option(name, SANE_TYPE_BUTTON);
+ int value = 0;
+ TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
+ return value;
+ }
+
+ void set_value_button(const std::string& name, bool value)
+ {
+ auto i = find_option(name, SANE_TYPE_BUTTON);
+ int value_int = value;
+ TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr));
+ }
+
+ int get_value_int(const std::string& name) const
+ {
+ auto i = find_option(name, SANE_TYPE_INT);
+ int value = 0;
+ TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
+ return value;
+ }
+
+ void set_value_int(const std::string& name, int value)
+ {
+ auto i = find_option(name, SANE_TYPE_INT);
+ TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value, nullptr));
+ }
+
+ float get_value_float(const std::string& name) const
+ {
+ auto i = find_option(name, SANE_TYPE_FIXED);
+ int value = 0;
+ TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
+ return static_cast<float>(SANE_UNFIX(value));
+ }
+
+ void set_value_float(const std::string& name, float value)
+ {
+ auto i = find_option(name, SANE_TYPE_FIXED);
+ int value_int = SANE_FIX(value);
+ TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr));
+ }
+
+ std::string get_value_string(const std::string& name) const
+ {
+ auto i = find_option(name, SANE_TYPE_STRING);
+ std::string value;
+ value.resize(options_[i].size + 1);
+ TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value.front(), nullptr));
+ value.resize(std::strlen(&value.front()));
+ return value;
+ }
+
+ void set_value_string(const std::string& name, const std::string& value)
+ {
+ auto i = find_option(name, SANE_TYPE_STRING);
+ TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE,
+ const_cast<char*>(&value.front()), nullptr));
+ }
+
+private:
+ SANE_Option_Descriptor fetch_option(int index)
+ {
+ const auto* option = sane_get_option_descriptor(handle_, index);
+ if (option == nullptr) {
+ throw std::runtime_error("Got nullptr option");
+ }
+ return *option;
+ }
+
+ std::size_t find_option(const std::string& name, SANE_Value_Type type) const
+ {
+ for (std::size_t i = 0; i < options_.size(); ++i) {
+ if (options_[i].name == name) {
+ if (options_[i].type != type) {
+ throw std::runtime_error("Option has incorrect type");
+ }
+ return i;
+ }
+ }
+ throw std::runtime_error("Could not find option");
+ }
+
+ SANE_Handle handle_;
+ std::vector<SANE_Option_Descriptor> options_;
+};
+
+
+void build_checkpoint(const genesys::Genesys_Device& dev,
+ genesys::TestScannerInterface& iface,
+ const std::string& checkpoint_name,
+ std::stringstream& out)
+{
+ out << "\n\n================\n"
+ << "Checkpoint: " << checkpoint_name << "\n"
+ << "================\n\n"
+ << "dev: " << genesys::format_indent_braced_list(4, dev) << "\n\n"
+ << "iface.cached_regs: "
+ << genesys::format_indent_braced_list(4, iface.cached_regs()) << "\n\n"
+ << "iface.cached_fe_regs: "
+ << genesys::format_indent_braced_list(4, iface.cached_fe_regs()) << "\n\n"
+ << "iface.last_progress_message: " << iface.last_progress_message() << "\n\n";
+ out << "iface.slope_tables: {\n";
+ for (const auto& kv : iface.recorded_slope_tables()) {
+ out << " " << kv.first << ": {";
+ for (unsigned i = 0; i < kv.second.size(); ++i) {
+ if (i % 10 == 0) {
+ out << "\n ";
+ }
+ out << ' ' << kv.second[i];
+ }
+ out << "\n }\n";
+ }
+ out << "}\n";
+ if (iface.recorded_key_values().empty()) {
+ out << "iface.recorded_key_values: []\n";
+ } else {
+ out << "iface.recorded_key_values: {\n";
+ for (const auto& kv : iface.recorded_key_values()) {
+ out << " " << kv.first << " : " << kv.second << '\n';
+ }
+ out << "}\n";
+ }
+ iface.recorded_key_values().clear();
+ out << "\n";
+}
+
+void run_single_test_scan(const TestConfig& config, std::stringstream& out)
+{
+ auto build_checkpoint_wrapper = [&](const genesys::Genesys_Device& dev,
+ genesys::TestScannerInterface& iface,
+ const std::string& checkpoint_name)
+ {
+ build_checkpoint(dev, iface, checkpoint_name, out);
+ };
+
+ genesys::enable_testing_mode(config.vendor_id, config.product_id, build_checkpoint_wrapper);
+
+ SANE_Handle handle;
+
+ TIE(sane_init(nullptr, nullptr));
+ TIE(sane_open(genesys::get_testing_device_name().c_str(), &handle));
+
+ SaneOptions options;
+ options.fetch(handle);
+
+ options.set_value_button("force-calibration", true);
+ options.set_value_string(SANE_NAME_SCAN_SOURCE,
+ genesys::scan_method_to_option_string(config.method));
+ options.set_value_string(SANE_NAME_SCAN_MODE,
+ genesys::scan_color_mode_to_option_string(config.color_mode));
+ if (config.color_mode != genesys::ScanColorMode::LINEART) {
+ options.set_value_int(SANE_NAME_BIT_DEPTH, config.depth);
+ }
+ options.set_value_int(SANE_NAME_SCAN_RESOLUTION, config.resolution);
+ options.close();
+
+ TIE(sane_start(handle));
+
+ SANE_Parameters params;
+ TIE(sane_get_parameters(handle, &params));
+
+ int buffer_size = 1024 * 1024;
+ std::vector<std::uint8_t> buffer;
+ buffer.resize(buffer_size);
+
+ std::uint64_t total_data_size = std::uint64_t(params.bytes_per_line) * params.lines;
+ std::uint64_t total_got_data = 0;
+
+ while (total_got_data < total_data_size) {
+ int ask_len = std::min<std::size_t>(buffer_size, total_data_size - total_got_data);
+
+ int got_data = 0;
+ auto status = sane_read(handle, buffer.data(), ask_len, &got_data);
+ total_got_data += got_data;
+ if (status == SANE_STATUS_EOF) {
+ break;
+ }
+ TIE(status);
+ }
+
+ sane_cancel(handle);
+ sane_close(handle);
+ sane_exit();
+
+ genesys::disable_testing_mode();
+}
+
+std::string read_file_to_string(const std::string& path)
+{
+ std::ifstream in;
+ in.open(path);
+ if (!in.is_open()) {
+ return "";
+ }
+ std::stringstream in_str;
+ in_str << in.rdbuf();
+ return in_str.str();
+}
+
+void write_string_to_file(const std::string& path, const std::string& contents)
+{
+ std::ofstream out;
+ out.open(path);
+ if (!out.is_open()) {
+ throw std::runtime_error("Could not open output file: " + path);
+ }
+ out << contents;
+ out.close();
+}
+
+struct TestResult
+{
+ bool success = true;
+ TestConfig config;
+ std::string failure_message;
+};
+
+TestResult perform_single_test(const TestConfig& config, const std::string& check_directory,
+ const std::string& output_directory)
+{
+ TestResult test_result;
+ test_result.config = config;
+
+ std::stringstream result_output_stream;
+ std::string exception_output;
+ try {
+ run_single_test_scan(config, result_output_stream);
+ } catch (const std::exception& exc) {
+ exception_output = std::string("got exception: ") + typeid(exc).name() +
+ " with message\n" + exc.what() + "\n";
+ test_result.success = false;
+ test_result.failure_message += exception_output;
+ } catch (...) {
+ exception_output = "got unknown exception\n";
+ test_result.success = false;
+ test_result.failure_message += exception_output;
+ }
+ auto result_output = result_output_stream.str();
+ if (!exception_output.empty()) {
+ result_output += "\n\n" + exception_output;
+ }
+
+ auto test_filename = config.name() + ".txt";
+ auto expected_session_path = check_directory + "/" + test_filename;
+ auto current_session_path = output_directory + "/" + test_filename;
+
+ auto expected_output = read_file_to_string(expected_session_path);
+
+ bool has_output = !output_directory.empty();
+
+ if (has_output) {
+ mkdir(output_directory.c_str(), 0777);
+ // note that check_directory and output_directory may be the same, so make sure removal
+ // happens after the expected output has already been read.
+ std::remove(current_session_path.c_str());
+ }
+
+ if (expected_output.empty()) {
+ test_result.failure_message += "the expected data file does not exist\n";
+ test_result.success = false;
+ } else if (expected_output != result_output) {
+ test_result.failure_message += "expected and current output are not equal\n";
+ if (has_output) {
+ test_result.failure_message += "To examine, run:\ndiff -u \"" + current_session_path +
+ "\" \"" + expected_session_path + "\"\n";
+ }
+ test_result.success = false;
+ }
+
+ if (has_output) {
+ write_string_to_file(current_session_path, result_output);
+ }
+ return test_result;
+}
+
+std::vector<TestConfig> get_all_test_configs()
+{
+ genesys::genesys_init_usb_device_tables();
+
+ std::vector<TestConfig> configs;
+ std::unordered_set<std::string> model_names;
+
+ for (const auto& usb_dev : *genesys::s_usb_devices) {
+ if (usb_dev.model.flags & GENESYS_FLAG_UNTESTED) {
+ continue;
+ }
+ if (model_names.find(usb_dev.model.name) != model_names.end()) {
+ continue;
+ }
+ model_names.insert(usb_dev.model.name);
+
+ for (auto scan_mode : { genesys::ScanColorMode::LINEART,
+ genesys::ScanColorMode::GRAY,
+ genesys::ScanColorMode::COLOR_SINGLE_PASS }) {
+
+ auto depth_values = usb_dev.model.bpp_gray_values;
+ if (scan_mode == genesys::ScanColorMode::COLOR_SINGLE_PASS) {
+ depth_values = usb_dev.model.bpp_color_values;
+ }
+ for (unsigned depth : depth_values) {
+ for (auto method_resolutions : usb_dev.model.resolutions) {
+ for (auto method : method_resolutions.methods) {
+ for (unsigned resolution : method_resolutions.get_resolutions()) {
+ TestConfig config;
+ config.vendor_id = usb_dev.vendor;
+ config.product_id = usb_dev.product;
+ config.model_name = usb_dev.model.name;
+ config.method = method;
+ config.depth = depth;
+ config.resolution = resolution;
+ config.color_mode = scan_mode;
+ configs.push_back(config);
+ }
+ }
+ }
+ }
+ }
+ }
+ return configs;
+}
+
+void print_help()
+{
+ std::cerr << "Usage:\n"
+ << "session_config_test [--test={test_name}] {check_directory} [{output_directory}]\n"
+ << "session_config_test --help\n"
+ << "session_config_test --print_test_names\n";
+}
+
+int main(int argc, const char* argv[])
+{
+ std::string check_directory;
+ std::string output_directory;
+ std::string test_name_filter;
+ bool print_test_names = false;
+
+ for (int argi = 1; argi < argc; ++argi) {
+ std::string arg = argv[argi];
+ if (arg.rfind("--test=", 0) == 0) {
+ test_name_filter = arg.substr(7);
+ } else if (arg == "-h" || arg == "--help") {
+ print_help();
+ return 0;
+ } else if (arg == "--print_test_names") {
+ print_test_names = true;
+ } else if (check_directory.empty()) {
+ check_directory = arg;
+ } else if (output_directory.empty()) {
+ output_directory = arg;
+ }
+ }
+
+ auto configs = get_all_test_configs();
+
+ if (print_test_names) {
+ for (const auto& config : configs) {
+ std::cout << config.name() << "\n";
+ }
+ return 0;
+ }
+
+ if (check_directory.empty()) {
+ print_help();
+ return 1;
+ }
+
+ bool test_success = true;
+ for (unsigned i = 0; i < configs.size(); ++i) {
+ const auto& config = configs[i];
+
+ if (!test_name_filter.empty() && config.name() != test_name_filter) {
+ continue;
+ }
+
+ auto result = perform_single_test(config, check_directory, output_directory);
+ std::cerr << "(" << i << "/" << configs.size() << "): "
+ << (result.success ? "SUCCESS: " : "FAIL: ")
+ << result.config.name() << "\n";
+ if (!result.success) {
+ std::cerr << result.failure_message;
+ }
+
+ test_success &= result.success;
+ }
+
+ if (!test_success) {
+ return 1;
+ }
+ return 0;
+}
diff --git a/testsuite/backend/genesys/tests.cpp b/testsuite/backend/genesys/tests.cpp
new file mode 100644
index 0000000..5fe0084
--- /dev/null
+++ b/testsuite/backend/genesys/tests.cpp
@@ -0,0 +1,37 @@
+/* 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"
+
+int main()
+{
+ genesys::test_calibration_parsing();
+ genesys::test_image();
+ genesys::test_image_pipeline();
+ genesys::test_motor();
+ genesys::test_row_buffer();
+ genesys::test_utilities();
+ return finish_tests();
+}
diff --git a/testsuite/backend/genesys/tests.h b/testsuite/backend/genesys/tests.h
new file mode 100644
index 0000000..c48c586
--- /dev/null
+++ b/testsuite/backend/genesys/tests.h
@@ -0,0 +1,37 @@
+/* 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_GENESYS_UNIT_TEST_H
+#define SANE_TESTSUITE_BACKEND_GENESYS_GENESYS_UNIT_TEST_H
+
+namespace genesys {
+
+void test_calibration_parsing();
+void test_image();
+void test_image_pipeline();
+void test_motor();
+void test_row_buffer();
+void test_utilities();
+
+} // namespace genesys
+
+#endif
diff --git a/testsuite/backend/genesys/tests_calibration.cpp b/testsuite/backend/genesys/tests_calibration.cpp
new file mode 100644
index 0000000..559f8a8
--- /dev/null
+++ b/testsuite/backend/genesys/tests_calibration.cpp
@@ -0,0 +1,135 @@
+/* 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 "../../../backend/genesys/low.h"
+
+#include <sstream>
+
+namespace genesys {
+
+Genesys_Calibration_Cache create_fake_calibration_entry()
+{
+ Genesys_Calibration_Cache calib;
+ calib.params.channels = 3;
+ calib.params.depth = 8;
+ calib.params.lines = 100;
+ calib.params.pixels = 200;
+
+ GenesysFrontendLayout wolfson_layout;
+ wolfson_layout.offset_addr = { 0x20, 0x21, 0x22 };
+ wolfson_layout.gain_addr = { 0x28, 0x29, 0x2a };
+
+ Genesys_Frontend fe;
+ fe.id = AdcId::WOLFSON_UMAX;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x03 },
+ { 0x02, 0x05 },
+ { 0x03, 0x11 },
+ { ' ', 0x80 }, // check whether space-like integer values are correctly serialized
+ { ',', 0x80 },
+ { '\r', '\n' },
+ { '\n', 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x02 },
+ { 0x29, 0x02 },
+ { 0x2a, 0x02 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ calib.frontend = fe;
+
+ Genesys_Sensor sensor;
+ sensor.sensor_id = SensorId::CCD_UMAX;
+ sensor.optical_res = 1200;
+ sensor.black_pixels = 48;
+ sensor.dummy_pixel = 64;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10800;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 230;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.custom_regs = {
+ { 0x08, 0x01 },
+ { 0x09, 0x03 },
+ { 0x0a, 0x05 },
+ { 0x0b, 0x07 },
+ { 0x16, 0x33 },
+ { 0x17, 0x05 },
+ { 0x18, 0x31 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x13 },
+ { 0x53, 0x17 },
+ { 0x54, 0x03 },
+ { 0x55, 0x07 },
+ { 0x56, 0x0b },
+ { 0x57, 0x0f },
+ { 0x58, 0x23 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 },
+ };
+ sensor.gamma = {1.0, 1.0, 1.0};
+ calib.sensor = sensor;
+
+ calib.calib_pixels = 12345;
+ calib.calib_channels = 3;
+ calib.average_size = 7;
+ calib.white_average_data = { 8, 7, 6, 5, 4, 3, 2 };
+ calib.dark_average_data = { 6, 5, 4, 3, 2, 18, 12 };
+ return calib;
+}
+
+void test_calibration_roundtrip()
+{
+ Genesys_Device::Calibration calibration = { create_fake_calibration_entry() };
+ Genesys_Device::Calibration deserialized;
+
+ std::stringstream str;
+ serialize(static_cast<std::ostream&>(str), calibration);
+ serialize(static_cast<std::istream&>(str), deserialized);
+ ASSERT_TRUE(calibration == deserialized);
+
+ int x;
+ str >> x;
+ ASSERT_TRUE(str.eof());
+}
+
+void test_calibration_parsing()
+{
+ test_calibration_roundtrip();
+}
+
+} // namespace genesys
diff --git a/testsuite/backend/genesys/tests_image.cpp b/testsuite/backend/genesys/tests_image.cpp
new file mode 100644
index 0000000..bc8b923
--- /dev/null
+++ b/testsuite/backend/genesys/tests_image.cpp
@@ -0,0 +1,576 @@
+/* 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.h"
+#include "../../../backend/genesys/image_pipeline.h"
+#include <vector>
+
+namespace genesys {
+
+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();
+}
+
+} // namespace genesys
diff --git a/testsuite/backend/genesys/tests_image_pipeline.cpp b/testsuite/backend/genesys/tests_image_pipeline.cpp
new file mode 100644
index 0000000..d4853d2
--- /dev/null
+++ b/testsuite/backend/genesys/tests_image_pipeline.cpp
@@ -0,0 +1,519 @@
+/* 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>
+
+namespace genesys {
+
+void test_image_buffer_genesys_usb()
+{
+ std::vector<std::size_t> requests;
+
+ auto on_read_usb = [&](std::size_t x, std::uint8_t* data)
+ {
+ (void) data;
+ requests.push_back(x);
+ };
+
+ FakeBufferModel model;
+ model.push_step(453120, 1);
+ model.push_step(56640, 3540);
+ ImageBufferGenesysUsb buffer{1086780, model, on_read_usb};
+
+ std::vector<std::uint8_t> dummy;
+ dummy.resize(1086780);
+
+ ASSERT_TRUE(buffer.get_data(453120, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+
+ std::vector<std::size_t> expected = {
+ 453120, 56576, 56576, 56576, 56832, 56576, 56576, 56576, 56832, 56576, 56576, 56576, 11008
+ };
+ ASSERT_EQ(requests, expected);
+}
+
+void test_image_buffer_genesys_usb_capped_remaining_bytes()
+{
+ std::vector<std::size_t> requests;
+
+ auto on_read_usb = [&](std::size_t x, std::uint8_t* data)
+ {
+ (void) data;
+ requests.push_back(x);
+ };
+
+ FakeBufferModel model;
+ model.push_step(453120, 1);
+ model.push_step(56640, 3540);
+ ImageBufferGenesysUsb buffer{1086780, model, on_read_usb};
+
+ std::vector<std::uint8_t> dummy;
+ dummy.resize(1086780);
+
+ ASSERT_TRUE(buffer.get_data(453120, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ ASSERT_TRUE(buffer.get_data(56640, dummy.data()));
+ buffer.set_remaining_size(10000);
+ ASSERT_FALSE(buffer.get_data(56640, dummy.data()));
+
+ std::vector<std::size_t> expected = {
+ // note that the sizes are rounded-up to 256 bytes
+ 453120, 56576, 56576, 56576, 56832, 10240
+ };
+ ASSERT_EQ(requests, expected);
+}
+
+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;
+ return true;
+ };
+
+ 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);
+
+ ASSERT_TRUE(stack.get_next_row_data(out_data.data()));
+ ASSERT_EQ(out_data, Data({0, 1, 2, 3}));
+ ASSERT_EQ(curr_index, 6u);
+
+ ASSERT_TRUE(stack.get_next_row_data(out_data.data()));
+ ASSERT_EQ(out_data, Data({4, 5, 6, 7}));
+ ASSERT_EQ(curr_index, 9u);
+
+ ASSERT_TRUE(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_i8()
+{
+ 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_deinterleave_lines_rgb888()
+{
+ using Data = std::vector<std::uint8_t>;
+
+ Data in_data = {
+ 1, 2, 3, 7, 8, 9, 13, 14, 15, 19, 20, 21,
+ 4, 5, 6, 10, 11, 12, 16, 17, 18, 22, 23, 24,
+ };
+
+ ImagePipelineStack stack;
+ stack.push_first_node<ImagePipelineNodeArraySource>(4, 2, PixelFormat::RGB888,
+ std::move(in_data));
+ stack.push_node<ImagePipelineNodeDeinterleaveLines>(2, 1);
+
+ 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;
+ expected_data.resize(24, 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_swap_16bit_endian()
+{
+ 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>(4, 1, PixelFormat::RGB161616,
+ std::move(in_data));
+ stack.push_node<ImagePipelineNodeSwap16BitEndian>();
+
+ ASSERT_EQ(stack.get_output_width(), 4u);
+ ASSERT_EQ(stack.get_output_height(), 1u);
+ ASSERT_EQ(stack.get_output_row_bytes(), 24u);
+ ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB161616);
+
+ auto out_data = stack.get_all_data();
+
+ Data expected_data = {
+ 0x20, 0x10, 0x11, 0x30, 0x31, 0x21,
+ 0x22, 0x12, 0x13, 0x32, 0x33, 0x23,
+ 0x24, 0x14, 0x15, 0x34, 0x35, 0x25,
+ 0x26, 0x16, 0x17, 0x36, 0x37, 0x27,
+ };
+
+ 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_node_calibrate_8bit()
+{
+ using Data = std::vector<std::uint8_t>;
+
+ Data in_data = {
+ 0x20, 0x38, 0x38
+ };
+
+ std::vector<std::uint16_t> bottom = {
+ 0x1000, 0x2000, 0x3000
+ };
+
+ std::vector<std::uint16_t> top = {
+ 0x3000, 0x4000, 0x5000
+ };
+
+ ImagePipelineStack stack;
+ stack.push_first_node<ImagePipelineNodeArraySource>(1, 1, PixelFormat::RGB888,
+ std::move(in_data));
+ stack.push_node<ImagePipelineNodeCalibrate>(bottom, top);
+
+ ASSERT_EQ(stack.get_output_width(), 1u);
+ ASSERT_EQ(stack.get_output_height(), 1u);
+ ASSERT_EQ(stack.get_output_row_bytes(), 3u);
+ ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888);
+
+ auto out_data = stack.get_all_data();
+
+ Data expected_data = {
+ // note that we don't handle rounding properly in the implementation
+ 0x80, 0xc1, 0x41
+ };
+
+ ASSERT_EQ(out_data, expected_data);
+}
+
+void test_node_calibrate_16bit()
+{
+ using Data = std::vector<std::uint8_t>;
+
+ Data in_data = {
+ 0x00, 0x20, 0x00, 0x38, 0x00, 0x38
+ };
+
+ std::vector<std::uint16_t> bottom = {
+ 0x1000, 0x2000, 0x3000
+ };
+
+ std::vector<std::uint16_t> top = {
+ 0x3000, 0x4000, 0x5000
+ };
+
+ ImagePipelineStack stack;
+ stack.push_first_node<ImagePipelineNodeArraySource>(1, 1, PixelFormat::RGB161616,
+ std::move(in_data));
+ stack.push_node<ImagePipelineNodeCalibrate>(bottom, top);
+
+ ASSERT_EQ(stack.get_output_width(), 1u);
+ ASSERT_EQ(stack.get_output_height(), 1u);
+ ASSERT_EQ(stack.get_output_row_bytes(), 6u);
+ ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB161616);
+
+ auto out_data = stack.get_all_data();
+
+ Data expected_data = {
+ // note that we don't handle rounding properly in the implementation
+ 0x00, 0x80, 0xff, 0xbf, 0x00, 0x40
+ };
+
+ ASSERT_EQ(out_data, expected_data);
+}
+
+void test_image_pipeline()
+{
+ test_image_buffer_genesys_usb();
+ test_image_buffer_genesys_usb_capped_remaining_bytes();
+ test_node_buffered_callable_source();
+ test_node_format_convert();
+ test_node_desegment_1_line();
+ test_node_deinterleave_lines_i8();
+ test_node_deinterleave_lines_rgb888();
+ test_node_swap_16bit_endian();
+ test_node_merge_mono_lines();
+ test_node_split_mono_lines();
+ test_node_component_shift_lines();
+ test_node_pixel_shift_lines();
+ test_node_calibrate_8bit();
+ test_node_calibrate_16bit();
+}
+
+} // namespace genesys
diff --git a/testsuite/backend/genesys/tests_motor.cpp b/testsuite/backend/genesys/tests_motor.cpp
new file mode 100644
index 0000000..07ca693
--- /dev/null
+++ b/testsuite/backend/genesys/tests_motor.cpp
@@ -0,0 +1,365 @@
+/* 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 "../../../backend/genesys/enums.h"
+
+namespace genesys {
+
+void test_create_slope_table3()
+{
+ auto asic_type = AsicType::GL841;
+ auto max_table_size = get_slope_table_max_size(asic_type);
+
+ Genesys_Motor motor;
+ motor.id = MotorId::CANON_LIDE_200;
+ motor.base_ydpi = 1200;
+ motor.optical_ydpi = 6400;
+ motor.slopes.push_back(MotorSlope::create_from_steps(10000, 1000, 20));
+ motor.slopes.push_back(MotorSlope::create_from_steps(10000, 1000, 20));
+ motor.slopes.push_back(MotorSlope::create_from_steps(10000, 1000, 16));
+
+ auto table = sanei_genesys_create_slope_table3(asic_type, motor, StepType::FULL, 10000,
+ motor.base_ydpi);
+
+ ASSERT_EQ(table.pixeltime_sum, 10000u);
+ ASSERT_EQ(table.steps_count, 1u);
+
+ std::vector<std::uint16_t> expected_steps = {
+ 10000,
+ };
+ expected_steps.resize(max_table_size, 10000);
+
+ ASSERT_EQ(table.table, expected_steps);
+
+ table = sanei_genesys_create_slope_table3(asic_type, motor, StepType::FULL, 2000,
+ motor.base_ydpi);
+
+ ASSERT_EQ(table.pixeltime_sum, 33830u);
+ ASSERT_EQ(table.steps_count, 7u);
+
+ expected_steps = {
+ 10000, 10000, 4099, 3028, 2511, 2192, 2000
+ };
+ expected_steps.resize(max_table_size, 2000);
+
+ ASSERT_EQ(table.table, expected_steps);
+
+ table = sanei_genesys_create_slope_table3(asic_type, motor, StepType::HALF, 10000,
+ motor.base_ydpi);
+
+ ASSERT_EQ(table.pixeltime_sum, 5000u);
+ ASSERT_EQ(table.steps_count, 1u);
+
+ expected_steps = {
+ 5000,
+ };
+ expected_steps.resize(max_table_size, 5000);
+
+
+ ASSERT_EQ(table.table, expected_steps);
+
+ table = sanei_genesys_create_slope_table3(asic_type, motor, StepType::HALF, 2000,
+ motor.base_ydpi);
+
+ ASSERT_EQ(table.pixeltime_sum, 16914u);
+ ASSERT_EQ(table.steps_count, 7u);
+
+ expected_steps = {
+ 5000, 5000, 2049, 1514, 1255, 1096, 1000
+ };
+ expected_steps.resize(max_table_size, 1000);
+
+ ASSERT_EQ(table.table, expected_steps);
+
+ table = sanei_genesys_create_slope_table3(asic_type, motor, StepType::QUARTER, 10000,
+ motor.base_ydpi);
+
+ ASSERT_EQ(table.pixeltime_sum, 2500u);
+ ASSERT_EQ(table.steps_count, 1u);
+
+ expected_steps = {
+ 2500,
+ };
+ expected_steps.resize(max_table_size, 2500);
+
+
+ ASSERT_EQ(table.table, expected_steps);
+
+ table = sanei_genesys_create_slope_table3(asic_type, motor, StepType::QUARTER, 2000,
+ motor.base_ydpi);
+
+ ASSERT_EQ(table.pixeltime_sum, 7680u);
+ ASSERT_EQ(table.steps_count, 6u);
+
+ expected_steps = {
+ 2500, 2500, 932, 683, 565, 500
+ };
+ expected_steps.resize(max_table_size, 500);
+
+ ASSERT_EQ(table.table, expected_steps);
+}
+
+void test_create_slope_table_small_full_step()
+{
+ unsigned max_table_size = 1024;
+
+ // created approximately from LIDE 110 slow table: { 62464, 7896, 2632, 0 }
+ MotorSlope slope;
+ slope.initial_speed_w = 62464;
+ slope.max_speed_w = 2632;
+ slope.acceleration = 1.2e-8;
+
+ auto table = create_slope_table(slope, 5000, StepType::FULL, 4, 8, max_table_size);
+
+ std::vector<std::uint16_t> expected_table = {
+ 62464, 62464, 6420, 5000
+ };
+ expected_table.resize(max_table_size, 5000);
+ ASSERT_EQ(table.table, expected_table);
+ ASSERT_EQ(table.steps_count, 8u);
+ ASSERT_EQ(table.pixeltime_sum, 156348u);
+
+
+ table = create_slope_table(slope, 3000, StepType::FULL, 4, 8, max_table_size);
+
+ expected_table = {
+ 62464, 62464, 6420, 4552, 3720, 3223, 3000
+ };
+ expected_table.resize(max_table_size, 3000);
+ ASSERT_EQ(table.table, expected_table);
+ ASSERT_EQ(table.steps_count, 8u);
+ ASSERT_EQ(table.pixeltime_sum, 148843u);
+}
+
+void test_create_slope_table_small_full_step_target_speed_too_high()
+{
+ unsigned max_table_size = 1024;
+
+ // created approximately from LIDE 110 slow table: { 62464, 7896, 2632, 0 }
+ MotorSlope slope;
+ slope.initial_speed_w = 62464;
+ slope.max_speed_w = 2632;
+ slope.acceleration = 1.2e-8;
+
+ auto table = create_slope_table(slope, 2000, StepType::FULL, 4, 8, max_table_size);
+
+ std::vector<std::uint16_t> expected_table = {
+ 62464, 62464, 6420, 4552, 3720, 3223, 2883, 2632
+ };
+ expected_table.resize(max_table_size, 2632);
+ ASSERT_EQ(table.table, expected_table);
+ ASSERT_EQ(table.steps_count, 8u);
+ ASSERT_EQ(table.pixeltime_sum, 148358u);
+}
+
+void test_create_slope_table_small_half_step()
+{
+ unsigned max_table_size = 1024;
+
+ // created approximately from LIDE 110 slow table: { 62464, 7896, 2632, 0 }
+ MotorSlope slope;
+ slope.initial_speed_w = 62464;
+ slope.max_speed_w = 2632;
+ slope.acceleration = 1.2e-8;
+
+ auto table = create_slope_table(slope, 5000, StepType::HALF, 4, 8, max_table_size);
+
+ std::vector<std::uint16_t> expected_table = {
+ 31232, 31232, 3210, 2500
+ };
+ expected_table.resize(max_table_size, 2500);
+ ASSERT_EQ(table.table, expected_table);
+ ASSERT_EQ(table.steps_count, 8u);
+ ASSERT_EQ(table.pixeltime_sum, 78174u);
+
+
+ table = create_slope_table(slope, 3000, StepType::HALF, 4, 8, max_table_size);
+
+ expected_table = {
+ 31232, 31232, 3210, 2276, 1860, 1611, 1500
+ };
+ expected_table.resize(max_table_size, 1500);
+ ASSERT_EQ(table.table, expected_table);
+ ASSERT_EQ(table.steps_count, 8u);
+ ASSERT_EQ(table.pixeltime_sum, 74421u);
+}
+
+void test_create_slope_table_large_full_step()
+{
+ unsigned max_table_size = 1024;
+
+ /* created approximately from Canon 8600F table:
+ 54612, 54612, 34604, 26280, 21708, 18688, 16564, 14936, 13652, 12616,
+ 11768, 11024, 10400, 9872, 9392, 8960, 8584, 8240, 7940, 7648,
+ 7404, 7160, 6948, 6732, 6544, 6376, 6208, 6056, 5912, 5776,
+ 5644, 5520, 5408, 5292, 5192, 5092, 5000, 4908, 4820, 4736,
+ 4660, 4580, 4508, 4440, 4368, 4304, 4240, 4184, 4124, 4068,
+ 4012, 3960, 3908, 3860, 3808, 3764, 3720, 3676, 3636, 3592,
+ 3552, 3516, 3476, 3440, 3400, 3368, 3332, 3300, 3268, 3236,
+ 3204, 3176, 3148, 3116, 3088, 3060, 3036, 3008, 2984, 2956,
+ 2932, 2908, 2884, 2860, 2836, 2816, 2796, 2772, 2752, 2732,
+ 2708, 2692, 2672, 2652, 2632, 2616, 2596, 2576, 2560, 2544,
+ 2528, 2508, 2492, 2476, 2460, 2444, 2432, 2416, 2400, 2384,
+ 2372, 2356, 2344, 2328, 2316, 2304, 2288, 2276, 2260, 2252,
+ 2236, 2224, 2212, 2200, 2188, 2176, 2164, 2156, 2144, 2132,
+ 2120, 2108, 2100, 2088, 2080, 2068, 2056, 2048, 2036, 2028,
+ 2020, 2008, 2000, 1988, 1980, 1972, 1964, 1952, 1944, 1936,
+ 1928, 1920, 1912, 1900, 1892, 1884, 1876, 1868, 1860, 1856,
+ 1848, 1840, 1832, 1824, 1816, 1808, 1800, 1796, 1788, 1780,
+ 1772, 1764, 1760, 1752, 1744, 1740, 1732, 1724, 1720, 1712,
+ 1708, 1700, 1692, 1688, 1680, 1676, 1668, 1664, 1656, 1652,
+ 1644, 1640, 1636, 1628, 1624, 1616, 1612, 1608, 1600, 1596,
+ 1592, 1584, 1580, 1576, 1568, 1564, 1560, 1556, 1548, 1544,
+ 1540, 1536, 1528, 1524, 1520, 1516, 1512, 1508, 1500,
+ */
+ MotorSlope slope;
+ slope.initial_speed_w = 54612;
+ slope.max_speed_w = 1500;
+ slope.acceleration = 1.013948e-9;
+
+ auto table = create_slope_table(slope, 3000, StepType::FULL, 4, 8, max_table_size);
+
+ std::vector<std::uint16_t> expected_table = {
+ 54612, 54612, 20570, 15090, 12481, 10880, 9770, 8943, 8295, 7771,
+ 7335, 6964, 6645, 6366, 6120, 5900, 5702, 5523, 5359, 5210,
+ 5072, 4945, 4826, 4716, 4613, 4517, 4426, 4341, 4260, 4184,
+ 4111, 4043, 3977, 3915, 3855, 3799, 3744, 3692, 3642, 3594,
+ 3548, 3503, 3461, 3419, 3379, 3341, 3304, 3268, 3233, 3199,
+ 3166, 3135, 3104, 3074, 3045, 3017, 3000,
+ };
+ expected_table.resize(max_table_size, 3000);
+ ASSERT_EQ(table.table, expected_table);
+ ASSERT_EQ(table.steps_count, 60u);
+ ASSERT_EQ(table.pixeltime_sum, 412616u);
+
+
+ table = create_slope_table(slope, 1500, StepType::FULL, 4, 8, max_table_size);
+
+ expected_table = {
+ 54612, 54612, 20570, 15090, 12481, 10880, 9770, 8943, 8295, 7771,
+ 7335, 6964, 6645, 6366, 6120, 5900, 5702, 5523, 5359, 5210,
+ 5072, 4945, 4826, 4716, 4613, 4517, 4426, 4341, 4260, 4184,
+ 4111, 4043, 3977, 3915, 3855, 3799, 3744, 3692, 3642, 3594,
+ 3548, 3503, 3461, 3419, 3379, 3341, 3304, 3268, 3233, 3199,
+ 3166, 3135, 3104, 3074, 3045, 3017, 2989, 2963, 2937, 2911,
+ 2886, 2862, 2839, 2816, 2794, 2772, 2750, 2729, 2709, 2689,
+ 2670, 2651, 2632, 2614, 2596, 2578, 2561, 2544, 2527, 2511,
+ 2495, 2480, 2464, 2449, 2435, 2420, 2406, 2392, 2378, 2364,
+ 2351, 2338, 2325, 2313, 2300, 2288, 2276, 2264, 2252, 2241,
+ 2229, 2218, 2207, 2196, 2186, 2175, 2165, 2155, 2145, 2135,
+ 2125, 2115, 2106, 2096, 2087, 2078, 2069, 2060, 2051, 2042,
+ 2034, 2025, 2017, 2009, 2000, 1992, 1984, 1977, 1969, 1961,
+ 1953, 1946, 1938, 1931, 1924, 1917, 1910, 1903, 1896, 1889,
+ 1882, 1875, 1869, 1862, 1855, 1849, 1843, 1836, 1830, 1824,
+ 1818, 1812, 1806, 1800, 1794, 1788, 1782, 1776, 1771, 1765,
+ 1760, 1754, 1749, 1743, 1738, 1733, 1727, 1722, 1717, 1712,
+ 1707, 1702, 1697, 1692, 1687, 1682, 1677, 1673, 1668, 1663,
+ 1659, 1654, 1649, 1645, 1640, 1636, 1631, 1627, 1623, 1618,
+ 1614, 1610, 1606, 1601, 1597, 1593, 1589, 1585, 1581, 1577,
+ 1573, 1569, 1565, 1561, 1557, 1554, 1550, 1546, 1542, 1539,
+ 1535, 1531, 1528, 1524, 1520, 1517, 1513, 1510, 1506, 1503,
+ 1500,
+ };
+ expected_table.resize(max_table_size, 1500);
+ ASSERT_EQ(table.table, expected_table);
+ ASSERT_EQ(table.steps_count, 224u);
+ ASSERT_EQ(table.pixeltime_sum, 734910u);
+}
+
+void test_create_slope_table_large_half_step()
+{
+ unsigned max_table_size = 1024;
+
+ // created approximately from Canon 8600F table, see the full step test for the data
+
+ MotorSlope slope;
+ slope.initial_speed_w = 54612;
+ slope.max_speed_w = 1500;
+ slope.acceleration = 1.013948e-9;
+
+ auto table = create_slope_table(slope, 3000, StepType::HALF, 4, 8, max_table_size);
+
+ std::vector<std::uint16_t> expected_table = {
+ 27306, 27306, 10285, 7545, 6240, 5440, 4885, 4471, 4147, 3885,
+ 3667, 3482, 3322, 3183, 3060, 2950, 2851, 2761, 2679, 2605,
+ 2536, 2472, 2413, 2358, 2306, 2258, 2213, 2170, 2130, 2092,
+ 2055, 2021, 1988, 1957, 1927, 1899, 1872, 1846, 1821, 1797,
+ 1774, 1751, 1730, 1709, 1689, 1670, 1652, 1634, 1616, 1599,
+ 1583, 1567, 1552, 1537, 1522, 1508, 1500,
+ };
+ expected_table.resize(max_table_size, 1500);
+ ASSERT_EQ(table.table, expected_table);
+ ASSERT_EQ(table.steps_count, 60u);
+ ASSERT_EQ(table.pixeltime_sum, 206294u);
+
+
+ table = create_slope_table(slope, 1500, StepType::HALF, 4, 8, max_table_size);
+
+ expected_table = {
+ 27306, 27306, 10285, 7545, 6240, 5440, 4885, 4471, 4147, 3885,
+ 3667, 3482, 3322, 3183, 3060, 2950, 2851, 2761, 2679, 2605,
+ 2536, 2472, 2413, 2358, 2306, 2258, 2213, 2170, 2130, 2092,
+ 2055, 2021, 1988, 1957, 1927, 1899, 1872, 1846, 1821, 1797,
+ 1774, 1751, 1730, 1709, 1689, 1670, 1652, 1634, 1616, 1599,
+ 1583, 1567, 1552, 1537, 1522, 1508, 1494, 1481, 1468, 1455,
+ 1443, 1431, 1419, 1408, 1397, 1386, 1375, 1364, 1354, 1344,
+ 1335, 1325, 1316, 1307, 1298, 1289, 1280, 1272, 1263, 1255,
+ 1247, 1240, 1232, 1224, 1217, 1210, 1203, 1196, 1189, 1182,
+ 1175, 1169, 1162, 1156, 1150, 1144, 1138, 1132, 1126, 1120,
+ 1114, 1109, 1103, 1098, 1093, 1087, 1082, 1077, 1072, 1067,
+ 1062, 1057, 1053, 1048, 1043, 1039, 1034, 1030, 1025, 1021,
+ 1017, 1012, 1008, 1004, 1000, 996, 992, 988, 984, 980,
+ 976, 973, 969, 965, 962, 958, 955, 951, 948, 944,
+ 941, 937, 934, 931, 927, 924, 921, 918, 915, 912,
+ 909, 906, 903, 900, 897, 894, 891, 888, 885, 882,
+ 880, 877, 874, 871, 869, 866, 863, 861, 858, 856,
+ 853, 851, 848, 846, 843, 841, 838, 836, 834, 831,
+ 829, 827, 824, 822, 820, 818, 815, 813, 811, 809,
+ 807, 805, 803, 800, 798, 796, 794, 792, 790, 788,
+ 786, 784, 782, 780, 778, 777, 775, 773, 771, 769,
+ 767, 765, 764, 762, 760, 758, 756, 755, 753, 751,
+ 750,
+ };
+ expected_table.resize(max_table_size, 750);
+ ASSERT_EQ(table.table, expected_table);
+ ASSERT_EQ(table.steps_count, 224u);
+ ASSERT_EQ(table.pixeltime_sum, 367399u);
+}
+
+void test_motor()
+{
+ test_create_slope_table3();
+ test_create_slope_table_small_full_step();
+ test_create_slope_table_small_full_step_target_speed_too_high();
+ test_create_slope_table_small_half_step();
+ test_create_slope_table_large_full_step();
+ test_create_slope_table_large_half_step();
+}
+
+} // namespace genesys
diff --git a/testsuite/backend/genesys/tests_printers.h b/testsuite/backend/genesys/tests_printers.h
new file mode 100644
index 0000000..90becea
--- /dev/null
+++ b/testsuite/backend/genesys/tests_printers.h
@@ -0,0 +1,62 @@
+/* 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_pixel.h"
+#include "../../../backend/genesys/utilities.h"
+#include <iostream>
+#include <iomanip>
+#include <vector>
+
+template<class T>
+std::ostream& operator<<(std::ostream& str, const std::vector<T>& arg)
+{
+ str << genesys::format_vector_unsigned(4, arg) << '\n';
+ return str;
+}
+
+inline std::ostream& operator<<(std::ostream& str, const genesys::PixelFormat& arg)
+{
+ str << static_cast<unsigned>(arg);
+ return str;
+}
+
+inline std::ostream& operator<<(std::ostream& str, const genesys::Pixel& arg)
+{
+ str << "{ " << arg.r << ", " << arg.g << ", " << arg.b << " }";
+ return str;
+}
+
+inline std::ostream& operator<<(std::ostream& str, const genesys::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
diff --git a/testsuite/backend/genesys/tests_row_buffer.cpp b/testsuite/backend/genesys/tests_row_buffer.cpp
new file mode 100644
index 0000000..73ca86c
--- /dev/null
+++ b/testsuite/backend/genesys/tests_row_buffer.cpp
@@ -0,0 +1,91 @@
+/* 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>
+
+namespace genesys {
+
+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);
+ }
+}
+
+} // namespace genesys
diff --git a/testsuite/backend/genesys/tests_utilities.cpp b/testsuite/backend/genesys/tests_utilities.cpp
new file mode 100644
index 0000000..49b9abe
--- /dev/null
+++ b/testsuite/backend/genesys/tests_utilities.cpp
@@ -0,0 +1,110 @@
+/* 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/utilities.h"
+
+namespace genesys {
+
+void test_utilities_compute_array_percentile_approx_empty()
+{
+ std::vector<std::uint16_t> data;
+ data.resize(1, 0);
+
+ ASSERT_RAISES(compute_array_percentile_approx(data.data(), data.data(), 0, 0, 0.0f),
+ SaneException);
+}
+
+void test_utilities_compute_array_percentile_approx_single_line()
+{
+ std::vector<std::uint16_t> data = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
+ };
+ std::vector<std::uint16_t> expected = data;
+ std::vector<std::uint16_t> result;
+ result.resize(data.size(), 0);
+
+ compute_array_percentile_approx(result.data(), data.data(), 1, data.size(), 0.5f);
+ ASSERT_EQ(result, expected);
+}
+
+void test_utilities_compute_array_percentile_approx_multiple_lines()
+{
+ std::vector<std::uint16_t> data = {
+ 5, 17, 4, 14, 3, 9, 9, 5, 10, 1,
+ 6, 1, 0, 18, 8, 5, 11, 11, 15, 12,
+ 6, 8, 7, 3, 2, 15, 5, 12, 3, 3,
+ 6, 12, 17, 6, 7, 7, 1, 6, 3, 18,
+ 10, 5, 8, 0, 14, 3, 3, 7, 10, 5,
+ 18, 7, 3, 11, 0, 14, 12, 19, 18, 11,
+ 5, 16, 2, 9, 8, 2, 7, 6, 11, 18,
+ 16, 5, 2, 2, 14, 18, 19, 13, 16, 1,
+ 5, 9, 14, 6, 17, 16, 1, 1, 16, 0,
+ 19, 18, 4, 12, 0, 7, 15, 3, 2, 6,
+ };
+ std::vector<std::uint16_t> result;
+ result.resize(10, 0);
+
+ std::vector<std::uint16_t> expected = {
+ 5, 1, 0, 0, 0, 2, 1, 1, 2, 0,
+ };
+ compute_array_percentile_approx(result.data(), data.data(), 10, 10, 0.0f);
+ ASSERT_EQ(result, expected);
+
+ expected = {
+ 5, 5, 2, 3, 2, 5, 3, 5, 3, 1,
+ };
+ compute_array_percentile_approx(result.data(), data.data(), 10, 10, 0.25f);
+ ASSERT_EQ(result, expected);
+
+ expected = {
+ 6, 9, 4, 9, 8, 9, 9, 7, 11, 6,
+ };
+ compute_array_percentile_approx(result.data(), data.data(), 10, 10, 0.5f);
+ ASSERT_EQ(result, expected);
+
+ expected = {
+ 16, 16, 8, 12, 14, 15, 12, 12, 16, 12,
+ };
+ compute_array_percentile_approx(result.data(), data.data(), 10, 10, 0.75f);
+ ASSERT_EQ(result, expected);
+
+ expected = {
+ 19, 18, 17, 18, 17, 18, 19, 19, 18, 18,
+ };
+ compute_array_percentile_approx(result.data(), data.data(), 10, 10, 1.0f);
+ ASSERT_EQ(result, expected);
+}
+
+void test_utilities()
+{
+ test_utilities_compute_array_percentile_approx_empty();
+ test_utilities_compute_array_percentile_approx_single_line();
+ test_utilities_compute_array_percentile_approx_multiple_lines();
+}
+
+} // namespace genesys