summaryrefslogtreecommitdiff
path: root/backend/genesys
diff options
context:
space:
mode:
Diffstat (limited to 'backend/genesys')
-rw-r--r--backend/genesys/buffer.cpp102
-rw-r--r--backend/genesys/buffer.h89
-rw-r--r--backend/genesys/calibration.h108
-rw-r--r--backend/genesys/command_set.h166
-rw-r--r--backend/genesys/conv.cpp238
-rw-r--r--backend/genesys/conv.h69
-rw-r--r--backend/genesys/device.cpp272
-rw-r--r--backend/genesys/device.h387
-rw-r--r--backend/genesys/enums.cpp131
-rw-r--r--backend/genesys/enums.h530
-rw-r--r--backend/genesys/error.cpp215
-rw-r--r--backend/genesys/error.h199
-rw-r--r--backend/genesys/fwd.h132
-rw-r--r--backend/genesys/genesys.cpp6172
-rw-r--r--backend/genesys/genesys.h258
-rw-r--r--backend/genesys/gl124.cpp2269
-rw-r--r--backend/genesys/gl124.h205
-rw-r--r--backend/genesys/gl124_registers.h316
-rw-r--r--backend/genesys/gl646.cpp3436
-rw-r--r--backend/genesys/gl646.h521
-rw-r--r--backend/genesys/gl646_registers.h176
-rw-r--r--backend/genesys/gl841.cpp4010
-rw-r--r--backend/genesys/gl841.h130
-rw-r--r--backend/genesys/gl841_registers.h269
-rw-r--r--backend/genesys/gl843.cpp3060
-rw-r--r--backend/genesys/gl843.h139
-rw-r--r--backend/genesys/gl843_registers.h382
-rw-r--r--backend/genesys/gl846.cpp2098
-rw-r--r--backend/genesys/gl846.h218
-rw-r--r--backend/genesys/gl846_registers.h351
-rw-r--r--backend/genesys/gl847.cpp2140
-rw-r--r--backend/genesys/gl847.h206
-rw-r--r--backend/genesys/gl847_registers.h333
-rw-r--r--backend/genesys/image.cpp204
-rw-r--r--backend/genesys/image.h87
-rw-r--r--backend/genesys/image_buffer.cpp203
-rw-r--r--backend/genesys/image_buffer.h129
-rw-r--r--backend/genesys/image_pipeline.cpp839
-rw-r--r--backend/genesys/image_pipeline.h572
-rw-r--r--backend/genesys/image_pixel.cpp509
-rw-r--r--backend/genesys/image_pixel.h144
-rw-r--r--backend/genesys/low.cpp1994
-rw-r--r--backend/genesys/low.h525
-rw-r--r--backend/genesys/motor.cpp180
-rw-r--r--backend/genesys/motor.h177
-rw-r--r--backend/genesys/register.h537
-rw-r--r--backend/genesys/register_cache.h92
-rw-r--r--backend/genesys/row_buffer.h214
-rw-r--r--backend/genesys/scanner_interface.cpp52
-rw-r--r--backend/genesys/scanner_interface.h112
-rw-r--r--backend/genesys/scanner_interface_usb.cpp515
-rw-r--r--backend/genesys/scanner_interface_usb.h98
-rw-r--r--backend/genesys/sensor.cpp160
-rw-r--r--backend/genesys/sensor.h470
-rw-r--r--backend/genesys/serialize.cpp0
-rw-r--r--backend/genesys/serialize.h150
-rw-r--r--backend/genesys/settings.cpp142
-rw-r--r--backend/genesys/settings.h328
-rw-r--r--backend/genesys/static_init.cpp70
-rw-r--r--backend/genesys/static_init.h88
-rw-r--r--backend/genesys/status.cpp66
-rw-r--r--backend/genesys/status.h68
-rw-r--r--backend/genesys/tables_frontend.cpp653
-rw-r--r--backend/genesys/tables_gpo.cpp415
-rw-r--r--backend/genesys/tables_model.cpp2958
-rw-r--r--backend/genesys/tables_motor.cpp325
-rw-r--r--backend/genesys/tables_motor_profile.cpp380
-rw-r--r--backend/genesys/tables_sensor.cpp3668
-rw-r--r--backend/genesys/test_scanner_interface.cpp229
-rw-r--r--backend/genesys/test_scanner_interface.h122
-rw-r--r--backend/genesys/test_settings.cpp106
-rw-r--r--backend/genesys/test_settings.h70
-rw-r--r--backend/genesys/test_usb_device.cpp141
-rw-r--r--backend/genesys/test_usb_device.h85
-rw-r--r--backend/genesys/usb_device.cpp147
-rw-r--r--backend/genesys/usb_device.h118
-rw-r--r--backend/genesys/utilities.h180
77 files changed, 47649 insertions, 0 deletions
diff --git a/backend/genesys/buffer.cpp b/backend/genesys/buffer.cpp
new file mode 100644
index 0000000..f17e361
--- /dev/null
+++ b/backend/genesys/buffer.cpp
@@ -0,0 +1,102 @@
+/* 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.
+*/
+
+#include "buffer.h"
+#include <cstring>
+#include <stdexcept>
+
+namespace genesys {
+
+void Genesys_Buffer::alloc(std::size_t size)
+{
+ buffer_.resize(size);
+ avail_ = 0;
+ pos_ = 0;
+}
+
+void Genesys_Buffer::clear()
+{
+ buffer_.clear();
+ avail_ = 0;
+ pos_ = 0;
+}
+
+void Genesys_Buffer::reset()
+{
+ avail_ = 0;
+ pos_ = 0;
+}
+
+std::uint8_t* Genesys_Buffer::get_write_pos(std::size_t size)
+{
+ if (avail_ + size > buffer_.size())
+ return nullptr;
+ if (pos_ + avail_ + size > buffer_.size())
+ {
+ std::memmove(buffer_.data(), buffer_.data() + pos_, avail_);
+ pos_ = 0;
+ }
+ return buffer_.data() + pos_ + avail_;
+}
+
+std::uint8_t* Genesys_Buffer::get_read_pos()
+{
+ return buffer_.data() + pos_;
+}
+
+void Genesys_Buffer::produce(std::size_t size)
+{
+ if (size > buffer_.size() - avail_)
+ throw std::runtime_error("buffer size exceeded");
+ avail_ += size;
+}
+
+void Genesys_Buffer::consume(std::size_t size)
+{
+ if (size > avail_)
+ throw std::runtime_error("no more data in buffer");
+ avail_ -= size;
+ pos_ += size;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/buffer.h b/backend/genesys/buffer.h
new file mode 100644
index 0000000..e9c889b
--- /dev/null
+++ b/backend/genesys/buffer.h
@@ -0,0 +1,89 @@
+/* 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_BUFFER_H
+#define BACKEND_GENESYS_BUFFER_H
+
+#include <vector>
+#include <cstddef>
+#include <cstdint>
+
+namespace genesys {
+
+/* A FIFO buffer. Note, that this is _not_ a ringbuffer.
+ if we need a block which does not fit at the end of our available data,
+ we move the available data to the beginning.
+*/
+struct Genesys_Buffer
+{
+ Genesys_Buffer() = default;
+
+ std::size_t size() const { return buffer_.size(); }
+ std::size_t avail() const { return avail_; }
+ std::size_t pos() const { return pos_; }
+
+ // TODO: refactor code that uses this function to no longer use it
+ void set_pos(std::size_t pos) { pos_ = pos; }
+
+ void alloc(std::size_t size);
+ void clear();
+
+ void reset();
+
+ std::uint8_t* get_write_pos(std::size_t size);
+ std::uint8_t* get_read_pos(); // TODO: mark as const
+
+ void produce(std::size_t size);
+ void consume(std::size_t size);
+
+private:
+ std::vector<std::uint8_t> buffer_;
+ // current position in read buffer
+ std::size_t pos_ = 0;
+ // data bytes currently in buffer
+ std::size_t avail_ = 0;
+};
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_BUFFER_H
diff --git a/backend/genesys/calibration.h b/backend/genesys/calibration.h
new file mode 100644
index 0000000..f14aaa3
--- /dev/null
+++ b/backend/genesys/calibration.h
@@ -0,0 +1,108 @@
+/* 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_CALIBRATION_H
+#define BACKEND_GENESYS_CALIBRATION_H
+
+#include "sensor.h"
+#include "settings.h"
+#include <ctime>
+
+namespace genesys {
+
+struct Genesys_Calibration_Cache
+{
+ Genesys_Calibration_Cache() = default;
+ ~Genesys_Calibration_Cache() = default;
+
+ // used to check if entry is compatible
+ SetupParams params;
+
+ std::time_t last_calibration = 0;
+
+ Genesys_Frontend frontend;
+ Genesys_Sensor sensor;
+
+ size_t calib_pixels = 0;
+ size_t calib_channels = 0;
+ size_t average_size = 0;
+ std::vector<std::uint16_t> white_average_data;
+ std::vector<std::uint16_t> dark_average_data;
+
+ bool operator==(const Genesys_Calibration_Cache& other) const
+ {
+ return params == other.params &&
+ last_calibration == other.last_calibration &&
+ frontend == other.frontend &&
+ sensor == other.sensor &&
+ calib_pixels == other.calib_pixels &&
+ calib_channels == other.calib_channels &&
+ average_size == other.average_size &&
+ white_average_data == other.white_average_data &&
+ dark_average_data == other.dark_average_data;
+ }
+};
+
+template<class Stream>
+void serialize(Stream& str, Genesys_Calibration_Cache& x)
+{
+ serialize(str, x.params);
+ serialize_newline(str);
+ serialize(str, x.last_calibration);
+ serialize_newline(str);
+ serialize(str, x.frontend);
+ serialize_newline(str);
+ serialize(str, x.sensor);
+ serialize_newline(str);
+ serialize(str, x.calib_pixels);
+ serialize(str, x.calib_channels);
+ serialize(str, x.average_size);
+ serialize_newline(str);
+ serialize(str, x.white_average_data);
+ serialize_newline(str);
+ serialize(str, x.dark_average_data);
+}
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_CALIBRATION_H
diff --git a/backend/genesys/command_set.h b/backend/genesys/command_set.h
new file mode 100644
index 0000000..ab3a4b6
--- /dev/null
+++ b/backend/genesys/command_set.h
@@ -0,0 +1,166 @@
+/* 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_COMMAND_SET_H
+#define BACKEND_GENESYS_COMMAND_SET_H
+
+#include "device.h"
+#include "fwd.h"
+#include <cstdint>
+
+namespace genesys {
+
+
+/** Scanner command set description.
+
+ This description contains parts which are common to all scanners with the
+ same command set, but may have different optical resolution and other
+ parameters.
+ */
+class CommandSet
+{
+public:
+ virtual ~CommandSet() = default;
+
+ virtual bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const = 0;
+
+ virtual void init(Genesys_Device* dev) const = 0;
+
+ virtual void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, int* channels,
+ int* total_size) const = 0;
+
+ virtual void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const = 0;
+ virtual void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const = 0;
+ virtual void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const = 0;
+
+ /** Set up registers for a scan. Similar to init_regs_for_scan except that the session is
+ already computed from the session
+ */
+ virtual void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const= 0;
+
+ virtual void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, std::uint8_t set) const = 0;
+ virtual void set_powersaving(Genesys_Device* dev, int delay) const = 0;
+ virtual void save_power(Genesys_Device* dev, bool enable) const = 0;
+
+ virtual void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, bool start_motor) const = 0;
+ virtual void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs,
+ bool check_stop) const = 0;
+
+
+ /**
+ * Send gamma tables to ASIC
+ */
+ virtual void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const = 0;
+
+ virtual void search_start_position(Genesys_Device* dev) const = 0;
+ virtual void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const = 0;
+ virtual void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const = 0;
+ virtual SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const = 0;
+
+ virtual void wait_for_motor_stop(Genesys_Device* dev) const = 0;
+ virtual void move_back_home(Genesys_Device* dev, bool wait_until_home) const = 0;
+
+ // Updates hardware sensor information in Genesys_Scanner.val[].
+ virtual void update_hardware_sensors(struct Genesys_Scanner* s) const = 0;
+
+ /** Whether the scanner needs to call update_home_sensor_gpio before reading the status of the
+ home sensor. On some chipsets this is unreliable until update_home_sensor_gpio() is called.
+ */
+ virtual bool needs_update_home_sensor_gpio() const { return false; }
+
+ /** Needed on some chipsets before reading the status of the home sensor to make this operation
+ reliable.
+ */
+ virtual void update_home_sensor_gpio(Genesys_Device& dev) const { (void) dev; }
+
+ // functions for sheetfed scanners
+
+ // load document into scanner
+ virtual void load_document(Genesys_Device* dev) const = 0;
+
+ /** Detects is the scanned document has left scanner. In this case it updates the amount of
+ data to read and set up flags in the dev struct
+ */
+ virtual void detect_document_end(Genesys_Device* dev) const = 0;
+
+ /// eject document from scanner
+ virtual void eject_document(Genesys_Device* dev) const = 0;
+ /**
+ * search for an black or white area in forward or reverse
+ * direction */
+ virtual void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ bool forward, bool black) const = 0;
+
+ /// move scanning head to transparency adapter
+ virtual void move_to_ta(Genesys_Device* dev) const = 0;
+
+ /// write shading data calibration to ASIC
+ virtual void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ std::uint8_t* data, int size) const = 0;
+
+ virtual bool has_send_shading_data() const
+ {
+ return true;
+ }
+
+ /// calculate an instance of ScanSession for scanning with the given settings
+ virtual ScanSession calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const = 0;
+
+ /// cold boot init function
+ virtual void asic_boot(Genesys_Device* dev, bool cold) const = 0;
+};
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_COMMAND_SET_H
diff --git a/backend/genesys/conv.cpp b/backend/genesys/conv.cpp
new file mode 100644
index 0000000..a87c463
--- /dev/null
+++ b/backend/genesys/conv.cpp
@@ -0,0 +1,238 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2005, 2006 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
+ Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr>
+
+ 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 "conv.h"
+#include "sane/sanei_magic.h"
+
+namespace genesys {
+
+/**
+ * uses the threshold/threshold_curve to control software binarization
+ * This code was taken from the epjistsu backend by m. allan noah
+ * @param dev device set up for the scan
+ * @param src pointer to raw data
+ * @param dst pointer where to store result
+ * @param width width of the processed line
+ * */
+void binarize_line(Genesys_Device* dev, std::uint8_t* src, std::uint8_t* dst, int width)
+{
+ DBG_HELPER(dbg);
+ int j, windowX, sum = 0;
+ int thresh;
+ int offset, addCol, dropCol;
+ unsigned char mask;
+
+ int x;
+ std::uint8_t min, max;
+
+ /* normalize line */
+ min = 255;
+ max = 0;
+ for (x = 0; x < width; x++)
+ {
+ if (src[x] > max)
+ {
+ max = src[x];
+ }
+ if (src[x] < min)
+ {
+ min = src[x];
+ }
+ }
+
+ /* safeguard against dark or white areas */
+ if(min>80)
+ min=0;
+ if(max<80)
+ max=255;
+ for (x = 0; x < width; x++)
+ {
+ src[x] = ((src[x] - min) * 255) / (max - min);
+ }
+
+ /* ~1mm works best, but the window needs to have odd # of pixels */
+ windowX = (6 * dev->settings.xres) / 150;
+ if (!(windowX % 2))
+ windowX++;
+
+ /* second, prefill the sliding sum */
+ for (j = 0; j < windowX; j++)
+ sum += src[j];
+
+ /* third, walk the input buffer, update the sliding sum, */
+ /* determine threshold, output bits */
+ for (j = 0; j < width; j++)
+ {
+ /* output image location */
+ offset = j % 8;
+ mask = 0x80 >> offset;
+ thresh = dev->settings.threshold;
+
+ /* move sum/update threshold only if there is a curve */
+ if (dev->settings.threshold_curve)
+ {
+ addCol = j + windowX / 2;
+ dropCol = addCol - windowX;
+
+ if (dropCol >= 0 && addCol < width)
+ {
+ sum -= src[dropCol];
+ sum += src[addCol];
+ }
+ thresh = dev->lineart_lut[sum / windowX];
+ }
+
+ /* use average to lookup threshold */
+ if (src[j] > thresh)
+ *dst &= ~mask; /* white */
+ else
+ *dst |= mask; /* black */
+
+ if (offset == 7)
+ dst++;
+ }
+}
+
+/**
+ * software lineart using data from a 8 bit gray scan. We assume true gray
+ * or monochrome scan as input.
+ */
+void genesys_gray_lineart(Genesys_Device* dev,
+ std::uint8_t* src_data, std::uint8_t* dst_data,
+ std::size_t pixels, std::size_t lines, std::uint8_t threshold)
+{
+ DBG_HELPER(dbg);
+ std::size_t y;
+
+ DBG(DBG_io2, "%s: converting %zu lines of %zu pixels\n", __func__, lines, pixels);
+ DBG(DBG_io2, "%s: threshold=%d\n", __func__, threshold);
+
+ for (y = 0; y < lines; y++)
+ {
+ binarize_line (dev, src_data + y * pixels, dst_data, pixels);
+ dst_data += pixels / 8;
+ }
+}
+
+/** Look in image for likely left/right/bottom paper edges, then crop image.
+ */
+void genesys_crop(Genesys_Scanner* s)
+{
+ DBG_HELPER(dbg);
+ Genesys_Device *dev = s->dev;
+ int top = 0;
+ int bottom = 0;
+ int left = 0;
+ int right = 0;
+
+ // first find edges if any
+ TIE(sanei_magic_findEdges(&s->params, dev->img_buffer.data(),
+ dev->settings.xres, dev->settings.yres,
+ &top, &bottom, &left, &right));
+
+ DBG (DBG_io, "%s: t:%d b:%d l:%d r:%d\n", __func__, top, bottom, left,
+ right);
+
+ // now crop the image
+ TIE(sanei_magic_crop (&(s->params), dev->img_buffer.data(), top, bottom, left, right));
+
+ /* update counters to new image size */
+ dev->total_bytes_to_read = s->params.bytes_per_line * s->params.lines;
+}
+
+/** Look in image for likely upper and left paper edges, then rotate
+ * image so that upper left corner of paper is upper left of image.
+ */
+void genesys_deskew(Genesys_Scanner *s, const Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+ Genesys_Device *dev = s->dev;
+
+ int x = 0, y = 0, bg;
+ double slope = 0;
+
+ bg=0;
+ if(s->params.format==SANE_FRAME_GRAY && s->params.depth == 1)
+ {
+ bg=0xff;
+ }
+ TIE(sanei_magic_findSkew(&s->params, dev->img_buffer.data(),
+ sensor.optical_res, sensor.optical_res,
+ &x, &y, &slope));
+
+ DBG(DBG_info, "%s: slope=%f => %f\n", __func__, slope, slope * 180 / M_PI);
+
+ // rotate image slope is in [-PI/2,PI/2]. Positive values rotate trigonometric direction wise
+ TIE(sanei_magic_rotate(&s->params, dev->img_buffer.data(),
+ x, y, slope, bg));
+}
+
+/** remove lone dots
+ */
+void genesys_despeck(Genesys_Scanner* s)
+{
+ DBG_HELPER(dbg);
+ TIE(sanei_magic_despeck(&s->params, s->dev->img_buffer.data(), s->despeck));
+}
+
+/** Look if image needs rotation and apply it
+ * */
+void genesys_derotate(Genesys_Scanner* s)
+{
+ DBG_HELPER(dbg);
+ int angle = 0;
+
+ TIE(sanei_magic_findTurn(&s->params, s->dev->img_buffer.data(),
+ s->resolution, s->resolution, &angle));
+
+ // apply rotation angle found
+ TIE(sanei_magic_turn(&s->params, s->dev->img_buffer.data(), angle));
+
+ // update counters to new image size
+ s->dev->total_bytes_to_read = s->params.bytes_per_line * s->params.lines;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/conv.h b/backend/genesys/conv.h
new file mode 100644
index 0000000..446a80d
--- /dev/null
+++ b/backend/genesys/conv.h
@@ -0,0 +1,69 @@
+/* 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_CONV_H
+#define BACKEND_GENESYS_CONV_H
+
+#include "device.h"
+#include "sensor.h"
+#include "genesys.h"
+
+namespace genesys {
+
+void binarize_line(Genesys_Device* dev, std::uint8_t* src, std::uint8_t* dst, int width);
+
+void genesys_gray_lineart(Genesys_Device* dev,
+ std::uint8_t* src_data, std::uint8_t* dst_data,
+ std::size_t pixels, size_t lines, std::uint8_t threshold);
+
+void genesys_crop(Genesys_Scanner* s);
+
+void genesys_deskew(Genesys_Scanner *s, const Genesys_Sensor& sensor);
+
+void genesys_despeck(Genesys_Scanner* s);
+
+void genesys_derotate(Genesys_Scanner* s);
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_CONV_H
diff --git a/backend/genesys/device.cpp b/backend/genesys/device.cpp
new file mode 100644
index 0000000..ba035fd
--- /dev/null
+++ b/backend/genesys/device.cpp
@@ -0,0 +1,272 @@
+/* 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 "device.h"
+#include "command_set.h"
+#include "low.h"
+#include "utilities.h"
+
+namespace genesys {
+
+std::vector<unsigned> MethodResolutions::get_resolutions() const
+{
+ std::vector<unsigned> ret;
+ std::copy(resolutions_x.begin(), resolutions_x.end(), std::back_inserter(ret));
+ std::copy(resolutions_y.begin(), resolutions_y.end(), std::back_inserter(ret));
+ // sort in decreasing order
+
+ std::sort(ret.begin(), ret.end(), std::greater<unsigned>());
+ ret.erase(std::unique(ret.begin(), ret.end()), ret.end());
+ return ret;
+}
+
+const MethodResolutions& Genesys_Model::get_resolution_settings(ScanMethod method) const
+{
+ for (const auto& res_for_method : resolutions) {
+ for (auto res_method : res_for_method.methods) {
+ if (res_method == method) {
+ return res_for_method;
+ }
+ }
+ }
+ throw SaneException("Could not find resolution settings for method %d",
+ static_cast<unsigned>(method));
+}
+
+std::vector<unsigned> Genesys_Model::get_resolutions(ScanMethod method) const
+{
+ return get_resolution_settings(method).get_resolutions();
+}
+
+Genesys_Device::~Genesys_Device()
+{
+ clear();
+}
+
+void Genesys_Device::clear()
+{
+ read_buffer.clear();
+ binarize_buffer.clear();
+ local_buffer.clear();
+
+ calib_file.clear();
+
+ calibration_cache.clear();
+
+ white_average_data.clear();
+ dark_average_data.clear();
+}
+
+ImagePipelineNodeBytesSource& Genesys_Device::get_pipeline_source()
+{
+ return static_cast<ImagePipelineNodeBytesSource&>(pipeline.front());
+}
+
+bool Genesys_Device::is_head_pos_known(ScanHeadId scan_head) const
+{
+ switch (scan_head) {
+ case ScanHeadId::PRIMARY: return is_head_pos_primary_known_;
+ case ScanHeadId::SECONDARY: return is_head_pos_secondary_known_;
+ case ScanHeadId::ALL: return is_head_pos_primary_known_ && is_head_pos_secondary_known_;
+ default:
+ throw SaneException("Unknown scan head ID");
+ }
+}
+unsigned Genesys_Device::head_pos(ScanHeadId scan_head) const
+{
+ switch (scan_head) {
+ case ScanHeadId::PRIMARY: return head_pos_primary_;
+ case ScanHeadId::SECONDARY: return head_pos_secondary_;
+ default:
+ throw SaneException("Unknown scan head ID");
+ }
+}
+
+void Genesys_Device::set_head_pos_unknown()
+{
+ is_head_pos_primary_known_ = false;
+ is_head_pos_secondary_known_ = false;
+}
+
+void Genesys_Device::set_head_pos_zero(ScanHeadId scan_head)
+{
+ if ((scan_head & ScanHeadId::PRIMARY) != ScanHeadId::NONE) {
+ head_pos_primary_ = 0;
+ is_head_pos_primary_known_ = true;
+ }
+ if ((scan_head & ScanHeadId::SECONDARY) != ScanHeadId::NONE) {
+ head_pos_secondary_ = 0;
+ is_head_pos_secondary_known_ = true;
+ }
+}
+
+void Genesys_Device::advance_head_pos_by_session(ScanHeadId scan_head)
+{
+ int motor_steps = session.params.starty +
+ (session.params.lines * motor.base_ydpi) / session.params.yres;
+ auto direction = has_flag(session.params.flags, ScanFlag::REVERSE) ? Direction::BACKWARD
+ : Direction::FORWARD;
+ advance_head_pos_by_steps(scan_head, direction, motor_steps);
+}
+
+static void advance_pos(unsigned& pos, Direction direction, unsigned offset)
+{
+ if (direction == Direction::FORWARD) {
+ pos += offset;
+ } else {
+ if (pos < offset) {
+ throw SaneException("Trying to advance head behind the home sensor");
+ }
+ pos -= offset;
+ }
+}
+
+void Genesys_Device::advance_head_pos_by_steps(ScanHeadId scan_head, Direction direction,
+ unsigned steps)
+{
+ if ((scan_head & ScanHeadId::PRIMARY) != ScanHeadId::NONE) {
+ if (!is_head_pos_primary_known_) {
+ throw SaneException("Trying to advance head while scanhead position is not known");
+ }
+ advance_pos(head_pos_primary_, direction, steps);
+ }
+ if ((scan_head & ScanHeadId::SECONDARY) != ScanHeadId::NONE) {
+ if (!is_head_pos_secondary_known_) {
+ throw SaneException("Trying to advance head while scanhead position is not known");
+ }
+ advance_pos(head_pos_secondary_, direction, steps);
+ }
+}
+
+void print_scan_position(std::ostream& out, const Genesys_Device& dev, ScanHeadId scan_head)
+{
+ if (dev.is_head_pos_known(scan_head)) {
+ out << dev.head_pos(scan_head);
+ } else {
+ out <<"(unknown)";
+ }
+}
+
+std::ostream& operator<<(std::ostream& out, const Genesys_Device& dev)
+{
+ StreamStateSaver state_saver{out};
+
+ out << "Genesys_Device{\n"
+ << std::hex
+ << " vendorId: 0x" << dev.vendorId << '\n'
+ << " productId: 0x" << dev.productId << '\n'
+ << std::dec
+ << " usb_mode: " << dev.usb_mode << '\n'
+ << " file_name: " << dev.file_name << '\n'
+ << " calib_file: " << dev.calib_file << '\n'
+ << " force_calibration: " << dev.force_calibration << '\n'
+ << " ignore_offsets: " << dev.ignore_offsets << '\n'
+ << " model: (not printed)\n"
+ << " reg: " << format_indent_braced_list(4, dev.reg) << '\n'
+ << " calib_reg: " << format_indent_braced_list(4, dev.calib_reg) << '\n'
+ << " settings: " << format_indent_braced_list(4, dev.settings) << '\n'
+ << " frontend: " << format_indent_braced_list(4, dev.frontend) << '\n'
+ << " frontend_initial: " << format_indent_braced_list(4, dev.frontend_initial) << '\n'
+ << " frontend_is_init: " << dev.frontend_is_init << '\n'
+ << " gpo.regs: " << format_indent_braced_list(4, dev.gpo.regs) << '\n'
+ << " motor: " << format_indent_braced_list(4, dev.motor) << '\n'
+ << " control[0..6]: " << std::hex
+ << static_cast<unsigned>(dev.control[0]) << ' '
+ << static_cast<unsigned>(dev.control[1]) << ' '
+ << static_cast<unsigned>(dev.control[2]) << ' '
+ << static_cast<unsigned>(dev.control[3]) << ' '
+ << static_cast<unsigned>(dev.control[4]) << ' '
+ << static_cast<unsigned>(dev.control[5]) << '\n' << std::dec
+ << " average_size: " << dev.average_size << '\n'
+ << " calib_pixels: " << dev.calib_pixels << '\n'
+ << " calib_lines: " << dev.calib_lines << '\n'
+ << " calib_channels: " << dev.calib_channels << '\n'
+ << " calib_resolution: " << dev.calib_resolution << '\n'
+ << " calib_total_bytes_to_read: " << dev.calib_total_bytes_to_read << '\n'
+ << " calib_session: " << format_indent_braced_list(4, dev.calib_session) << '\n'
+ << " calib_pixels_offset: " << dev.calib_pixels_offset << '\n'
+ << " gamma_override_tables[0].size(): " << dev.gamma_override_tables[0].size() << '\n'
+ << " gamma_override_tables[1].size(): " << dev.gamma_override_tables[1].size() << '\n'
+ << " gamma_override_tables[2].size(): " << dev.gamma_override_tables[2].size() << '\n'
+ << " white_average_data.size(): " << dev.white_average_data.size() << '\n'
+ << " dark_average_data.size(): " << dev.dark_average_data.size() << '\n'
+ << " already_initialized: " << dev.already_initialized << '\n'
+ << " scanhead_position[PRIMARY]: ";
+ print_scan_position(out, dev, ScanHeadId::PRIMARY);
+ out << '\n'
+ << " scanhead_position[SECONDARY]: ";
+ print_scan_position(out, dev, ScanHeadId::SECONDARY);
+ out << '\n'
+ << " read_active: " << dev.read_active << '\n'
+ << " parking: " << dev.parking << '\n'
+ << " document: " << dev.document << '\n'
+ << " read_buffer.size(): " << dev.read_buffer.size() << '\n'
+ << " binarize_buffer.size(): " << dev.binarize_buffer.size() << '\n'
+ << " local_buffer.size(): " << dev.local_buffer.size() << '\n'
+ << " oe_buffer.size(): " << dev.oe_buffer.size() << '\n'
+ << " total_bytes_read: " << dev.total_bytes_read << '\n'
+ << " total_bytes_to_read: " << dev.total_bytes_to_read << '\n'
+ << " session: " << format_indent_braced_list(4, dev.session) << '\n'
+ << " lineart_lut: (not printed)\n"
+ << " calibration_cache: (not printed)\n"
+ << " line_count: " << dev.line_count << '\n'
+ << " segment_order: "
+ << format_indent_braced_list(4, format_vector_unsigned(4, dev.segment_order)) << '\n'
+ << " buffer_image: " << dev.buffer_image << '\n'
+ << " img_buffer.size(): " << dev.img_buffer.size() << '\n'
+ << '}';
+ return out;
+}
+
+void apply_reg_settings_to_device(Genesys_Device& dev, const GenesysRegisterSettingSet& regs)
+{
+ for (const auto& reg : regs) {
+ uint8_t val = dev.interface->read_register(reg.address);
+ val = (val & ~reg.mask) | (reg.value & reg.mask);
+ dev.interface->write_register(reg.address, val);
+ }
+}
+
+} // namespace genesys
diff --git a/backend/genesys/device.h b/backend/genesys/device.h
new file mode 100644
index 0000000..6c744c9
--- /dev/null
+++ b/backend/genesys/device.h
@@ -0,0 +1,387 @@
+/* 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_DEVICE_H
+#define BACKEND_GENESYS_DEVICE_H
+
+#include "calibration.h"
+#include "command_set.h"
+#include "buffer.h"
+#include "enums.h"
+#include "image_pipeline.h"
+#include "motor.h"
+#include "settings.h"
+#include "sensor.h"
+#include "register.h"
+#include "usb_device.h"
+#include "scanner_interface.h"
+#include <vector>
+
+namespace genesys {
+
+struct Genesys_Gpo
+{
+ Genesys_Gpo() = default;
+
+ // Genesys_Gpo
+ GpioId id = GpioId::UNKNOWN;
+
+ /* GL646 and possibly others:
+ - have the value registers at 0x66 and 0x67
+ - have the enable registers at 0x68 and 0x69
+
+ GL841, GL842, GL843, GL846, GL848 and possibly others:
+ - have the value registers at 0x6c and 0x6d.
+ - have the enable registers at 0x6e and 0x6f.
+ */
+ GenesysRegisterSettingSet regs;
+};
+
+/// Stores a SANE_Fixed value which is automatically converted from and to floating-point values
+class FixedFloat
+{
+public:
+ FixedFloat() = default;
+ FixedFloat(const FixedFloat&) = default;
+ FixedFloat(double number) : value_{SANE_FIX(number)} {}
+ FixedFloat& operator=(const FixedFloat&) = default;
+ FixedFloat& operator=(double number) { value_ = SANE_FIX(number); return *this; }
+
+ operator double() const { return value(); }
+
+ double value() const { return SANE_UNFIX(value_); }
+
+private:
+ SANE_Fixed value_ = 0;
+};
+
+struct MethodResolutions
+{
+ std::vector<ScanMethod> methods;
+ std::vector<unsigned> resolutions_x;
+ std::vector<unsigned> resolutions_y;
+
+ unsigned get_min_resolution_x() const
+ {
+ return *std::min_element(resolutions_x.begin(), resolutions_x.end());
+ }
+
+ unsigned get_min_resolution_y() const
+ {
+ return *std::min_element(resolutions_y.begin(), resolutions_y.end());
+ }
+
+ std::vector<unsigned> get_resolutions() const;
+};
+
+/** @brief structure to describe a scanner model
+ * This structure describes a model. It is composed of information on the
+ * sensor, the motor, scanner geometry and flags to drive operation.
+ */
+struct Genesys_Model
+{
+ Genesys_Model() = default;
+
+ const char* name = nullptr;
+ const char* vendor = nullptr;
+ const char* model = nullptr;
+ ModelId model_id = ModelId::UNKNOWN;
+
+ AsicType asic_type = AsicType::UNKNOWN;
+
+ // possible x and y resolutions for each method supported by the scanner
+ std::vector<MethodResolutions> resolutions;
+
+ // possible depths in gray mode
+ std::vector<unsigned> bpp_gray_values;
+ // possible depths in color mode
+ std::vector<unsigned> bpp_color_values;
+
+ // the default scanning method. This is used when moving the head for example
+ ScanMethod default_method = ScanMethod::FLATBED;
+
+ // All offsets below are with respect to the sensor home position
+
+ // Start of scan area in mm
+ FixedFloat x_offset = 0;
+
+ // Start of scan area in mm (Amount of feeding needed to get to the medium)
+ FixedFloat y_offset = 0;
+
+ // Size of scan area in mm
+ FixedFloat x_size = 0;
+
+ // Size of scan area in mm
+ FixedFloat y_size = 0;
+
+ // Start of white strip in mm
+ FixedFloat y_offset_calib_white = 0;
+
+ // Start of black mark in mm
+ FixedFloat x_offset_calib_black = 0;
+
+ // Start of scan area in transparency mode in mm
+ FixedFloat x_offset_ta = 0;
+
+ // Start of scan area in transparency mode in mm
+ FixedFloat y_offset_ta = 0;
+
+ // Size of scan area in transparency mode in mm
+ FixedFloat x_size_ta = 0;
+
+ // Size of scan area in transparency mode in mm
+ FixedFloat y_size_ta = 0;
+
+ // The position of the sensor when it's aligned with the lamp for transparency scanning
+ FixedFloat y_offset_sensor_to_ta = 0;
+
+ // Start of white strip in transparency mode in mm
+ FixedFloat y_offset_calib_white_ta = 0;
+
+ // Start of black strip in transparency mode in mm
+ FixedFloat y_offset_calib_black_ta = 0;
+
+ // Size of scan area after paper sensor stop sensing document in mm
+ FixedFloat post_scan = 0;
+
+ // Amount of feeding needed to eject document after finishing scanning in mm
+ FixedFloat eject_feed = 0;
+
+ // Line-distance correction (in pixel at optical_ydpi) for CCD scanners
+ SANE_Int ld_shift_r = 0;
+ SANE_Int ld_shift_g = 0;
+ SANE_Int ld_shift_b = 0;
+
+ // Order of the CCD/CIS colors
+ ColorOrder line_mode_color_order = ColorOrder::RGB;
+
+ // Is this a CIS or CCD scanner?
+ bool is_cis = false;
+
+ // Is this sheetfed scanner?
+ bool is_sheetfed = false;
+
+ // sensor type
+ SensorId sensor_id = SensorId::UNKNOWN;
+ // Analog-Digital converter type
+ AdcId adc_id = AdcId::UNKNOWN;
+ // General purpose output type
+ GpioId gpio_id = GpioId::UNKNOWN;
+ // stepper motor type
+ MotorId motor_id = MotorId::UNKNOWN;
+
+ // Which hacks are needed for this scanner?
+ SANE_Word flags = 0;
+
+ // Button flags, described existing buttons for the model
+ SANE_Word buttons = 0;
+
+ // how many lines are used for shading calibration
+ SANE_Int shading_lines = 0;
+ // how many lines are used for shading calibration in TA mode
+ SANE_Int shading_ta_lines = 0;
+ // how many lines are used to search start position
+ SANE_Int search_lines = 0;
+
+ const MethodResolutions& get_resolution_settings(ScanMethod method) const;
+
+ std::vector<unsigned> get_resolutions(ScanMethod method) const;
+};
+
+/**
+ * Describes the current device status for the backend
+ * session. This should be more accurately called
+ * Genesys_Session .
+ */
+struct Genesys_Device
+{
+ Genesys_Device() = default;
+ ~Genesys_Device();
+
+ using Calibration = std::vector<Genesys_Calibration_Cache>;
+
+ // frees commonly used data
+ void clear();
+
+ SANE_Word vendorId = 0; /**< USB vendor identifier */
+ SANE_Word productId = 0; /**< USB product identifier */
+
+ // USB mode:
+ // 0: not set
+ // 1: USB 1.1
+ // 2: USB 2.0
+ SANE_Int usb_mode = 0;
+
+ std::string file_name;
+ std::string calib_file;
+
+ // if enabled, no calibration data will be loaded or saved to files
+ SANE_Int force_calibration = 0;
+ // if enabled, will ignore the scan offsets and start scanning at true origin. This allows
+ // acquiring the positions of the black and white strips and the actual scan area
+ bool ignore_offsets = false;
+
+ Genesys_Model *model = nullptr;
+
+ // pointers to low level functions
+ std::unique_ptr<CommandSet> cmd_set;
+
+ Genesys_Register_Set reg;
+ Genesys_Register_Set calib_reg;
+ Genesys_Settings settings;
+ Genesys_Frontend frontend, frontend_initial;
+
+ // whether the frontend is initialized. This is currently used just to preserve historical
+ // behavior
+ bool frontend_is_init = false;
+
+ Genesys_Gpo gpo;
+ Genesys_Motor motor;
+ std::uint8_t control[6] = {};
+
+ size_t average_size = 0;
+ // number of pixels used during shading calibration
+ size_t calib_pixels = 0;
+ // number of lines used during shading calibration
+ size_t calib_lines = 0;
+ size_t calib_channels = 0;
+ size_t calib_resolution = 0;
+ // bytes to read from USB when calibrating. If 0, this is not set
+ size_t calib_total_bytes_to_read = 0;
+
+ // the session that was configured for calibration
+ ScanSession calib_session;
+
+ // certain scanners support much higher resolution when scanning transparency, but we can't
+ // read whole width of the scanner as a single line at that resolution. Thus for stuff like
+ // calibration we want to read only the possible calibration area.
+ size_t calib_pixels_offset = 0;
+
+ // gamma overrides. If a respective array is not empty then it means that the gamma for that
+ // color is overridden.
+ std::vector<std::uint16_t> gamma_override_tables[3];
+
+ std::vector<std::uint16_t> white_average_data;
+ std::vector<std::uint16_t> dark_average_data;
+
+ bool already_initialized = false;
+
+ bool read_active = false;
+ // signal wether the park command has been issued
+ bool parking = false;
+
+ // for sheetfed scanner's, is TRUE when there is a document in the scanner
+ bool document = false;
+
+ Genesys_Buffer read_buffer;
+
+ // buffer for digital lineart from gray data
+ Genesys_Buffer binarize_buffer;
+ // local buffer for gray data during dynamix lineart
+ Genesys_Buffer local_buffer;
+
+ // total bytes read sent to frontend
+ size_t total_bytes_read = 0;
+ // total bytes read to be sent to frontend
+ size_t total_bytes_to_read = 0;
+
+ // contains computed data for the current setup
+ ScanSession session;
+
+ // look up table used in dynamic rasterization
+ unsigned char lineart_lut[256] = {};
+
+ Calibration calibration_cache;
+
+ // number of scan lines used during scan
+ int line_count = 0;
+
+ // array describing the order of the sub-segments of the sensor
+ std::vector<unsigned> segment_order;
+
+ // buffer to handle even/odd data
+ Genesys_Buffer oe_buffer = {};
+
+ // stores information about how the input image should be processed
+ ImagePipelineStack pipeline;
+
+ // an buffer that allows reading from `pipeline` in chunks of any size
+ ImageBuffer pipeline_buffer;
+
+ // when true the scanned picture is first buffered to allow software image enhancements
+ bool buffer_image = false;
+
+ // image buffer where the scanned picture is stored
+ std::vector<std::uint8_t> img_buffer;
+
+ ImagePipelineNodeBytesSource& get_pipeline_source();
+
+ std::unique_ptr<ScannerInterface> interface;
+
+ bool is_head_pos_known(ScanHeadId scan_head) const;
+ unsigned head_pos(ScanHeadId scan_head) const;
+ void set_head_pos_unknown();
+ void set_head_pos_zero(ScanHeadId scan_head);
+ void advance_head_pos_by_session(ScanHeadId scan_head);
+ void advance_head_pos_by_steps(ScanHeadId scan_head, Direction direction, unsigned steps);
+
+private:
+ // the position of the primary scan head in motor->base_dpi units
+ unsigned head_pos_primary_ = 0;
+ bool is_head_pos_primary_known_ = true;
+
+ // the position of the secondary scan head in motor->base_dpi units. Only certain scanners
+ // have a secondary scan head.
+ unsigned head_pos_secondary_ = 0;
+ bool is_head_pos_secondary_known_ = true;
+
+ friend class ScannerInterfaceUsb;
+};
+
+std::ostream& operator<<(std::ostream& out, const Genesys_Device& dev);
+
+void apply_reg_settings_to_device(Genesys_Device& dev, const GenesysRegisterSettingSet& regs);
+
+} // namespace genesys
+
+#endif
diff --git a/backend/genesys/enums.cpp b/backend/genesys/enums.cpp
new file mode 100644
index 0000000..f515cfd
--- /dev/null
+++ b/backend/genesys/enums.cpp
@@ -0,0 +1,131 @@
+/* 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 "enums.h"
+#include "genesys.h"
+#include <iomanip>
+
+namespace genesys {
+
+const char* scan_method_to_option_string(ScanMethod method)
+{
+ switch (method) {
+ case ScanMethod::FLATBED: return STR_FLATBED;
+ case ScanMethod::TRANSPARENCY: return STR_TRANSPARENCY_ADAPTER;
+ case ScanMethod::TRANSPARENCY_INFRARED: return STR_TRANSPARENCY_ADAPTER_INFRARED;
+ }
+ throw SaneException("Unknown scan method %d", static_cast<unsigned>(method));
+}
+
+ScanMethod option_string_to_scan_method(const std::string& str)
+{
+ if (str == STR_FLATBED) {
+ return ScanMethod::FLATBED;
+ } else if (str == STR_TRANSPARENCY_ADAPTER) {
+ return ScanMethod::TRANSPARENCY;
+ } else if (str == STR_TRANSPARENCY_ADAPTER_INFRARED) {
+ return ScanMethod::TRANSPARENCY_INFRARED;
+ }
+ throw SaneException("Unknown scan method option %s", str.c_str());
+}
+
+const char* scan_color_mode_to_option_string(ScanColorMode mode)
+{
+ switch (mode) {
+ case ScanColorMode::COLOR_SINGLE_PASS: return SANE_VALUE_SCAN_MODE_COLOR;
+ case ScanColorMode::GRAY: return SANE_VALUE_SCAN_MODE_GRAY;
+ case ScanColorMode::HALFTONE: return SANE_VALUE_SCAN_MODE_HALFTONE;
+ case ScanColorMode::LINEART: return SANE_VALUE_SCAN_MODE_LINEART;
+ }
+ throw SaneException("Unknown scan mode %d", static_cast<unsigned>(mode));
+}
+
+ScanColorMode option_string_to_scan_color_mode(const std::string& str)
+{
+ if (str == SANE_VALUE_SCAN_MODE_COLOR) {
+ return ScanColorMode::COLOR_SINGLE_PASS;
+ } else if (str == SANE_VALUE_SCAN_MODE_GRAY) {
+ return ScanColorMode::GRAY;
+ } else if (str == SANE_VALUE_SCAN_MODE_HALFTONE) {
+ return ScanColorMode::HALFTONE;
+ } else if (str == SANE_VALUE_SCAN_MODE_LINEART) {
+ return ScanColorMode::LINEART;
+ }
+ throw SaneException("Unknown scan color mode %s", str.c_str());
+}
+
+
+std::ostream& operator<<(std::ostream& out, ColorFilter mode)
+{
+ switch (mode) {
+ case ColorFilter::RED: out << "RED"; break;
+ case ColorFilter::GREEN: out << "GREEN"; break;
+ case ColorFilter::BLUE: out << "BLUE"; break;
+ case ColorFilter::NONE: out << "NONE"; break;
+ default: out << static_cast<unsigned>(mode); break;
+ }
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, StepType type)
+{
+ switch (type) {
+ case StepType::FULL: out << "1/1"; break;
+ case StepType::HALF: out << "1/2"; break;
+ case StepType::QUARTER: out << "1/4"; break;
+ case StepType::EIGHTH: out << "1/8"; break;
+ default: out << static_cast<unsigned>(type); break;
+ }
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, ScanFlag flags)
+{
+ StreamStateSaver state_saver{out};
+ out << "0x" << std::hex << static_cast<unsigned>(flags);
+ return out;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/enums.h b/backend/genesys/enums.h
new file mode 100644
index 0000000..810c4ca
--- /dev/null
+++ b/backend/genesys/enums.h
@@ -0,0 +1,530 @@
+/* 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_ENUMS_H
+#define BACKEND_GENESYS_ENUMS_H
+
+#include <iostream>
+#include "serialize.h"
+
+namespace genesys {
+
+enum class ScanMethod : unsigned {
+ // normal scan method
+ FLATBED = 0,
+ // scan using transparency adaptor
+ TRANSPARENCY = 1,
+ // scan using transparency adaptor via infrared channel
+ TRANSPARENCY_INFRARED = 2
+};
+
+inline std::ostream& operator<<(std::ostream& out, ScanMethod mode)
+{
+ switch (mode) {
+ case ScanMethod::FLATBED: out << "FLATBED"; return out;
+ case ScanMethod::TRANSPARENCY: out << "TRANSPARENCY"; return out;
+ case ScanMethod::TRANSPARENCY_INFRARED: out << "TRANSPARENCY_INFRARED"; return out;
+ }
+ return out;
+}
+
+inline void serialize(std::istream& str, ScanMethod& x)
+{
+ unsigned value;
+ serialize(str, value);
+ x = static_cast<ScanMethod>(value);
+}
+
+inline void serialize(std::ostream& str, ScanMethod& x)
+{
+ unsigned value = static_cast<unsigned>(x);
+ serialize(str, value);
+}
+
+const char* scan_method_to_option_string(ScanMethod method);
+ScanMethod option_string_to_scan_method(const std::string& str);
+
+enum class ScanColorMode : unsigned {
+ LINEART = 0,
+ HALFTONE,
+ GRAY,
+ COLOR_SINGLE_PASS
+};
+
+inline std::ostream& operator<<(std::ostream& out, ScanColorMode mode)
+{
+ switch (mode) {
+ case ScanColorMode::LINEART: out << "LINEART"; return out;
+ case ScanColorMode::HALFTONE: out << "HALFTONE"; return out;
+ case ScanColorMode::GRAY: out << "GRAY"; return out;
+ case ScanColorMode::COLOR_SINGLE_PASS: out << "COLOR_SINGLE_PASS"; return out;
+ }
+ return out;
+}
+
+inline void serialize(std::istream& str, ScanColorMode& x)
+{
+ unsigned value;
+ serialize(str, value);
+ x = static_cast<ScanColorMode>(value);
+}
+
+inline void serialize(std::ostream& str, ScanColorMode& x)
+{
+ unsigned value = static_cast<unsigned>(x);
+ serialize(str, value);
+}
+
+const char* scan_color_mode_to_option_string(ScanColorMode mode);
+ScanColorMode option_string_to_scan_color_mode(const std::string& str);
+
+
+enum class ScanHeadId : unsigned {
+ NONE = 0,
+ PRIMARY = 1 << 0,
+ SECONDARY = 1 << 1,
+ ALL = PRIMARY | SECONDARY,
+};
+
+inline ScanHeadId operator|(ScanHeadId left, ScanHeadId right)
+{
+ return static_cast<ScanHeadId>(static_cast<unsigned>(left) | static_cast<unsigned>(right));
+}
+
+inline ScanHeadId operator&(ScanHeadId left, ScanHeadId right)
+{
+ return static_cast<ScanHeadId>(static_cast<unsigned>(left) & static_cast<unsigned>(right));
+}
+
+
+enum class ColorFilter : unsigned {
+ RED = 0,
+ GREEN,
+ BLUE,
+ NONE
+};
+
+std::ostream& operator<<(std::ostream& out, ColorFilter mode);
+
+inline void serialize(std::istream& str, ColorFilter& x)
+{
+ unsigned value;
+ serialize(str, value);
+ x = static_cast<ColorFilter>(value);
+}
+
+inline void serialize(std::ostream& str, ColorFilter& x)
+{
+ unsigned value = static_cast<unsigned>(x);
+ serialize(str, value);
+}
+
+enum class ColorOrder
+{
+ RGB,
+ GBR,
+ BGR,
+};
+
+/* Enum value naming conventions:
+ Full name must be included with the following exceptions:
+
+ Canon scanners omit "Canoscan" if present
+*/
+enum class ModelId : unsigned
+{
+ UNKNOWN = 0,
+ CANON_4400F,
+ CANON_5600F,
+ CANON_8400F,
+ CANON_8600F,
+ CANON_IMAGE_FORMULA_101,
+ CANON_LIDE_50,
+ CANON_LIDE_60,
+ CANON_LIDE_80,
+ CANON_LIDE_100,
+ CANON_LIDE_110,
+ CANON_LIDE_120,
+ CANON_LIDE_200,
+ CANON_LIDE_210,
+ CANON_LIDE_220,
+ CANON_LIDE_700F,
+ DCT_DOCKETPORT_487,
+ HP_SCANJET_2300C,
+ HP_SCANJET_2400C,
+ HP_SCANJET_3670,
+ HP_SCANJET_4850C,
+ HP_SCANJET_G4010,
+ HP_SCANJET_G4050,
+ HP_SCANJET_N6310,
+ MEDION_MD5345,
+ PANASONIC_KV_SS080,
+ PENTAX_DSMOBILE_600,
+ PLUSTEK_OPTICBOOK_3800,
+ PLUSTEK_OPTICFILM_7200I,
+ PLUSTEK_OPTICFILM_7300,
+ PLUSTEK_OPTICFILM_7500I,
+ PLUSTEK_OPTICPRO_3600,
+ PLUSTEK_OPTICPRO_ST12,
+ PLUSTEK_OPTICPRO_ST24,
+ SYSCAN_DOCKETPORT_465,
+ SYSCAN_DOCKETPORT_467,
+ SYSCAN_DOCKETPORT_485,
+ SYSCAN_DOCKETPORT_665,
+ SYSCAN_DOCKETPORT_685,
+ UMAX_ASTRA_4500,
+ VISIONEER_7100,
+ VISIONEER_ROADWARRIOR,
+ VISIONEER_STROBE_XP100_REVISION3,
+ VISIONEER_STROBE_XP200,
+ VISIONEER_STROBE_XP300,
+ XEROX_2400,
+ XEROX_TRAVELSCANNER_100,
+};
+
+enum class SensorId : unsigned
+{
+ UNKNOWN = 0,
+ CCD_5345,
+ CCD_CANON_4400F,
+ CCD_CANON_8400F,
+ CCD_CANON_8600F,
+ CCD_DP665,
+ CCD_DP685,
+ CCD_DSMOBILE600,
+ CCD_G4050,
+ CCD_HP2300,
+ CCD_HP2400,
+ CCD_HP3670,
+ CCD_HP_N6310,
+ CCD_HP_4850C,
+ CCD_IMG101,
+ CCD_KVSS080,
+ CCD_PLUSTEK_OPTICBOOK_3800,
+ CCD_PLUSTEK_OPTICFILM_7200I,
+ CCD_PLUSTEK_OPTICFILM_7300,
+ CCD_PLUSTEK_OPTICFILM_7500I,
+ CCD_PLUSTEK_OPTICPRO_3600,
+ CCD_ROADWARRIOR,
+ CCD_ST12, // SONY ILX548: 5340 Pixel ???
+ CCD_ST24, // SONY ILX569: 10680 Pixel ???
+ CCD_UMAX,
+ CCD_XP300,
+ CIS_CANON_LIDE_35,
+ CIS_CANON_LIDE_80,
+ CIS_CANON_LIDE_100,
+ CIS_CANON_LIDE_110,
+ CIS_CANON_LIDE_120,
+ CIS_CANON_LIDE_200,
+ CIS_CANON_LIDE_210,
+ CIS_CANON_LIDE_220,
+ CIS_CANON_LIDE_700F,
+ CIS_XP200,
+};
+
+inline void serialize(std::istream& str, SensorId& x)
+{
+ unsigned value;
+ serialize(str, value);
+ x = static_cast<SensorId>(value);
+}
+
+inline void serialize(std::ostream& str, SensorId& x)
+{
+ unsigned value = static_cast<unsigned>(x);
+ serialize(str, value);
+}
+
+
+enum class AdcId : unsigned
+{
+ UNKNOWN = 0,
+ AD_XP200,
+ CANON_LIDE_35,
+ CANON_LIDE_80,
+ CANON_LIDE_110,
+ CANON_LIDE_120,
+ CANON_LIDE_200,
+ CANON_LIDE_700F,
+ CANON_4400F,
+ CANON_8400F,
+ CANON_8600F,
+ G4050,
+ IMG101,
+ KVSS080,
+ PLUSTEK_OPTICBOOK_3800,
+ PLUSTEK_OPTICFILM_7200I,
+ PLUSTEK_OPTICFILM_7300,
+ PLUSTEK_OPTICFILM_7500I,
+ PLUSTEK_OPTICPRO_3600,
+ WOLFSON_5345,
+ WOLFSON_DSM600,
+ WOLFSON_HP2300,
+ WOLFSON_HP2400,
+ WOLFSON_HP3670,
+ WOLFSON_ST12,
+ WOLFSON_ST24,
+ WOLFSON_UMAX,
+ WOLFSON_XP300,
+};
+
+inline void serialize(std::istream& str, AdcId& x)
+{
+ unsigned value;
+ serialize(str, value);
+ x = static_cast<AdcId>(value);
+}
+
+inline void serialize(std::ostream& str, AdcId& x)
+{
+ unsigned value = static_cast<unsigned>(x);
+ serialize(str, value);
+}
+
+enum class GpioId : unsigned
+{
+ UNKNOWN = 0,
+ CANON_LIDE_35,
+ CANON_LIDE_80,
+ CANON_LIDE_110,
+ CANON_LIDE_120,
+ CANON_LIDE_200,
+ CANON_LIDE_210,
+ CANON_LIDE_700F,
+ CANON_4400F,
+ CANON_8400F,
+ CANON_8600F,
+ DP665,
+ DP685,
+ G4050,
+ HP2300,
+ HP2400,
+ HP3670,
+ HP_N6310,
+ IMG101,
+ KVSS080,
+ MD_5345,
+ PLUSTEK_OPTICBOOK_3800,
+ PLUSTEK_OPTICFILM_7200I,
+ PLUSTEK_OPTICFILM_7300,
+ PLUSTEK_OPTICFILM_7500I,
+ PLUSTEK_OPTICPRO_3600,
+ ST12,
+ ST24,
+ UMAX,
+ XP200,
+ XP300,
+};
+
+enum class MotorId : unsigned
+{
+ UNKNOWN = 0,
+ CANON_LIDE_100,
+ CANON_LIDE_110,
+ CANON_LIDE_120,
+ CANON_LIDE_200,
+ CANON_LIDE_210,
+ CANON_LIDE_35,
+ CANON_LIDE_700,
+ CANON_LIDE_80,
+ CANON_4400F,
+ CANON_8400F,
+ CANON_8600F,
+ DP665,
+ DSMOBILE_600,
+ G4050,
+ HP2300,
+ HP2400,
+ HP3670,
+ IMG101,
+ KVSS080,
+ MD_5345,
+ PLUSTEK_OPTICBOOK_3800,
+ PLUSTEK_OPTICFILM_7200I,
+ PLUSTEK_OPTICFILM_7300,
+ PLUSTEK_OPTICFILM_7500I,
+ PLUSTEK_OPTICPRO_3600,
+ ROADWARRIOR,
+ ST24,
+ UMAX,
+ XP200,
+ XP300,
+};
+
+enum class StepType : unsigned
+{
+ FULL = 0,
+ HALF = 1,
+ QUARTER = 2,
+ EIGHTH = 3,
+};
+
+std::ostream& operator<<(std::ostream& out, StepType type);
+
+inline bool operator<(StepType lhs, StepType rhs)
+{
+ return static_cast<unsigned>(lhs) < static_cast<unsigned>(rhs);
+}
+inline bool operator<=(StepType lhs, StepType rhs)
+{
+ return static_cast<unsigned>(lhs) <= static_cast<unsigned>(rhs);
+}
+inline bool operator>(StepType lhs, StepType rhs)
+{
+ return static_cast<unsigned>(lhs) > static_cast<unsigned>(rhs);
+}
+inline bool operator>=(StepType lhs, StepType rhs)
+{
+ return static_cast<unsigned>(lhs) >= static_cast<unsigned>(rhs);
+}
+
+enum class AsicType : unsigned
+{
+ UNKNOWN = 0,
+ GL646,
+ GL841,
+ GL843,
+ GL845,
+ GL846,
+ GL847,
+ GL124,
+};
+
+
+enum class ScanFlag : unsigned
+{
+ NONE = 0,
+ SINGLE_LINE = 1 << 0,
+ DISABLE_SHADING = 1 << 1,
+ DISABLE_GAMMA = 1 << 2,
+ DISABLE_BUFFER_FULL_MOVE = 1 << 3,
+ IGNORE_LINE_DISTANCE = 1 << 4,
+ DISABLE_LAMP = 1 << 5,
+ CALIBRATION = 1 << 6,
+ FEEDING = 1 << 7,
+ USE_XPA = 1 << 8,
+ ENABLE_LEDADD = 1 << 9,
+ USE_XCORRECTION = 1 << 10,
+ REVERSE = 1 << 11,
+};
+
+inline ScanFlag operator|(ScanFlag left, ScanFlag right)
+{
+ return static_cast<ScanFlag>(static_cast<unsigned>(left) | static_cast<unsigned>(right));
+}
+
+inline ScanFlag& operator|=(ScanFlag& left, ScanFlag right)
+{
+ left = left | right;
+ return left;
+}
+
+inline ScanFlag operator&(ScanFlag left, ScanFlag right)
+{
+ return static_cast<ScanFlag>(static_cast<unsigned>(left) & static_cast<unsigned>(right));
+}
+
+inline bool has_flag(ScanFlag flags, ScanFlag which)
+{
+ return (flags & which) == which;
+}
+
+inline void serialize(std::istream& str, ScanFlag& x)
+{
+ unsigned value;
+ serialize(str, value);
+ x = static_cast<ScanFlag>(value);
+}
+
+inline void serialize(std::ostream& str, ScanFlag& x)
+{
+ unsigned value = static_cast<unsigned>(x);
+ serialize(str, value);
+}
+
+std::ostream& operator<<(std::ostream& out, ScanFlag flags);
+
+
+
+enum class MotorFlag : unsigned
+{
+ NONE = 0,
+ AUTO_GO_HOME = 1 << 0,
+ DISABLE_BUFFER_FULL_MOVE = 1 << 2,
+ FEED = 1 << 3,
+ USE_XPA = 1 << 4,
+ REVERSE = 1 << 5,
+};
+
+inline MotorFlag operator|(MotorFlag left, MotorFlag right)
+{
+ return static_cast<MotorFlag>(static_cast<unsigned>(left) | static_cast<unsigned>(right));
+}
+
+inline MotorFlag& operator|=(MotorFlag& left, MotorFlag right)
+{
+ left = left | right;
+ return left;
+}
+
+inline MotorFlag operator&(MotorFlag left, MotorFlag right)
+{
+ return static_cast<MotorFlag>(static_cast<unsigned>(left) & static_cast<unsigned>(right));
+}
+
+inline bool has_flag(MotorFlag flags, MotorFlag which)
+{
+ return (flags & which) == which;
+}
+
+
+enum class Direction : unsigned
+{
+ FORWARD = 0,
+ BACKWARD = 1
+};
+
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_ENUMS_H
diff --git a/backend/genesys/error.cpp b/backend/genesys/error.cpp
new file mode 100644
index 0000000..6c921c1
--- /dev/null
+++ b/backend/genesys/error.cpp
@@ -0,0 +1,215 @@
+/* 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 "error.h"
+#include <cstdarg>
+
+namespace genesys {
+
+extern "C" void sanei_debug_msg(int level, int max_level, const char *be, const char *fmt,
+ std::va_list ap);
+
+#if (defined(__GNUC__) || defined(__CLANG__)) && (defined(__linux__) || defined(__APPLE__))
+extern "C" char* __cxa_get_globals();
+#endif
+
+static unsigned num_uncaught_exceptions()
+{
+#if __cplusplus >= 201703L
+ int count = std::uncaught_exceptions();
+ return count >= 0 ? count : 0;
+#elif (defined(__GNUC__) || defined(__CLANG__)) && (defined(__linux__) || defined(__APPLE__))
+ // the format of the __cxa_eh_globals struct is enshrined into the Itanium C++ ABI and it's
+ // very unlikely we'll get issues referencing it directly
+ char* cxa_eh_globals_ptr = __cxa_get_globals();
+ return *reinterpret_cast<unsigned*>(cxa_eh_globals_ptr + sizeof(void*));
+#else
+ return std::uncaught_exception() ? 1 : 0;
+#endif
+}
+
+SaneException::SaneException(SANE_Status status) : status_(status)
+{
+ set_msg();
+}
+
+SaneException::SaneException(SANE_Status status, const char* format, ...) : status_(status)
+{
+ std::va_list args;
+ va_start(args, format);
+ set_msg(format, args);
+ va_end(args);
+}
+
+SaneException::SaneException(const char* format, ...) : status_(SANE_STATUS_INVAL)
+{
+ std::va_list args;
+ va_start(args, format);
+ set_msg(format, args);
+ va_end(args);
+}
+
+SANE_Status SaneException::status() const
+{
+ return status_;
+}
+
+const char* SaneException::what() const noexcept
+{
+ return msg_.c_str();
+}
+
+void SaneException::set_msg()
+{
+ const char* status_msg = sane_strstatus(status_);
+ std::size_t status_msg_len = std::strlen(status_msg);
+ msg_.reserve(status_msg_len);
+ msg_ = status_msg;
+}
+
+void SaneException::set_msg(const char* format, std::va_list vlist)
+{
+ const char* status_msg = sane_strstatus(status_);
+ std::size_t status_msg_len = std::strlen(status_msg);
+
+ std::va_list vlist2;
+ va_copy(vlist2, vlist);
+ int msg_len = std::vsnprintf(nullptr, 0, format, vlist2);
+ va_end(vlist2);
+
+ if (msg_len < 0) {
+ const char* formatting_error_msg = "(error formatting arguments)";
+ msg_.reserve(std::strlen(formatting_error_msg) + 3 + status_msg_len);
+ msg_ = formatting_error_msg;
+ msg_ += " : ";
+ msg_ += status_msg;
+ return;
+ }
+
+ msg_.reserve(msg_len + status_msg_len + 3);
+ msg_.resize(msg_len + 1, ' ');
+ std::vsnprintf(&msg_[0], msg_len + 1, format, vlist);
+ msg_.resize(msg_len, ' ');
+
+ msg_ += " : ";
+ msg_ += status_msg;
+}
+
+DebugMessageHelper::DebugMessageHelper(const char* func)
+{
+ func_ = func;
+ num_exceptions_on_enter_ = num_uncaught_exceptions();
+ msg_[0] = '\0';
+ DBG(DBG_proc, "%s: start\n", func_);
+}
+
+DebugMessageHelper::DebugMessageHelper(const char* func, const char* format, ...)
+{
+ func_ = func;
+ num_exceptions_on_enter_ = num_uncaught_exceptions();
+ msg_[0] = '\0';
+ DBG(DBG_proc, "%s: start\n", func_);
+ DBG(DBG_proc, "%s: ", func_);
+
+ std::va_list args;
+ va_start(args, format);
+ sanei_debug_msg(DBG_proc, DBG_LEVEL, STRINGIFY(BACKEND_NAME), format, args);
+ va_end(args);
+ DBG(DBG_proc, "\n");
+}
+
+
+DebugMessageHelper::~DebugMessageHelper()
+{
+ if (num_exceptions_on_enter_ < num_uncaught_exceptions()) {
+ if (msg_[0] != '\0') {
+ DBG(DBG_error, "%s: failed during %s\n", func_, msg_);
+ } else {
+ DBG(DBG_error, "%s: failed\n", func_);
+ }
+ } else {
+ DBG(DBG_proc, "%s: completed\n", func_);
+ }
+}
+
+void DebugMessageHelper::vstatus(const char* format, ...)
+{
+ std::va_list args;
+ va_start(args, format);
+ std::vsnprintf(msg_, MAX_BUF_SIZE, format, args);
+ va_end(args);
+}
+
+void DebugMessageHelper::log(unsigned level, const char* msg)
+{
+ DBG(level, "%s: %s\n", func_, msg);
+}
+
+void DebugMessageHelper::vlog(unsigned level, const char* format, ...)
+{
+ std::string msg;
+
+ std::va_list args;
+
+ va_start(args, format);
+ int msg_len = std::vsnprintf(nullptr, 0, format, args);
+ va_end(args);
+
+ if (msg_len < 0) {
+ DBG(level, "%s: error formatting error message: %s\n", func_, format);
+ return;
+ }
+ msg.resize(msg_len + 1, ' ');
+
+ va_start(args, format);
+ std::vsnprintf(&msg.front(), msg.size(), format, args);
+ va_end(args);
+
+ msg.resize(msg_len, ' '); // strip the null character
+
+ DBG(level, "%s: %s\n", func_, msg.c_str());
+}
+
+} // namespace genesys
diff --git a/backend/genesys/error.h b/backend/genesys/error.h
new file mode 100644
index 0000000..5aba8cf
--- /dev/null
+++ b/backend/genesys/error.h
@@ -0,0 +1,199 @@
+/* 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_ERROR_H
+#define BACKEND_GENESYS_ERROR_H
+
+#include "../include/sane/config.h"
+#include "../include/sane/sane.h"
+#include "../include/sane/sanei_backend.h"
+
+#include <stdexcept>
+#include <cstdarg>
+#include <cstring>
+#include <string>
+#include <new>
+
+#define DBG_error0 0 /* errors/warnings printed even with devuglevel 0 */
+#define DBG_error 1 /* fatal errors */
+#define DBG_init 2 /* initialization and scanning time messages */
+#define DBG_warn 3 /* warnings and non-fatal errors */
+#define DBG_info 4 /* informational messages */
+#define DBG_proc 5 /* starting/finishing functions */
+#define DBG_io 6 /* io functions */
+#define DBG_io2 7 /* io functions that are called very often */
+#define DBG_data 8 /* log image data */
+
+namespace genesys {
+
+class SaneException : public std::exception {
+public:
+ SaneException(SANE_Status status);
+ SaneException(SANE_Status status, const char* format, ...)
+ #ifdef __GNUC__
+ __attribute__((format(printf, 3, 4)))
+ #endif
+ ;
+
+ SaneException(const char* format, ...)
+ #ifdef __GNUC__
+ __attribute__((format(printf, 2, 3)))
+ #endif
+ ;
+
+ SANE_Status status() const;
+ const char* what() const noexcept override;
+
+private:
+
+ void set_msg();
+ void set_msg(const char* format, std::va_list vlist);
+
+ std::string msg_;
+ SANE_Status status_;
+};
+
+// call a function and throw an exception on error
+#define TIE(function) \
+ do { \
+ SANE_Status tmp_status = function; \
+ if (tmp_status != SANE_STATUS_GOOD) { \
+ throw ::genesys::SaneException(tmp_status); \
+ } \
+ } while (false)
+
+class DebugMessageHelper {
+public:
+ static constexpr unsigned MAX_BUF_SIZE = 120;
+
+ DebugMessageHelper(const char* func);
+ DebugMessageHelper(const char* func, const char* format, ...)
+ #ifdef __GNUC__
+ __attribute__((format(printf, 3, 4)))
+ #endif
+ ;
+
+ ~DebugMessageHelper();
+
+ void status(const char* msg) { vstatus("%s", msg); }
+ void vstatus(const char* format, ...)
+ #ifdef __GNUC__
+ __attribute__((format(printf, 2, 3)))
+ #endif
+ ;
+
+ void clear() { msg_[0] = '\n'; }
+
+ void log(unsigned level, const char* msg);
+ void vlog(unsigned level, const char* format, ...)
+ #ifdef __GNUC__
+ __attribute__((format(printf, 3, 4)))
+ #endif
+ ;
+
+private:
+ const char* func_ = nullptr;
+ char msg_[MAX_BUF_SIZE];
+ unsigned num_exceptions_on_enter_ = 0;
+};
+
+
+#if defined(__GNUC__) || defined(__clang__)
+#define GENESYS_CURRENT_FUNCTION __PRETTY_FUNCTION__
+#elif defined(__FUNCSIG__)
+#define GENESYS_CURRENT_FUNCTION __FUNCSIG__
+#else
+#define GENESYS_CURRENT_FUNCTION __func__
+#endif
+
+#define DBG_HELPER(var) DebugMessageHelper var(GENESYS_CURRENT_FUNCTION)
+#define DBG_HELPER_ARGS(var, ...) DebugMessageHelper var(GENESYS_CURRENT_FUNCTION, __VA_ARGS__)
+
+template<class F>
+SANE_Status wrap_exceptions_to_status_code(const char* func, F&& function)
+{
+ try {
+ function();
+ return SANE_STATUS_GOOD;
+ } catch (const SaneException& exc) {
+ DBG(DBG_error, "%s: got error: %s\n", func, exc.what());
+ return exc.status();
+ } catch (const std::bad_alloc& exc) {
+ (void) exc;
+ DBG(DBG_error, "%s: failed to allocate memory\n", func);
+ return SANE_STATUS_NO_MEM;
+ } catch (const std::exception& exc) {
+ DBG(DBG_error, "%s: got uncaught exception: %s\n", func, exc.what());
+ return SANE_STATUS_INVAL;
+ } catch (...) {
+ DBG(DBG_error, "%s: got unknown uncaught exception\n", func);
+ return SANE_STATUS_INVAL;
+ }
+}
+
+template<class F>
+void catch_all_exceptions(const char* func, F&& function)
+{
+ try {
+ function();
+ } catch (const SaneException& exc) {
+ DBG(DBG_error, "%s: got exception: %s\n", func, exc.what());
+ } catch (const std::bad_alloc& exc) {
+ DBG(DBG_error, "%s: got exception: could not allocate memory: %s\n", func, exc.what());
+ } catch (const std::exception& exc) {
+ DBG(DBG_error, "%s: got uncaught exception: %s\n", func, exc.what());
+ } catch (...) {
+ DBG(DBG_error, "%s: got unknown uncaught exception\n", func);
+ }
+}
+
+inline void wrap_status_code_to_exception(SANE_Status status)
+{
+ if (status == SANE_STATUS_GOOD)
+ return;
+ throw SaneException(status);
+}
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_ERROR_H
diff --git a/backend/genesys/fwd.h b/backend/genesys/fwd.h
new file mode 100644
index 0000000..2d55f98
--- /dev/null
+++ b/backend/genesys/fwd.h
@@ -0,0 +1,132 @@
+/* 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_FWD_H
+#define BACKEND_GENESYS_FWD_H
+
+namespace genesys {
+
+// buffer.h
+struct Genesys_Buffer;
+
+// calibration.h
+struct Genesys_Calibration_Cache;
+
+// command_set.h
+class CommandSet;
+
+// device.h
+class FixedFloat;
+struct Genesys_Gpo;
+struct MethodResolutions;
+struct Genesys_Model;
+struct Genesys_Device;
+
+// error.h
+class DebugMessageHelper;
+class SaneException;
+
+// genesys.h
+class GenesysButton;
+struct Genesys_Scanner;
+
+// image.h
+class Image;
+
+// image_buffer.h
+class ImageBuffer;
+class FakeBufferModel;
+class ImageBufferGenesysUsb;
+
+// image_pipeline.h
+class ImagePipelineNode;
+// ImagePipelineNode* skipped
+class ImagePipelineStack;
+
+// image_pixel.h
+struct Pixel;
+struct RawPixel;
+
+// low.h
+struct Genesys_USB_Device_Entry;
+struct Motor_Profile;
+
+// motor.h
+struct Genesys_Motor;
+struct MotorSlope;
+struct MotorSlopeTable;
+
+// register.h
+class Genesys_Register_Set;
+struct GenesysRegisterSetState;
+
+// row_buffer.h
+class RowBuffer;
+
+// usb_device.h
+class IUsbDevice;
+class UsbDevice;
+
+// scanner_interface.h
+class ScannerInterface;
+class ScannerInterfaceUsb;
+class TestScannerInterface;
+
+// sensor.h
+class ResolutionFilter;
+struct GenesysFrontendLayout;
+struct Genesys_Frontend;
+struct SensorExposure;
+struct Genesys_Sensor;
+
+// settings.h
+struct Genesys_Settings;
+struct SetupParams;
+struct ScanSession;
+
+// test_usb_device.h
+class TestUsbDevice;
+
+} // namespace genesys
+
+#endif
diff --git a/backend/genesys/genesys.cpp b/backend/genesys/genesys.cpp
new file mode 100644
index 0000000..7c25168
--- /dev/null
+++ b/backend/genesys/genesys.cpp
@@ -0,0 +1,6172 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
+ Copyright (C) 2004, 2005 Gerhard Jaeger <gerhard@gjaeger.de>
+ Copyright (C) 2004-2016 Stéphane Voltz <stef.dev@free.fr>
+ Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
+ Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com>
+ Copyright (C) 2007 Luke <iceyfor@gmail.com>
+ Copyright (C) 2010 Chris Berry <s0457957@sms.ed.ac.uk> and Michael Rickmann <mrickma@gwdg.de>
+ for Plustek Opticbook 3600 support
+
+ Dynamic rasterization code was taken from the epjistsu backend by
+ m. allan noah <kitno455 at gmail dot com>
+
+ Software processing for deskew, crop and dspeckle are inspired by allan's
+ noah work in the fujitsu backend
+
+ 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.
+*/
+
+/*
+ * SANE backend for Genesys Logic GL646/GL841/GL842/GL843/GL846/GL847/GL124 based scanners
+ */
+
+#define DEBUG_NOT_STATIC
+
+#include "genesys.h"
+#include "conv.h"
+#include "gl124_registers.h"
+#include "gl841_registers.h"
+#include "gl843_registers.h"
+#include "gl846_registers.h"
+#include "gl847_registers.h"
+#include "usb_device.h"
+#include "utilities.h"
+#include "scanner_interface_usb.h"
+#include "test_scanner_interface.h"
+#include "test_settings.h"
+#include "../include/sane/sanei_config.h"
+#include "../include/sane/sanei_magic.h"
+
+#include <array>
+#include <cmath>
+#include <cstring>
+#include <fstream>
+#include <iterator>
+#include <list>
+#include <numeric>
+#include <exception>
+#include <vector>
+
+#ifndef SANE_GENESYS_API_LINKAGE
+#define SANE_GENESYS_API_LINKAGE extern "C"
+#endif
+
+namespace genesys {
+
+// Data that we allocate to back SANE_Device objects in s_sane_devices
+struct SANE_Device_Data
+{
+ std::string name;
+};
+
+namespace {
+ StaticInit<std::list<Genesys_Scanner>> s_scanners;
+ StaticInit<std::vector<SANE_Device>> s_sane_devices;
+ StaticInit<std::vector<SANE_Device_Data>> s_sane_devices_data;
+ StaticInit<std::vector<SANE_Device*>> s_sane_devices_ptrs;
+ StaticInit<std::list<Genesys_Device>> s_devices;
+
+ // Maximum time for lamp warm-up
+ constexpr unsigned WARMUP_TIME = 65;
+} // namespace
+
+static SANE_String_Const mode_list[] = {
+ SANE_VALUE_SCAN_MODE_COLOR,
+ SANE_VALUE_SCAN_MODE_GRAY,
+ /* SANE_TITLE_HALFTONE, currently unused */
+ SANE_VALUE_SCAN_MODE_LINEART,
+ nullptr
+};
+
+static SANE_String_Const color_filter_list[] = {
+ SANE_I18N ("Red"),
+ SANE_I18N ("Green"),
+ SANE_I18N ("Blue"),
+ nullptr
+};
+
+static SANE_String_Const cis_color_filter_list[] = {
+ SANE_I18N ("Red"),
+ SANE_I18N ("Green"),
+ SANE_I18N ("Blue"),
+ SANE_I18N ("None"),
+ nullptr
+};
+
+static SANE_Range swdespeck_range = {
+ 1,
+ 9,
+ 1
+};
+
+static SANE_Range time_range = {
+ 0, /* minimum */
+ 60, /* maximum */
+ 0 /* quantization */
+};
+
+static const SANE_Range u12_range = {
+ 0, /* minimum */
+ 4095, /* maximum */
+ 0 /* quantization */
+};
+
+static const SANE_Range u14_range = {
+ 0, /* minimum */
+ 16383, /* maximum */
+ 0 /* quantization */
+};
+
+static const SANE_Range u16_range = {
+ 0, /* minimum */
+ 65535, /* maximum */
+ 0 /* quantization */
+};
+
+static const SANE_Range percentage_range = {
+ SANE_FIX (0), /* minimum */
+ SANE_FIX (100), /* maximum */
+ SANE_FIX (1) /* quantization */
+};
+
+static const SANE_Range threshold_curve_range = {
+ 0, /* minimum */
+ 127, /* maximum */
+ 1 /* quantization */
+};
+
+/**
+ * range for brightness and contrast
+ */
+static const SANE_Range enhance_range = {
+ -100, /* minimum */
+ 100, /* maximum */
+ 1 /* quantization */
+};
+
+/**
+ * range for expiration time
+ */
+static const SANE_Range expiration_range = {
+ -1, /* minimum */
+ 30000, /* maximum */
+ 1 /* quantization */
+};
+
+const Genesys_Sensor& sanei_genesys_find_sensor_any(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ for (const auto& sensor : *s_sensors) {
+ if (dev->model->sensor_id == sensor.sensor_id) {
+ return sensor;
+ }
+ }
+ throw std::runtime_error("Given device does not have sensor defined");
+}
+
+Genesys_Sensor* find_sensor_impl(Genesys_Device* dev, unsigned dpi, unsigned channels,
+ ScanMethod scan_method)
+{
+ DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels,
+ static_cast<unsigned>(scan_method));
+ for (auto& sensor : *s_sensors) {
+ if (dev->model->sensor_id == sensor.sensor_id && sensor.resolutions.matches(dpi) &&
+ sensor.matches_channel_count(channels) && sensor.method == scan_method)
+ {
+ return &sensor;
+ }
+ }
+ return nullptr;
+}
+
+bool sanei_genesys_has_sensor(Genesys_Device* dev, unsigned dpi, unsigned channels,
+ ScanMethod scan_method)
+{
+ DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels,
+ static_cast<unsigned>(scan_method));
+ return find_sensor_impl(dev, dpi, channels, scan_method) != nullptr;
+}
+
+const Genesys_Sensor& sanei_genesys_find_sensor(Genesys_Device* dev, unsigned dpi, unsigned channels,
+ ScanMethod scan_method)
+{
+ DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels,
+ static_cast<unsigned>(scan_method));
+ const auto* sensor = find_sensor_impl(dev, dpi, channels, scan_method);
+ if (sensor)
+ return *sensor;
+ throw std::runtime_error("Given device does not have sensor defined");
+}
+
+Genesys_Sensor& sanei_genesys_find_sensor_for_write(Genesys_Device* dev, unsigned dpi,
+ unsigned channels,
+ ScanMethod scan_method)
+{
+ DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels,
+ static_cast<unsigned>(scan_method));
+ auto* sensor = find_sensor_impl(dev, dpi, channels, scan_method);
+ if (sensor)
+ return *sensor;
+ throw std::runtime_error("Given device does not have sensor defined");
+}
+
+
+std::vector<std::reference_wrapper<const Genesys_Sensor>>
+ sanei_genesys_find_sensors_all(Genesys_Device* dev, ScanMethod scan_method)
+{
+ DBG_HELPER_ARGS(dbg, "scan_method: %d", static_cast<unsigned>(scan_method));
+ std::vector<std::reference_wrapper<const Genesys_Sensor>> ret;
+ for (const Genesys_Sensor& sensor : sanei_genesys_find_sensors_all_for_write(dev, scan_method)) {
+ ret.push_back(sensor);
+ }
+ return ret;
+}
+
+std::vector<std::reference_wrapper<Genesys_Sensor>>
+ sanei_genesys_find_sensors_all_for_write(Genesys_Device* dev, ScanMethod scan_method)
+{
+ DBG_HELPER_ARGS(dbg, "scan_method: %d", static_cast<unsigned>(scan_method));
+ std::vector<std::reference_wrapper<Genesys_Sensor>> ret;
+ for (auto& sensor : *s_sensors) {
+ if (dev->model->sensor_id == sensor.sensor_id && sensor.method == scan_method) {
+ ret.push_back(sensor);
+ }
+ }
+ return ret;
+}
+
+void sanei_genesys_init_structs (Genesys_Device * dev)
+{
+ DBG_HELPER(dbg);
+
+ bool gpo_ok = false;
+ bool motor_ok = false;
+ bool fe_ok = false;
+
+ /* initialize the GPO data stuff */
+ for (const auto& gpo : *s_gpo) {
+ if (dev->model->gpio_id == gpo.id) {
+ dev->gpo = gpo;
+ gpo_ok = true;
+ break;
+ }
+ }
+
+ // initialize the motor data stuff
+ for (const auto& motor : *s_motors) {
+ if (dev->model->motor_id == motor.id) {
+ dev->motor = motor;
+ motor_ok = true;
+ break;
+ }
+ }
+
+ for (const auto& frontend : *s_frontends) {
+ if (dev->model->adc_id == frontend.id) {
+ dev->frontend_initial = frontend;
+ dev->frontend = frontend;
+ fe_ok = true;
+ break;
+ }
+ }
+
+ if (!motor_ok || !gpo_ok || !fe_ok) {
+ throw SaneException("bad description(s) for fe/gpo/motor=%d/%d/%d\n",
+ static_cast<unsigned>(dev->model->sensor_id),
+ static_cast<unsigned>(dev->model->gpio_id),
+ static_cast<unsigned>(dev->model->motor_id));
+ }
+}
+
+/* Generate slope table for motor movement */
+/**
+ * This function generates a slope table using the slope from the motor struct
+ * truncated at the given exposure time or step count, whichever comes first.
+ * The summed time of the acceleration steps is returned, and the
+ * number of accerelation steps is put into used_steps.
+ *
+ * @param dev Device struct
+ * @param slope_table Table to write to
+ * @param step_type Generate table for this step_type. 0=>full, 1=>half,
+ * 2=>quarter
+ * @param exposure_time Minimum exposure time of a scan line
+ * @param yres Resolution of a scan line
+ * @param used_steps Final number of steps is stored here
+ * @return Motor slope table
+ * @note all times in pixel time
+ */
+MotorSlopeTable sanei_genesys_create_slope_table3(AsicType asic_type, const Genesys_Motor& motor,
+ StepType step_type, int exposure_time,
+ unsigned yres)
+{
+ unsigned target_speed_w = (exposure_time * yres) / motor.base_ydpi;
+
+ return create_slope_table(motor.get_slope(step_type), target_speed_w, step_type, 1, 1,
+ get_slope_table_max_size(asic_type));
+}
+
+/** @brief computes gamma table
+ * Generates a gamma table of the given length within 0 and the given
+ * maximum value
+ * @param gamma_table gamma table to fill
+ * @param size size of the table
+ * @param maximum value allowed for gamma
+ * @param gamma_max maximum gamma value
+ * @param gamma gamma to compute values
+ * @return a gamma table filled with the computed values
+ * */
+void
+sanei_genesys_create_gamma_table (std::vector<uint16_t>& gamma_table, int size,
+ float maximum, float gamma_max, float gamma)
+{
+ gamma_table.clear();
+ gamma_table.resize(size, 0);
+
+ int i;
+ float value;
+
+ DBG(DBG_proc, "%s: size = %d, ""maximum = %g, gamma_max = %g, gamma = %g\n", __func__, size,
+ maximum, gamma_max, gamma);
+ for (i = 0; i < size; i++)
+ {
+ value = static_cast<float>(gamma_max * std::pow(static_cast<double>(i) / size, 1.0 / gamma));
+ if (value > maximum) {
+ value = maximum;
+ }
+ gamma_table[i] = static_cast<std::uint16_t>(value);
+ }
+ DBG(DBG_proc, "%s: completed\n", __func__);
+}
+
+void sanei_genesys_create_default_gamma_table(Genesys_Device* dev,
+ std::vector<uint16_t>& gamma_table, float gamma)
+{
+ int size = 0;
+ int max = 0;
+ if (dev->model->asic_type == AsicType::GL646) {
+ if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) {
+ size = 16384;
+ } else {
+ size = 4096;
+ }
+ max = size - 1;
+ } else if (dev->model->asic_type == AsicType::GL124 ||
+ dev->model->asic_type == AsicType::GL846 ||
+ dev->model->asic_type == AsicType::GL847) {
+ size = 257;
+ max = 65535;
+ } else {
+ size = 256;
+ max = 65535;
+ }
+ sanei_genesys_create_gamma_table(gamma_table, size, max, max, gamma);
+}
+
+/* computes the exposure_time on the basis of the given vertical dpi,
+ the number of pixels the ccd needs to send,
+ the step_type and the corresponding maximum speed from the motor struct */
+/*
+ Currently considers maximum motor speed at given step_type, minimum
+ line exposure needed for conversion and led exposure time.
+
+ TODO: Should also consider maximum transfer rate: ~6.5MB/s.
+ Note: The enhance option of the scanners does _not_ help. It only halves
+ the amount of pixels transfered.
+ */
+SANE_Int sanei_genesys_exposure_time2(Genesys_Device * dev, float ydpi,
+ StepType step_type, int endpixel, int exposure_by_led)
+{
+ int exposure_by_ccd = endpixel + 32;
+ unsigned max_speed_motor_w = dev->motor.get_slope(step_type).max_speed_w;
+ int exposure_by_motor = static_cast<int>((max_speed_motor_w * dev->motor.base_ydpi) / ydpi);
+
+ int exposure = exposure_by_ccd;
+
+ if (exposure < exposure_by_motor)
+ exposure = exposure_by_motor;
+
+ if (exposure < exposure_by_led && dev->model->is_cis)
+ exposure = exposure_by_led;
+
+ DBG(DBG_info, "%s: ydpi=%d, step=%d, endpixel=%d led=%d => exposure=%d\n", __func__,
+ static_cast<int>(ydpi), static_cast<unsigned>(step_type), endpixel,
+ exposure_by_led, exposure);
+ return exposure;
+}
+
+
+/* Sends a block of shading information to the scanner.
+ The data is placed at address 0x0000 for color mode, gray mode and
+ unconditionally for the following CCD chips: HP2300, HP2400 and HP5345
+ In the other cases (lineart, halftone on ccd chips not mentioned) the
+ addresses are 0x2a00 for dpihw==0, 0x5500 for dpihw==1 and 0xa800 for
+ dpihw==2. //Note: why this?
+
+ The data needs to be of size "size", and in little endian byte order.
+ */
+static void genesys_send_offset_and_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ uint8_t* data, int size)
+{
+ DBG_HELPER_ARGS(dbg, "(size = %d)", size);
+ int dpihw;
+ int start_address;
+
+ /* ASIC higher than gl843 doesn't have register 2A/2B, so we route to
+ * a per ASIC shading data loading function if available.
+ * It is also used for scanners using SHDAREA */
+ if (dev->cmd_set->has_send_shading_data()) {
+ dev->cmd_set->send_shading_data(dev, sensor, data, size);
+ return;
+ }
+
+ /* gl646, gl84[123] case */
+ dpihw = dev->reg.get8(0x05) >> 6;
+
+ /* TODO invert the test so only the 2 models behaving like that are
+ * tested instead of adding all the others */
+ /* many scanners send coefficient for lineart/gray like in color mode */
+ if ((dev->settings.scan_mode == ScanColorMode::LINEART ||
+ dev->settings.scan_mode == ScanColorMode::HALFTONE)
+ && dev->model->sensor_id != SensorId::CCD_PLUSTEK_OPTICBOOK_3800
+ && dev->model->sensor_id != SensorId::CCD_KVSS080
+ && dev->model->sensor_id != SensorId::CCD_G4050
+ && dev->model->sensor_id != SensorId::CCD_HP_4850C
+ && dev->model->sensor_id != SensorId::CCD_CANON_4400F
+ && dev->model->sensor_id != SensorId::CCD_CANON_8400F
+ && dev->model->sensor_id != SensorId::CCD_CANON_8600F
+ && dev->model->sensor_id != SensorId::CCD_DSMOBILE600
+ && dev->model->sensor_id != SensorId::CCD_XP300
+ && dev->model->sensor_id != SensorId::CCD_DP665
+ && dev->model->sensor_id != SensorId::CCD_DP685
+ && dev->model->sensor_id != SensorId::CIS_CANON_LIDE_80
+ && dev->model->sensor_id != SensorId::CCD_ROADWARRIOR
+ && dev->model->sensor_id != SensorId::CCD_HP2300
+ && dev->model->sensor_id != SensorId::CCD_HP2400
+ && dev->model->sensor_id != SensorId::CCD_HP3670
+ && dev->model->sensor_id != SensorId::CCD_5345) /* lineart, halftone */
+ {
+ if (dpihw == 0) { /* 600 dpi */
+ start_address = 0x02a00;
+ } else if (dpihw == 1) { /* 1200 dpi */
+ start_address = 0x05500;
+ } else if (dpihw == 2) { /* 2400 dpi */
+ start_address = 0x0a800;
+ } else { /* reserved */
+ throw SaneException("unknown dpihw");
+ }
+ }
+ else { // color
+ start_address = 0x00;
+ }
+
+ dev->interface->write_buffer(0x3c, start_address, data, size);
+}
+
+// ?
+void sanei_genesys_init_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ int pixels_per_line)
+{
+ DBG_HELPER_ARGS(dbg, "pixels_per_line: %d", pixels_per_line);
+
+ if (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) {
+ return;
+ }
+
+ int channels;
+ int i;
+
+ if (dev->cmd_set->has_send_shading_data()) {
+ return;
+ }
+
+ DBG(DBG_proc, "%s (pixels_per_line = %d)\n", __func__, pixels_per_line);
+
+ // BUG: GRAY shouldn't probably be in the if condition below. Discovered when refactoring
+ if (dev->settings.scan_mode == ScanColorMode::GRAY ||
+ dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS)
+ {
+ channels = 3;
+ } else {
+ channels = 1;
+ }
+
+ // 16 bit black, 16 bit white
+ std::vector<uint8_t> shading_data(pixels_per_line * 4 * channels, 0);
+
+ uint8_t* shading_data_ptr = shading_data.data();
+
+ for (i = 0; i < pixels_per_line * channels; i++)
+ {
+ *shading_data_ptr++ = 0x00; /* dark lo */
+ *shading_data_ptr++ = 0x00; /* dark hi */
+ *shading_data_ptr++ = 0x00; /* white lo */
+ *shading_data_ptr++ = 0x40; /* white hi -> 0x4000 */
+ }
+
+ genesys_send_offset_and_shading(dev, sensor, shading_data.data(),
+ pixels_per_line * 4 * channels);
+}
+
+
+// Find the position of the reference point: takes gray level 8 bits data and find
+// first CCD usable pixel and top of scanning area
+void sanei_genesys_search_reference_point(Genesys_Device* dev, Genesys_Sensor& sensor,
+ const uint8_t* src_data, int start_pixel, int dpi,
+ int width, int height)
+{
+ DBG_HELPER(dbg);
+ int x, y;
+ int current, left, top = 0;
+ int size, count;
+ int level = 80; /* edge threshold level */
+
+ // sanity check
+ if ((width < 3) || (height < 3)) {
+ throw SaneException("invalid width or height");
+ }
+
+ /* transformed image data */
+ size = width * height;
+ std::vector<uint8_t> image2(size, 0);
+ std::vector<uint8_t> image(size, 0);
+
+ /* laplace filter to denoise picture */
+ std::memcpy(image2.data(), src_data, size);
+ std::memcpy(image.data(), src_data, size); // to initialize unprocessed part of the image buffer
+
+ for (y = 1; y < height - 1; y++) {
+ for (x = 1; x < width - 1; x++) {
+ image[y * width + x] =
+ (image2[(y - 1) * width + x + 1] + 2 * image2[(y - 1) * width + x] +
+ image2[(y - 1) * width + x - 1] + 2 * image2[y * width + x + 1] +
+ 4 * image2[y * width + x] + 2 * image2[y * width + x - 1] +
+ image2[(y + 1) * width + x + 1] + 2 * image2[(y + 1) * width + x] +
+ image2[(y + 1) * width + x - 1]) / 16;
+ }
+ }
+
+ image2 = image;
+ if (DBG_LEVEL >= DBG_data)
+ sanei_genesys_write_pnm_file("gl_laplace.pnm", image.data(), 8, 1, width, height);
+
+ /* apply X direction sobel filter
+ -1 0 1
+ -2 0 2
+ -1 0 1
+ and finds threshold level
+ */
+ level = 0;
+ for (y = 2; y < height - 2; y++) {
+ for (x = 2; x < width - 2; x++) {
+ current = image2[(y - 1) * width + x + 1] - image2[(y - 1) * width + x - 1] +
+ 2 * image2[y * width + x + 1] - 2 * image2[y * width + x - 1] +
+ image2[(y + 1) * width + x + 1] - image2[(y + 1) * width + x - 1];
+ if (current < 0)
+ current = -current;
+ if (current > 255)
+ current = 255;
+ image[y * width + x] = current;
+ if (current > level)
+ level = current;
+ }
+ }
+ if (DBG_LEVEL >= DBG_data)
+ sanei_genesys_write_pnm_file("gl_xsobel.pnm", image.data(), 8, 1, width, height);
+
+ /* set up detection level */
+ level = level / 3;
+
+ /* find left black margin first
+ todo: search top before left
+ we average the result of N searches */
+ left = 0;
+ count = 0;
+ for (y = 2; y < 11; y++)
+ {
+ x = 8;
+ while ((x < width / 2) && (image[y * width + x] < level))
+ {
+ image[y * width + x] = 255;
+ x++;
+ }
+ count++;
+ left += x;
+ }
+ if (DBG_LEVEL >= DBG_data)
+ sanei_genesys_write_pnm_file("gl_detected-xsobel.pnm", image.data(), 8, 1, width, height);
+ left = left / count;
+
+ // turn it in CCD pixel at full sensor optical resolution
+ sensor.ccd_start_xoffset = start_pixel + (left * sensor.optical_res) / dpi;
+
+ /* find top edge by detecting black strip */
+ /* apply Y direction sobel filter
+ -1 -2 -1
+ 0 0 0
+ 1 2 1
+ */
+ level = 0;
+ for (y = 2; y < height - 2; y++) {
+ for (x = 2; x < width - 2; x++) {
+ current = -image2[(y - 1) * width + x + 1] - 2 * image2[(y - 1) * width + x] -
+ image2[(y - 1) * width + x - 1] + image2[(y + 1) * width + x + 1] +
+ 2 * image2[(y + 1) * width + x] + image2[(y + 1) * width + x - 1];
+ if (current < 0)
+ current = -current;
+ if (current > 255)
+ current = 255;
+ image[y * width + x] = current;
+ if (current > level)
+ level = current;
+ }
+ }
+ if (DBG_LEVEL >= DBG_data)
+ sanei_genesys_write_pnm_file("gl_ysobel.pnm", image.data(), 8, 1, width, height);
+
+ /* set up detection level */
+ level = level / 3;
+
+ /* search top of horizontal black stripe : TODO yet another flag */
+ if (dev->model->sensor_id == SensorId::CCD_5345
+ && dev->model->motor_id == MotorId::MD_5345)
+ {
+ top = 0;
+ count = 0;
+ for (x = width / 2; x < width - 1; x++)
+ {
+ y = 2;
+ while ((y < height) && (image[x + y * width] < level))
+ {
+ image[y * width + x] = 255;
+ y++;
+ }
+ count++;
+ top += y;
+ }
+ if (DBG_LEVEL >= DBG_data)
+ sanei_genesys_write_pnm_file("gl_detected-ysobel.pnm", image.data(), 8, 1, width, height);
+ top = top / count;
+
+ /* bottom of black stripe is of fixed witdh, this hardcoded value
+ * will be moved into device struct if more such values are needed */
+ top += 10;
+ dev->model->y_offset_calib_white = (top * MM_PER_INCH) / dpi;
+ DBG(DBG_info, "%s: black stripe y_offset = %f mm \n", __func__,
+ dev->model->y_offset_calib_white.value());
+ }
+
+ /* find white corner in dark area : TODO yet another flag */
+ if ((dev->model->sensor_id == SensorId::CCD_HP2300 && dev->model->motor_id == MotorId::HP2300) ||
+ (dev->model->sensor_id == SensorId::CCD_HP2400 && dev->model->motor_id == MotorId::HP2400) ||
+ (dev->model->sensor_id == SensorId::CCD_HP3670 && dev->model->motor_id == MotorId::HP3670))
+ {
+ top = 0;
+ count = 0;
+ for (x = 10; x < 60; x++)
+ {
+ y = 2;
+ while ((y < height) && (image[x + y * width] < level))
+ y++;
+ top += y;
+ count++;
+ }
+ top = top / count;
+ dev->model->y_offset_calib_white = (top * MM_PER_INCH) / dpi;
+ DBG(DBG_info, "%s: white corner y_offset = %f mm\n", __func__,
+ dev->model->y_offset_calib_white.value());
+ }
+
+ DBG(DBG_proc, "%s: ccd_start_xoffset = %d, left = %d, top = %d\n", __func__,
+ sensor.ccd_start_xoffset, left, top);
+}
+
+namespace gl843 {
+ void gl843_park_xpa_lamp(Genesys_Device* dev);
+ void gl843_set_xpa_motor_power(Genesys_Device* dev, Genesys_Register_Set& regs, bool set);
+} // namespace gl843
+
+namespace gl124 {
+ void gl124_setup_scan_gpio(Genesys_Device* dev, int resolution);
+} // namespace gl124
+
+void scanner_clear_scan_and_feed_counts(Genesys_Device& dev)
+{
+ switch (dev.model->asic_type) {
+ case AsicType::GL843: {
+ dev.interface->write_register(gl843::REG_0x0D,
+ gl843::REG_0x0D_CLRLNCNT | gl843::REG_0x0D_CLRMCNT);
+ break;
+ }
+ case AsicType::GL845:
+ case AsicType::GL846: {
+ dev.interface->write_register(gl846::REG_0x0D,
+ gl846::REG_0x0D_CLRLNCNT | gl846::REG_0x0D_CLRMCNT);
+ break;
+ }
+ case AsicType::GL847:{
+ dev.interface->write_register(gl847::REG_0x0D,
+ gl847::REG_0x0D_CLRLNCNT | gl847::REG_0x0D_CLRMCNT);
+ break;
+ }
+ case AsicType::GL124:{
+ dev.interface->write_register(gl124::REG_0x0D,
+ gl124::REG_0x0D_CLRLNCNT | gl124::REG_0x0D_CLRMCNT);
+ break;
+ }
+ default:
+ throw SaneException("Unsupported asic type");
+ }
+}
+
+void scanner_clear_scan_and_feed_counts2(Genesys_Device& dev)
+{
+ // FIXME: switch to scanner_clear_scan_and_feed_counts when updating tests
+ switch (dev.model->asic_type) {
+ case AsicType::GL843: {
+ dev.interface->write_register(gl843::REG_0x0D, gl843::REG_0x0D_CLRLNCNT);
+ dev.interface->write_register(gl843::REG_0x0D, gl843::REG_0x0D_CLRMCNT);
+ break;
+ }
+ case AsicType::GL845:
+ case AsicType::GL846: {
+ dev.interface->write_register(gl846::REG_0x0D, gl846::REG_0x0D_CLRLNCNT);
+ dev.interface->write_register(gl846::REG_0x0D, gl846::REG_0x0D_CLRMCNT);
+ break;
+ }
+ case AsicType::GL847: {
+ dev.interface->write_register(gl847::REG_0x0D, gl847::REG_0x0D_CLRLNCNT);
+ dev.interface->write_register(gl847::REG_0x0D, gl847::REG_0x0D_CLRMCNT);
+ break;
+ }
+ case AsicType::GL124: {
+ dev.interface->write_register(gl124::REG_0x0D, gl124::REG_0x0D_CLRLNCNT);
+ dev.interface->write_register(gl124::REG_0x0D, gl124::REG_0x0D_CLRMCNT);
+ break;
+ }
+ default:
+ throw SaneException("Unsupported asic type");
+ }
+}
+
+bool scanner_is_motor_stopped(Genesys_Device& dev)
+{
+ switch (dev.model->asic_type) {
+ case AsicType::GL646: {
+ auto status = scanner_read_status(dev);
+ return !status.is_motor_enabled && status.is_feeding_finished;
+ }
+ case AsicType::GL841: {
+ auto reg = dev.interface->read_register(gl841::REG_0x40);
+
+ return (!(reg & gl841::REG_0x40_DATAENB) && !(reg & gl841::REG_0x40_MOTMFLG));
+ }
+ case AsicType::GL843: {
+ auto status = scanner_read_status(dev);
+ auto reg = dev.interface->read_register(gl843::REG_0x40);
+
+ return (!(reg & gl843::REG_0x40_DATAENB) && !(reg & gl843::REG_0x40_MOTMFLG) &&
+ !status.is_motor_enabled);
+ }
+ case AsicType::GL845:
+ case AsicType::GL846: {
+ auto status = scanner_read_status(dev);
+ auto reg = dev.interface->read_register(gl846::REG_0x40);
+
+ return (!(reg & gl846::REG_0x40_DATAENB) && !(reg & gl846::REG_0x40_MOTMFLG) &&
+ !status.is_motor_enabled);
+ }
+ case AsicType::GL847: {
+ auto status = scanner_read_status(dev);
+ auto reg = dev.interface->read_register(gl847::REG_0x40);
+
+ return (!(reg & gl847::REG_0x40_DATAENB) && !(reg & gl847::REG_0x40_MOTMFLG) &&
+ !status.is_motor_enabled);
+ }
+ case AsicType::GL124: {
+ auto status = scanner_read_status(dev);
+ auto reg = dev.interface->read_register(gl124::REG_0x100);
+
+ return (!(reg & gl124::REG_0x100_DATAENB) && !(reg & gl124::REG_0x100_MOTMFLG) &&
+ !status.is_motor_enabled);
+ }
+ default:
+ throw SaneException("Unsupported asic type");
+ }
+}
+
+void scanner_stop_action(Genesys_Device& dev)
+{
+ DBG_HELPER(dbg);
+
+ switch (dev.model->asic_type) {
+ case AsicType::GL843:
+ case AsicType::GL845:
+ case AsicType::GL846:
+ case AsicType::GL847:
+ case AsicType::GL124:
+ break;
+ default:
+ throw SaneException("Unsupported asic type");
+ }
+
+ if (dev.cmd_set->needs_update_home_sensor_gpio()) {
+ dev.cmd_set->update_home_sensor_gpio(dev);
+ }
+
+ if (scanner_is_motor_stopped(dev)) {
+ DBG(DBG_info, "%s: already stopped\n", __func__);
+ return;
+ }
+
+ scanner_stop_action_no_move(dev, dev.reg);
+
+ if (is_testing_mode()) {
+ return;
+ }
+
+ for (unsigned i = 0; i < 10; ++i) {
+ if (scanner_is_motor_stopped(dev)) {
+ return;
+ }
+
+ dev.interface->sleep_ms(100);
+ }
+
+ throw SaneException(SANE_STATUS_IO_ERROR, "could not stop motor");
+}
+
+void scanner_stop_action_no_move(Genesys_Device& dev, genesys::Genesys_Register_Set& regs)
+{
+ switch (dev.model->asic_type) {
+ case AsicType::GL646:
+ case AsicType::GL841:
+ case AsicType::GL843:
+ case AsicType::GL845:
+ case AsicType::GL846:
+ case AsicType::GL847:
+ case AsicType::GL124:
+ break;
+ default:
+ throw SaneException("Unsupported asic type");
+ }
+
+ regs_set_optical_off(dev.model->asic_type, regs);
+ // same across all supported ASICs
+ dev.interface->write_register(0x01, regs.get8(0x01));
+
+ // looks like certain scanners lock up if we try to scan immediately after stopping previous
+ // action.
+ dev.interface->sleep_ms(100);
+}
+
+void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, Direction direction)
+{
+ DBG_HELPER_ARGS(dbg, "steps=%d direction=%d", steps, static_cast<unsigned>(direction));
+
+ auto local_reg = dev.reg;
+
+ unsigned resolution = dev.model->get_resolution_settings(scan_method).get_min_resolution_y();
+
+ const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 3, scan_method);
+
+ bool uses_secondary_head = (scan_method == ScanMethod::TRANSPARENCY ||
+ scan_method == ScanMethod::TRANSPARENCY_INFRARED);
+ bool uses_secondary_pos = uses_secondary_head &&
+ dev.model->default_method == ScanMethod::FLATBED;
+
+ if (!dev.is_head_pos_known(ScanHeadId::PRIMARY)) {
+ throw SaneException("Unknown head position");
+ }
+ if (uses_secondary_pos && !dev.is_head_pos_known(ScanHeadId::SECONDARY)) {
+ throw SaneException("Unknown head position");
+ }
+ if (direction == Direction::BACKWARD && steps > dev.head_pos(ScanHeadId::PRIMARY)) {
+ throw SaneException("Trying to feed behind the home position %d %d",
+ steps, dev.head_pos(ScanHeadId::PRIMARY));
+ }
+ if (uses_secondary_pos && direction == Direction::BACKWARD &&
+ steps > dev.head_pos(ScanHeadId::SECONDARY))
+ {
+ throw SaneException("Trying to feed behind the home position %d %d",
+ steps, dev.head_pos(ScanHeadId::SECONDARY));
+ }
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = resolution;
+ session.params.startx = 0;
+ session.params.starty = steps;
+ session.params.pixels = 100;
+ session.params.lines = 3;
+ session.params.depth = 8;
+ session.params.channels = 3;
+ session.params.scan_method = scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ if (dev.model->asic_type == AsicType::GL843) {
+ session.params.color_filter = ColorFilter::RED;
+ } else {
+ session.params.color_filter = dev.settings.color_filter;
+ }
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::FEEDING |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+
+ if (dev.model->asic_type == AsicType::GL124) {
+ session.params.flags |= ScanFlag::DISABLE_BUFFER_FULL_MOVE;
+ }
+
+ if (direction == Direction::BACKWARD) {
+ session.params.flags |= ScanFlag::REVERSE;
+ }
+
+ compute_session(&dev, session, sensor);
+
+ dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session);
+
+ if (dev.model->asic_type != AsicType::GL843) {
+ regs_set_exposure(dev.model->asic_type, local_reg, {0, 0, 0});
+ }
+ scanner_clear_scan_and_feed_counts2(dev);
+
+ dev.interface->write_registers(local_reg);
+ if (uses_secondary_head) {
+ gl843::gl843_set_xpa_motor_power(&dev, local_reg, true);
+ }
+
+ try {
+ scanner_start_action(dev, true);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&]() {
+ gl843::gl843_set_xpa_motor_power(&dev, local_reg, false);
+ });
+ catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); });
+ // restore original registers
+ catch_all_exceptions(__func__, [&]() { dev.interface->write_registers(dev.reg); });
+ throw;
+ }
+
+ if (is_testing_mode()) {
+ dev.interface->test_checkpoint("feed");
+
+ dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, direction, steps);
+ if (uses_secondary_pos) {
+ dev.advance_head_pos_by_steps(ScanHeadId::SECONDARY, direction, steps);
+ }
+
+ // FIXME: why don't we stop the scanner like on other ASICs
+ if (dev.model->asic_type != AsicType::GL843) {
+ scanner_stop_action(dev);
+ }
+ if (uses_secondary_head) {
+ gl843::gl843_set_xpa_motor_power(&dev, local_reg, false);
+ }
+ return;
+ }
+
+ // wait until feed count reaches the required value
+ // FIXME: should porbably wait for some timeout
+ Status status;
+ for (unsigned i = 0;; ++i) {
+ status = scanner_read_status(dev);
+ if (status.is_feeding_finished || (
+ direction == Direction::BACKWARD && status.is_at_home))
+ {
+ break;
+ }
+ dev.interface->sleep_ms(10);
+ }
+
+ // FIXME: why don't we stop the scanner like on other ASICs
+ if (dev.model->asic_type != AsicType::GL843) {
+ scanner_stop_action(dev);
+ }
+ if (uses_secondary_head) {
+ gl843::gl843_set_xpa_motor_power(&dev, local_reg, false);
+ }
+
+ dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, direction, steps);
+ if (uses_secondary_pos) {
+ dev.advance_head_pos_by_steps(ScanHeadId::SECONDARY, direction, steps);
+ }
+
+ // looks like certain scanners lock up if we scan immediately after feeding
+ dev.interface->sleep_ms(100);
+}
+
+void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home)
+{
+ DBG_HELPER_ARGS(dbg, "wait_until_home = %d", wait_until_home);
+
+ switch (dev.model->asic_type) {
+ case AsicType::GL843:
+ case AsicType::GL845:
+ case AsicType::GL846:
+ case AsicType::GL847:
+ case AsicType::GL124:
+ break;
+ default:
+ throw SaneException("Unsupported asic type");
+ }
+
+ // FIXME: also check whether the scanner actually has a secondary head
+ if (!dev.is_head_pos_known(ScanHeadId::SECONDARY) ||
+ dev.head_pos(ScanHeadId::SECONDARY) > 0 ||
+ dev.settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev.settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ scanner_move_back_home_ta(dev);
+ }
+
+ if (dev.is_head_pos_known(ScanHeadId::PRIMARY) &&
+ dev.head_pos(ScanHeadId::PRIMARY) > 1000)
+ {
+ // leave 500 steps for regular slow back home
+ scanner_move(dev, dev.model->default_method, dev.head_pos(ScanHeadId::PRIMARY) - 500,
+ Direction::BACKWARD);
+ }
+
+ if (dev.cmd_set->needs_update_home_sensor_gpio()) {
+ dev.cmd_set->update_home_sensor_gpio(dev);
+ }
+
+ auto status = scanner_read_reliable_status(dev);
+
+ if (status.is_at_home) {
+ dbg.log(DBG_info, "already at home");
+ dev.set_head_pos_zero(ScanHeadId::PRIMARY);
+ return;
+ }
+
+ if (dev.model->model_id == ModelId::CANON_LIDE_210) {
+ // move the head back a little first
+ if (dev.is_head_pos_known(ScanHeadId::PRIMARY) &&
+ dev.head_pos(ScanHeadId::PRIMARY) > 30)
+ {
+ scanner_move(dev, dev.model->default_method, 20, Direction::BACKWARD);
+ }
+ }
+
+ Genesys_Register_Set local_reg = dev.reg;
+ unsigned resolution = sanei_genesys_get_lowest_ydpi(&dev);
+
+ const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 1, dev.model->default_method);
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = resolution;
+ session.params.startx = 100;
+ if (dev.model->asic_type == AsicType::GL843) {
+ session.params.starty = 40000;
+ } else {
+ session.params.starty = 30000;
+ }
+ session.params.pixels = 100;
+ session.params.lines = 100;
+ session.params.depth = 8;
+ session.params.channels = 1;
+ session.params.scan_method = dev.settings.scan_method;
+ if (dev.model->asic_type == AsicType::GL843) {
+ session.params.scan_mode = ScanColorMode::LINEART;
+ session.params.color_filter = dev.settings.color_filter;
+ } else {
+ session.params.scan_mode = ScanColorMode::GRAY;
+ session.params.color_filter = ColorFilter::RED;
+ }
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::IGNORE_LINE_DISTANCE |
+ ScanFlag::REVERSE;
+ if (dev.model->asic_type == AsicType::GL843) {
+ session.params.flags |= ScanFlag::DISABLE_BUFFER_FULL_MOVE;
+ }
+
+ compute_session(&dev, session, sensor);
+
+ dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session);
+
+ scanner_clear_scan_and_feed_counts(dev);
+
+ dev.interface->write_registers(local_reg);
+
+ if (dev.model->asic_type == AsicType::GL124) {
+ gl124::gl124_setup_scan_gpio(&dev, resolution);
+ }
+
+ try {
+ scanner_start_action(dev, true);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); });
+ // restore original registers
+ catch_all_exceptions(__func__, [&]()
+ {
+ dev.interface->write_registers(dev.reg);
+ });
+ throw;
+ }
+
+ if (dev.cmd_set->needs_update_home_sensor_gpio()) {
+ dev.cmd_set->update_home_sensor_gpio(dev);
+ }
+
+ if (is_testing_mode()) {
+ dev.interface->test_checkpoint("move_back_home");
+ dev.set_head_pos_zero(ScanHeadId::PRIMARY);
+ return;
+ }
+
+ if (wait_until_home) {
+ for (unsigned i = 0; i < 300; ++i) {
+ auto status = scanner_read_status(dev);
+
+ if (status.is_at_home) {
+ dbg.log(DBG_info, "reached home position");
+ if (dev.model->asic_type == AsicType::GL846 ||
+ dev.model->asic_type == AsicType::GL847)
+ {
+ scanner_stop_action(dev);
+ }
+ dev.set_head_pos_zero(ScanHeadId::PRIMARY);
+ return;
+ }
+
+ dev.interface->sleep_ms(100);
+ }
+
+ // when we come here then the scanner needed too much time for this, so we better stop
+ // the motor
+ catch_all_exceptions(__func__, [&](){ scanner_stop_action(dev); });
+ dev.set_head_pos_unknown();
+ throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for scanhead to go home");
+ }
+ dbg.log(DBG_info, "scanhead is still moving");
+}
+
+void scanner_move_back_home_ta(Genesys_Device& dev)
+{
+ DBG_HELPER(dbg);
+
+ switch (dev.model->asic_type) {
+ case AsicType::GL843:
+ break;
+ default:
+ throw SaneException("Unsupported asic type");
+ }
+
+ Genesys_Register_Set local_reg = dev.reg;
+
+ auto scan_method = ScanMethod::TRANSPARENCY;
+ unsigned resolution = dev.model->get_resolution_settings(scan_method).get_min_resolution_y();
+
+ const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 1, scan_method);
+
+ if (dev.is_head_pos_known(ScanHeadId::SECONDARY) &&
+ dev.head_pos(ScanHeadId::SECONDARY) > 1000)
+ {
+ // leave 500 steps for regular slow back home
+ scanner_move(dev, scan_method, dev.head_pos(ScanHeadId::SECONDARY) - 500,
+ Direction::BACKWARD);
+ }
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = resolution;
+ session.params.startx = 100;
+ session.params.starty = 30000;
+ session.params.pixels = 100;
+ session.params.lines = 100;
+ session.params.depth = 8;
+ session.params.channels = 1;
+ session.params.scan_method = scan_method;
+ session.params.scan_mode = ScanColorMode::GRAY;
+ session.params.color_filter = ColorFilter::RED;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::IGNORE_LINE_DISTANCE |
+ ScanFlag::REVERSE;
+
+ compute_session(&dev, session, sensor);
+
+ dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session);
+
+ scanner_clear_scan_and_feed_counts(dev);
+
+ dev.interface->write_registers(local_reg);
+ gl843::gl843_set_xpa_motor_power(&dev, local_reg, true);
+
+ try {
+ scanner_start_action(dev, true);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); });
+ // restore original registers
+ catch_all_exceptions(__func__, [&]() { dev.interface->write_registers(dev.reg); });
+ throw;
+ }
+
+ if (is_testing_mode()) {
+ dev.interface->test_checkpoint("move_back_home_ta");
+
+ if (dev.is_head_pos_known(ScanHeadId::PRIMARY)) {
+ if (dev.head_pos(ScanHeadId::PRIMARY) > dev.head_pos(ScanHeadId::SECONDARY)) {
+ dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, Direction::BACKWARD,
+ dev.head_pos(ScanHeadId::SECONDARY));
+ } else {
+ dev.set_head_pos_zero(ScanHeadId::PRIMARY);
+ }
+ dev.set_head_pos_zero(ScanHeadId::SECONDARY);
+ }
+
+ scanner_stop_action(dev);
+ gl843::gl843_set_xpa_motor_power(&dev, local_reg, false);
+ return;
+ }
+
+ for (unsigned i = 0; i < 1200; ++i) {
+
+ auto status = scanner_read_status(dev);
+
+ if (status.is_at_home) {
+ dbg.log(DBG_info, "TA reached home position");
+
+ if (dev.is_head_pos_known(ScanHeadId::PRIMARY)) {
+ if (dev.head_pos(ScanHeadId::PRIMARY) > dev.head_pos(ScanHeadId::SECONDARY)) {
+ dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, Direction::BACKWARD,
+ dev.head_pos(ScanHeadId::SECONDARY));
+ } else {
+ dev.set_head_pos_zero(ScanHeadId::PRIMARY);
+ }
+ dev.set_head_pos_zero(ScanHeadId::SECONDARY);
+ }
+
+ scanner_stop_action(dev);
+ gl843::gl843_set_xpa_motor_power(&dev, local_reg, false);
+ return;
+ }
+
+ dev.interface->sleep_ms(100);
+ }
+
+ throw SaneException("Timeout waiting for XPA lamp to park");
+}
+
+void sanei_genesys_calculate_zmod(bool two_table,
+ uint32_t exposure_time,
+ const std::vector<uint16_t>& slope_table,
+ unsigned acceleration_steps,
+ unsigned move_steps,
+ unsigned buffer_acceleration_steps,
+ uint32_t* out_z1, uint32_t* out_z2)
+{
+ DBG(DBG_info, "%s: two_table=%d\n", __func__, two_table);
+
+ // acceleration total time
+ unsigned sum = std::accumulate(slope_table.begin(), slope_table.begin() + acceleration_steps,
+ 0, std::plus<unsigned>());
+
+ /* Z1MOD:
+ c = sum(slope_table; reg_stepno)
+ d = reg_fwdstep * <cruising speed>
+ Z1MOD = (c+d) % exposure_time
+ */
+ *out_z1 = (sum + buffer_acceleration_steps * slope_table[acceleration_steps - 1]) % exposure_time;
+
+ /* Z2MOD:
+ a = sum(slope_table; reg_stepno)
+ b = move_steps or 1 if 2 tables
+ Z1MOD = (a+b) % exposure_time
+ */
+ if (!two_table) {
+ sum = sum + (move_steps * slope_table[acceleration_steps - 1]);
+ } else {
+ sum = sum + slope_table[acceleration_steps - 1];
+ }
+ *out_z2 = sum % exposure_time;
+}
+
+static uint8_t genesys_adjust_gain(double* applied_multi, double multi, uint8_t gain)
+{
+ double voltage, original_voltage;
+ uint8_t new_gain = 0;
+
+ DBG(DBG_proc, "%s: multi=%f, gain=%d\n", __func__, multi, gain);
+
+ voltage = 0.5 + gain * 0.25;
+ original_voltage = voltage;
+
+ voltage *= multi;
+
+ new_gain = static_cast<std::uint8_t>((voltage - 0.5) * 4);
+ if (new_gain > 0x0e)
+ new_gain = 0x0e;
+
+ voltage = 0.5 + (new_gain) * 0.25;
+
+ *applied_multi = voltage / original_voltage;
+
+ DBG(DBG_proc, "%s: orig voltage=%.2f, new voltage=%.2f, *applied_multi=%f, new_gain=%d\n",
+ __func__, original_voltage, voltage, *applied_multi, new_gain);
+
+ return new_gain;
+}
+
+
+// todo: is return status necessary (unchecked?)
+static void genesys_average_white(Genesys_Device* dev, Genesys_Sensor& sensor, int channels,
+ int channel, uint8_t* data, int size, int *max_average)
+{
+
+ DBG_HELPER_ARGS(dbg, "channels=%d, channel=%d, size=%d", channels, channel, size);
+ int gain_white_ref, sum, range;
+ int average;
+ int i;
+
+ range = size / 50;
+
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ gain_white_ref = sensor.fau_gain_white_ref * 256;
+ } else {
+ gain_white_ref = sensor.gain_white_ref * 256;
+ }
+
+ if (range < 1)
+ range = 1;
+
+ size = size / (2 * range * channels);
+
+ data += (channel * 2);
+
+ *max_average = 0;
+
+ while (size--)
+ {
+ sum = 0;
+ for (i = 0; i < range; i++)
+ {
+ sum += (*data);
+ sum += *(data + 1) * 256;
+ data += (2 * channels); /* byte based */
+ }
+
+ average = (sum / range);
+ if (average > *max_average)
+ *max_average = average;
+ }
+
+ DBG(DBG_proc, "%s: max_average=%d, gain_white_ref = %d, finished\n", __func__, *max_average,
+ gain_white_ref);
+
+ if (*max_average >= gain_white_ref)
+ throw SaneException(SANE_STATUS_INVAL);
+}
+
+/* todo: understand, values are too high */
+static int
+genesys_average_black (Genesys_Device * dev, int channel,
+ uint8_t * data, int pixels)
+{
+ int i;
+ int sum;
+ int pixel_step;
+
+ DBG(DBG_proc, "%s: channel=%d, pixels=%d\n", __func__, channel, pixels);
+
+ sum = 0;
+
+ if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS)
+ {
+ data += (channel * 2);
+ pixel_step = 3 * 2;
+ }
+ else
+ {
+ pixel_step = 2;
+ }
+
+ for (i = 0; i < pixels; i++)
+ {
+ sum += *data;
+ sum += *(data + 1) * 256;
+
+ data += pixel_step;
+ }
+
+ DBG(DBG_proc, "%s = %d\n", __func__, sum / pixels);
+
+ return sum / pixels;
+}
+
+
+// todo: check; it works but the lines 1, 2, and 3 are too dark even with the
+// same offset and gain settings?
+static void genesys_coarse_calibration(Genesys_Device* dev, Genesys_Sensor& sensor)
+{
+ DBG_HELPER_ARGS(dbg, "scan_mode = %d", static_cast<unsigned>(dev->settings.scan_mode));
+ int black_pixels;
+ int white_average;
+ uint8_t offset[4] = { 0xa0, 0x00, 0xa0, 0x40 }; /* first value isn't used */
+ uint16_t white[12], dark[12];
+ int i, j;
+
+ black_pixels = sensor.black_pixels
+ * dev->settings.xres / sensor.optical_res;
+
+ unsigned channels = dev->settings.get_channels();
+
+ DBG(DBG_info, "channels %d y_size %f xres %d\n", channels, dev->model->y_size.value(),
+ dev->settings.xres);
+ unsigned size = static_cast<unsigned>(channels * 2 * dev->model->y_size * dev->settings.xres /
+ MM_PER_INCH);
+ /* 1 1 mm 1/inch inch/mm */
+
+ std::vector<uint8_t> calibration_data(size);
+ std::vector<uint8_t> all_data(size * 4, 1);
+
+ dev->cmd_set->set_fe(dev, sensor, AFE_INIT);
+
+ dev->frontend.set_gain(0, 2);
+ dev->frontend.set_gain(1, 2);
+ dev->frontend.set_gain(2, 2); // TODO: ? was 2
+ dev->frontend.set_offset(0, offset[0]);
+ dev->frontend.set_offset(1, offset[0]);
+ dev->frontend.set_offset(2, offset[0]);
+
+ for (i = 0; i < 4; i++) /* read 4 lines */
+ {
+ if (i < 3) /* first 3 lines */
+ {
+ dev->frontend.set_offset(0, offset[i]);
+ dev->frontend.set_offset(1, offset[i]);
+ dev->frontend.set_offset(2, offset[i]);
+ }
+
+ if (i == 1) /* second line */
+ {
+ double applied_multi;
+ double gain_white_ref;
+
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ gain_white_ref = sensor.fau_gain_white_ref * 256;
+ } else {
+ gain_white_ref = sensor.gain_white_ref * 256;
+ }
+
+ // white and black are defined downwards
+
+ uint8_t gain0 = genesys_adjust_gain(&applied_multi,
+ gain_white_ref / (white[0] - dark[0]),
+ dev->frontend.get_gain(0));
+ uint8_t gain1 = genesys_adjust_gain(&applied_multi,
+ gain_white_ref / (white[1] - dark[1]),
+ dev->frontend.get_gain(1));
+ uint8_t gain2 = genesys_adjust_gain(&applied_multi,
+ gain_white_ref / (white[2] - dark[2]),
+ dev->frontend.get_gain(2));
+ // FIXME: looks like overwritten data. Are the above calculations doing
+ // anything at all?
+ dev->frontend.set_gain(0, gain0);
+ dev->frontend.set_gain(1, gain1);
+ dev->frontend.set_gain(2, gain2);
+ dev->frontend.set_gain(0, 2);
+ dev->frontend.set_gain(1, 2);
+ dev->frontend.set_gain(2, 2);
+
+ dev->interface->write_fe_register(0x28, dev->frontend.get_gain(0));
+ dev->interface->write_fe_register(0x29, dev->frontend.get_gain(1));
+ dev->interface->write_fe_register(0x2a, dev->frontend.get_gain(2));
+ }
+
+ if (i == 3) /* last line */
+ {
+ double x, y, rate;
+
+ for (j = 0; j < 3; j++)
+ {
+
+ x = static_cast<double>(dark[(i - 2) * 3 + j] -
+ dark[(i - 1) * 3 + j]) * 254 / (offset[i - 1] / 2 -
+ offset[i - 2] / 2);
+ y = x - x * (offset[i - 1] / 2) / 254 - dark[(i - 1) * 3 + j];
+ rate = (x - DARK_VALUE - y) * 254 / x + 0.5;
+
+ uint8_t curr_offset = static_cast<uint8_t>(rate);
+
+ if (curr_offset > 0x7f) {
+ curr_offset = 0x7f;
+ }
+ curr_offset <<= 1;
+ dev->frontend.set_offset(j, curr_offset);
+ }
+ }
+ dev->interface->write_fe_register(0x20, dev->frontend.get_offset(0));
+ dev->interface->write_fe_register(0x21, dev->frontend.get_offset(1));
+ dev->interface->write_fe_register(0x22, dev->frontend.get_offset(2));
+
+ DBG(DBG_info,
+ "%s: doing scan: gain: %d/%d/%d, offset: %d/%d/%d\n", __func__,
+ dev->frontend.get_gain(0),
+ dev->frontend.get_gain(1),
+ dev->frontend.get_gain(2),
+ dev->frontend.get_offset(0),
+ dev->frontend.get_offset(1),
+ dev->frontend.get_offset(2));
+
+
+ dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, false);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("coarse_calibration");
+ dev->cmd_set->end_scan(dev, &dev->calib_reg, true);
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size);
+ std::memcpy(all_data.data() + i * size, calibration_data.data(), size);
+ if (i == 3) /* last line */
+ {
+ std::vector<uint8_t> all_data_8(size * 4 / 2);
+ unsigned int count;
+
+ for (count = 0; count < static_cast<unsigned>(size * 4 / 2); count++) {
+ all_data_8[count] = all_data[count * 2 + 1];
+ }
+ sanei_genesys_write_pnm_file("gl_coarse.pnm", all_data_8.data(), 8, channels, size / 6, 4);
+ }
+
+ dev->cmd_set->end_scan(dev, &dev->calib_reg, true);
+
+ if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS)
+ {
+ for (j = 0; j < 3; j++)
+ {
+ genesys_average_white(dev, sensor, 3, j, calibration_data.data(), size, &white_average);
+ white[i * 3 + j] = white_average;
+ dark[i * 3 + j] =
+ genesys_average_black (dev, j, calibration_data.data(),
+ black_pixels);
+ DBG(DBG_info, "%s: white[%d]=%d, black[%d]=%d\n", __func__,
+ i * 3 + j, white[i * 3 + j], i * 3 + j, dark[i * 3 + j]);
+ }
+ }
+ else /* one color-component modes */
+ {
+ genesys_average_white(dev, sensor, 1, 0, calibration_data.data(), size, &white_average);
+ white[i * 3 + 0] = white[i * 3 + 1] = white[i * 3 + 2] =
+ white_average;
+ dark[i * 3 + 0] = dark[i * 3 + 1] = dark[i * 3 + 2] =
+ genesys_average_black (dev, 0, calibration_data.data(), black_pixels);
+ }
+ } /* for (i = 0; i < 4; i++) */
+
+ DBG(DBG_info, "%s: final: gain: %d/%d/%d, offset: %d/%d/%d\n", __func__,
+ dev->frontend.get_gain(0),
+ dev->frontend.get_gain(1),
+ dev->frontend.get_gain(2),
+ dev->frontend.get_offset(0),
+ dev->frontend.get_offset(1),
+ dev->frontend.get_offset(2));
+}
+
+/**
+ * scans a white area with motor and lamp off to get the per CCD pixel offset
+ * that will be used to compute shading coefficient
+ * @param dev scanner's device
+ */
+static void genesys_shading_calibration_impl(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ std::vector<std::uint16_t>& out_average_data,
+ bool is_dark, const std::string& log_filename_prefix)
+{
+ DBG_HELPER(dbg);
+
+ debug_dump(DBG_info, dev->calib_session);
+
+ size_t size;
+ uint32_t pixels_per_line;
+ uint8_t channels;
+
+ /* end pixel - start pixel */
+ pixels_per_line = dev->calib_pixels;
+ channels = dev->calib_channels;
+
+ uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset;
+
+ // FIXME: we set this during both dark and white calibration. A cleaner approach should
+ // probably be used
+ dev->average_size = channels * out_pixels_per_line;
+
+ out_average_data.clear();
+ out_average_data.resize(dev->average_size);
+
+ if (is_dark && dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) {
+ // FIXME: dark shading currently not supported on infrared transparency scans
+ return;
+ }
+
+ // FIXME: the current calculation is likely incorrect on non-GL843 implementations,
+ // but this needs checking
+ if (dev->calib_total_bytes_to_read > 0) {
+ size = dev->calib_total_bytes_to_read;
+ } else if (dev->model->asic_type == AsicType::GL843) {
+ size = channels * 2 * pixels_per_line * dev->calib_lines;
+ } else {
+ size = channels * 2 * pixels_per_line * (dev->calib_lines + 1);
+ }
+
+ std::vector<uint16_t> calibration_data(size / 2);
+
+ bool motor = true;
+ if (dev->model->flags & GENESYS_FLAG_SHADING_NO_MOVE)
+ {
+ motor = false;
+ }
+
+ // turn off motor and lamp power for flatbed scanners, but not for sheetfed scanners
+ // because they have a calibration sheet with a sufficient black strip
+ if (is_dark && !dev->model->is_sheetfed) {
+ sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, false);
+ sanei_genesys_set_motor_power(dev->calib_reg, motor);
+ } else {
+ sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, true);
+ sanei_genesys_set_motor_power(dev->calib_reg, motor);
+ }
+
+ dev->interface->write_registers(dev->calib_reg);
+
+ if (is_dark) {
+ // wait some time to let lamp to get dark
+ dev->interface->sleep_ms(200);
+ } else if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION) {
+ // make sure lamp is bright again
+ // FIXME: what about scanners that take a long time to warm the lamp?
+ dev->interface->sleep_ms(500);
+ }
+
+ bool start_motor = !is_dark;
+ dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, start_motor);
+
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint(is_dark ? "dark_shading_calibration"
+ : "white_shading_calibration");
+ dev->cmd_set->end_scan(dev, &dev->calib_reg, true);
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, reinterpret_cast<std::uint8_t*>(calibration_data.data()),
+ size);
+
+ dev->cmd_set->end_scan(dev, &dev->calib_reg, true);
+
+ if (dev->model->flags & GENESYS_FLAG_16BIT_DATA_INVERTED) {
+ for (std::size_t i = 0; i < size / 2; ++i) {
+ auto value = calibration_data[i];
+ value = ((value >> 8) & 0xff) | ((value << 8) & 0xff00);
+ calibration_data[i] = value;
+ }
+ }
+
+ std::fill(out_average_data.begin(),
+ out_average_data.begin() + dev->calib_pixels_offset * channels, 0);
+
+ compute_array_percentile_approx(out_average_data.data() + dev->calib_pixels_offset * channels,
+ calibration_data.data(),
+ dev->calib_lines, pixels_per_line * channels,
+ 0.5f);
+
+ if (DBG_LEVEL >= DBG_data) {
+ sanei_genesys_write_pnm_file16((log_filename_prefix + "_shading.pnm").c_str(),
+ calibration_data.data(),
+ channels, pixels_per_line, dev->calib_lines);
+ sanei_genesys_write_pnm_file16((log_filename_prefix + "_average.pnm").c_str(),
+ out_average_data.data(),
+ channels, out_pixels_per_line, 1);
+ }
+}
+
+
+static void genesys_dark_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+ genesys_shading_calibration_impl(dev, sensor, dev->dark_average_data, true, "gl_black_");
+}
+/*
+ * this function builds dummy dark calibration data so that we can
+ * compute shading coefficient in a clean way
+ * todo: current values are hardcoded, we have to find if they
+ * can be computed from previous calibration data (when doing offset
+ * calibration ?)
+ */
+static void genesys_dummy_dark_shading(Genesys_Device* dev, const Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+ uint32_t pixels_per_line;
+ uint8_t channels;
+ uint32_t skip, xend;
+ int dummy1, dummy2, dummy3; /* dummy black average per channel */
+
+ pixels_per_line = dev->calib_pixels;
+ channels = dev->calib_channels;
+
+ uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset;
+
+ dev->average_size = channels * out_pixels_per_line;
+ dev->dark_average_data.clear();
+ dev->dark_average_data.resize(dev->average_size, 0);
+
+ /* we average values on 'the left' where CCD pixels are under casing and
+ give darkest values. We then use these as dummy dark calibration */
+ if (dev->settings.xres <= sensor.optical_res / 2)
+ {
+ skip = 4;
+ xend = 36;
+ }
+ else
+ {
+ skip = 4;
+ xend = 68;
+ }
+ if (dev->model->sensor_id==SensorId::CCD_G4050 ||
+ dev->model->sensor_id==SensorId::CCD_HP_4850C
+ || dev->model->sensor_id==SensorId::CCD_CANON_4400F
+ || dev->model->sensor_id==SensorId::CCD_CANON_8400F
+ || dev->model->sensor_id==SensorId::CCD_KVSS080)
+ {
+ skip = 2;
+ xend = sensor.black_pixels;
+ }
+
+ /* average each channels on half left margin */
+ dummy1 = 0;
+ dummy2 = 0;
+ dummy3 = 0;
+
+ for (unsigned x = skip + 1; x <= xend; x++) {
+ dummy1 += dev->white_average_data[channels * x];
+ if (channels > 1) {
+ dummy2 += dev->white_average_data[channels * x + 1];
+ dummy3 += dev->white_average_data[channels * x + 2];
+ }
+ }
+
+ dummy1 /= (xend - skip);
+ if (channels > 1)
+ {
+ dummy2 /= (xend - skip);
+ dummy3 /= (xend - skip);
+ }
+ DBG(DBG_proc, "%s: dummy1=%d, dummy2=%d, dummy3=%d \n", __func__, dummy1, dummy2, dummy3);
+
+ /* fill dark_average */
+ for (unsigned x = 0; x < out_pixels_per_line; x++) {
+ dev->dark_average_data[channels * x] = dummy1;
+ if (channels > 1) {
+ dev->dark_average_data[channels * x + 1] = dummy2;
+ dev->dark_average_data[channels * x + 2] = dummy3;
+ }
+ }
+}
+
+
+static void genesys_repark_sensor_before_shading(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK) {
+ dev->cmd_set->move_back_home(dev, true);
+
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ dev->cmd_set->move_to_ta(dev);
+ }
+ }
+}
+
+static void genesys_repark_sensor_after_white_shading(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK) {
+ dev->cmd_set->move_back_home(dev, true);
+ }
+}
+
+static void genesys_white_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+ genesys_shading_calibration_impl(dev, sensor, dev->white_average_data, false, "gl_white_");
+}
+
+// This calibration uses a scan over the calibration target, comprising a black and a white strip.
+// (So the motor must be on.)
+static void genesys_dark_white_shading_calibration(Genesys_Device* dev,
+ const Genesys_Sensor& sensor)
+{
+ DBG_HELPER_ARGS(dbg, "lines = %zu", dev->calib_lines);
+ size_t size;
+ uint32_t pixels_per_line;
+ uint8_t channels;
+ unsigned int x;
+ uint32_t dark, white, dark_sum, white_sum, dark_count, white_count, col,
+ dif;
+
+ pixels_per_line = dev->calib_pixels;
+ channels = dev->calib_channels;
+
+ uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset;
+
+ dev->average_size = channels * out_pixels_per_line;
+
+ dev->white_average_data.clear();
+ dev->white_average_data.resize(dev->average_size);
+
+ dev->dark_average_data.clear();
+ dev->dark_average_data.resize(dev->average_size);
+
+ if (dev->calib_total_bytes_to_read > 0)
+ size = dev->calib_total_bytes_to_read;
+ else
+ size = channels * 2 * pixels_per_line * dev->calib_lines;
+
+ std::vector<uint8_t> calibration_data(size);
+
+ bool motor = true;
+ if (dev->model->flags & GENESYS_FLAG_SHADING_NO_MOVE)
+ {
+ motor = false;
+ }
+
+ // turn on motor and lamp power
+ sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, true);
+ sanei_genesys_set_motor_power(dev->calib_reg, motor);
+
+ dev->interface->write_registers(dev->calib_reg);
+
+ dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, false);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("dark_white_shading_calibration");
+ dev->cmd_set->end_scan(dev, &dev->calib_reg, true);
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size);
+
+ dev->cmd_set->end_scan(dev, &dev->calib_reg, true);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ if (dev->model->is_cis)
+ {
+ sanei_genesys_write_pnm_file("gl_black_white_shading.pnm", calibration_data.data(),
+ 16, 1, pixels_per_line*channels,
+ dev->calib_lines);
+ }
+ else
+ {
+ sanei_genesys_write_pnm_file("gl_black_white_shading.pnm", calibration_data.data(),
+ 16, channels, pixels_per_line,
+ dev->calib_lines);
+ }
+ }
+
+
+ std::fill(dev->dark_average_data.begin(),
+ dev->dark_average_data.begin() + dev->calib_pixels_offset * channels, 0);
+ std::fill(dev->white_average_data.begin(),
+ dev->white_average_data.begin() + dev->calib_pixels_offset * channels, 0);
+
+ uint16_t* average_white = dev->white_average_data.data() + dev->calib_pixels_offset * channels;
+ uint16_t* average_dark = dev->dark_average_data.data() + dev->calib_pixels_offset * channels;
+
+ for (x = 0; x < pixels_per_line * channels; x++)
+ {
+ dark = 0xffff;
+ white = 0;
+
+ for (std::size_t y = 0; y < dev->calib_lines; y++)
+ {
+ col = calibration_data[(x + y * pixels_per_line * channels) * 2];
+ col |=
+ calibration_data[(x + y * pixels_per_line * channels) * 2 +
+ 1] << 8;
+
+ if (col > white)
+ white = col;
+ if (col < dark)
+ dark = col;
+ }
+
+ dif = white - dark;
+
+ dark = dark + dif / 8;
+ white = white - dif / 8;
+
+ dark_count = 0;
+ dark_sum = 0;
+
+ white_count = 0;
+ white_sum = 0;
+
+ for (std::size_t y = 0; y < dev->calib_lines; y++)
+ {
+ col = calibration_data[(x + y * pixels_per_line * channels) * 2];
+ col |=
+ calibration_data[(x + y * pixels_per_line * channels) * 2 +
+ 1] << 8;
+
+ if (col >= white)
+ {
+ white_sum += col;
+ white_count++;
+ }
+ if (col <= dark)
+ {
+ dark_sum += col;
+ dark_count++;
+ }
+
+ }
+
+ dark_sum /= dark_count;
+ white_sum /= white_count;
+
+ *average_dark++ = dark_sum;
+ *average_white++ = white_sum;
+ }
+
+ if (DBG_LEVEL >= DBG_data) {
+ sanei_genesys_write_pnm_file16("gl_white_average.pnm", dev->white_average_data.data(),
+ channels, out_pixels_per_line, 1);
+ sanei_genesys_write_pnm_file16("gl_dark_average.pnm", dev->dark_average_data.data(),
+ channels, out_pixels_per_line, 1);
+ }
+}
+
+/* computes one coefficient given bright-dark value
+ * @param coeff factor giving 1.00 gain
+ * @param target desired target code
+ * @param value brght-dark value
+ * */
+static unsigned int
+compute_coefficient (unsigned int coeff, unsigned int target, unsigned int value)
+{
+ int result;
+
+ if (value > 0)
+ {
+ result = (coeff * target) / value;
+ if (result >= 65535)
+ {
+ result = 65535;
+ }
+ }
+ else
+ {
+ result = coeff;
+ }
+ return result;
+}
+
+/** @brief compute shading coefficients for LiDE scanners
+ * The dark/white shading is actually performed _after_ reducing
+ * resolution via averaging. only dark/white shading data for what would be
+ * first pixel at full resolution is used.
+ *
+ * scanner raw input to output value calculation:
+ * o=(i-off)*(gain/coeff)
+ *
+ * from datasheet:
+ * off=dark_average
+ * gain=coeff*bright_target/(bright_average-dark_average)
+ * works for dark_target==0
+ *
+ * what we want is these:
+ * bright_target=(bright_average-off)*(gain/coeff)
+ * dark_target=(dark_average-off)*(gain/coeff)
+ * leading to
+ * off = (dark_average*bright_target - bright_average*dark_target)/(bright_target - dark_target)
+ * gain = (bright_target - dark_target)/(bright_average - dark_average)*coeff
+ *
+ * @param dev scanner's device
+ * @param shading_data memory area where to store the computed shading coefficients
+ * @param pixels_per_line number of pixels per line
+ * @param words_per_color memory words per color channel
+ * @param channels number of color channels (actually 1 or 3)
+ * @param o shading coefficients left offset
+ * @param coeff 4000h or 2000h depending on fast scan mode or not (GAIN4 bit)
+ * @param target_bright value of the white target code
+ * @param target_dark value of the black target code
+*/
+static void
+compute_averaged_planar (Genesys_Device * dev, const Genesys_Sensor& sensor,
+ uint8_t * shading_data,
+ unsigned int pixels_per_line,
+ unsigned int words_per_color,
+ unsigned int channels,
+ unsigned int o,
+ unsigned int coeff,
+ unsigned int target_bright,
+ unsigned int target_dark)
+{
+ unsigned int x, i, j, br, dk, res, avgpixels, basepixels, val;
+ unsigned int fill,factor;
+
+ DBG(DBG_info, "%s: pixels=%d, offset=%d\n", __func__, pixels_per_line, o);
+
+ /* initialize result */
+ memset (shading_data, 0xff, words_per_color * 3 * 2);
+
+ /*
+ strangely i can write 0x20000 bytes beginning at 0x00000 without overwriting
+ slope tables - which begin at address 0x10000(for 1200dpi hw mode):
+ memory is organized in words(2 bytes) instead of single bytes. explains
+ quite some things
+ */
+/*
+ another one: the dark/white shading is actually performed _after_ reducing
+ resolution via averaging. only dark/white shading data for what would be
+ first pixel at full resolution is used.
+ */
+/*
+ scanner raw input to output value calculation:
+ o=(i-off)*(gain/coeff)
+
+ from datasheet:
+ off=dark_average
+ gain=coeff*bright_target/(bright_average-dark_average)
+ works for dark_target==0
+
+ what we want is these:
+ bright_target=(bright_average-off)*(gain/coeff)
+ dark_target=(dark_average-off)*(gain/coeff)
+ leading to
+ off = (dark_average*bright_target - bright_average*dark_target)/(bright_target - dark_target)
+ gain = (bright_target - dark_target)/(bright_average - dark_average)*coeff
+ */
+ res = dev->settings.xres;
+
+ if (sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres) > 1)
+ {
+ res *= 2;
+ }
+
+ /* this should be evenly dividable */
+ basepixels = sensor.optical_res / res;
+
+ /* gl841 supports 1/1 1/2 1/3 1/4 1/5 1/6 1/8 1/10 1/12 1/15 averaging */
+ if (basepixels < 1)
+ avgpixels = 1;
+ else if (basepixels < 6)
+ avgpixels = basepixels;
+ else if (basepixels < 8)
+ avgpixels = 6;
+ else if (basepixels < 10)
+ avgpixels = 8;
+ else if (basepixels < 12)
+ avgpixels = 10;
+ else if (basepixels < 15)
+ avgpixels = 12;
+ else
+ avgpixels = 15;
+
+ /* LiDE80 packs shading data */
+ if (dev->model->sensor_id != SensorId::CIS_CANON_LIDE_80) {
+ factor=1;
+ fill=avgpixels;
+ }
+ else
+ {
+ factor=avgpixels;
+ fill=1;
+ }
+
+ DBG(DBG_info, "%s: averaging over %d pixels\n", __func__, avgpixels);
+ DBG(DBG_info, "%s: packing factor is %d\n", __func__, factor);
+ DBG(DBG_info, "%s: fill length is %d\n", __func__, fill);
+
+ for (x = 0; x <= pixels_per_line - avgpixels; x += avgpixels)
+ {
+ if ((x + o) * 2 * 2 + 3 > words_per_color * 2)
+ break;
+
+ for (j = 0; j < channels; j++)
+ {
+
+ dk = 0;
+ br = 0;
+ for (i = 0; i < avgpixels; i++)
+ {
+ // dark data
+ dk += dev->dark_average_data[(x + i + pixels_per_line * j)];
+ // white data
+ br += dev->white_average_data[(x + i + pixels_per_line * j)];
+ }
+
+ br /= avgpixels;
+ dk /= avgpixels;
+
+ if (br * target_dark > dk * target_bright)
+ val = 0;
+ else if (dk * target_bright - br * target_dark >
+ 65535 * (target_bright - target_dark))
+ val = 65535;
+ else
+ {
+ val = (dk * target_bright - br * target_dark) / (target_bright - target_dark);
+ }
+
+ /*fill all pixels, even if only the last one is relevant*/
+ for (i = 0; i < fill; i++)
+ {
+ shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j] = val & 0xff;
+ shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 1] = val >> 8;
+ }
+
+ val = br - dk;
+
+ if (65535 * val > (target_bright - target_dark) * coeff)
+ {
+ val = (coeff * (target_bright - target_dark)) / val;
+ }
+ else
+ {
+ val = 65535;
+ }
+
+ /*fill all pixels, even if only the last one is relevant*/
+ for (i = 0; i < fill; i++)
+ {
+ shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 2] = val & 0xff;
+ shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 3] = val >> 8;
+ }
+ }
+
+ /* fill remaining channels */
+ for (j = channels; j < 3; j++)
+ {
+ for (i = 0; i < fill; i++)
+ {
+ shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j ] = shading_data[(x/factor + o + i) * 2 * 2 ];
+ shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 1] = shading_data[(x/factor + o + i) * 2 * 2 + 1];
+ shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 2] = shading_data[(x/factor + o + i) * 2 * 2 + 2];
+ shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 3] = shading_data[(x/factor + o + i) * 2 * 2 + 3];
+ }
+ }
+ }
+}
+
+static std::array<unsigned, 3> color_order_to_cmat(ColorOrder color_order)
+{
+ switch (color_order) {
+ case ColorOrder::RGB: return {0, 1, 2};
+ case ColorOrder::GBR: return {2, 0, 1};
+ default:
+ throw std::logic_error("Unknown color order");
+ }
+}
+
+/**
+ * Computes shading coefficient using formula in data sheet. 16bit data values
+ * manipulated here are little endian. For now we assume deletion scanning type
+ * and that there is always 3 channels.
+ * @param dev scanner's device
+ * @param shading_data memory area where to store the computed shading coefficients
+ * @param pixels_per_line number of pixels per line
+ * @param channels number of color channels (actually 1 or 3)
+ * @param cmat color transposition matrix
+ * @param offset shading coefficients left offset
+ * @param coeff 4000h or 2000h depending on fast scan mode or not
+ * @param target value of the target code
+ */
+static void compute_coefficients(Genesys_Device * dev,
+ uint8_t * shading_data,
+ unsigned int pixels_per_line,
+ unsigned int channels,
+ ColorOrder color_order,
+ int offset,
+ unsigned int coeff,
+ unsigned int target)
+{
+ uint8_t *ptr; /* contain 16bit words in little endian */
+ unsigned int x, c;
+ unsigned int val, br, dk;
+ unsigned int start, end;
+
+ DBG(DBG_io, "%s: pixels_per_line=%d, coeff=0x%04x\n", __func__, pixels_per_line, coeff);
+
+ auto cmat = color_order_to_cmat(color_order);
+
+ /* compute start & end values depending of the offset */
+ if (offset < 0)
+ {
+ start = -1 * offset;
+ end = pixels_per_line;
+ }
+ else
+ {
+ start = 0;
+ end = pixels_per_line - offset;
+ }
+
+ for (c = 0; c < channels; c++)
+ {
+ for (x = start; x < end; x++)
+ {
+ /* TODO if channels=1 , use filter to know the base addr */
+ ptr = shading_data + 4 * ((x + offset) * channels + cmat[c]);
+
+ // dark data
+ dk = dev->dark_average_data[x * channels + c];
+
+ // white data
+ br = dev->white_average_data[x * channels + c];
+
+ /* compute coeff */
+ val=compute_coefficient(coeff,target,br-dk);
+
+ /* assign it */
+ ptr[0] = dk & 255;
+ ptr[1] = dk / 256;
+ ptr[2] = val & 0xff;
+ ptr[3] = val / 256;
+
+ }
+ }
+}
+
+/**
+ * Computes shading coefficient using formula in data sheet. 16bit data values
+ * manipulated here are little endian. Data is in planar form, ie grouped by
+ * lines of the same color component.
+ * @param dev scanner's device
+ * @param shading_data memory area where to store the computed shading coefficients
+ * @param factor averaging factor when the calibration scan is done at a higher resolution
+ * than the final scan
+ * @param pixels_per_line number of pixels per line
+ * @param words_per_color total number of shading data words for one color element
+ * @param channels number of color channels (actually 1 or 3)
+ * @param cmat transcoding matrix for color channel order
+ * @param offset shading coefficients left offset
+ * @param coeff 4000h or 2000h depending on fast scan mode or not
+ * @param target white target value
+ */
+static void compute_planar_coefficients(Genesys_Device * dev,
+ uint8_t * shading_data,
+ unsigned int factor,
+ unsigned int pixels_per_line,
+ unsigned int words_per_color,
+ unsigned int channels,
+ ColorOrder color_order,
+ unsigned int offset,
+ unsigned int coeff,
+ unsigned int target)
+{
+ uint8_t *ptr; /* contains 16bit words in little endian */
+ uint32_t x, c, i;
+ uint32_t val, dk, br;
+
+ auto cmat = color_order_to_cmat(color_order);
+
+ DBG(DBG_io, "%s: factor=%d, pixels_per_line=%d, words=0x%X, coeff=0x%04x\n", __func__, factor,
+ pixels_per_line, words_per_color, coeff);
+ for (c = 0; c < channels; c++)
+ {
+ /* shading data is larger than pixels_per_line so offset can be neglected */
+ for (x = 0; x < pixels_per_line; x+=factor)
+ {
+ /* x2 because of 16 bit values, and x2 since one coeff for dark
+ * and another for white */
+ ptr = shading_data + words_per_color * cmat[c] * 2 + (x + offset) * 4;
+
+ dk = 0;
+ br = 0;
+
+ /* average case */
+ for(i=0;i<factor;i++)
+ {
+ dk += dev->dark_average_data[((x+i) + pixels_per_line * c)];
+ br += dev->white_average_data[((x+i) + pixels_per_line * c)];
+ }
+ dk /= factor;
+ br /= factor;
+
+ val = compute_coefficient (coeff, target, br - dk);
+
+ /* we duplicate the information to have calibration data at optical resolution */
+ for (i = 0; i < factor; i++)
+ {
+ ptr[0 + 4 * i] = dk & 255;
+ ptr[1 + 4 * i] = dk / 256;
+ ptr[2 + 4 * i] = val & 0xff;
+ ptr[3 + 4 * i] = val / 256;
+ }
+ }
+ }
+ /* in case of gray level scan, we duplicate shading information on all
+ * three color channels */
+ if(channels==1)
+ {
+ memcpy(shading_data+cmat[1]*2*words_per_color,
+ shading_data+cmat[0]*2*words_per_color,
+ words_per_color*2);
+ memcpy(shading_data+cmat[2]*2*words_per_color,
+ shading_data+cmat[0]*2*words_per_color,
+ words_per_color*2);
+ }
+}
+
+static void
+compute_shifted_coefficients (Genesys_Device * dev,
+ const Genesys_Sensor& sensor,
+ uint8_t * shading_data,
+ unsigned int pixels_per_line,
+ unsigned int channels,
+ ColorOrder color_order,
+ int offset,
+ unsigned int coeff,
+ unsigned int target_dark,
+ unsigned int target_bright,
+ unsigned int patch_size) /* contigous extent */
+{
+ unsigned int x, avgpixels, basepixels, i, j, val1, val2;
+ unsigned int br_tmp [3], dk_tmp [3];
+ uint8_t *ptr = shading_data + offset * 3 * 4; /* contain 16bit words in little endian */
+ unsigned int patch_cnt = offset * 3; /* at start, offset of first patch */
+
+ auto cmat = color_order_to_cmat(color_order);
+
+ x = dev->settings.xres;
+ if (sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres) > 1)
+ x *= 2; /* scanner is using half-ccd mode */
+ basepixels = sensor.optical_res / x; /*this should be evenly dividable */
+
+ /* gl841 supports 1/1 1/2 1/3 1/4 1/5 1/6 1/8 1/10 1/12 1/15 averaging */
+ if (basepixels < 1)
+ avgpixels = 1;
+ else if (basepixels < 6)
+ avgpixels = basepixels;
+ else if (basepixels < 8)
+ avgpixels = 6;
+ else if (basepixels < 10)
+ avgpixels = 8;
+ else if (basepixels < 12)
+ avgpixels = 10;
+ else if (basepixels < 15)
+ avgpixels = 12;
+ else
+ avgpixels = 15;
+ DBG(DBG_info, "%s: pixels_per_line=%d, coeff=0x%04x, averaging over %d pixels\n", __func__,
+ pixels_per_line, coeff, avgpixels);
+
+ for (x = 0; x <= pixels_per_line - avgpixels; x += avgpixels) {
+ memset (&br_tmp, 0, sizeof(br_tmp));
+ memset (&dk_tmp, 0, sizeof(dk_tmp));
+
+ for (i = 0; i < avgpixels; i++) {
+ for (j = 0; j < channels; j++) {
+ br_tmp[j] += dev->white_average_data[((x + i) * channels + j)];
+ dk_tmp[i] += dev->dark_average_data[((x + i) * channels + j)];
+ }
+ }
+ for (j = 0; j < channels; j++) {
+ br_tmp[j] /= avgpixels;
+ dk_tmp[j] /= avgpixels;
+
+ if (br_tmp[j] * target_dark > dk_tmp[j] * target_bright)
+ val1 = 0;
+ else if (dk_tmp[j] * target_bright - br_tmp[j] * target_dark > 65535 * (target_bright - target_dark))
+ val1 = 65535;
+ else
+ val1 = (dk_tmp[j] * target_bright - br_tmp[j] * target_dark) / (target_bright - target_dark);
+
+ val2 = br_tmp[j] - dk_tmp[j];
+ if (65535 * val2 > (target_bright - target_dark) * coeff)
+ val2 = (coeff * (target_bright - target_dark)) / val2;
+ else
+ val2 = 65535;
+
+ br_tmp[j] = val1;
+ dk_tmp[j] = val2;
+ }
+ for (i = 0; i < avgpixels; i++) {
+ for (j = 0; j < channels; j++) {
+ * ptr++ = br_tmp[ cmat[j] ] & 0xff;
+ * ptr++ = br_tmp[ cmat[j] ] >> 8;
+ * ptr++ = dk_tmp[ cmat[j] ] & 0xff;
+ * ptr++ = dk_tmp[ cmat[j] ] >> 8;
+ patch_cnt++;
+ if (patch_cnt == patch_size) {
+ patch_cnt = 0;
+ val1 = cmat[2];
+ cmat[2] = cmat[1];
+ cmat[1] = cmat[0];
+ cmat[0] = val1;
+ }
+ }
+ }
+ }
+}
+
+static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+
+ if (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) {
+ return;
+ }
+
+ uint32_t pixels_per_line;
+ uint8_t channels;
+ int o;
+ unsigned int length; /**> number of shading calibration data words */
+ unsigned int factor;
+ unsigned int coeff, target_code, words_per_color = 0;
+
+ pixels_per_line = dev->calib_pixels + dev->calib_pixels_offset;
+ channels = dev->calib_channels;
+
+ /* we always build data for three channels, even for gray
+ * we make the shading data such that each color channel data line is contiguous
+ * to the next one, which allow to write the 3 channels in 1 write
+ * during genesys_send_shading_coefficient, some values are words, other bytes
+ * hence the x2 factor */
+ switch (dev->reg.get8(0x05) >> 6)
+ {
+ /* 600 dpi */
+ case 0:
+ words_per_color = 0x2a00;
+ break;
+ /* 1200 dpi */
+ case 1:
+ words_per_color = 0x5500;
+ break;
+ /* 2400 dpi */
+ case 2:
+ words_per_color = 0xa800;
+ break;
+ /* 4800 dpi */
+ case 3:
+ words_per_color = 0x15000;
+ break;
+ }
+
+ /* special case, memory is aligned on 0x5400, this has yet to be explained */
+ /* could be 0xa800 because sensor is truly 2400 dpi, then halved because
+ * we only set 1200 dpi */
+ if(dev->model->sensor_id==SensorId::CIS_CANON_LIDE_80)
+ {
+ words_per_color = 0x5400;
+ }
+
+ length = words_per_color * 3 * 2;
+
+ /* allocate computed size */
+ // contains 16bit words in little endian
+ std::vector<uint8_t> shading_data(length, 0);
+
+ /* TARGET/(Wn-Dn) = white gain -> ~1.xxx then it is multiplied by 0x2000
+ or 0x4000 to give an integer
+ Wn = white average for column n
+ Dn = dark average for column n
+ */
+ if (get_registers_gain4_bit(dev->model->asic_type, dev->calib_reg)) {
+ coeff = 0x4000;
+ } else {
+ coeff = 0x2000;
+ }
+
+ /* compute avg factor */
+ if(dev->settings.xres>sensor.optical_res)
+ {
+ factor=1;
+ }
+ else
+ {
+ factor=sensor.optical_res/dev->settings.xres;
+ }
+
+ /* for GL646, shading data is planar if REG_0x01_FASTMOD is set and
+ * chunky if not. For now we rely on the fact that we know that
+ * each sensor is used only in one mode. Currently only the CIS_XP200
+ * sets REG_0x01_FASTMOD.
+ */
+
+ /* TODO setup a struct in genesys_devices that
+ * will handle these settings instead of having this switch growing up */
+ switch (dev->model->sensor_id)
+ {
+ case SensorId::CCD_XP300:
+ case SensorId::CCD_ROADWARRIOR:
+ case SensorId::CCD_DP665:
+ case SensorId::CCD_DP685:
+ case SensorId::CCD_DSMOBILE600:
+ target_code = 0xdc00;
+ o = 4;
+ compute_planar_coefficients (dev,
+ shading_data.data(),
+ factor,
+ pixels_per_line,
+ words_per_color,
+ channels,
+ ColorOrder::RGB,
+ o,
+ coeff,
+ target_code);
+ break;
+ case SensorId::CIS_XP200:
+ target_code = 0xdc00;
+ o = 2;
+ compute_planar_coefficients (dev,
+ shading_data.data(),
+ 1,
+ pixels_per_line,
+ words_per_color,
+ channels,
+ ColorOrder::GBR,
+ o,
+ coeff,
+ target_code);
+ break;
+ case SensorId::CCD_HP2300:
+ target_code = 0xdc00;
+ o = 2;
+ if(dev->settings.xres<=sensor.optical_res/2)
+ {
+ o = o - sensor.dummy_pixel / 2;
+ }
+ compute_coefficients (dev,
+ shading_data.data(),
+ pixels_per_line,
+ 3,
+ ColorOrder::RGB,
+ o,
+ coeff,
+ target_code);
+ break;
+ case SensorId::CCD_5345:
+ target_code = 0xe000;
+ o = 4;
+ if(dev->settings.xres<=sensor.optical_res/2)
+ {
+ o = o - sensor.dummy_pixel;
+ }
+ compute_coefficients (dev,
+ shading_data.data(),
+ pixels_per_line,
+ 3,
+ ColorOrder::RGB,
+ o,
+ coeff,
+ target_code);
+ break;
+ case SensorId::CCD_HP3670:
+ case SensorId::CCD_HP2400:
+ target_code = 0xe000;
+ // offset is dependent on ccd_pixels_per_system_pixel(), but we couldn't use this in
+ // common code previously.
+ // FIXME: use sensor.ccd_pixels_per_system_pixel()
+ if(dev->settings.xres<=300)
+ {
+ o = -10;
+ }
+ else if(dev->settings.xres<=600)
+ {
+ o = -6;
+ }
+ else
+ {
+ o = +2;
+ }
+ compute_coefficients (dev,
+ shading_data.data(),
+ pixels_per_line,
+ 3,
+ ColorOrder::RGB,
+ o,
+ coeff,
+ target_code);
+ break;
+ case SensorId::CCD_KVSS080:
+ case SensorId::CCD_PLUSTEK_OPTICBOOK_3800:
+ case SensorId::CCD_G4050:
+ case SensorId::CCD_HP_4850C:
+ case SensorId::CCD_CANON_4400F:
+ case SensorId::CCD_CANON_8400F:
+ case SensorId::CCD_CANON_8600F:
+ case SensorId::CCD_PLUSTEK_OPTICFILM_7200I:
+ case SensorId::CCD_PLUSTEK_OPTICFILM_7300:
+ case SensorId::CCD_PLUSTEK_OPTICFILM_7500I:
+ target_code = 0xe000;
+ o = 0;
+ compute_coefficients (dev,
+ shading_data.data(),
+ pixels_per_line,
+ 3,
+ ColorOrder::RGB,
+ o,
+ coeff,
+ target_code);
+ break;
+ case SensorId::CIS_CANON_LIDE_700F:
+ case SensorId::CIS_CANON_LIDE_100:
+ case SensorId::CIS_CANON_LIDE_200:
+ case SensorId::CIS_CANON_LIDE_110:
+ case SensorId::CIS_CANON_LIDE_120:
+ case SensorId::CIS_CANON_LIDE_210:
+ case SensorId::CIS_CANON_LIDE_220:
+ /* TODO store this in a data struct so we avoid
+ * growing this switch */
+ switch(dev->model->sensor_id)
+ {
+ case SensorId::CIS_CANON_LIDE_110:
+ case SensorId::CIS_CANON_LIDE_120:
+ case SensorId::CIS_CANON_LIDE_210:
+ case SensorId::CIS_CANON_LIDE_220:
+ case SensorId::CIS_CANON_LIDE_700F:
+ target_code = 0xc000;
+ break;
+ default:
+ target_code = 0xdc00;
+ }
+ words_per_color=pixels_per_line*2;
+ length = words_per_color * 3 * 2;
+ shading_data.clear();
+ shading_data.resize(length, 0);
+ compute_planar_coefficients (dev,
+ shading_data.data(),
+ 1,
+ pixels_per_line,
+ words_per_color,
+ channels,
+ ColorOrder::RGB,
+ 0,
+ coeff,
+ target_code);
+ break;
+ case SensorId::CIS_CANON_LIDE_35:
+ compute_averaged_planar (dev, sensor,
+ shading_data.data(),
+ pixels_per_line,
+ words_per_color,
+ channels,
+ 4,
+ coeff,
+ 0xe000,
+ 0x0a00);
+ break;
+ case SensorId::CIS_CANON_LIDE_80:
+ compute_averaged_planar (dev, sensor,
+ shading_data.data(),
+ pixels_per_line,
+ words_per_color,
+ channels,
+ 0,
+ coeff,
+ 0xe000,
+ 0x0800);
+ break;
+ case SensorId::CCD_PLUSTEK_OPTICPRO_3600:
+ compute_shifted_coefficients (dev, sensor,
+ shading_data.data(),
+ pixels_per_line,
+ channels,
+ ColorOrder::RGB,
+ 12, /* offset */
+ coeff,
+ 0x0001, /* target_dark */
+ 0xf900, /* target_bright */
+ 256); /* patch_size: contigous extent */
+ break;
+ default:
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "sensor %d not supported",
+ static_cast<unsigned>(dev->model->sensor_id));
+ break;
+ }
+
+ // do the actual write of shading calibration data to the scanner
+ genesys_send_offset_and_shading(dev, sensor, shading_data.data(), length);
+}
+
+
+/**
+ * search calibration cache list for an entry matching required scan.
+ * If one is found, set device calibration with it
+ * @param dev scanner's device
+ * @return false if no matching cache entry has been
+ * found, true if one has been found and used.
+ */
+static bool
+genesys_restore_calibration(Genesys_Device * dev, Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+
+ // if no cache or no function to evaluate cache entry ther can be no match/
+ if (dev->calibration_cache.empty()) {
+ return false;
+ }
+
+ auto session = dev->cmd_set->calculate_scan_session(dev, sensor, dev->settings);
+
+ /* we walk the link list of calibration cache in search for a
+ * matching one */
+ for (auto& cache : dev->calibration_cache)
+ {
+ if (sanei_genesys_is_compatible_calibration(dev, session, &cache, false)) {
+ dev->frontend = cache.frontend;
+ /* we don't restore the gamma fields */
+ sensor.exposure = cache.sensor.exposure;
+
+ dev->average_size = cache.average_size;
+ dev->calib_pixels = cache.calib_pixels;
+ dev->calib_channels = cache.calib_channels;
+
+ dev->dark_average_data = cache.dark_average_data;
+ dev->white_average_data = cache.white_average_data;
+
+ if (!dev->cmd_set->has_send_shading_data()) {
+ genesys_send_shading_coefficient(dev, sensor);
+ }
+
+ DBG(DBG_proc, "%s: restored\n", __func__);
+ return true;
+ }
+ }
+ DBG(DBG_proc, "%s: completed(nothing found)\n", __func__);
+ return false;
+}
+
+
+static void genesys_save_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+#ifdef HAVE_SYS_TIME_H
+ struct timeval time;
+#endif
+
+ auto session = dev->cmd_set->calculate_scan_session(dev, sensor, dev->settings);
+
+ auto found_cache_it = dev->calibration_cache.end();
+ for (auto cache_it = dev->calibration_cache.begin(); cache_it != dev->calibration_cache.end();
+ cache_it++)
+ {
+ if (sanei_genesys_is_compatible_calibration(dev, session, &*cache_it, true)) {
+ found_cache_it = cache_it;
+ break;
+ }
+ }
+
+ /* if we found on overridable cache, we reuse it */
+ if (found_cache_it == dev->calibration_cache.end())
+ {
+ /* create a new cache entry and insert it in the linked list */
+ dev->calibration_cache.push_back(Genesys_Calibration_Cache());
+ found_cache_it = std::prev(dev->calibration_cache.end());
+ }
+
+ found_cache_it->average_size = dev->average_size;
+
+ found_cache_it->dark_average_data = dev->dark_average_data;
+ found_cache_it->white_average_data = dev->white_average_data;
+
+ found_cache_it->params = session.params;
+ found_cache_it->frontend = dev->frontend;
+ found_cache_it->sensor = sensor;
+
+ found_cache_it->calib_pixels = dev->calib_pixels;
+ found_cache_it->calib_channels = dev->calib_channels;
+
+#ifdef HAVE_SYS_TIME_H
+ gettimeofday(&time, nullptr);
+ found_cache_it->last_calibration = time.tv_sec;
+#endif
+}
+
+/**
+ * does the calibration process for a flatbed scanner
+ * - offset calibration
+ * - gain calibration
+ * - shading calibration
+ * @param dev device to calibrate
+ */
+static void genesys_flatbed_calibration(Genesys_Device* dev, Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+ uint32_t pixels_per_line;
+
+ unsigned coarse_res = sensor.optical_res;
+ if (dev->settings.yres <= sensor.optical_res / 2) {
+ coarse_res /= 2;
+ }
+
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ coarse_res = 1600;
+ }
+
+ if (dev->model->model_id == ModelId::CANON_4400F ||
+ dev->model->model_id == ModelId::CANON_8600F)
+ {
+ coarse_res = 1200;
+ }
+
+ /* do offset calibration if needed */
+ if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION)
+ {
+ dev->interface->record_progress_message("offset_calibration");
+ dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg);
+
+ /* since all the registers are set up correctly, just use them */
+ dev->interface->record_progress_message("coarse_gain_calibration");
+ dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, coarse_res);
+ } else {
+ /* since we have 2 gain calibration proc, skip second if first one was
+ used. */
+ dev->interface->record_progress_message("init_regs_for_coarse_calibration");
+ dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg);
+
+ dev->interface->record_progress_message("genesys_coarse_calibration");
+ genesys_coarse_calibration(dev, sensor);
+ }
+
+ if (dev->model->is_cis)
+ {
+ /* the afe now sends valid data for doing led calibration */
+ dev->interface->record_progress_message("led_calibration");
+ switch (dev->model->asic_type) {
+ case AsicType::GL124:
+ case AsicType::GL845:
+ case AsicType::GL846:
+ case AsicType::GL847: {
+ auto calib_exposure = dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg);
+ for (auto& sensor_update :
+ sanei_genesys_find_sensors_all_for_write(dev, sensor.method)) {
+ sensor_update.get().exposure = calib_exposure;
+ }
+ sensor.exposure = calib_exposure;
+ break;
+ }
+ default: {
+ sensor.exposure = dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg);
+ }
+ }
+
+
+ /* calibrate afe again to match new exposure */
+ if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION) {
+ dev->interface->record_progress_message("offset_calibration");
+ dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg);
+
+ // since all the registers are set up correctly, just use them
+
+ dev->interface->record_progress_message("coarse_gain_calibration");
+ dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, coarse_res);
+ } else {
+ // since we have 2 gain calibration proc, skip second if first one was used
+ dev->interface->record_progress_message("init_regs_for_coarse_calibration");
+ dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg);
+
+ dev->interface->record_progress_message("genesys_coarse_calibration");
+ genesys_coarse_calibration(dev, sensor);
+ }
+ }
+
+ /* we always use sensor pixel number when the ASIC can't handle multi-segments sensor */
+ if (!(dev->model->flags & GENESYS_FLAG_SIS_SENSOR))
+ {
+ pixels_per_line = static_cast<std::uint32_t>((dev->model->x_size * dev->settings.xres) /
+ MM_PER_INCH);
+ }
+ else
+ {
+ pixels_per_line = sensor.sensor_pixels;
+ }
+
+ // send default shading data
+ dev->interface->record_progress_message("sanei_genesys_init_shading_data");
+ sanei_genesys_init_shading_data(dev, sensor, pixels_per_line);
+
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ dev->cmd_set->move_to_ta(dev);
+ }
+
+ // shading calibration
+ if (dev->model->flags & GENESYS_FLAG_DARK_WHITE_CALIBRATION) {
+ dev->interface->record_progress_message("init_regs_for_shading");
+ dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg);
+
+ dev->interface->record_progress_message("genesys_dark_white_shading_calibration");
+ genesys_dark_white_shading_calibration(dev, sensor);
+ } else {
+ DBG(DBG_proc, "%s : genesys_dark_shading_calibration dev->calib_reg ", __func__);
+ debug_dump(DBG_proc, dev->calib_reg);
+
+ if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION) {
+ dev->interface->record_progress_message("init_regs_for_shading");
+ dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg);
+
+ dev->interface->record_progress_message("genesys_dark_shading_calibration");
+ genesys_dark_shading_calibration(dev, sensor);
+ genesys_repark_sensor_before_shading(dev);
+ }
+
+ dev->interface->record_progress_message("init_regs_for_shading2");
+ dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg);
+
+ dev->interface->record_progress_message("genesys_white_shading_calibration");
+ genesys_white_shading_calibration(dev, sensor);
+ genesys_repark_sensor_after_white_shading(dev);
+
+ if (!(dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)) {
+ genesys_dummy_dark_shading(dev, sensor);
+ }
+ }
+
+ if (!dev->cmd_set->has_send_shading_data()) {
+ dev->interface->record_progress_message("genesys_send_shading_coefficient");
+ genesys_send_shading_coefficient(dev, sensor);
+ }
+}
+
+/**
+ * Does the calibration process for a sheetfed scanner
+ * - offset calibration
+ * - gain calibration
+ * - shading calibration
+ * During calibration a predefined calibration sheet with specific black and white
+ * areas is used.
+ * @param dev device to calibrate
+ */
+static void genesys_sheetfed_calibration(Genesys_Device* dev, Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+ bool forward = true;
+
+ // first step, load document
+ dev->cmd_set->load_document(dev);
+
+ /* led, offset and gain calibration are influenced by scan
+ * settings. So we set it to sensor resolution */
+ dev->settings.xres = sensor.optical_res;
+ /* XP200 needs to calibrate a full and half sensor's resolution */
+ if (dev->model->sensor_id == SensorId::CIS_XP200 &&
+ dev->settings.xres <= sensor.optical_res / 2)
+ {
+ dev->settings.xres /= 2;
+ }
+
+ /* the afe needs to sends valid data even before calibration */
+
+ /* go to a white area */
+ try {
+ dev->cmd_set->search_strip(dev, sensor, forward, false);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
+ throw;
+ }
+
+ if (dev->model->is_cis)
+ {
+ dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg);
+ }
+
+ /* calibrate afe */
+ if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION)
+ {
+ dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg);
+
+ /* since all the registers are set up correctly, just use them */
+
+ dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, sensor.optical_res);
+ }
+ else
+ /* since we have 2 gain calibration proc, skip second if first one was
+ used. */
+ {
+ dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg);
+
+ genesys_coarse_calibration(dev, sensor);
+ }
+
+ /* search for a full width black strip and then do a 16 bit scan to
+ * gather black shading data */
+ if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)
+ {
+ /* seek black/white reverse/forward */
+ try {
+ dev->cmd_set->search_strip(dev, sensor, forward, true);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
+ throw;
+ }
+
+ dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg);
+
+ try {
+ genesys_dark_shading_calibration(dev, sensor);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
+ throw;
+ }
+ forward = false;
+ }
+
+
+ /* go to a white area */
+ try {
+ dev->cmd_set->search_strip(dev, sensor, forward, false);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
+ throw;
+ }
+
+ genesys_repark_sensor_before_shading(dev);
+
+ dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg);
+
+ try {
+ genesys_white_shading_calibration(dev, sensor);
+ genesys_repark_sensor_after_white_shading(dev);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
+ throw;
+ }
+
+ // in case we haven't black shading data, build it from black pixels of white calibration
+ // FIXME: shouldn't we use genesys_dummy_dark_shading() ?
+ if (!(dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)) {
+ dev->dark_average_data.clear();
+ dev->dark_average_data.resize(dev->average_size, 0x0f0f);
+ /* XXX STEF XXX
+ * with black point in white shading, build an average black
+ * pixel and use it to fill the dark_average
+ * dev->calib_pixels
+ (sensor.sensor_pixels * dev->settings.xres) / sensor.optical_res,
+ dev->calib_lines,
+ */
+ }
+
+ /* send the shading coefficient when doing whole line shading
+ * but not when using SHDAREA like GL124 */
+ if (!dev->cmd_set->has_send_shading_data()) {
+ genesys_send_shading_coefficient(dev, sensor);
+ }
+
+ // save the calibration data
+ genesys_save_calibration(dev, sensor);
+
+ // and finally eject calibration sheet
+ dev->cmd_set->eject_document(dev);
+
+ // restore settings
+ dev->settings.xres = sensor.optical_res;
+}
+
+/**
+ * does the calibration process for a device
+ * @param dev device to calibrate
+ */
+static void genesys_scanner_calibration(Genesys_Device* dev, Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+ if (!dev->model->is_sheetfed) {
+ genesys_flatbed_calibration(dev, sensor);
+ return;
+ }
+ genesys_sheetfed_calibration(dev, sensor);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/* High level (exported) functions */
+/* ------------------------------------------------------------------------ */
+
+/*
+ * wait lamp to be warm enough by scanning the same line until
+ * differences between two scans are below a threshold
+ */
+static void genesys_warmup_lamp(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ unsigned seconds = 0;
+ int pixel;
+ int channels, total_size;
+ double first_average = 0;
+ double second_average = 0;
+ int difference = 255;
+ int lines = 3;
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+
+ dev->cmd_set->init_regs_for_warmup(dev, sensor, &dev->reg, &channels, &total_size);
+ std::vector<uint8_t> first_line(total_size);
+ std::vector<uint8_t> second_line(total_size);
+
+ do
+ {
+ DBG(DBG_info, "%s: one more loop\n", __func__);
+ dev->cmd_set->begin_scan(dev, sensor, &dev->reg, false);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("warmup_lamp");
+ dev->cmd_set->end_scan(dev, &dev->reg, true);
+ return;
+ }
+
+ wait_until_buffer_non_empty(dev);
+
+ try {
+ sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size);
+ } catch (...) {
+ // FIXME: document why this retry is here
+ sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size);
+ }
+
+ dev->cmd_set->end_scan(dev, &dev->reg, true);
+
+ dev->interface->sleep_ms(1000);
+ seconds++;
+
+ dev->cmd_set->begin_scan(dev, sensor, &dev->reg, false);
+
+ wait_until_buffer_non_empty(dev);
+
+ sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size);
+ dev->cmd_set->end_scan(dev, &dev->reg, true);
+
+ /* compute difference between the two scans */
+ for (pixel = 0; pixel < total_size; pixel++)
+ {
+ // 16 bit data
+ if (dev->session.params.depth == 16) {
+ first_average += (first_line[pixel] + first_line[pixel + 1] * 256);
+ second_average += (second_line[pixel] + second_line[pixel + 1] * 256);
+ pixel++;
+ }
+ else
+ {
+ first_average += first_line[pixel];
+ second_average += second_line[pixel];
+ }
+ }
+ if (dev->session.params.depth == 16) {
+ first_average /= pixel;
+ second_average /= pixel;
+ difference = static_cast<int>(std::fabs(first_average - second_average));
+ DBG(DBG_info, "%s: average = %.2f, diff = %.3f\n", __func__,
+ 100 * ((second_average) / (256 * 256)),
+ 100 * (difference / second_average));
+
+ if (second_average > (100 * 256)
+ && (difference / second_average) < 0.002)
+ break;
+ }
+ else
+ {
+ first_average /= pixel;
+ second_average /= pixel;
+ if (DBG_LEVEL >= DBG_data)
+ {
+ sanei_genesys_write_pnm_file("gl_warmup1.pnm", first_line.data(), 8, channels,
+ total_size / (lines * channels), lines);
+ sanei_genesys_write_pnm_file("gl_warmup2.pnm", second_line.data(), 8, channels,
+ total_size / (lines * channels), lines);
+ }
+ DBG(DBG_info, "%s: average 1 = %.2f, average 2 = %.2f\n", __func__, first_average,
+ second_average);
+ /* if delta below 15/255 ~= 5.8%, lamp is considred warm enough */
+ if (fabs (first_average - second_average) < 15
+ && second_average > 55)
+ break;
+ }
+
+ /* sleep another second before next loop */
+ dev->interface->sleep_ms(1000);
+ seconds++;
+ } while (seconds < WARMUP_TIME);
+
+ if (seconds >= WARMUP_TIME)
+ {
+ throw SaneException(SANE_STATUS_IO_ERROR,
+ "warmup timed out after %d seconds. Lamp defective?", seconds);
+ }
+ else
+ {
+ DBG(DBG_info, "%s: warmup succeeded after %d seconds\n", __func__, seconds);
+ }
+}
+
+
+// High-level start of scanning
+static void genesys_start_scan(Genesys_Device* dev, bool lamp_off)
+{
+ DBG_HELPER(dbg);
+ unsigned int steps, expected;
+
+ /* since not all scanners are set ot wait for head to park
+ * we check we are not still parking before starting a new scan */
+ if (dev->parking) {
+ sanei_genesys_wait_for_home(dev);
+ }
+
+ // disable power saving
+ dev->cmd_set->save_power(dev, false);
+
+ /* wait for lamp warmup : until a warmup for TRANSPARENCY is designed, skip
+ * it when scanning from XPA. */
+ if (!(dev->model->flags & GENESYS_FLAG_SKIP_WARMUP)
+ && (dev->settings.scan_method == ScanMethod::FLATBED))
+ {
+ genesys_warmup_lamp(dev);
+ }
+
+ /* set top left x and y values by scanning the internals if flatbed scanners */
+ if (!dev->model->is_sheetfed) {
+ /* do the geometry detection only once */
+ if ((dev->model->flags & GENESYS_FLAG_SEARCH_START)
+ && (dev->model->y_offset_calib_white == 0))
+ {
+ dev->cmd_set->search_start_position (dev);
+
+ dev->parking = false;
+ dev->cmd_set->move_back_home(dev, true);
+ }
+ else
+ {
+ /* Go home */
+ /* TODO: check we can drop this since we cannot have the
+ scanner's head wandering here */
+ dev->parking = false;
+ dev->cmd_set->move_back_home(dev, true);
+ }
+ }
+
+ /* move to calibration area for transparency adapter */
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ dev->cmd_set->move_to_ta(dev);
+ }
+
+ /* load document if needed (for sheetfed scanner for instance) */
+ if (dev->model->is_sheetfed) {
+ dev->cmd_set->load_document(dev);
+ }
+
+ auto& sensor = sanei_genesys_find_sensor_for_write(dev, dev->settings.xres,
+ dev->settings.get_channels(),
+ dev->settings.scan_method);
+
+ // send gamma tables. They have been set to device or user value
+ // when setting option value */
+ dev->cmd_set->send_gamma_table(dev, sensor);
+
+ /* try to use cached calibration first */
+ if (!genesys_restore_calibration (dev, sensor))
+ {
+ /* calibration : sheetfed scanners can't calibrate before each scan */
+ /* and also those who have the NO_CALIBRATION flag */
+ if (!(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION) && !dev->model->is_sheetfed) {
+ genesys_scanner_calibration(dev, sensor);
+ genesys_save_calibration (dev, sensor);
+ }
+ else
+ {
+ DBG(DBG_warn, "%s: no calibration done\n", __func__);
+ }
+ }
+
+ /* build look up table for dynamic lineart */
+ if (dev->settings.scan_mode == ScanColorMode::LINEART) {
+ sanei_genesys_load_lut(dev->lineart_lut, 8, 8, 50, 205, dev->settings.threshold_curve,
+ dev->settings.threshold-127);
+ }
+
+ dev->cmd_set->wait_for_motor_stop(dev);
+
+ if (dev->cmd_set->needs_home_before_init_regs_for_scan(dev)) {
+ dev->cmd_set->move_back_home(dev, true);
+ }
+
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ dev->cmd_set->move_to_ta(dev);
+ }
+
+ dev->cmd_set->init_regs_for_scan(dev, sensor);
+
+ /* no lamp during scan */
+ if (lamp_off) {
+ sanei_genesys_set_lamp_power(dev, sensor, dev->reg, false);
+ }
+
+ /* GL124 is using SHDAREA, so we have to wait for scan to be set up before
+ * sending shading data */
+ if (dev->cmd_set->has_send_shading_data() &&
+ !(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION))
+ {
+ genesys_send_shading_coefficient(dev, sensor);
+ }
+
+ // now send registers for scan
+ dev->interface->write_registers(dev->reg);
+
+ // start effective scan
+ dev->cmd_set->begin_scan(dev, sensor, &dev->reg, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("start_scan");
+ return;
+ }
+
+ /*do we really need this? the valid data check should be sufficent -- pierre*/
+ /* waits for head to reach scanning position */
+ expected = dev->reg.get8(0x3d) * 65536
+ + dev->reg.get8(0x3e) * 256
+ + dev->reg.get8(0x3f);
+ do
+ {
+ // wait some time between each test to avoid overloading USB and CPU
+ dev->interface->sleep_ms(100);
+ sanei_genesys_read_feed_steps (dev, &steps);
+ }
+ while (steps < expected);
+
+ wait_until_buffer_non_empty(dev);
+
+ // we wait for at least one word of valid scan data
+ // this is also done in sanei_genesys_read_data_from_scanner -- pierre
+ if (!dev->model->is_sheetfed) {
+ do {
+ dev->interface->sleep_ms(100);
+ sanei_genesys_read_valid_words(dev, &steps);
+ }
+ while (steps < 1);
+ }
+}
+
+static void genesys_fill_read_buffer(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+
+ /* for sheetfed scanner, we must check is document is shorter than
+ * the requested scan */
+ if (dev->model->is_sheetfed) {
+ dev->cmd_set->detect_document_end(dev);
+ }
+
+ std::size_t size = dev->read_buffer.size() - dev->read_buffer.avail();
+
+ /* due to sensors and motors, not all data can be directly used. It
+ * may have to be read from another intermediate buffer and then processed.
+ * There are currently 3 intermediate stages:
+ * - handling of odd/even sensors
+ * - handling of line interpolation for motors that can't have low
+ * enough dpi
+ * - handling of multi-segments sensors
+ *
+ * This is also the place where full duplex data will be handled.
+ */
+ dev->pipeline_buffer.get_data(size, dev->read_buffer.get_write_pos(size));
+
+ dev->read_buffer.produce(size);
+}
+
+/* this function does the effective data read in a manner that suits
+ the scanner. It does data reordering and resizing if need.
+ It also manages EOF and I/O errors, and line distance correction.
+ Returns true on success, false on end-of-file.
+*/
+static void genesys_read_ordered_data(Genesys_Device* dev, SANE_Byte* destination, size_t* len)
+{
+ DBG_HELPER(dbg);
+ size_t bytes = 0;
+ uint8_t *work_buffer_src;
+ Genesys_Buffer *src_buffer;
+
+ if (!dev->read_active) {
+ *len = 0;
+ throw SaneException("read is not active");
+ }
+
+ DBG(DBG_info, "%s: frontend requested %zu bytes\n", __func__, *len);
+ DBG(DBG_info, "%s: bytes_to_read=%zu, total_bytes_read=%zu\n", __func__,
+ dev->total_bytes_to_read, dev->total_bytes_read);
+
+ /* is there data left to scan */
+ if (dev->total_bytes_read >= dev->total_bytes_to_read)
+ {
+ /* issue park command immediatly in case scanner can handle it
+ * so we save time */
+ if (!dev->model->is_sheetfed && !(dev->model->flags & GENESYS_FLAG_MUST_WAIT) &&
+ !dev->parking)
+ {
+ dev->cmd_set->move_back_home(dev, false);
+ dev->parking = true;
+ }
+ throw SaneException(SANE_STATUS_EOF, "nothing more to scan: EOF");
+ }
+
+/* convert data */
+/*
+ 0. fill_read_buffer
+-------------- read_buffer ----------------------
+ 1a). (opt)uncis (assumes color components to be laid out
+ planar)
+ 1b). (opt)reverse_RGB (assumes pixels to be BGR or BBGGRR))
+-------------- lines_buffer ----------------------
+ 2a). (opt)line_distance_correction (assumes RGB or RRGGBB)
+ 2b). (opt)unstagger (assumes pixels to be depth*channels/8
+ bytes long, unshrinked)
+------------- shrink_buffer ---------------------
+ 3. (opt)shrink_lines (assumes component separation in pixels)
+-------------- out_buffer -----------------------
+ 4. memcpy to destination (for lineart with bit reversal)
+*/
+/*FIXME: for lineart we need sub byte addressing in buffers, or conversion to
+ bytes at 0. and back to bits at 4.
+Problems with the first approach:
+ - its not clear how to check if we need to output an incomplete byte
+ because it is the last one.
+ */
+/*FIXME: add lineart support for gl646. in the meantime add logic to convert
+ from gray to lineart at the end? would suffer the above problem,
+ total_bytes_to_read and total_bytes_read help in that case.
+ */
+
+ if (is_testing_mode()) {
+ if (dev->total_bytes_read + *len > dev->total_bytes_to_read) {
+ *len = dev->total_bytes_to_read - dev->total_bytes_read;
+ }
+ dev->total_bytes_read += *len;
+ } else {
+ genesys_fill_read_buffer(dev);
+
+ src_buffer = &(dev->read_buffer);
+
+ /* move data to destination */
+ bytes = std::min(src_buffer->avail(), *len);
+
+ work_buffer_src = src_buffer->get_read_pos();
+
+ std::memcpy(destination, work_buffer_src, bytes);
+ *len = bytes;
+
+ /* avoid signaling some extra data because we have treated a full block
+ * on the last block */
+ if (dev->total_bytes_read + *len > dev->total_bytes_to_read) {
+ *len = dev->total_bytes_to_read - dev->total_bytes_read;
+ }
+
+ /* count bytes sent to frontend */
+ dev->total_bytes_read += *len;
+
+ src_buffer->consume(bytes);
+ }
+
+ /* end scan if all needed data have been read */
+ if(dev->total_bytes_read >= dev->total_bytes_to_read)
+ {
+ dev->cmd_set->end_scan(dev, &dev->reg, true);
+ if (dev->model->is_sheetfed) {
+ dev->cmd_set->eject_document (dev);
+ }
+ }
+
+ DBG(DBG_proc, "%s: completed, %zu bytes read\n", __func__, bytes);
+}
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Start of higher level functions */
+/* ------------------------------------------------------------------------ */
+
+static size_t
+max_string_size (const SANE_String_Const strings[])
+{
+ size_t size, max_size = 0;
+ SANE_Int i;
+
+ for (i = 0; strings[i]; ++i)
+ {
+ size = strlen (strings[i]) + 1;
+ if (size > max_size)
+ max_size = size;
+ }
+ return max_size;
+}
+
+static std::size_t max_string_size(const std::vector<const char*>& strings)
+{
+ std::size_t max_size = 0;
+ for (const auto& s : strings) {
+ if (!s) {
+ continue;
+ }
+ max_size = std::max(max_size, std::strlen(s));
+ }
+ return max_size;
+}
+
+static unsigned pick_resolution(const std::vector<unsigned>& resolutions, unsigned resolution,
+ const char* direction)
+{
+ DBG_HELPER(dbg);
+
+ if (resolutions.empty())
+ throw SaneException("Empty resolution list");
+
+ unsigned best_res = resolutions.front();
+ unsigned min_diff = abs_diff(best_res, resolution);
+
+ for (auto it = std::next(resolutions.begin()); it != resolutions.end(); ++it) {
+ unsigned curr_diff = abs_diff(*it, resolution);
+ if (curr_diff < min_diff) {
+ min_diff = curr_diff;
+ best_res = *it;
+ }
+ }
+
+ if (best_res != resolution) {
+ DBG(DBG_warn, "%s: using resolution %d that is nearest to %d for direction %s\n",
+ __func__, best_res, resolution, direction);
+ }
+ return best_res;
+}
+
+static void calc_parameters(Genesys_Scanner* s)
+{
+ DBG_HELPER(dbg);
+ double tl_x = 0, tl_y = 0, br_x = 0, br_y = 0;
+
+ tl_x = SANE_UNFIX(s->pos_top_left_x);
+ tl_y = SANE_UNFIX(s->pos_top_left_y);
+ br_x = SANE_UNFIX(s->pos_bottom_right_x);
+ br_y = SANE_UNFIX(s->pos_bottom_right_y);
+
+ s->params.last_frame = true; /* only single pass scanning supported */
+
+ if (s->mode == SANE_VALUE_SCAN_MODE_GRAY || s->mode == SANE_VALUE_SCAN_MODE_LINEART) {
+ s->params.format = SANE_FRAME_GRAY;
+ } else {
+ s->params.format = SANE_FRAME_RGB;
+ }
+
+ if (s->mode == SANE_VALUE_SCAN_MODE_LINEART) {
+ s->params.depth = 1;
+ } else {
+ s->params.depth = s->bit_depth;
+ }
+
+ s->dev->settings.scan_method = s->scan_method;
+ const auto& resolutions = s->dev->model->get_resolution_settings(s->dev->settings.scan_method);
+
+ s->dev->settings.depth = s->bit_depth;
+
+ /* interpolation */
+ s->dev->settings.disable_interpolation = s->disable_interpolation;
+
+ // FIXME: use correct sensor
+ const auto& sensor = sanei_genesys_find_sensor_any(s->dev);
+
+ // hardware settings
+ if (static_cast<unsigned>(s->resolution) > sensor.optical_res &&
+ s->dev->settings.disable_interpolation)
+ {
+ s->dev->settings.xres = sensor.optical_res;
+ } else {
+ s->dev->settings.xres = s->resolution;
+ }
+ s->dev->settings.yres = s->resolution;
+
+ s->dev->settings.xres = pick_resolution(resolutions.resolutions_x, s->dev->settings.xres, "X");
+ s->dev->settings.yres = pick_resolution(resolutions.resolutions_y, s->dev->settings.yres, "Y");
+
+ s->params.lines = static_cast<unsigned>(((br_y - tl_y) * s->dev->settings.yres) /
+ MM_PER_INCH);
+ unsigned pixels_per_line = static_cast<unsigned>(((br_x - tl_x) * s->dev->settings.xres) /
+ MM_PER_INCH);
+
+ /* we need an even pixels number
+ * TODO invert test logic or generalize behaviour across all ASICs */
+ if ((s->dev->model->flags & GENESYS_FLAG_SIS_SENSOR) ||
+ s->dev->model->asic_type == AsicType::GL847 ||
+ s->dev->model->asic_type == AsicType::GL124 ||
+ s->dev->model->asic_type == AsicType::GL845 ||
+ s->dev->model->asic_type == AsicType::GL846 ||
+ s->dev->model->asic_type == AsicType::GL843)
+ {
+ if (s->dev->settings.xres <= 1200) {
+ pixels_per_line = (pixels_per_line / 4) * 4;
+ } else if (s->dev->settings.xres < s->dev->settings.yres) {
+ // BUG: this is an artifact of the fact that the resolution was twice as large than
+ // the actual resolution when scanning above the supported scanner X resolution
+ pixels_per_line = (pixels_per_line / 8) * 8;
+ } else {
+ pixels_per_line = (pixels_per_line / 16) * 16;
+ }
+ }
+
+ /* corner case for true lineart for sensor with several segments
+ * or when xres is doubled to match yres */
+ if (s->dev->settings.xres >= 1200 && (
+ s->dev->model->asic_type == AsicType::GL124 ||
+ s->dev->model->asic_type == AsicType::GL847 ||
+ s->dev->session.params.xres < s->dev->session.params.yres))
+ {
+ if (s->dev->settings.xres < s->dev->settings.yres) {
+ // FIXME: this is an artifact of the fact that the resolution was twice as large than
+ // the actual resolution when scanning above the supported scanner X resolution
+ pixels_per_line = (pixels_per_line / 8) * 8;
+ } else {
+ pixels_per_line = (pixels_per_line / 16) * 16;
+ }
+ }
+
+ unsigned xres_factor = s->resolution / s->dev->settings.xres;
+
+ unsigned bytes_per_line = 0;
+
+ if (s->params.depth > 8)
+ {
+ s->params.depth = 16;
+ bytes_per_line = 2 * pixels_per_line;
+ }
+ else if (s->params.depth == 1)
+ {
+ // round down pixel number. This will is lossy operation, at most 7 pixels will be lost
+ pixels_per_line = (pixels_per_line / 8) * 8;
+ bytes_per_line = pixels_per_line / 8;
+ } else {
+ bytes_per_line = pixels_per_line;
+ }
+
+ if (s->params.format == SANE_FRAME_RGB) {
+ bytes_per_line *= 3;
+ }
+
+ s->dev->settings.scan_mode = option_string_to_scan_color_mode(s->mode);
+
+ s->dev->settings.lines = s->params.lines;
+ s->dev->settings.pixels = pixels_per_line;
+ s->dev->settings.requested_pixels = pixels_per_line * xres_factor;
+ s->params.pixels_per_line = pixels_per_line * xres_factor;
+ s->params.bytes_per_line = bytes_per_line * xres_factor;
+ s->dev->settings.tl_x = tl_x;
+ s->dev->settings.tl_y = tl_y;
+
+ // threshold setting
+ s->dev->settings.threshold = static_cast<int>(2.55 * (SANE_UNFIX(s->threshold)));
+
+ // color filter
+ if (s->color_filter == "Red") {
+ s->dev->settings.color_filter = ColorFilter::RED;
+ } else if (s->color_filter == "Green") {
+ s->dev->settings.color_filter = ColorFilter::GREEN;
+ } else if (s->color_filter == "Blue") {
+ s->dev->settings.color_filter = ColorFilter::BLUE;
+ } else {
+ s->dev->settings.color_filter = ColorFilter::NONE;
+ }
+
+ // true gray
+ if (s->color_filter == "None") {
+ s->dev->settings.true_gray = 1;
+ } else {
+ s->dev->settings.true_gray = 0;
+ }
+
+ // threshold curve for dynamic rasterization
+ s->dev->settings.threshold_curve = s->threshold_curve;
+
+ /* some digital processing requires the whole picture to be buffered */
+ /* no digital processing takes place when doing preview, or when bit depth is
+ * higher than 8 bits */
+ if ((s->swdespeck || s->swcrop || s->swdeskew || s->swderotate ||(SANE_UNFIX(s->swskip)>0))
+ && (!s->preview)
+ && (s->bit_depth <= 8))
+ {
+ s->dev->buffer_image = true;
+ }
+ else
+ {
+ s->dev->buffer_image = false;
+ }
+
+ /* brigthness and contrast only for for 8 bit scans */
+ if(s->bit_depth <= 8)
+ {
+ s->dev->settings.contrast = (s->contrast * 127) / 100;
+ s->dev->settings.brightness = (s->brightness * 127) / 100;
+ }
+ else
+ {
+ s->dev->settings.contrast=0;
+ s->dev->settings.brightness=0;
+ }
+
+ /* cache expiration time */
+ s->dev->settings.expiration_time = s->expiration_time;
+}
+
+
+static void create_bpp_list (Genesys_Scanner * s, const std::vector<unsigned>& bpp)
+{
+ s->bpp_list[0] = bpp.size();
+ std::reverse_copy(bpp.begin(), bpp.end(), s->bpp_list + 1);
+}
+
+/** @brief this function initialize a gamma vector based on the ASIC:
+ * Set up a default gamma table vector based on device description
+ * gl646: 12 or 14 bits gamma table depending on GENESYS_FLAG_14BIT_GAMMA
+ * gl84x: 16 bits
+ * gl12x: 16 bits
+ * @param scanner pointer to scanner session to get options
+ * @param option option number of the gamma table to set
+ */
+static void
+init_gamma_vector_option (Genesys_Scanner * scanner, int option)
+{
+ /* the option is inactive until the custom gamma control
+ * is enabled */
+ scanner->opt[option].type = SANE_TYPE_INT;
+ scanner->opt[option].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED;
+ scanner->opt[option].unit = SANE_UNIT_NONE;
+ scanner->opt[option].constraint_type = SANE_CONSTRAINT_RANGE;
+ if (scanner->dev->model->asic_type == AsicType::GL646) {
+ if ((scanner->dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) != 0)
+ {
+ scanner->opt[option].size = 16384 * sizeof (SANE_Word);
+ scanner->opt[option].constraint.range = &u14_range;
+ }
+ else
+ { /* 12 bits gamma tables */
+ scanner->opt[option].size = 4096 * sizeof (SANE_Word);
+ scanner->opt[option].constraint.range = &u12_range;
+ }
+ }
+ else
+ { /* other asics have 16 bits words gamma table */
+ scanner->opt[option].size = 256 * sizeof (SANE_Word);
+ scanner->opt[option].constraint.range = &u16_range;
+ }
+}
+
+/**
+ * allocate a geometry range
+ * @param size maximum size of the range
+ * @return a pointer to a valid range or nullptr
+ */
+static SANE_Range create_range(float size)
+{
+ SANE_Range range;
+ range.min = SANE_FIX(0.0);
+ range.max = SANE_FIX(size);
+ range.quant = SANE_FIX(0.0);
+ return range;
+}
+
+/** @brief generate calibration cache file nam
+ * Generates the calibration cache file name to use.
+ * Tries to store the chache in $HOME/.sane or
+ * then fallbacks to $TMPDIR or TMP. The filename
+ * uses the model name if only one scanner is plugged
+ * else is uses the device name when several identical
+ * scanners are in use.
+ * @param currdev current scanner device
+ * @return an allocated string containing a file name
+ */
+static std::string calibration_filename(Genesys_Device *currdev)
+{
+ std::string ret;
+ ret.resize(PATH_MAX);
+
+ char filename[80];
+ unsigned int count;
+ unsigned int i;
+
+ /* first compute the DIR where we can store cache:
+ * 1 - home dir
+ * 2 - $TMPDIR
+ * 3 - $TMP
+ * 4 - tmp dir
+ * 5 - temp dir
+ * 6 - then resort to current dir
+ */
+ char* ptr = std::getenv("HOME");
+ if (ptr == nullptr) {
+ ptr = std::getenv("USERPROFILE");
+ }
+ if (ptr == nullptr) {
+ ptr = std::getenv("TMPDIR");
+ }
+ if (ptr == nullptr) {
+ ptr = std::getenv("TMP");
+ }
+
+ /* now choose filename:
+ * 1 - if only one scanner, name of the model
+ * 2 - if several scanners of the same model, use device name,
+ * replacing special chars
+ */
+ count=0;
+ /* count models of the same names if several scanners attached */
+ if(s_devices->size() > 1) {
+ for (const auto& dev : *s_devices) {
+ if (dev.model->model_id == currdev->model->model_id) {
+ count++;
+ }
+ }
+ }
+ if(count>1)
+ {
+ std::snprintf(filename, sizeof(filename), "%s.cal", currdev->file_name.c_str());
+ for(i=0;i<strlen(filename);i++)
+ {
+ if(filename[i]==':'||filename[i]==PATH_SEP)
+ {
+ filename[i]='_';
+ }
+ }
+ }
+ else
+ {
+ snprintf(filename,sizeof(filename),"%s.cal",currdev->model->name);
+ }
+
+ /* build final final name : store dir + filename */
+ if (ptr == nullptr) {
+ int size = std::snprintf(&ret.front(), ret.size(), "%s", filename);
+ ret.resize(size);
+ }
+ else
+ {
+ int size = 0;
+#ifdef HAVE_MKDIR
+ /* make sure .sane directory exists in existing store dir */
+ size = std::snprintf(&ret.front(), ret.size(), "%s%c.sane", ptr, PATH_SEP);
+ ret.resize(size);
+ mkdir(ret.c_str(), 0700);
+
+ ret.resize(PATH_MAX);
+#endif
+ size = std::snprintf(&ret.front(), ret.size(), "%s%c.sane%c%s",
+ ptr, PATH_SEP, PATH_SEP, filename);
+ ret.resize(size);
+ }
+
+ DBG(DBG_info, "%s: calibration filename >%s<\n", __func__, ret.c_str());
+
+ return ret;
+}
+
+static void set_resolution_option_values(Genesys_Scanner& s, bool reset_resolution_value)
+{
+ auto resolutions = s.dev->model->get_resolutions(s.scan_method);
+
+ s.opt_resolution_values.resize(resolutions.size() + 1, 0);
+ s.opt_resolution_values[0] = resolutions.size();
+ std::copy(resolutions.begin(), resolutions.end(), s.opt_resolution_values.begin() + 1);
+
+ s.opt[OPT_RESOLUTION].constraint.word_list = s.opt_resolution_values.data();
+
+ if (reset_resolution_value) {
+ s.resolution = *std::min_element(resolutions.begin(), resolutions.end());
+ }
+}
+
+static void set_xy_range_option_values(Genesys_Scanner& s)
+{
+ if (s.scan_method == ScanMethod::FLATBED)
+ {
+ s.opt_x_range = create_range(static_cast<float>(s.dev->model->x_size));
+ s.opt_y_range = create_range(static_cast<float>(s.dev->model->y_size));
+ }
+ else
+ {
+ s.opt_x_range = create_range(static_cast<float>(s.dev->model->x_size_ta));
+ s.opt_y_range = create_range(static_cast<float>(s.dev->model->y_size_ta));
+ }
+
+ s.opt[OPT_TL_X].constraint.range = &s.opt_x_range;
+ s.opt[OPT_TL_Y].constraint.range = &s.opt_y_range;
+ s.opt[OPT_BR_X].constraint.range = &s.opt_x_range;
+ s.opt[OPT_BR_Y].constraint.range = &s.opt_y_range;
+
+ s.pos_top_left_x = 0;
+ s.pos_top_left_y = 0;
+ s.pos_bottom_right_x = s.opt_x_range.max;
+ s.pos_bottom_right_y = s.opt_y_range.max;
+}
+
+static void init_options(Genesys_Scanner* s)
+{
+ DBG_HELPER(dbg);
+ SANE_Int option;
+ Genesys_Model *model = s->dev->model;
+
+ memset (s->opt, 0, sizeof (s->opt));
+
+ for (option = 0; option < NUM_OPTIONS; ++option)
+ {
+ s->opt[option].size = sizeof (SANE_Word);
+ s->opt[option].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+ }
+ s->opt[OPT_NUM_OPTS].name = SANE_NAME_NUM_OPTIONS;
+ s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
+ s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
+ s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
+ s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
+
+ /* "Mode" group: */
+ s->opt[OPT_MODE_GROUP].name = "scanmode-group";
+ s->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode");
+ s->opt[OPT_MODE_GROUP].desc = "";
+ s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_MODE_GROUP].size = 0;
+ s->opt[OPT_MODE_GROUP].cap = 0;
+ s->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ /* scan mode */
+ s->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
+ s->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
+ s->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
+ s->opt[OPT_MODE].type = SANE_TYPE_STRING;
+ s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_MODE].size = max_string_size (mode_list);
+ s->opt[OPT_MODE].constraint.string_list = mode_list;
+ s->mode = SANE_VALUE_SCAN_MODE_GRAY;
+
+ /* scan source */
+ s->opt_source_values.clear();
+ for (const auto& resolution_setting : model->resolutions) {
+ for (auto method : resolution_setting.methods) {
+ s->opt_source_values.push_back(scan_method_to_option_string(method));
+ }
+ }
+ s->opt_source_values.push_back(nullptr);
+
+ s->opt[OPT_SOURCE].name = SANE_NAME_SCAN_SOURCE;
+ s->opt[OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE;
+ s->opt[OPT_SOURCE].desc = SANE_DESC_SCAN_SOURCE;
+ s->opt[OPT_SOURCE].type = SANE_TYPE_STRING;
+ s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_SOURCE].size = max_string_size(s->opt_source_values);
+ s->opt[OPT_SOURCE].constraint.string_list = s->opt_source_values.data();
+ if (s->opt_source_values.size() < 2) {
+ throw SaneException("No scan methods specified for scanner");
+ }
+ s->scan_method = model->default_method;
+
+ /* preview */
+ s->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW;
+ s->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW;
+ s->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW;
+ s->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_PREVIEW].unit = SANE_UNIT_NONE;
+ s->opt[OPT_PREVIEW].constraint_type = SANE_CONSTRAINT_NONE;
+ s->preview = false;
+
+ /* bit depth */
+ s->opt[OPT_BIT_DEPTH].name = SANE_NAME_BIT_DEPTH;
+ s->opt[OPT_BIT_DEPTH].title = SANE_TITLE_BIT_DEPTH;
+ s->opt[OPT_BIT_DEPTH].desc = SANE_DESC_BIT_DEPTH;
+ s->opt[OPT_BIT_DEPTH].type = SANE_TYPE_INT;
+ s->opt[OPT_BIT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_BIT_DEPTH].size = sizeof (SANE_Word);
+ s->opt[OPT_BIT_DEPTH].constraint.word_list = s->bpp_list;
+ create_bpp_list (s, model->bpp_gray_values);
+ s->bit_depth = model->bpp_gray_values[0];
+
+ // resolution
+ s->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
+ s->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
+ s->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
+ s->opt[OPT_RESOLUTION].type = SANE_TYPE_INT;
+ s->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
+ s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ set_resolution_option_values(*s, true);
+
+ /* "Geometry" group: */
+ s->opt[OPT_GEOMETRY_GROUP].name = SANE_NAME_GEOMETRY;
+ s->opt[OPT_GEOMETRY_GROUP].title = SANE_I18N ("Geometry");
+ s->opt[OPT_GEOMETRY_GROUP].desc = "";
+ s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED;
+ s->opt[OPT_GEOMETRY_GROUP].size = 0;
+ s->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ s->opt_x_range = create_range(static_cast<float>(model->x_size));
+ s->opt_y_range = create_range(static_cast<float>(model->y_size));
+
+ // scan area
+ s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
+ s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
+ s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
+ s->opt[OPT_TL_X].type = SANE_TYPE_FIXED;
+ s->opt[OPT_TL_X].unit = SANE_UNIT_MM;
+ s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
+
+ s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
+ s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
+ s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
+ s->opt[OPT_TL_Y].type = SANE_TYPE_FIXED;
+ s->opt[OPT_TL_Y].unit = SANE_UNIT_MM;
+ s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+
+ s->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X;
+ s->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
+ s->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X;
+ s->opt[OPT_BR_X].type = SANE_TYPE_FIXED;
+ s->opt[OPT_BR_X].unit = SANE_UNIT_MM;
+ s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
+
+ s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
+ s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
+ s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
+ s->opt[OPT_BR_Y].type = SANE_TYPE_FIXED;
+ s->opt[OPT_BR_Y].unit = SANE_UNIT_MM;
+ s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+
+ set_xy_range_option_values(*s);
+
+ /* "Enhancement" group: */
+ s->opt[OPT_ENHANCEMENT_GROUP].name = SANE_NAME_ENHANCEMENT;
+ s->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N ("Enhancement");
+ s->opt[OPT_ENHANCEMENT_GROUP].desc = "";
+ s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_ENHANCEMENT_GROUP].cap = SANE_CAP_ADVANCED;
+ s->opt[OPT_ENHANCEMENT_GROUP].size = 0;
+ s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ /* custom-gamma table */
+ s->opt[OPT_CUSTOM_GAMMA].name = SANE_NAME_CUSTOM_GAMMA;
+ s->opt[OPT_CUSTOM_GAMMA].title = SANE_TITLE_CUSTOM_GAMMA;
+ s->opt[OPT_CUSTOM_GAMMA].desc = SANE_DESC_CUSTOM_GAMMA;
+ s->opt[OPT_CUSTOM_GAMMA].type = SANE_TYPE_BOOL;
+ s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_ADVANCED;
+ s->custom_gamma = false;
+
+ /* grayscale gamma vector */
+ s->opt[OPT_GAMMA_VECTOR].name = SANE_NAME_GAMMA_VECTOR;
+ s->opt[OPT_GAMMA_VECTOR].title = SANE_TITLE_GAMMA_VECTOR;
+ s->opt[OPT_GAMMA_VECTOR].desc = SANE_DESC_GAMMA_VECTOR;
+ init_gamma_vector_option (s, OPT_GAMMA_VECTOR);
+
+ /* red gamma vector */
+ s->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R;
+ s->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R;
+ s->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R;
+ init_gamma_vector_option (s, OPT_GAMMA_VECTOR_R);
+
+ /* green gamma vector */
+ s->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G;
+ s->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G;
+ s->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G;
+ init_gamma_vector_option (s, OPT_GAMMA_VECTOR_G);
+
+ /* blue gamma vector */
+ s->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B;
+ s->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B;
+ s->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B;
+ init_gamma_vector_option (s, OPT_GAMMA_VECTOR_B);
+
+ /* currently, there are only gamma table options in this group,
+ * so if the scanner doesn't support gamma table, disable the
+ * whole group */
+ if (!(model->flags & GENESYS_FLAG_CUSTOM_GAMMA))
+ {
+ s->opt[OPT_ENHANCEMENT_GROUP].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;
+ DBG(DBG_info, "%s: custom gamma disabled\n", __func__);
+ }
+
+ /* software base image enhancements, these are consuming as many
+ * memory than used by the full scanned image and may fail at high
+ * resolution
+ */
+ /* software deskew */
+ s->opt[OPT_SWDESKEW].name = "swdeskew";
+ s->opt[OPT_SWDESKEW].title = "Software deskew";
+ s->opt[OPT_SWDESKEW].desc = "Request backend to rotate skewed pages digitally";
+ s->opt[OPT_SWDESKEW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_SWDESKEW].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
+ s->swdeskew = false;
+
+ /* software deskew */
+ s->opt[OPT_SWDESPECK].name = "swdespeck";
+ s->opt[OPT_SWDESPECK].title = "Software despeck";
+ s->opt[OPT_SWDESPECK].desc = "Request backend to remove lone dots digitally";
+ s->opt[OPT_SWDESPECK].type = SANE_TYPE_BOOL;
+ s->opt[OPT_SWDESPECK].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
+ s->swdespeck = false;
+
+ /* software despeckle radius */
+ s->opt[OPT_DESPECK].name = "despeck";
+ s->opt[OPT_DESPECK].title = "Software despeckle diameter";
+ s->opt[OPT_DESPECK].desc = "Maximum diameter of lone dots to remove from scan";
+ s->opt[OPT_DESPECK].type = SANE_TYPE_INT;
+ s->opt[OPT_DESPECK].unit = SANE_UNIT_NONE;
+ s->opt[OPT_DESPECK].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_DESPECK].constraint.range = &swdespeck_range;
+ s->opt[OPT_DESPECK].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED | SANE_CAP_INACTIVE;
+ s->despeck = 1;
+
+ /* crop by software */
+ s->opt[OPT_SWCROP].name = "swcrop";
+ s->opt[OPT_SWCROP].title = SANE_I18N ("Software crop");
+ s->opt[OPT_SWCROP].desc = SANE_I18N ("Request backend to remove border from pages digitally");
+ s->opt[OPT_SWCROP].type = SANE_TYPE_BOOL;
+ s->opt[OPT_SWCROP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
+ s->opt[OPT_SWCROP].unit = SANE_UNIT_NONE;
+ s->swcrop = false;
+
+ /* Software blank page skip */
+ s->opt[OPT_SWSKIP].name = "swskip";
+ s->opt[OPT_SWSKIP].title = SANE_I18N ("Software blank skip percentage");
+ s->opt[OPT_SWSKIP].desc = SANE_I18N("Request driver to discard pages with low numbers of dark pixels");
+ s->opt[OPT_SWSKIP].type = SANE_TYPE_FIXED;
+ s->opt[OPT_SWSKIP].unit = SANE_UNIT_PERCENT;
+ s->opt[OPT_SWSKIP].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_SWSKIP].constraint.range = &(percentage_range);
+ s->opt[OPT_SWSKIP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
+ s->swskip = 0; // disable by default
+
+ /* Software Derotate */
+ s->opt[OPT_SWDEROTATE].name = "swderotate";
+ s->opt[OPT_SWDEROTATE].title = SANE_I18N ("Software derotate");
+ s->opt[OPT_SWDEROTATE].desc = SANE_I18N("Request driver to detect and correct 90 degree image rotation");
+ s->opt[OPT_SWDEROTATE].type = SANE_TYPE_BOOL;
+ s->opt[OPT_SWDEROTATE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
+ s->opt[OPT_SWDEROTATE].unit = SANE_UNIT_NONE;
+ s->swderotate = false;
+
+ /* Software brightness */
+ s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
+ s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
+ s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
+ s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
+ s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
+ s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BRIGHTNESS].constraint.range = &(enhance_range);
+ s->opt[OPT_BRIGHTNESS].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+ s->brightness = 0; // disable by default
+
+ /* Sowftware contrast */
+ s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
+ s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
+ s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
+ s->opt[OPT_CONTRAST].type = SANE_TYPE_INT;
+ s->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CONTRAST].constraint.range = &(enhance_range);
+ s->opt[OPT_CONTRAST].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+ s->contrast = 0; // disable by default
+
+ /* "Extras" group: */
+ s->opt[OPT_EXTRAS_GROUP].name = "extras-group";
+ s->opt[OPT_EXTRAS_GROUP].title = SANE_I18N ("Extras");
+ s->opt[OPT_EXTRAS_GROUP].desc = "";
+ s->opt[OPT_EXTRAS_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_EXTRAS_GROUP].cap = SANE_CAP_ADVANCED;
+ s->opt[OPT_EXTRAS_GROUP].size = 0;
+ s->opt[OPT_EXTRAS_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ /* BW threshold */
+ s->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD;
+ s->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD;
+ s->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD;
+ s->opt[OPT_THRESHOLD].type = SANE_TYPE_FIXED;
+ s->opt[OPT_THRESHOLD].unit = SANE_UNIT_PERCENT;
+ s->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_THRESHOLD].constraint.range = &percentage_range;
+ s->threshold = SANE_FIX(50);
+
+ /* BW threshold curve */
+ s->opt[OPT_THRESHOLD_CURVE].name = "threshold-curve";
+ s->opt[OPT_THRESHOLD_CURVE].title = SANE_I18N ("Threshold curve");
+ s->opt[OPT_THRESHOLD_CURVE].desc = SANE_I18N ("Dynamic threshold curve, from light to dark, normally 50-65");
+ s->opt[OPT_THRESHOLD_CURVE].type = SANE_TYPE_INT;
+ s->opt[OPT_THRESHOLD_CURVE].unit = SANE_UNIT_NONE;
+ s->opt[OPT_THRESHOLD_CURVE].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_THRESHOLD_CURVE].constraint.range = &threshold_curve_range;
+ s->threshold_curve = 50;
+
+ /* disable_interpolation */
+ s->opt[OPT_DISABLE_INTERPOLATION].name = "disable-interpolation";
+ s->opt[OPT_DISABLE_INTERPOLATION].title =
+ SANE_I18N ("Disable interpolation");
+ s->opt[OPT_DISABLE_INTERPOLATION].desc =
+ SANE_I18N
+ ("When using high resolutions where the horizontal resolution is smaller "
+ "than the vertical resolution this disables horizontal interpolation.");
+ s->opt[OPT_DISABLE_INTERPOLATION].type = SANE_TYPE_BOOL;
+ s->opt[OPT_DISABLE_INTERPOLATION].unit = SANE_UNIT_NONE;
+ s->opt[OPT_DISABLE_INTERPOLATION].constraint_type = SANE_CONSTRAINT_NONE;
+ s->disable_interpolation = false;
+
+ /* color filter */
+ s->opt[OPT_COLOR_FILTER].name = "color-filter";
+ s->opt[OPT_COLOR_FILTER].title = SANE_I18N ("Color filter");
+ s->opt[OPT_COLOR_FILTER].desc =
+ SANE_I18N
+ ("When using gray or lineart this option selects the used color.");
+ s->opt[OPT_COLOR_FILTER].type = SANE_TYPE_STRING;
+ s->opt[OPT_COLOR_FILTER].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ /* true gray not yet supported for GL847 and GL124 scanners */
+ if (!model->is_cis || model->asic_type==AsicType::GL847 || model->asic_type==AsicType::GL124) {
+ s->opt[OPT_COLOR_FILTER].size = max_string_size (color_filter_list);
+ s->opt[OPT_COLOR_FILTER].constraint.string_list = color_filter_list;
+ s->color_filter = s->opt[OPT_COLOR_FILTER].constraint.string_list[1];
+ }
+ else
+ {
+ s->opt[OPT_COLOR_FILTER].size = max_string_size (cis_color_filter_list);
+ s->opt[OPT_COLOR_FILTER].constraint.string_list = cis_color_filter_list;
+ /* default to "None" ie true gray */
+ s->color_filter = s->opt[OPT_COLOR_FILTER].constraint.string_list[3];
+ }
+
+ // no support for color filter for cis+gl646 scanners
+ if (model->asic_type == AsicType::GL646 && model->is_cis) {
+ DISABLE (OPT_COLOR_FILTER);
+ }
+
+ /* calibration store file name */
+ s->opt[OPT_CALIBRATION_FILE].name = "calibration-file";
+ s->opt[OPT_CALIBRATION_FILE].title = SANE_I18N ("Calibration file");
+ s->opt[OPT_CALIBRATION_FILE].desc = SANE_I18N ("Specify the calibration file to use");
+ s->opt[OPT_CALIBRATION_FILE].type = SANE_TYPE_STRING;
+ s->opt[OPT_CALIBRATION_FILE].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CALIBRATION_FILE].size = PATH_MAX;
+ s->opt[OPT_CALIBRATION_FILE].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
+ s->opt[OPT_CALIBRATION_FILE].constraint_type = SANE_CONSTRAINT_NONE;
+ s->calibration_file.clear();
+ /* disable option if ran as root */
+#ifdef HAVE_GETUID
+ if(geteuid()==0)
+ {
+ DISABLE (OPT_CALIBRATION_FILE);
+ }
+#endif
+
+ /* expiration time for calibration cache entries */
+ s->opt[OPT_EXPIRATION_TIME].name = "expiration-time";
+ s->opt[OPT_EXPIRATION_TIME].title = SANE_I18N ("Calibration cache expiration time");
+ s->opt[OPT_EXPIRATION_TIME].desc = SANE_I18N ("Time (in minutes) before a cached calibration expires. "
+ "A value of 0 means cache is not used. A negative value means cache never expires.");
+ s->opt[OPT_EXPIRATION_TIME].type = SANE_TYPE_INT;
+ s->opt[OPT_EXPIRATION_TIME].unit = SANE_UNIT_NONE;
+ s->opt[OPT_EXPIRATION_TIME].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_EXPIRATION_TIME].constraint.range = &expiration_range;
+ s->expiration_time = 60; // 60 minutes by default
+
+ /* Powersave time (turn lamp off) */
+ s->opt[OPT_LAMP_OFF_TIME].name = "lamp-off-time";
+ s->opt[OPT_LAMP_OFF_TIME].title = SANE_I18N ("Lamp off time");
+ s->opt[OPT_LAMP_OFF_TIME].desc =
+ SANE_I18N
+ ("The lamp will be turned off after the given time (in minutes). "
+ "A value of 0 means, that the lamp won't be turned off.");
+ s->opt[OPT_LAMP_OFF_TIME].type = SANE_TYPE_INT;
+ s->opt[OPT_LAMP_OFF_TIME].unit = SANE_UNIT_NONE;
+ s->opt[OPT_LAMP_OFF_TIME].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_LAMP_OFF_TIME].constraint.range = &time_range;
+ s->lamp_off_time = 15; // 15 minutes
+
+ /* turn lamp off during scan */
+ s->opt[OPT_LAMP_OFF].name = "lamp-off-scan";
+ s->opt[OPT_LAMP_OFF].title = SANE_I18N ("Lamp off during scan");
+ s->opt[OPT_LAMP_OFF].desc = SANE_I18N ("The lamp will be turned off during scan. ");
+ s->opt[OPT_LAMP_OFF].type = SANE_TYPE_BOOL;
+ s->opt[OPT_LAMP_OFF].unit = SANE_UNIT_NONE;
+ s->opt[OPT_LAMP_OFF].constraint_type = SANE_CONSTRAINT_NONE;
+ s->lamp_off = false;
+
+ s->opt[OPT_SENSOR_GROUP].name = SANE_NAME_SENSORS;
+ s->opt[OPT_SENSOR_GROUP].title = SANE_TITLE_SENSORS;
+ s->opt[OPT_SENSOR_GROUP].desc = SANE_DESC_SENSORS;
+ s->opt[OPT_SENSOR_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_SENSOR_GROUP].cap = SANE_CAP_ADVANCED;
+ s->opt[OPT_SENSOR_GROUP].size = 0;
+ s->opt[OPT_SENSOR_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ s->opt[OPT_SCAN_SW].name = SANE_NAME_SCAN;
+ s->opt[OPT_SCAN_SW].title = SANE_TITLE_SCAN;
+ s->opt[OPT_SCAN_SW].desc = SANE_DESC_SCAN;
+ s->opt[OPT_SCAN_SW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_SCAN_SW].unit = SANE_UNIT_NONE;
+ if (model->buttons & GENESYS_HAS_SCAN_SW)
+ s->opt[OPT_SCAN_SW].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
+ else
+ s->opt[OPT_SCAN_SW].cap = SANE_CAP_INACTIVE;
+
+ /* SANE_NAME_FILE is not for buttons */
+ s->opt[OPT_FILE_SW].name = "file";
+ s->opt[OPT_FILE_SW].title = SANE_I18N ("File button");
+ s->opt[OPT_FILE_SW].desc = SANE_I18N ("File button");
+ s->opt[OPT_FILE_SW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_FILE_SW].unit = SANE_UNIT_NONE;
+ if (model->buttons & GENESYS_HAS_FILE_SW)
+ s->opt[OPT_FILE_SW].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
+ else
+ s->opt[OPT_FILE_SW].cap = SANE_CAP_INACTIVE;
+
+ s->opt[OPT_EMAIL_SW].name = SANE_NAME_EMAIL;
+ s->opt[OPT_EMAIL_SW].title = SANE_TITLE_EMAIL;
+ s->opt[OPT_EMAIL_SW].desc = SANE_DESC_EMAIL;
+ s->opt[OPT_EMAIL_SW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_EMAIL_SW].unit = SANE_UNIT_NONE;
+ if (model->buttons & GENESYS_HAS_EMAIL_SW)
+ s->opt[OPT_EMAIL_SW].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
+ else
+ s->opt[OPT_EMAIL_SW].cap = SANE_CAP_INACTIVE;
+
+ s->opt[OPT_COPY_SW].name = SANE_NAME_COPY;
+ s->opt[OPT_COPY_SW].title = SANE_TITLE_COPY;
+ s->opt[OPT_COPY_SW].desc = SANE_DESC_COPY;
+ s->opt[OPT_COPY_SW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_COPY_SW].unit = SANE_UNIT_NONE;
+ if (model->buttons & GENESYS_HAS_COPY_SW)
+ s->opt[OPT_COPY_SW].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
+ else
+ s->opt[OPT_COPY_SW].cap = SANE_CAP_INACTIVE;
+
+ s->opt[OPT_PAGE_LOADED_SW].name = SANE_NAME_PAGE_LOADED;
+ s->opt[OPT_PAGE_LOADED_SW].title = SANE_TITLE_PAGE_LOADED;
+ s->opt[OPT_PAGE_LOADED_SW].desc = SANE_DESC_PAGE_LOADED;
+ s->opt[OPT_PAGE_LOADED_SW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_PAGE_LOADED_SW].unit = SANE_UNIT_NONE;
+ if (model->buttons & GENESYS_HAS_PAGE_LOADED_SW)
+ s->opt[OPT_PAGE_LOADED_SW].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
+ else
+ s->opt[OPT_PAGE_LOADED_SW].cap = SANE_CAP_INACTIVE;
+
+ /* OCR button */
+ s->opt[OPT_OCR_SW].name = "ocr";
+ s->opt[OPT_OCR_SW].title = SANE_I18N ("OCR button");
+ s->opt[OPT_OCR_SW].desc = SANE_I18N ("OCR button");
+ s->opt[OPT_OCR_SW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_OCR_SW].unit = SANE_UNIT_NONE;
+ if (model->buttons & GENESYS_HAS_OCR_SW)
+ s->opt[OPT_OCR_SW].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
+ else
+ s->opt[OPT_OCR_SW].cap = SANE_CAP_INACTIVE;
+
+ /* power button */
+ s->opt[OPT_POWER_SW].name = "power";
+ s->opt[OPT_POWER_SW].title = SANE_I18N ("Power button");
+ s->opt[OPT_POWER_SW].desc = SANE_I18N ("Power button");
+ s->opt[OPT_POWER_SW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_POWER_SW].unit = SANE_UNIT_NONE;
+ if (model->buttons & GENESYS_HAS_POWER_SW)
+ s->opt[OPT_POWER_SW].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
+ else
+ s->opt[OPT_POWER_SW].cap = SANE_CAP_INACTIVE;
+
+ /* extra button */
+ s->opt[OPT_EXTRA_SW].name = "extra";
+ s->opt[OPT_EXTRA_SW].title = SANE_I18N ("Extra button");
+ s->opt[OPT_EXTRA_SW].desc = SANE_I18N ("Extra button");
+ s->opt[OPT_EXTRA_SW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_EXTRA_SW].unit = SANE_UNIT_NONE;
+ if (model->buttons & GENESYS_HAS_EXTRA_SW)
+ s->opt[OPT_EXTRA_SW].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
+ else
+ s->opt[OPT_EXTRA_SW].cap = SANE_CAP_INACTIVE;
+
+ /* calibration needed */
+ s->opt[OPT_NEED_CALIBRATION_SW].name = "need-calibration";
+ s->opt[OPT_NEED_CALIBRATION_SW].title = SANE_I18N ("Needs calibration");
+ s->opt[OPT_NEED_CALIBRATION_SW].desc = SANE_I18N ("The scanner needs calibration for the current settings");
+ s->opt[OPT_NEED_CALIBRATION_SW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_NEED_CALIBRATION_SW].unit = SANE_UNIT_NONE;
+ if (model->buttons & GENESYS_HAS_CALIBRATE)
+ s->opt[OPT_NEED_CALIBRATION_SW].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
+ else
+ s->opt[OPT_NEED_CALIBRATION_SW].cap = SANE_CAP_INACTIVE;
+
+ /* button group */
+ s->opt[OPT_BUTTON_GROUP].name = "buttons";
+ s->opt[OPT_BUTTON_GROUP].title = SANE_I18N ("Buttons");
+ s->opt[OPT_BUTTON_GROUP].desc = "";
+ s->opt[OPT_BUTTON_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_BUTTON_GROUP].cap = SANE_CAP_ADVANCED;
+ s->opt[OPT_BUTTON_GROUP].size = 0;
+ s->opt[OPT_BUTTON_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ /* calibrate button */
+ s->opt[OPT_CALIBRATE].name = "calibrate";
+ s->opt[OPT_CALIBRATE].title = SANE_I18N ("Calibrate");
+ s->opt[OPT_CALIBRATE].desc =
+ SANE_I18N ("Start calibration using special sheet");
+ s->opt[OPT_CALIBRATE].type = SANE_TYPE_BUTTON;
+ s->opt[OPT_CALIBRATE].unit = SANE_UNIT_NONE;
+ if (model->buttons & GENESYS_HAS_CALIBRATE)
+ s->opt[OPT_CALIBRATE].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED |
+ SANE_CAP_AUTOMATIC;
+ else
+ s->opt[OPT_CALIBRATE].cap = SANE_CAP_INACTIVE;
+
+ /* clear calibration cache button */
+ s->opt[OPT_CLEAR_CALIBRATION].name = "clear-calibration";
+ s->opt[OPT_CLEAR_CALIBRATION].title = SANE_I18N ("Clear calibration");
+ s->opt[OPT_CLEAR_CALIBRATION].desc = SANE_I18N ("Clear calibration cache");
+ s->opt[OPT_CLEAR_CALIBRATION].type = SANE_TYPE_BUTTON;
+ s->opt[OPT_CLEAR_CALIBRATION].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CLEAR_CALIBRATION].size = 0;
+ s->opt[OPT_CLEAR_CALIBRATION].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_CLEAR_CALIBRATION].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
+
+ /* force calibration cache button */
+ s->opt[OPT_FORCE_CALIBRATION].name = "force-calibration";
+ s->opt[OPT_FORCE_CALIBRATION].title = SANE_I18N("Force calibration");
+ s->opt[OPT_FORCE_CALIBRATION].desc = SANE_I18N("Force calibration ignoring all and any calibration caches");
+ s->opt[OPT_FORCE_CALIBRATION].type = SANE_TYPE_BUTTON;
+ s->opt[OPT_FORCE_CALIBRATION].unit = SANE_UNIT_NONE;
+ s->opt[OPT_FORCE_CALIBRATION].size = 0;
+ s->opt[OPT_FORCE_CALIBRATION].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_FORCE_CALIBRATION].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
+
+ // ignore offsets option
+ s->opt[OPT_IGNORE_OFFSETS].name = "ignore-internal-offsets";
+ s->opt[OPT_IGNORE_OFFSETS].title = SANE_I18N("Ignore internal offsets");
+ s->opt[OPT_IGNORE_OFFSETS].desc =
+ SANE_I18N("Acquires the image including the internal calibration areas of the scanner");
+ s->opt[OPT_IGNORE_OFFSETS].type = SANE_TYPE_BUTTON;
+ s->opt[OPT_IGNORE_OFFSETS].unit = SANE_UNIT_NONE;
+ s->opt[OPT_IGNORE_OFFSETS].size = 0;
+ s->opt[OPT_IGNORE_OFFSETS].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_IGNORE_OFFSETS].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT |
+ SANE_CAP_ADVANCED;
+
+ calc_parameters(s);
+}
+
+static bool present;
+
+// this function is passed to C API, it must not throw
+static SANE_Status
+check_present (SANE_String_Const devname) noexcept
+{
+ DBG_HELPER_ARGS(dbg, "%s detected.", devname);
+ present = true;
+ return SANE_STATUS_GOOD;
+}
+
+static Genesys_Device* attach_usb_device(const char* devname,
+ std::uint16_t vendor_id, std::uint16_t product_id)
+{
+ Genesys_USB_Device_Entry* found_usb_dev = nullptr;
+ for (auto& usb_dev : *s_usb_devices) {
+ if (usb_dev.vendor == vendor_id &&
+ usb_dev.product == product_id)
+ {
+ found_usb_dev = &usb_dev;
+ break;
+ }
+ }
+
+ if (found_usb_dev == nullptr) {
+ throw SaneException("vendor 0x%xd product 0x%xd is not supported by this backend",
+ vendor_id, product_id);
+ }
+
+ s_devices->emplace_back();
+ Genesys_Device* dev = &s_devices->back();
+ dev->file_name = devname;
+
+ dev->model = &found_usb_dev->model;
+ dev->vendorId = found_usb_dev->vendor;
+ dev->productId = found_usb_dev->product;
+ dev->usb_mode = 0; // i.e. unset
+ dev->already_initialized = false;
+ return dev;
+}
+
+static Genesys_Device* attach_device_by_name(SANE_String_Const devname, bool may_wait)
+{
+ DBG_HELPER_ARGS(dbg, " devname: %s, may_wait = %d", devname, may_wait);
+
+ if (!devname) {
+ throw SaneException("devname must not be nullptr");
+ }
+
+ for (auto& dev : *s_devices) {
+ if (dev.file_name == devname) {
+ DBG(DBG_info, "%s: device `%s' was already in device list\n", __func__, devname);
+ return &dev;
+ }
+ }
+
+ DBG(DBG_info, "%s: trying to open device `%s'\n", __func__, devname);
+
+ UsbDevice usb_dev;
+
+ usb_dev.open(devname);
+ DBG(DBG_info, "%s: device `%s' successfully opened\n", __func__, devname);
+
+ int vendor, product;
+ usb_dev.get_vendor_product(vendor, product);
+ usb_dev.close();
+
+ /* KV-SS080 is an auxiliary device which requires a master device to be here */
+ if(vendor == 0x04da && product == 0x100f)
+ {
+ present = false;
+ sanei_usb_find_devices (vendor, 0x1006, check_present);
+ sanei_usb_find_devices (vendor, 0x1007, check_present);
+ sanei_usb_find_devices (vendor, 0x1010, check_present);
+ if (present == false) {
+ throw SaneException("master device not present");
+ }
+ }
+
+ Genesys_Device* dev = attach_usb_device(devname, vendor, product);
+
+ DBG(DBG_info, "%s: found %s flatbed scanner %s at %s\n", __func__, dev->model->vendor,
+ dev->model->model, dev->file_name.c_str());
+
+ return dev;
+}
+
+// this function is passed to C API and must not throw
+static SANE_Status attach_one_device(SANE_String_Const devname) noexcept
+{
+ DBG_HELPER(dbg);
+ return wrap_exceptions_to_status_code(__func__, [=]()
+ {
+ attach_device_by_name(devname, false);
+ });
+}
+
+/* configuration framework functions */
+
+// this function is passed to C API, it must not throw
+static SANE_Status
+config_attach_genesys(SANEI_Config __sane_unused__ *config, const char *devname) noexcept
+{
+ /* the devname has been processed and is ready to be used
+ * directly. Since the backend is an USB only one, we can
+ * call sanei_usb_attach_matching_devices straight */
+ sanei_usb_attach_matching_devices (devname, attach_one_device);
+
+ return SANE_STATUS_GOOD;
+}
+
+/* probes for scanner to attach to the backend */
+static void probe_genesys_devices()
+{
+ DBG_HELPER(dbg);
+ if (is_testing_mode()) {
+ attach_usb_device(get_testing_device_name().c_str(),
+ get_testing_vendor_id(), get_testing_product_id());
+ return;
+ }
+
+ SANEI_Config config;
+
+ // set configuration options structure : no option for this backend
+ config.descriptors = nullptr;
+ config.values = nullptr;
+ config.count = 0;
+
+ TIE(sanei_configure_attach(GENESYS_CONFIG_FILE, &config, config_attach_genesys));
+
+ DBG(DBG_info, "%s: %zu devices currently attached\n", __func__, s_devices->size());
+}
+
+/**
+ * This should be changed if one of the substructures of
+ Genesys_Calibration_Cache change, but it must be changed if there are
+ changes that don't change size -- at least for now, as we store most
+ of Genesys_Calibration_Cache as is.
+*/
+static const char* CALIBRATION_IDENT = "sane_genesys";
+static const int CALIBRATION_VERSION = 21;
+
+bool read_calibration(std::istream& str, Genesys_Device::Calibration& calibration,
+ const std::string& path)
+{
+ DBG_HELPER(dbg);
+
+ std::string ident;
+ serialize(str, ident);
+
+ if (ident != CALIBRATION_IDENT) {
+ DBG(DBG_info, "%s: Incorrect calibration file '%s' header\n", __func__, path.c_str());
+ return false;
+ }
+
+ size_t version;
+ serialize(str, version);
+
+ if (version != CALIBRATION_VERSION) {
+ DBG(DBG_info, "%s: Incorrect calibration file '%s' version\n", __func__, path.c_str());
+ return false;
+ }
+
+ calibration.clear();
+ serialize(str, calibration);
+ return true;
+}
+
+/**
+ * reads previously cached calibration data
+ * from file defined in dev->calib_file
+ */
+static bool sanei_genesys_read_calibration(Genesys_Device::Calibration& calibration,
+ const std::string& path)
+{
+ DBG_HELPER(dbg);
+
+ std::ifstream str;
+ str.open(path);
+ if (!str.is_open()) {
+ DBG(DBG_info, "%s: Cannot open %s\n", __func__, path.c_str());
+ return false;
+ }
+
+ return read_calibration(str, calibration, path);
+}
+
+void write_calibration(std::ostream& str, Genesys_Device::Calibration& calibration)
+{
+ std::string ident = CALIBRATION_IDENT;
+ serialize(str, ident);
+ size_t version = CALIBRATION_VERSION;
+ serialize(str, version);
+ serialize_newline(str);
+ serialize(str, calibration);
+}
+
+static void write_calibration(Genesys_Device::Calibration& calibration, const std::string& path)
+{
+ DBG_HELPER(dbg);
+
+ std::ofstream str;
+ str.open(path);
+ if (!str.is_open()) {
+ throw SaneException("Cannot open calibration for writing");
+ }
+ write_calibration(str, calibration);
+}
+
+/** @brief buffer scanned picture
+ * In order to allow digital processing, we must be able to put all the
+ * scanned picture in a buffer.
+ */
+static void genesys_buffer_image(Genesys_Scanner *s)
+{
+ DBG_HELPER(dbg);
+ size_t maximum; /**> maximum bytes size of the scan */
+ size_t len; /**> length of scanned data read */
+ size_t total; /**> total of butes read */
+ size_t size; /**> size of image buffer */
+ size_t read_size; /**> size of reads */
+ int lines; /** number of lines of the scan */
+ Genesys_Device *dev = s->dev;
+
+ /* compute maximum number of lines for the scan */
+ if (s->params.lines > 0)
+ {
+ lines = s->params.lines;
+ }
+ else
+ {
+ lines = static_cast<int>((dev->model->y_size * dev->settings.yres) / MM_PER_INCH);
+ }
+ DBG(DBG_info, "%s: buffering %d lines of %d bytes\n", __func__, lines,
+ s->params.bytes_per_line);
+
+ /* maximum bytes to read */
+ maximum = s->params.bytes_per_line * lines;
+ if (s->dev->settings.scan_mode == ScanColorMode::LINEART) {
+ maximum *= 8;
+ }
+
+ /* initial size of the read buffer */
+ size =
+ ((2048 * 2048) / s->params.bytes_per_line) * s->params.bytes_per_line;
+
+ /* read size */
+ read_size = size / 2;
+
+ dev->img_buffer.resize(size);
+
+ /* loop reading data until we reach maximum or EOF */
+ total = 0;
+ while (total < maximum) {
+ len = size - maximum;
+ if (len > read_size)
+ {
+ len = read_size;
+ }
+
+ try {
+ genesys_read_ordered_data(dev, dev->img_buffer.data() + total, &len);
+ } catch (const SaneException& e) {
+ if (e.status() == SANE_STATUS_EOF) {
+ // ideally we shouldn't end up here, but because computations are duplicated and
+ // slightly different everywhere in the genesys backend, we have no other choice
+ break;
+ }
+ throw;
+ }
+ total += len;
+
+ // do we need to enlarge read buffer ?
+ if (total + read_size > size) {
+ size += read_size;
+ dev->img_buffer.resize(size);
+ }
+ }
+
+ /* since digital processing is going to take place,
+ * issue head parking command so that the head move while
+ * computing so we can save time
+ */
+ if (!dev->model->is_sheetfed && !dev->parking) {
+ dev->cmd_set->move_back_home(dev, dev->model->flags & GENESYS_FLAG_MUST_WAIT);
+ dev->parking = !(s->dev->model->flags & GENESYS_FLAG_MUST_WAIT);
+ }
+
+ /* in case of dynamic lineart, we have buffered gray data which
+ * must be converted to lineart first */
+ if (s->dev->settings.scan_mode == ScanColorMode::LINEART) {
+ total/=8;
+ std::vector<uint8_t> lineart(total);
+
+ genesys_gray_lineart (dev,
+ dev->img_buffer.data(),
+ lineart.data(),
+ dev->settings.pixels,
+ (total*8)/dev->settings.pixels,
+ dev->settings.threshold);
+ dev->img_buffer = lineart;
+ }
+
+ /* update counters */
+ dev->total_bytes_to_read = total;
+ dev->total_bytes_read = 0;
+
+ /* update params */
+ s->params.lines = total / s->params.bytes_per_line;
+ if (DBG_LEVEL >= DBG_io2)
+ {
+ sanei_genesys_write_pnm_file("gl_unprocessed.pnm", dev->img_buffer.data(), s->params.depth,
+ s->params.format==SANE_FRAME_RGB ? 3 : 1,
+ s->params.pixels_per_line, s->params.lines);
+ }
+}
+
+/* -------------------------- SANE API functions ------------------------- */
+
+void sane_init_impl(SANE_Int * version_code, SANE_Auth_Callback authorize)
+{
+ DBG_INIT ();
+ DBG_HELPER_ARGS(dbg, "authorize %s null", authorize ? "!=" : "==");
+ DBG(DBG_init, "SANE Genesys backend from %s\n", PACKAGE_STRING);
+
+ if (!is_testing_mode()) {
+#ifdef HAVE_LIBUSB
+ DBG(DBG_init, "SANE Genesys backend built with libusb-1.0\n");
+#endif
+#ifdef HAVE_LIBUSB_LEGACY
+ DBG(DBG_init, "SANE Genesys backend built with libusb\n");
+#endif
+ }
+
+ if (version_code) {
+ *version_code = SANE_VERSION_CODE(SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, 0);
+ }
+
+ if (!is_testing_mode()) {
+ sanei_usb_init();
+ }
+
+ /* init sanei_magic */
+ sanei_magic_init();
+
+ s_scanners.init();
+ s_devices.init();
+ s_sane_devices.init();
+ s_sane_devices_data.init();
+ s_sane_devices_ptrs.init();
+ genesys_init_sensor_tables();
+ genesys_init_frontend_tables();
+ genesys_init_gpo_tables();
+ genesys_init_motor_tables();
+ genesys_init_motor_profile_tables();
+ genesys_init_usb_device_tables();
+
+
+ DBG(DBG_info, "%s: %s endian machine\n", __func__,
+#ifdef WORDS_BIGENDIAN
+ "big"
+#else
+ "little"
+#endif
+ );
+
+ // cold-plug case :detection of allready connected scanners
+ probe_genesys_devices();
+}
+
+
+SANE_GENESYS_API_LINKAGE
+SANE_Status sane_init(SANE_Int * version_code, SANE_Auth_Callback authorize)
+{
+ return wrap_exceptions_to_status_code(__func__, [=]()
+ {
+ sane_init_impl(version_code, authorize);
+ });
+}
+
+void
+sane_exit_impl(void)
+{
+ DBG_HELPER(dbg);
+
+ if (!is_testing_mode()) {
+ sanei_usb_exit();
+ }
+
+ run_functions_at_backend_exit();
+}
+
+SANE_GENESYS_API_LINKAGE
+void sane_exit()
+{
+ catch_all_exceptions(__func__, [](){ sane_exit_impl(); });
+}
+
+void sane_get_devices_impl(const SANE_Device *** device_list, SANE_Bool local_only)
+{
+ DBG_HELPER_ARGS(dbg, "local_only = %s", local_only ? "true" : "false");
+
+ if (!is_testing_mode()) {
+ // hot-plug case : detection of newly connected scanners */
+ sanei_usb_scan_devices();
+ }
+ probe_genesys_devices();
+
+ s_sane_devices->clear();
+ s_sane_devices_data->clear();
+ s_sane_devices_ptrs->clear();
+ s_sane_devices->reserve(s_devices->size());
+ s_sane_devices_data->reserve(s_devices->size());
+ s_sane_devices_ptrs->reserve(s_devices->size() + 1);
+
+ for (auto dev_it = s_devices->begin(); dev_it != s_devices->end();) {
+
+ if (is_testing_mode()) {
+ present = true;
+ } else {
+ present = false;
+ sanei_usb_find_devices(dev_it->vendorId, dev_it->productId, check_present);
+ }
+
+ if (present) {
+ s_sane_devices->emplace_back();
+ s_sane_devices_data->emplace_back();
+ auto& sane_device = s_sane_devices->back();
+ auto& sane_device_data = s_sane_devices_data->back();
+ sane_device_data.name = dev_it->file_name;
+ sane_device.name = sane_device_data.name.c_str();
+ sane_device.vendor = dev_it->model->vendor;
+ sane_device.model = dev_it->model->model;
+ sane_device.type = "flatbed scanner";
+ s_sane_devices_ptrs->push_back(&sane_device);
+ dev_it++;
+ } else {
+ dev_it = s_devices->erase(dev_it);
+ }
+ }
+ s_sane_devices_ptrs->push_back(nullptr);
+
+ *const_cast<SANE_Device***>(device_list) = s_sane_devices_ptrs->data();
+}
+
+SANE_GENESYS_API_LINKAGE
+SANE_Status sane_get_devices(const SANE_Device *** device_list, SANE_Bool local_only)
+{
+ return wrap_exceptions_to_status_code(__func__, [=]()
+ {
+ sane_get_devices_impl(device_list, local_only);
+ });
+}
+
+static void sane_open_impl(SANE_String_Const devicename, SANE_Handle * handle)
+{
+ DBG_HELPER_ARGS(dbg, "devicename = %s", devicename);
+ Genesys_Device* dev = nullptr;
+
+ /* devicename="" or devicename="genesys" are default values that use
+ * first available device
+ */
+ if (devicename[0] && strcmp ("genesys", devicename) != 0) {
+ /* search for the given devicename in the device list */
+ for (auto& d : *s_devices) {
+ if (d.file_name == devicename) {
+ dev = &d;
+ break;
+ }
+ }
+
+ if (dev) {
+ DBG(DBG_info, "%s: found `%s' in devlist\n", __func__, dev->model->name);
+ } else if (is_testing_mode()) {
+ DBG(DBG_info, "%s: couldn't find `%s' in devlist, not attaching", __func__, devicename);
+ } else {
+ DBG(DBG_info, "%s: couldn't find `%s' in devlist, trying attach\n", __func__,
+ devicename);
+ dbg.status("attach_device_by_name");
+ dev = attach_device_by_name(devicename, true);
+ dbg.clear();
+ }
+ } else {
+ // empty devicename or "genesys" -> use first device
+ if (!s_devices->empty()) {
+ dev = &s_devices->front();
+ DBG(DBG_info, "%s: empty devicename, trying `%s'\n", __func__, dev->file_name.c_str());
+ }
+ }
+
+ if (!dev) {
+ throw SaneException("could not find the device to open: %s", devicename);
+ }
+
+ if (dev->model->flags & GENESYS_FLAG_UNTESTED)
+ {
+ DBG(DBG_error0, "WARNING: Your scanner is not fully supported or at least \n");
+ DBG(DBG_error0, " had only limited testing. Please be careful and \n");
+ DBG(DBG_error0, " report any failure/success to \n");
+ DBG(DBG_error0, " sane-devel@alioth-lists.debian.net. Please provide as many\n");
+ DBG(DBG_error0, " details as possible, e.g. the exact name of your\n");
+ DBG(DBG_error0, " scanner and what does (not) work.\n");
+ }
+
+ dbg.vstatus("open device '%s'", dev->file_name.c_str());
+
+ if (is_testing_mode()) {
+ auto interface = std::unique_ptr<TestScannerInterface>{new TestScannerInterface{dev}};
+ interface->set_checkpoint_callback(get_testing_checkpoint_callback());
+ dev->interface = std::move(interface);
+ } else {
+ dev->interface = std::unique_ptr<ScannerInterfaceUsb>{new ScannerInterfaceUsb{dev}};
+ }
+ dev->interface->get_usb_device().open(dev->file_name.c_str());
+ dbg.clear();
+
+ s_scanners->push_back(Genesys_Scanner());
+ auto* s = &s_scanners->back();
+
+ s->dev = dev;
+ s->scanning = false;
+ s->dev->parking = false;
+ s->dev->read_active = false;
+ s->dev->force_calibration = 0;
+ s->dev->line_count = 0;
+
+ *handle = s;
+
+ if (!dev->already_initialized) {
+ sanei_genesys_init_structs (dev);
+ }
+
+ init_options(s);
+
+ sanei_genesys_init_cmd_set(s->dev);
+
+ // FIXME: we create sensor tables for the sensor, this should happen when we know which sensor
+ // we will select
+ dev->cmd_set->init(dev);
+
+ // some hardware capabilities are detected through sensors
+ s->dev->cmd_set->update_hardware_sensors (s);
+
+ /* here is the place to fetch a stored calibration cache */
+ if (s->dev->force_calibration == 0)
+ {
+ auto path = calibration_filename(s->dev);
+ s->calibration_file = path;
+ s->dev->calib_file = path;
+ DBG(DBG_info, "%s: Calibration filename set to:\n", __func__);
+ DBG(DBG_info, "%s: >%s<\n", __func__, s->dev->calib_file.c_str());
+
+ catch_all_exceptions(__func__, [&]()
+ {
+ sanei_genesys_read_calibration(s->dev->calibration_cache, s->dev->calib_file);
+ });
+ }
+}
+
+SANE_GENESYS_API_LINKAGE
+SANE_Status sane_open(SANE_String_Const devicename, SANE_Handle* handle)
+{
+ return wrap_exceptions_to_status_code(__func__, [=]()
+ {
+ sane_open_impl(devicename, handle);
+ });
+}
+
+void
+sane_close_impl(SANE_Handle handle)
+{
+ DBG_HELPER(dbg);
+
+ /* remove handle from list of open handles: */
+ auto it = s_scanners->end();
+ for (auto it2 = s_scanners->begin(); it2 != s_scanners->end(); it2++)
+ {
+ if (&*it2 == handle) {
+ it = it2;
+ break;
+ }
+ }
+ if (it == s_scanners->end())
+ {
+ DBG(DBG_error, "%s: invalid handle %p\n", __func__, handle);
+ return; /* oops, not a handle we know about */
+ }
+
+ Genesys_Scanner* s = &*it;
+
+ /* eject document for sheetfed scanners */
+ if (s->dev->model->is_sheetfed) {
+ catch_all_exceptions(__func__, [&](){ s->dev->cmd_set->eject_document(s->dev); });
+ }
+ else
+ {
+ /* in case scanner is parking, wait for the head
+ * to reach home position */
+ if (s->dev->parking) {
+ sanei_genesys_wait_for_home(s->dev);
+ }
+ }
+
+ // enable power saving before leaving
+ s->dev->cmd_set->save_power(s->dev, true);
+
+ // here is the place to store calibration cache
+ if (s->dev->force_calibration == 0 && !is_testing_mode()) {
+ catch_all_exceptions(__func__, [&](){ write_calibration(s->dev->calibration_cache,
+ s->dev->calib_file); });
+ }
+
+ s->dev->already_initialized = false;
+
+ s->dev->clear();
+
+ // LAMP OFF : same register across all the ASICs */
+ s->dev->interface->write_register(0x03, 0x00);
+
+ catch_all_exceptions(__func__, [&](){ s->dev->interface->get_usb_device().clear_halt(); });
+
+ // we need this to avoid these ASIC getting stuck in bulk writes
+ catch_all_exceptions(__func__, [&](){ s->dev->interface->get_usb_device().reset(); });
+
+ // not freeing s->dev because it's in the dev list
+ catch_all_exceptions(__func__, [&](){ s->dev->interface->get_usb_device().close(); });
+
+ s_scanners->erase(it);
+}
+
+SANE_GENESYS_API_LINKAGE
+void sane_close(SANE_Handle handle)
+{
+ catch_all_exceptions(__func__, [=]()
+ {
+ sane_close_impl(handle);
+ });
+}
+
+const SANE_Option_Descriptor *
+sane_get_option_descriptor_impl(SANE_Handle handle, SANE_Int option)
+{
+ DBG_HELPER(dbg);
+ Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle);
+
+ if (static_cast<unsigned>(option) >= NUM_OPTIONS) {
+ return nullptr;
+ }
+
+ DBG(DBG_io2, "%s: option = %s (%d)\n", __func__, s->opt[option].name, option);
+ return s->opt + option;
+}
+
+
+SANE_GENESYS_API_LINKAGE
+const SANE_Option_Descriptor* sane_get_option_descriptor(SANE_Handle handle, SANE_Int option)
+{
+ const SANE_Option_Descriptor* ret = nullptr;
+ catch_all_exceptions(__func__, [&]()
+ {
+ ret = sane_get_option_descriptor_impl(handle, option);
+ });
+ return ret;
+}
+
+static void print_option(DebugMessageHelper& dbg, const Genesys_Scanner& s, int option, void* val)
+{
+ switch (s.opt[option].type) {
+ case SANE_TYPE_INT: {
+ dbg.vlog(DBG_proc, "value: %d", *reinterpret_cast<SANE_Word*>(val));
+ return;
+ }
+ case SANE_TYPE_BOOL: {
+ dbg.vlog(DBG_proc, "value: %s", *reinterpret_cast<SANE_Bool*>(val) ? "true" : "false");
+ return;
+ }
+ case SANE_TYPE_FIXED: {
+ dbg.vlog(DBG_proc, "value: %f", SANE_UNFIX(*reinterpret_cast<SANE_Word*>(val)));
+ return;
+ }
+ case SANE_TYPE_STRING: {
+ dbg.vlog(DBG_proc, "value: %s", reinterpret_cast<char*>(val));
+ return;
+ }
+ default: break;
+ }
+ dbg.log(DBG_proc, "value: (non-printable)");
+}
+
+static void get_option_value(Genesys_Scanner* s, int option, void* val)
+{
+ DBG_HELPER_ARGS(dbg, "option: %s (%d)", s->opt[option].name, option);
+ unsigned int i;
+ SANE_Word* table = nullptr;
+ std::vector<uint16_t> gamma_table;
+ unsigned option_size = 0;
+
+ const Genesys_Sensor* sensor = nullptr;
+ if (sanei_genesys_has_sensor(s->dev, s->dev->settings.xres, s->dev->settings.get_channels(),
+ s->dev->settings.scan_method))
+ {
+ sensor = &sanei_genesys_find_sensor(s->dev, s->dev->settings.xres,
+ s->dev->settings.get_channels(),
+ s->dev->settings.scan_method);
+ }
+
+ switch (option)
+ {
+ /* geometry */
+ case OPT_TL_X:
+ *reinterpret_cast<SANE_Word*>(val) = s->pos_top_left_x;
+ break;
+ case OPT_TL_Y:
+ *reinterpret_cast<SANE_Word*>(val) = s->pos_top_left_y;
+ break;
+ case OPT_BR_X:
+ *reinterpret_cast<SANE_Word*>(val) = s->pos_bottom_right_x;
+ break;
+ case OPT_BR_Y:
+ *reinterpret_cast<SANE_Word*>(val) = s->pos_bottom_right_y;
+ break;
+ /* word options: */
+ case OPT_NUM_OPTS:
+ *reinterpret_cast<SANE_Word*>(val) = NUM_OPTIONS;
+ break;
+ case OPT_RESOLUTION:
+ *reinterpret_cast<SANE_Word*>(val) = s->resolution;
+ break;
+ case OPT_BIT_DEPTH:
+ *reinterpret_cast<SANE_Word*>(val) = s->bit_depth;
+ break;
+ case OPT_PREVIEW:
+ *reinterpret_cast<SANE_Word*>(val) = s->preview;
+ break;
+ case OPT_THRESHOLD:
+ *reinterpret_cast<SANE_Word*>(val) = s->threshold;
+ break;
+ case OPT_THRESHOLD_CURVE:
+ *reinterpret_cast<SANE_Word*>(val) = s->threshold_curve;
+ break;
+ case OPT_DISABLE_INTERPOLATION:
+ *reinterpret_cast<SANE_Word*>(val) = s->disable_interpolation;
+ break;
+ case OPT_LAMP_OFF:
+ *reinterpret_cast<SANE_Word*>(val) = s->lamp_off;
+ break;
+ case OPT_LAMP_OFF_TIME:
+ *reinterpret_cast<SANE_Word*>(val) = s->lamp_off_time;
+ break;
+ case OPT_SWDESKEW:
+ *reinterpret_cast<SANE_Word*>(val) = s->swdeskew;
+ break;
+ case OPT_SWCROP:
+ *reinterpret_cast<SANE_Word*>(val) = s->swcrop;
+ break;
+ case OPT_SWDESPECK:
+ *reinterpret_cast<SANE_Word*>(val) = s->swdespeck;
+ break;
+ case OPT_SWDEROTATE:
+ *reinterpret_cast<SANE_Word*>(val) = s->swderotate;
+ break;
+ case OPT_SWSKIP:
+ *reinterpret_cast<SANE_Word*>(val) = s->swskip;
+ break;
+ case OPT_DESPECK:
+ *reinterpret_cast<SANE_Word*>(val) = s->despeck;
+ break;
+ case OPT_CONTRAST:
+ *reinterpret_cast<SANE_Word*>(val) = s->contrast;
+ break;
+ case OPT_BRIGHTNESS:
+ *reinterpret_cast<SANE_Word*>(val) = s->brightness;
+ break;
+ case OPT_EXPIRATION_TIME:
+ *reinterpret_cast<SANE_Word*>(val) = s->expiration_time;
+ break;
+ case OPT_CUSTOM_GAMMA:
+ *reinterpret_cast<SANE_Word*>(val) = s->custom_gamma;
+ break;
+
+ /* string options: */
+ case OPT_MODE:
+ std::strcpy(reinterpret_cast<char*>(val), s->mode.c_str());
+ break;
+ case OPT_COLOR_FILTER:
+ std::strcpy(reinterpret_cast<char*>(val), s->color_filter.c_str());
+ break;
+ case OPT_CALIBRATION_FILE:
+ std::strcpy(reinterpret_cast<char*>(val), s->calibration_file.c_str());
+ break;
+ case OPT_SOURCE:
+ std::strcpy(reinterpret_cast<char*>(val), scan_method_to_option_string(s->scan_method));
+ break;
+
+ /* word array options */
+ case OPT_GAMMA_VECTOR:
+ if (!sensor)
+ throw SaneException("Unsupported scanner mode selected");
+
+ table = reinterpret_cast<SANE_Word*>(val);
+ if (s->color_filter == "Red") {
+ gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_RED);
+ } else if (s->color_filter == "Blue") {
+ gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_BLUE);
+ } else {
+ gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_GREEN);
+ }
+ option_size = s->opt[option].size / sizeof (SANE_Word);
+ if (gamma_table.size() != option_size) {
+ throw std::runtime_error("The size of the gamma tables does not match");
+ }
+ for (i = 0; i < option_size; i++) {
+ table[i] = gamma_table[i];
+ }
+ break;
+ case OPT_GAMMA_VECTOR_R:
+ if (!sensor)
+ throw SaneException("Unsupported scanner mode selected");
+
+ table = reinterpret_cast<SANE_Word*>(val);
+ gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_RED);
+ option_size = s->opt[option].size / sizeof (SANE_Word);
+ if (gamma_table.size() != option_size) {
+ throw std::runtime_error("The size of the gamma tables does not match");
+ }
+ for (i = 0; i < option_size; i++) {
+ table[i] = gamma_table[i];
+ }
+ break;
+ case OPT_GAMMA_VECTOR_G:
+ if (!sensor)
+ throw SaneException("Unsupported scanner mode selected");
+
+ table = reinterpret_cast<SANE_Word*>(val);
+ gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_GREEN);
+ option_size = s->opt[option].size / sizeof (SANE_Word);
+ if (gamma_table.size() != option_size) {
+ throw std::runtime_error("The size of the gamma tables does not match");
+ }
+ for (i = 0; i < option_size; i++) {
+ table[i] = gamma_table[i];
+ }
+ break;
+ case OPT_GAMMA_VECTOR_B:
+ if (!sensor)
+ throw SaneException("Unsupported scanner mode selected");
+
+ table = reinterpret_cast<SANE_Word*>(val);
+ gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_BLUE);
+ option_size = s->opt[option].size / sizeof (SANE_Word);
+ if (gamma_table.size() != option_size) {
+ throw std::runtime_error("The size of the gamma tables does not match");
+ }
+ for (i = 0; i < option_size; i++) {
+ table[i] = gamma_table[i];
+ }
+ break;
+ /* sensors */
+ case OPT_SCAN_SW:
+ case OPT_FILE_SW:
+ case OPT_EMAIL_SW:
+ case OPT_COPY_SW:
+ case OPT_PAGE_LOADED_SW:
+ case OPT_OCR_SW:
+ case OPT_POWER_SW:
+ case OPT_EXTRA_SW:
+ s->dev->cmd_set->update_hardware_sensors(s);
+ *reinterpret_cast<SANE_Bool*>(val) = s->buttons[genesys_option_to_button(option)].read();
+ break;
+
+ case OPT_NEED_CALIBRATION_SW: {
+ if (!sensor) {
+ throw SaneException("Unsupported scanner mode selected");
+ }
+
+ // scanner needs calibration for current mode unless a matching calibration cache is
+ // found
+
+ bool result = true;
+
+ auto session = s->dev->cmd_set->calculate_scan_session(s->dev, *sensor,
+ s->dev->settings);
+
+ for (auto& cache : s->dev->calibration_cache) {
+ if (sanei_genesys_is_compatible_calibration(s->dev, session, &cache, false)) {
+ *reinterpret_cast<SANE_Bool*>(val) = SANE_FALSE;
+ }
+ }
+ *reinterpret_cast<SANE_Bool*>(val) = result;
+ break;
+ }
+ default:
+ DBG(DBG_warn, "%s: can't get unknown option %d\n", __func__, option);
+ }
+ print_option(dbg, *s, option, val);
+}
+
+/** @brief set calibration file value
+ * Set calibration file value. Load new cache values from file if it exists,
+ * else creates the file*/
+static void set_calibration_value(Genesys_Scanner* s, const char* val)
+{
+ DBG_HELPER(dbg);
+
+ std::string new_calib_path = val;
+ Genesys_Device::Calibration new_calibration;
+
+ bool is_calib_success = false;
+ catch_all_exceptions(__func__, [&]()
+ {
+ is_calib_success = sanei_genesys_read_calibration(new_calibration, new_calib_path);
+ });
+
+ if (!is_calib_success) {
+ return;
+ }
+
+ s->dev->calibration_cache = std::move(new_calibration);
+ s->dev->calib_file = new_calib_path;
+ s->calibration_file = new_calib_path;
+ DBG(DBG_info, "%s: Calibration filename set to '%s':\n", __func__, new_calib_path.c_str());
+}
+
+/* sets an option , called by sane_control_option */
+static void set_option_value(Genesys_Scanner* s, int option, void *val, SANE_Int* myinfo)
+{
+ DBG_HELPER_ARGS(dbg, "option: %s (%d)", s->opt[option].name, option);
+ print_option(dbg, *s, option, val);
+
+ SANE_Word *table;
+ unsigned int i;
+ unsigned option_size = 0;
+
+ switch (option)
+ {
+ case OPT_TL_X:
+ s->pos_top_left_x = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_TL_Y:
+ s->pos_top_left_y = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_BR_X:
+ s->pos_bottom_right_x = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_BR_Y:
+ s->pos_bottom_right_y = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_RESOLUTION:
+ s->resolution = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_THRESHOLD:
+ s->threshold = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_THRESHOLD_CURVE:
+ s->threshold_curve = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_SWCROP:
+ s->swcrop = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_SWDESKEW:
+ s->swdeskew = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_DESPECK:
+ s->despeck = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_SWDEROTATE:
+ s->swderotate = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_SWSKIP:
+ s->swskip = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_DISABLE_INTERPOLATION:
+ s->disable_interpolation = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_LAMP_OFF:
+ s->lamp_off = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_PREVIEW:
+ s->preview = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_BRIGHTNESS:
+ s->brightness = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_CONTRAST:
+ s->contrast = *reinterpret_cast<SANE_Word*>(val);
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS;
+ break;
+ case OPT_SWDESPECK:
+ s->swdespeck = *reinterpret_cast<SANE_Word*>(val);
+ if (s->swdespeck) {
+ ENABLE(OPT_DESPECK);
+ } else {
+ DISABLE(OPT_DESPECK);
+ }
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
+ break;
+ /* software enhancement functions only apply to 8 or 1 bits data */
+ case OPT_BIT_DEPTH:
+ s->bit_depth = *reinterpret_cast<SANE_Word*>(val);
+ if(s->bit_depth>8)
+ {
+ DISABLE(OPT_SWDESKEW);
+ DISABLE(OPT_SWDESPECK);
+ DISABLE(OPT_SWCROP);
+ DISABLE(OPT_DESPECK);
+ DISABLE(OPT_SWDEROTATE);
+ DISABLE(OPT_SWSKIP);
+ DISABLE(OPT_CONTRAST);
+ DISABLE(OPT_BRIGHTNESS);
+ }
+ else
+ {
+ ENABLE(OPT_SWDESKEW);
+ ENABLE(OPT_SWDESPECK);
+ ENABLE(OPT_SWCROP);
+ ENABLE(OPT_DESPECK);
+ ENABLE(OPT_SWDEROTATE);
+ ENABLE(OPT_SWSKIP);
+ ENABLE(OPT_CONTRAST);
+ ENABLE(OPT_BRIGHTNESS);
+ }
+ calc_parameters(s);
+ *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
+ break;
+ case OPT_SOURCE: {
+ auto scan_method = option_string_to_scan_method(reinterpret_cast<const char*>(val));
+ if (s->scan_method != scan_method) {
+ s->scan_method = scan_method;
+
+ set_xy_range_option_values(*s);
+ set_resolution_option_values(*s, false);
+
+ *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
+ }
+ break;
+ }
+ case OPT_MODE:
+ s->mode = reinterpret_cast<const char*>(val);
+
+ if (s->mode == SANE_VALUE_SCAN_MODE_LINEART)
+ {
+ ENABLE (OPT_THRESHOLD);
+ ENABLE (OPT_THRESHOLD_CURVE);
+ DISABLE (OPT_BIT_DEPTH);
+ if (s->dev->model->asic_type != AsicType::GL646 || !s->dev->model->is_cis) {
+ ENABLE(OPT_COLOR_FILTER);
+ }
+ }
+ else
+ {
+ DISABLE (OPT_THRESHOLD);
+ DISABLE (OPT_THRESHOLD_CURVE);
+ if (s->mode == SANE_VALUE_SCAN_MODE_GRAY)
+ {
+ if (s->dev->model->asic_type != AsicType::GL646 || !s->dev->model->is_cis) {
+ ENABLE(OPT_COLOR_FILTER);
+ }
+ create_bpp_list (s, s->dev->model->bpp_gray_values);
+ s->bit_depth = s->dev->model->bpp_gray_values[0];
+ }
+ else
+ {
+ DISABLE (OPT_COLOR_FILTER);
+ create_bpp_list (s, s->dev->model->bpp_color_values);
+ s->bit_depth = s->dev->model->bpp_color_values[0];
+ }
+ }
+ calc_parameters(s);
+
+ /* if custom gamma, toggle gamma table options according to the mode */
+ if (s->custom_gamma)
+ {
+ if (s->mode == SANE_VALUE_SCAN_MODE_COLOR)
+ {
+ DISABLE (OPT_GAMMA_VECTOR);
+ ENABLE (OPT_GAMMA_VECTOR_R);
+ ENABLE (OPT_GAMMA_VECTOR_G);
+ ENABLE (OPT_GAMMA_VECTOR_B);
+ }
+ else
+ {
+ ENABLE (OPT_GAMMA_VECTOR);
+ DISABLE (OPT_GAMMA_VECTOR_R);
+ DISABLE (OPT_GAMMA_VECTOR_G);
+ DISABLE (OPT_GAMMA_VECTOR_B);
+ }
+ }
+
+ *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
+ break;
+ case OPT_COLOR_FILTER:
+ s->color_filter = reinterpret_cast<const char*>(val);
+ calc_parameters(s);
+ break;
+ case OPT_CALIBRATION_FILE:
+ if (s->dev->force_calibration == 0) {
+ set_calibration_value(s, reinterpret_cast<const char*>(val));
+ }
+ break;
+ case OPT_LAMP_OFF_TIME:
+ if (*reinterpret_cast<SANE_Word*>(val) != s->lamp_off_time) {
+ s->lamp_off_time = *reinterpret_cast<SANE_Word*>(val);
+ s->dev->cmd_set->set_powersaving(s->dev, s->lamp_off_time);
+ }
+ break;
+ case OPT_EXPIRATION_TIME:
+ if (*reinterpret_cast<SANE_Word*>(val) != s->expiration_time) {
+ s->expiration_time = *reinterpret_cast<SANE_Word*>(val);
+ // BUG: this is most likely not intended behavior, found out during refactor
+ s->dev->cmd_set->set_powersaving(s->dev, s->expiration_time);
+ }
+ break;
+
+ case OPT_CUSTOM_GAMMA:
+ *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
+ s->custom_gamma = *reinterpret_cast<SANE_Bool*>(val);
+
+ if (s->custom_gamma) {
+ if (s->mode == SANE_VALUE_SCAN_MODE_COLOR)
+ {
+ DISABLE (OPT_GAMMA_VECTOR);
+ ENABLE (OPT_GAMMA_VECTOR_R);
+ ENABLE (OPT_GAMMA_VECTOR_G);
+ ENABLE (OPT_GAMMA_VECTOR_B);
+ }
+ else
+ {
+ ENABLE (OPT_GAMMA_VECTOR);
+ DISABLE (OPT_GAMMA_VECTOR_R);
+ DISABLE (OPT_GAMMA_VECTOR_G);
+ DISABLE (OPT_GAMMA_VECTOR_B);
+ }
+ }
+ else
+ {
+ DISABLE (OPT_GAMMA_VECTOR);
+ DISABLE (OPT_GAMMA_VECTOR_R);
+ DISABLE (OPT_GAMMA_VECTOR_G);
+ DISABLE (OPT_GAMMA_VECTOR_B);
+ for (auto& table : s->dev->gamma_override_tables) {
+ table.clear();
+ }
+ }
+ break;
+
+ case OPT_GAMMA_VECTOR:
+ table = reinterpret_cast<SANE_Word*>(val);
+ option_size = s->opt[option].size / sizeof (SANE_Word);
+
+ s->dev->gamma_override_tables[GENESYS_RED].resize(option_size);
+ s->dev->gamma_override_tables[GENESYS_GREEN].resize(option_size);
+ s->dev->gamma_override_tables[GENESYS_BLUE].resize(option_size);
+ for (i = 0; i < option_size; i++) {
+ s->dev->gamma_override_tables[GENESYS_RED][i] = table[i];
+ s->dev->gamma_override_tables[GENESYS_GREEN][i] = table[i];
+ s->dev->gamma_override_tables[GENESYS_BLUE][i] = table[i];
+ }
+ break;
+ case OPT_GAMMA_VECTOR_R:
+ table = reinterpret_cast<SANE_Word*>(val);
+ option_size = s->opt[option].size / sizeof (SANE_Word);
+ s->dev->gamma_override_tables[GENESYS_RED].resize(option_size);
+ for (i = 0; i < option_size; i++) {
+ s->dev->gamma_override_tables[GENESYS_RED][i] = table[i];
+ }
+ break;
+ case OPT_GAMMA_VECTOR_G:
+ table = reinterpret_cast<SANE_Word*>(val);
+ option_size = s->opt[option].size / sizeof (SANE_Word);
+ s->dev->gamma_override_tables[GENESYS_GREEN].resize(option_size);
+ for (i = 0; i < option_size; i++) {
+ s->dev->gamma_override_tables[GENESYS_GREEN][i] = table[i];
+ }
+ break;
+ case OPT_GAMMA_VECTOR_B:
+ table = reinterpret_cast<SANE_Word*>(val);
+ option_size = s->opt[option].size / sizeof (SANE_Word);
+ s->dev->gamma_override_tables[GENESYS_BLUE].resize(option_size);
+ for (i = 0; i < option_size; i++) {
+ s->dev->gamma_override_tables[GENESYS_BLUE][i] = table[i];
+ }
+ break;
+ case OPT_CALIBRATE: {
+ auto& sensor = sanei_genesys_find_sensor_for_write(s->dev, s->dev->settings.xres,
+ s->dev->settings.get_channels(),
+ s->dev->settings.scan_method);
+ catch_all_exceptions(__func__, [&]()
+ {
+ s->dev->cmd_set->save_power(s->dev, false);
+ genesys_scanner_calibration(s->dev, sensor);
+ });
+ catch_all_exceptions(__func__, [&]()
+ {
+ s->dev->cmd_set->save_power(s->dev, true);
+ });
+ *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
+ break;
+ }
+ case OPT_CLEAR_CALIBRATION:
+ s->dev->calibration_cache.clear();
+
+ /* remove file */
+ unlink(s->dev->calib_file.c_str());
+ /* signals that sensors will have to be read again */
+ *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
+ break;
+ case OPT_FORCE_CALIBRATION:
+ s->dev->force_calibration = 1;
+ s->dev->calibration_cache.clear();
+ s->dev->calib_file.clear();
+
+ /* signals that sensors will have to be read again */
+ *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
+ break;
+
+ case OPT_IGNORE_OFFSETS: {
+ s->dev->ignore_offsets = true;
+ break;
+ }
+ default:
+ DBG(DBG_warn, "%s: can't set unknown option %d\n", __func__, option);
+ }
+}
+
+
+/* sets and gets scanner option values */
+void sane_control_option_impl(SANE_Handle handle, SANE_Int option,
+ SANE_Action action, void *val, SANE_Int * info)
+{
+ Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle);
+ auto action_str = (action == SANE_ACTION_GET_VALUE) ? "get" :
+ (action == SANE_ACTION_SET_VALUE) ? "set" :
+ (action == SANE_ACTION_SET_AUTO) ? "set_auto" : "unknown";
+ DBG_HELPER_ARGS(dbg, "action = %s, option = %s (%d)", action_str,
+ s->opt[option].name, option);
+
+ SANE_Word cap;
+ SANE_Int myinfo = 0;
+
+ if (info) {
+ *info = 0;
+ }
+
+ if (s->scanning) {
+ throw SaneException(SANE_STATUS_DEVICE_BUSY,
+ "don't call this function while scanning (option = %s (%d))",
+ s->opt[option].name, option);
+ }
+ if (option >= NUM_OPTIONS || option < 0) {
+ throw SaneException("option %d >= NUM_OPTIONS || option < 0", option);
+ }
+
+ cap = s->opt[option].cap;
+
+ if (!SANE_OPTION_IS_ACTIVE (cap)) {
+ throw SaneException("option %d is inactive", option);
+ }
+
+ switch (action) {
+ case SANE_ACTION_GET_VALUE:
+ get_option_value(s, option, val);
+ break;
+
+ case SANE_ACTION_SET_VALUE:
+ if (!SANE_OPTION_IS_SETTABLE (cap)) {
+ throw SaneException("option %d is not settable", option);
+ }
+
+ TIE(sanei_constrain_value(s->opt + option, val, &myinfo));
+
+ set_option_value(s, option, val, &myinfo);
+ break;
+
+ case SANE_ACTION_SET_AUTO:
+ throw SaneException("SANE_ACTION_SET_AUTO unsupported since no option "
+ "has SANE_CAP_AUTOMATIC");
+ default:
+ throw SaneException("unknown action %d for option %d", action, option);
+ }
+
+ if (info)
+ *info = myinfo;
+}
+
+SANE_GENESYS_API_LINKAGE
+SANE_Status sane_control_option(SANE_Handle handle, SANE_Int option,
+ SANE_Action action, void *val, SANE_Int * info)
+{
+ return wrap_exceptions_to_status_code(__func__, [=]()
+ {
+ sane_control_option_impl(handle, option, action, val, info);
+ });
+}
+
+void sane_get_parameters_impl(SANE_Handle handle, SANE_Parameters* params)
+{
+ DBG_HELPER(dbg);
+ Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle);
+
+ /* don't recompute parameters once data reading is active, ie during scan */
+ if (!s->dev->read_active) {
+ calc_parameters(s);
+ }
+ if (params)
+ {
+ *params = s->params;
+
+ /* in the case of a sheetfed scanner, when full height is specified
+ * we override the computed line number with -1 to signal that we
+ * don't know the real document height.
+ * We don't do that doing buffering image for digital processing
+ */
+ if (s->dev->model->is_sheetfed && !s->dev->buffer_image &&
+ s->pos_bottom_right_y == s->opt[OPT_BR_Y].constraint.range->max)
+ {
+ params->lines = -1;
+ }
+ }
+ debug_dump(DBG_proc, *params);
+}
+
+SANE_GENESYS_API_LINKAGE
+SANE_Status sane_get_parameters(SANE_Handle handle, SANE_Parameters* params)
+{
+ return wrap_exceptions_to_status_code(__func__, [=]()
+ {
+ sane_get_parameters_impl(handle, params);
+ });
+}
+
+void sane_start_impl(SANE_Handle handle)
+{
+ DBG_HELPER(dbg);
+ Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle);
+
+ if (s->pos_top_left_x >= s->pos_bottom_right_x) {
+ throw SaneException("top left x >= bottom right x");
+ }
+ if (s->pos_top_left_y >= s->pos_bottom_right_y) {
+ throw SaneException("top left y >= bottom right y");
+ }
+
+ /* First make sure we have a current parameter set. Some of the
+ parameters will be overwritten below, but that's OK. */
+
+ calc_parameters(s);
+ genesys_start_scan(s->dev, s->lamp_off);
+
+ s->scanning = true;
+
+ /* allocate intermediate buffer when doing dynamic lineart */
+ if (s->dev->settings.scan_mode == ScanColorMode::LINEART) {
+ s->dev->binarize_buffer.clear();
+ s->dev->binarize_buffer.alloc(s->dev->settings.pixels);
+ s->dev->local_buffer.clear();
+ s->dev->local_buffer.alloc(s->dev->binarize_buffer.size() * 8);
+ }
+
+ /* if one of the software enhancement option is selected,
+ * we do the scan internally, process picture then put it an internal
+ * buffer. Since cropping may change scan parameters, we recompute them
+ * at the end */
+ if (s->dev->buffer_image)
+ {
+ genesys_buffer_image(s);
+
+ /* check if we need to skip this page, sheetfed scanners
+ * can go to next doc while flatbed ones can't */
+ if (s->swskip > 0 && IS_ACTIVE(OPT_SWSKIP)) {
+ auto status = sanei_magic_isBlank(&s->params,
+ s->dev->img_buffer.data(),
+ SANE_UNFIX(s->swskip));
+
+ if (status == SANE_STATUS_NO_DOCS && s->dev->model->is_sheetfed) {
+ DBG(DBG_info, "%s: blank page, recurse\n", __func__);
+ sane_start(handle);
+ return;
+ }
+
+ if (status != SANE_STATUS_GOOD) {
+ throw SaneException(status);
+ }
+ }
+
+ if (s->swdeskew) {
+ const auto& sensor = sanei_genesys_find_sensor(s->dev, s->dev->settings.xres,
+ s->dev->settings.get_channels(),
+ s->dev->settings.scan_method);
+ catch_all_exceptions(__func__, [&](){ genesys_deskew(s, sensor); });
+ }
+
+ if (s->swdespeck) {
+ catch_all_exceptions(__func__, [&](){ genesys_despeck(s); });
+ }
+
+ if(s->swcrop) {
+ catch_all_exceptions(__func__, [&](){ genesys_crop(s); });
+ }
+
+ if(s->swderotate) {
+ catch_all_exceptions(__func__, [&](){ genesys_derotate(s); });
+ }
+ }
+}
+
+SANE_GENESYS_API_LINKAGE
+SANE_Status sane_start(SANE_Handle handle)
+{
+ return wrap_exceptions_to_status_code(__func__, [=]()
+ {
+ sane_start_impl(handle);
+ });
+}
+
+void sane_read_impl(SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int* len)
+{
+ DBG_HELPER(dbg);
+ Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle);
+ Genesys_Device *dev;
+ size_t local_len;
+
+ if (!s) {
+ throw SaneException("handle is nullptr");
+ }
+
+ dev=s->dev;
+ if (!dev) {
+ throw SaneException("dev is nullptr");
+ }
+
+ if (!buf) {
+ throw SaneException("buf is nullptr");
+ }
+
+ if (!len) {
+ throw SaneException("len is nullptr");
+ }
+
+ *len = 0;
+
+ if (!s->scanning) {
+ throw SaneException(SANE_STATUS_CANCELLED,
+ "scan was cancelled, is over or has not been initiated yet");
+ }
+
+ DBG(DBG_proc, "%s: start, %d maximum bytes required\n", __func__, max_len);
+ DBG(DBG_io2, "%s: bytes_to_read=%zu, total_bytes_read=%zu\n", __func__,
+ dev->total_bytes_to_read, dev->total_bytes_read);
+
+ if(dev->total_bytes_read>=dev->total_bytes_to_read)
+ {
+ DBG(DBG_proc, "%s: nothing more to scan: EOF\n", __func__);
+
+ /* issue park command immediatly in case scanner can handle it
+ * so we save time */
+ if (!dev->model->is_sheetfed && !(dev->model->flags & GENESYS_FLAG_MUST_WAIT) &&
+ !dev->parking)
+ {
+ dev->cmd_set->move_back_home(dev, false);
+ dev->parking = true;
+ }
+ throw SaneException(SANE_STATUS_EOF);
+ }
+
+ local_len = max_len;
+
+ /* in case of image processing, all data has been stored in
+ * buffer_image. So read data from it if it exists, else from scanner */
+ if(!dev->buffer_image)
+ {
+ /* dynamic lineart is another kind of digital processing that needs
+ * another layer of buffering on top of genesys_read_ordered_data */
+ if (dev->settings.scan_mode == ScanColorMode::LINEART) {
+ /* if buffer is empty, fill it with genesys_read_ordered_data */
+ if(dev->binarize_buffer.avail() == 0)
+ {
+ /* store gray data */
+ local_len=dev->local_buffer.size();
+ dev->local_buffer.reset();
+ genesys_read_ordered_data(dev, dev->local_buffer.get_write_pos(local_len),
+ &local_len);
+ dev->local_buffer.produce(local_len);
+
+ dev->binarize_buffer.reset();
+ if (!is_testing_mode()) {
+ genesys_gray_lineart(dev, dev->local_buffer.get_read_pos(),
+ dev->binarize_buffer.get_write_pos(local_len / 8),
+ dev->settings.pixels,
+ local_len / dev->settings.pixels,
+ dev->settings.threshold);
+ }
+ dev->binarize_buffer.produce(local_len / 8);
+ }
+
+ /* return data from lineart buffer if any, up to the available amount */
+ local_len = max_len;
+ if (static_cast<std::size_t>(max_len) > dev->binarize_buffer.avail())
+ {
+ local_len=dev->binarize_buffer.avail();
+ }
+ if(local_len)
+ {
+ memcpy(buf, dev->binarize_buffer.get_read_pos(), local_len);
+ dev->binarize_buffer.consume(local_len);
+ }
+ }
+ else
+ {
+ // most usual case, direct read of data from scanner */
+ genesys_read_ordered_data(dev, buf, &local_len);
+ }
+ }
+ else /* read data from buffer */
+ {
+ if(dev->total_bytes_read+local_len>dev->total_bytes_to_read)
+ {
+ local_len=dev->total_bytes_to_read-dev->total_bytes_read;
+ }
+ memcpy(buf, dev->img_buffer.data() + dev->total_bytes_read, local_len);
+ dev->total_bytes_read+=local_len;
+ }
+
+ *len = local_len;
+ if (local_len > static_cast<std::size_t>(max_len)) {
+ fprintf (stderr, "[genesys] sane_read: returning incorrect length!!\n");
+ }
+ DBG(DBG_proc, "%s: %d bytes returned\n", __func__, *len);
+}
+
+SANE_GENESYS_API_LINKAGE
+SANE_Status sane_read(SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int* len)
+{
+ return wrap_exceptions_to_status_code(__func__, [=]()
+ {
+ sane_read_impl(handle, buf, max_len, len);
+ });
+}
+
+void sane_cancel_impl(SANE_Handle handle)
+{
+ DBG_HELPER(dbg);
+ Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle);
+
+ s->scanning = false;
+ s->dev->read_active = false;
+ s->dev->img_buffer.clear();
+
+ /* no need to end scan if we are parking the head */
+ if (!s->dev->parking) {
+ s->dev->cmd_set->end_scan(s->dev, &s->dev->reg, true);
+ }
+
+ /* park head if flatbed scanner */
+ if (!s->dev->model->is_sheetfed) {
+ if (!s->dev->parking) {
+ s->dev->cmd_set->move_back_home (s->dev, s->dev->model->flags &
+ GENESYS_FLAG_MUST_WAIT);
+
+ s->dev->parking = !(s->dev->model->flags & GENESYS_FLAG_MUST_WAIT);
+ }
+ }
+ else
+ { /* in case of sheetfed scanners, we have to eject the document if still present */
+ s->dev->cmd_set->eject_document(s->dev);
+ }
+
+ /* enable power saving mode unless we are parking .... */
+ if (!s->dev->parking) {
+ s->dev->cmd_set->save_power(s->dev, true);
+ }
+
+ return;
+}
+
+SANE_GENESYS_API_LINKAGE
+void sane_cancel(SANE_Handle handle)
+{
+ catch_all_exceptions(__func__, [=]() { sane_cancel_impl(handle); });
+}
+
+void sane_set_io_mode_impl(SANE_Handle handle, SANE_Bool non_blocking)
+{
+ DBG_HELPER_ARGS(dbg, "handle = %p, non_blocking = %s", handle,
+ non_blocking == SANE_TRUE ? "true" : "false");
+ Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle);
+
+ if (!s->scanning) {
+ throw SaneException("not scanning");
+ }
+ if (non_blocking) {
+ throw SaneException(SANE_STATUS_UNSUPPORTED);
+ }
+}
+
+SANE_GENESYS_API_LINKAGE
+SANE_Status sane_set_io_mode(SANE_Handle handle, SANE_Bool non_blocking)
+{
+ return wrap_exceptions_to_status_code(__func__, [=]()
+ {
+ sane_set_io_mode_impl(handle, non_blocking);
+ });
+}
+
+void sane_get_select_fd_impl(SANE_Handle handle, SANE_Int* fd)
+{
+ DBG_HELPER_ARGS(dbg, "handle = %p, fd = %p", handle, reinterpret_cast<void*>(fd));
+ Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle);
+
+ if (!s->scanning) {
+ throw SaneException("not scanning");
+ }
+ throw SaneException(SANE_STATUS_UNSUPPORTED);
+}
+
+SANE_GENESYS_API_LINKAGE
+SANE_Status sane_get_select_fd(SANE_Handle handle, SANE_Int* fd)
+{
+ return wrap_exceptions_to_status_code(__func__, [=]()
+ {
+ sane_get_select_fd_impl(handle, fd);
+ });
+}
+
+GenesysButtonName genesys_option_to_button(int option)
+{
+ switch (option) {
+ case OPT_SCAN_SW: return BUTTON_SCAN_SW;
+ case OPT_FILE_SW: return BUTTON_FILE_SW;
+ case OPT_EMAIL_SW: return BUTTON_EMAIL_SW;
+ case OPT_COPY_SW: return BUTTON_COPY_SW;
+ case OPT_PAGE_LOADED_SW: return BUTTON_PAGE_LOADED_SW;
+ case OPT_OCR_SW: return BUTTON_OCR_SW;
+ case OPT_POWER_SW: return BUTTON_POWER_SW;
+ case OPT_EXTRA_SW: return BUTTON_EXTRA_SW;
+ default: throw std::runtime_error("Unknown option to convert to button index");
+ }
+}
+
+} // namespace genesys
diff --git a/backend/genesys/genesys.h b/backend/genesys/genesys.h
new file mode 100644
index 0000000..255bf76
--- /dev/null
+++ b/backend/genesys/genesys.h
@@ -0,0 +1,258 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
+ Copyright (C) 2005-2013 Stephane Voltz <stef.dev@free.fr>
+ Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com>
+ Copyright (C) 2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
+
+ 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 GENESYS_H
+#define GENESYS_H
+
+#ifndef BACKEND_NAME
+# define BACKEND_NAME genesys
+#endif
+
+#include "low.h"
+#include <queue>
+
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+#if defined(_WIN32) || defined(HAVE_OS2_H)
+# define PATH_SEP '\\'
+#else
+# define PATH_SEP '/'
+#endif
+
+
+#define ENABLE(OPTION) s->opt[OPTION].cap &= ~SANE_CAP_INACTIVE
+#define DISABLE(OPTION) s->opt[OPTION].cap |= SANE_CAP_INACTIVE
+#define IS_ACTIVE(OPTION) (((s->opt[OPTION].cap) & SANE_CAP_INACTIVE) == 0)
+
+#define GENESYS_CONFIG_FILE "genesys.conf"
+
+#ifndef SANE_I18N
+#define SANE_I18N(text) text
+#endif
+
+#define STR_FLATBED SANE_I18N("Flatbed")
+#define STR_TRANSPARENCY_ADAPTER SANE_I18N("Transparency Adapter")
+#define STR_TRANSPARENCY_ADAPTER_INFRARED SANE_I18N("Transparency Adapter Infrared")
+
+namespace genesys {
+
+/** List of SANE options
+ */
+enum Genesys_Option
+{
+ OPT_NUM_OPTS = 0,
+
+ OPT_MODE_GROUP,
+ OPT_MODE,
+ OPT_SOURCE,
+ OPT_PREVIEW,
+ OPT_BIT_DEPTH,
+ OPT_RESOLUTION,
+
+ OPT_GEOMETRY_GROUP,
+ OPT_TL_X, /* top-left x */
+ OPT_TL_Y, /* top-left y */
+ OPT_BR_X, /* bottom-right x */
+ OPT_BR_Y, /* bottom-right y */
+
+ /* advanced image enhancement options */
+ OPT_ENHANCEMENT_GROUP,
+ OPT_CUSTOM_GAMMA, /* toggle to enable custom gamma tables */
+ OPT_GAMMA_VECTOR,
+ OPT_GAMMA_VECTOR_R,
+ OPT_GAMMA_VECTOR_G,
+ OPT_GAMMA_VECTOR_B,
+ OPT_SWDESKEW,
+ OPT_SWCROP,
+ OPT_SWDESPECK,
+ OPT_DESPECK,
+ OPT_SWSKIP,
+ OPT_SWDEROTATE,
+ OPT_BRIGHTNESS,
+ OPT_CONTRAST,
+
+ OPT_EXTRAS_GROUP,
+ OPT_LAMP_OFF_TIME,
+ OPT_LAMP_OFF,
+ OPT_THRESHOLD,
+ OPT_THRESHOLD_CURVE,
+ OPT_DISABLE_INTERPOLATION,
+ OPT_COLOR_FILTER,
+ OPT_CALIBRATION_FILE,
+ OPT_EXPIRATION_TIME,
+
+ OPT_SENSOR_GROUP,
+ OPT_SCAN_SW,
+ OPT_FILE_SW,
+ OPT_EMAIL_SW,
+ OPT_COPY_SW,
+ OPT_PAGE_LOADED_SW,
+ OPT_OCR_SW,
+ OPT_POWER_SW,
+ OPT_EXTRA_SW,
+ OPT_NEED_CALIBRATION_SW,
+ OPT_BUTTON_GROUP,
+ OPT_CALIBRATE,
+ OPT_CLEAR_CALIBRATION,
+ OPT_FORCE_CALIBRATION,
+ OPT_IGNORE_OFFSETS,
+
+ /* must come last: */
+ NUM_OPTIONS
+};
+
+enum GenesysButtonName : unsigned {
+ BUTTON_SCAN_SW = 0,
+ BUTTON_FILE_SW,
+ BUTTON_EMAIL_SW,
+ BUTTON_COPY_SW,
+ BUTTON_PAGE_LOADED_SW,
+ BUTTON_OCR_SW,
+ BUTTON_POWER_SW,
+ BUTTON_EXTRA_SW,
+ NUM_BUTTONS
+};
+
+GenesysButtonName genesys_option_to_button(int option);
+
+class GenesysButton {
+public:
+ void write(bool value)
+ {
+ if (value == value_) {
+ return;
+ }
+ values_to_read_.push(value);
+ value_ = value;
+ }
+
+ bool read()
+ {
+ if (values_to_read_.empty()) {
+ return value_;
+ }
+ bool ret = values_to_read_.front();
+ values_to_read_.pop();
+ return ret;
+ }
+
+private:
+ bool value_ = false;
+ std::queue<bool> values_to_read_;
+};
+
+/** Scanner object. Should have better be called Session than Scanner
+ */
+struct Genesys_Scanner
+{
+ Genesys_Scanner() = default;
+ ~Genesys_Scanner() = default;
+
+ // Next scanner in list
+ struct Genesys_Scanner *next;
+
+ // Low-level device object
+ Genesys_Device* dev = nullptr;
+
+ // SANE data
+ // We are currently scanning
+ bool scanning;
+ // Option descriptors
+ SANE_Option_Descriptor opt[NUM_OPTIONS];
+
+ std::vector<SANE_Word> opt_resolution_values;
+ SANE_Range opt_x_range = {};
+ SANE_Range opt_y_range = {};
+ std::vector<const char*> opt_source_values;
+
+ // Option values
+ SANE_Word bit_depth = 0;
+ SANE_Word resolution = 0;
+ bool preview = false;
+ SANE_Word threshold = 0;
+ SANE_Word threshold_curve = 0;
+ bool disable_interpolation = false;
+ bool lamp_off = false;
+ SANE_Word lamp_off_time = 0;
+ bool swdeskew = false;
+ bool swcrop = false;
+ bool swdespeck = false;
+ bool swderotate = false;
+ SANE_Word swskip = 0;
+ SANE_Word despeck = 0;
+ SANE_Word contrast = 0;
+ SANE_Word brightness = 0;
+ SANE_Word expiration_time = 0;
+ bool custom_gamma = false;
+
+ SANE_Word pos_top_left_y = 0;
+ SANE_Word pos_top_left_x = 0;
+ SANE_Word pos_bottom_right_y = 0;
+ SANE_Word pos_bottom_right_x = 0;
+
+ std::string mode, color_filter;
+
+ // the value of the source option
+ ScanMethod scan_method = ScanMethod::FLATBED;
+
+ std::string calibration_file;
+ // Button states
+ GenesysButton buttons[NUM_BUTTONS];
+
+ // SANE Parameters
+ SANE_Parameters params = {};
+ SANE_Int bpp_list[5] = {};
+};
+
+void write_calibration(std::ostream& str, Genesys_Device::Calibration& cache);
+bool read_calibration(std::istream& str, Genesys_Device::Calibration& cache,
+ const std::string& path);
+
+} // namespace genesys
+
+#endif /* not GENESYS_H */
diff --git a/backend/genesys/gl124.cpp b/backend/genesys/gl124.cpp
new file mode 100644
index 0000000..054f1ef
--- /dev/null
+++ b/backend/genesys/gl124.cpp
@@ -0,0 +1,2269 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2010-2016 Stéphane Voltz <stef.dev@free.fr>
+
+
+ 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 "gl124.h"
+#include "gl124_registers.h"
+#include "test_settings.h"
+
+#include <vector>
+
+namespace genesys {
+namespace gl124 {
+
+/** @brief set all registers to default values .
+ * This function is called only once at the beginning and
+ * fills register startup values for registers reused across scans.
+ * Those that are rarely modified or not modified are written
+ * individually.
+ * @param dev device structure holding register set to initialize
+ */
+static void
+gl124_init_registers (Genesys_Device * dev)
+{
+ DBG_HELPER(dbg);
+
+ dev->reg.clear();
+
+ // default to LiDE 110
+ dev->reg.init_reg(0x01, 0xa2); // + REG_0x01_SHDAREA
+ dev->reg.init_reg(0x02, 0x90);
+ dev->reg.init_reg(0x03, 0x50);
+ dev->reg.init_reg(0x04, 0x03);
+ dev->reg.init_reg(0x05, 0x00);
+
+ if(dev->model->sensor_id == SensorId::CIS_CANON_LIDE_120) {
+ dev->reg.init_reg(0x06, 0x50);
+ dev->reg.init_reg(0x07, 0x00);
+ } else {
+ dev->reg.init_reg(0x03, 0x50 & ~REG_0x03_AVEENB);
+ dev->reg.init_reg(0x06, 0x50 | REG_0x06_GAIN4);
+ }
+ dev->reg.init_reg(0x09, 0x00);
+ dev->reg.init_reg(0x0a, 0xc0);
+ dev->reg.init_reg(0x0b, 0x2a);
+ dev->reg.init_reg(0x0c, 0x12);
+ dev->reg.init_reg(0x11, 0x00);
+ dev->reg.init_reg(0x12, 0x00);
+ dev->reg.init_reg(0x13, 0x0f);
+ dev->reg.init_reg(0x14, 0x00);
+ dev->reg.init_reg(0x15, 0x80);
+ dev->reg.init_reg(0x16, 0x10); // SENSOR_DEF
+ dev->reg.init_reg(0x17, 0x04); // SENSOR_DEF
+ dev->reg.init_reg(0x18, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x19, 0x01); // SENSOR_DEF
+ dev->reg.init_reg(0x1a, 0x30); // SENSOR_DEF
+ dev->reg.init_reg(0x1b, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x1c, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x1d, 0x01); // SENSOR_DEF
+ dev->reg.init_reg(0x1e, 0x10);
+ dev->reg.init_reg(0x1f, 0x00);
+ dev->reg.init_reg(0x20, 0x15); // SENSOR_DEF
+ dev->reg.init_reg(0x21, 0x00);
+ if(dev->model->sensor_id != SensorId::CIS_CANON_LIDE_120) {
+ dev->reg.init_reg(0x22, 0x02);
+ } else {
+ dev->reg.init_reg(0x22, 0x14);
+ }
+ dev->reg.init_reg(0x23, 0x00);
+ dev->reg.init_reg(0x24, 0x00);
+ dev->reg.init_reg(0x25, 0x00);
+ dev->reg.init_reg(0x26, 0x0d);
+ dev->reg.init_reg(0x27, 0x48);
+ dev->reg.init_reg(0x28, 0x00);
+ dev->reg.init_reg(0x29, 0x56);
+ dev->reg.init_reg(0x2a, 0x5e);
+ dev->reg.init_reg(0x2b, 0x02);
+ dev->reg.init_reg(0x2c, 0x02);
+ dev->reg.init_reg(0x2d, 0x58);
+ dev->reg.init_reg(0x3b, 0x00);
+ dev->reg.init_reg(0x3c, 0x00);
+ dev->reg.init_reg(0x3d, 0x00);
+ dev->reg.init_reg(0x3e, 0x00);
+ dev->reg.init_reg(0x3f, 0x02);
+ dev->reg.init_reg(0x40, 0x00);
+ dev->reg.init_reg(0x41, 0x00);
+ dev->reg.init_reg(0x42, 0x00);
+ dev->reg.init_reg(0x43, 0x00);
+ dev->reg.init_reg(0x44, 0x00);
+ dev->reg.init_reg(0x45, 0x00);
+ dev->reg.init_reg(0x46, 0x00);
+ dev->reg.init_reg(0x47, 0x00);
+ dev->reg.init_reg(0x48, 0x00);
+ dev->reg.init_reg(0x49, 0x00);
+ dev->reg.init_reg(0x4f, 0x00);
+ dev->reg.init_reg(0x52, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x53, 0x02); // SENSOR_DEF
+ dev->reg.init_reg(0x54, 0x04); // SENSOR_DEF
+ dev->reg.init_reg(0x55, 0x06); // SENSOR_DEF
+ dev->reg.init_reg(0x56, 0x04); // SENSOR_DEF
+ dev->reg.init_reg(0x57, 0x04); // SENSOR_DEF
+ dev->reg.init_reg(0x58, 0x04); // SENSOR_DEF
+ dev->reg.init_reg(0x59, 0x04); // SENSOR_DEF
+ dev->reg.init_reg(0x5a, 0x1a); // SENSOR_DEF
+ dev->reg.init_reg(0x5b, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x5c, 0xc0); // SENSOR_DEF
+ dev->reg.init_reg(0x5f, 0x00);
+ dev->reg.init_reg(0x60, 0x02);
+ dev->reg.init_reg(0x61, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x62, 0x00);
+ dev->reg.init_reg(0x63, 0x00);
+ dev->reg.init_reg(0x64, 0x00);
+ dev->reg.init_reg(0x65, 0x00);
+ dev->reg.init_reg(0x66, 0x00);
+ dev->reg.init_reg(0x67, 0x00);
+ dev->reg.init_reg(0x68, 0x00);
+ dev->reg.init_reg(0x69, 0x00);
+ dev->reg.init_reg(0x6a, 0x00);
+ dev->reg.init_reg(0x6b, 0x00);
+ dev->reg.init_reg(0x6c, 0x00);
+ dev->reg.init_reg(0x6e, 0x00);
+ dev->reg.init_reg(0x6f, 0x00);
+
+ if (dev->model->sensor_id != SensorId::CIS_CANON_LIDE_120) {
+ dev->reg.init_reg(0x6d, 0xd0);
+ dev->reg.init_reg(0x71, 0x08);
+ } else {
+ dev->reg.init_reg(0x6d, 0x00);
+ dev->reg.init_reg(0x71, 0x1f);
+ }
+ dev->reg.init_reg(0x70, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x71, 0x08); // SENSOR_DEF
+ dev->reg.init_reg(0x72, 0x08); // SENSOR_DEF
+ dev->reg.init_reg(0x73, 0x0a); // SENSOR_DEF
+
+ // CKxMAP
+ dev->reg.init_reg(0x74, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x75, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x76, 0x3c); // SENSOR_DEF
+ dev->reg.init_reg(0x77, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x78, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x79, 0x9f); // SENSOR_DEF
+ dev->reg.init_reg(0x7a, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x7b, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x7c, 0x55); // SENSOR_DEF
+
+ dev->reg.init_reg(0x7d, 0x00);
+ dev->reg.init_reg(0x7e, 0x08);
+ dev->reg.init_reg(0x7f, 0x58);
+
+ if (dev->model->sensor_id != SensorId::CIS_CANON_LIDE_120) {
+ dev->reg.init_reg(0x80, 0x00);
+ dev->reg.init_reg(0x81, 0x14);
+ } else {
+ dev->reg.init_reg(0x80, 0x00);
+ dev->reg.init_reg(0x81, 0x10);
+ }
+
+ // STRPIXEL
+ dev->reg.init_reg(0x82, 0x00);
+ dev->reg.init_reg(0x83, 0x00);
+ dev->reg.init_reg(0x84, 0x00);
+
+ // ENDPIXEL
+ dev->reg.init_reg(0x85, 0x00);
+ dev->reg.init_reg(0x86, 0x00);
+ dev->reg.init_reg(0x87, 0x00);
+
+ dev->reg.init_reg(0x88, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x89, 0x65); // SENSOR_DEF
+ dev->reg.init_reg(0x8a, 0x00);
+ dev->reg.init_reg(0x8b, 0x00);
+ dev->reg.init_reg(0x8c, 0x00);
+ dev->reg.init_reg(0x8d, 0x00);
+ dev->reg.init_reg(0x8e, 0x00);
+ dev->reg.init_reg(0x8f, 0x00);
+ dev->reg.init_reg(0x90, 0x00);
+ dev->reg.init_reg(0x91, 0x00);
+ dev->reg.init_reg(0x92, 0x00);
+ dev->reg.init_reg(0x93, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x94, 0x14); // SENSOR_DEF
+ dev->reg.init_reg(0x95, 0x30); // SENSOR_DEF
+ dev->reg.init_reg(0x96, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x97, 0x90); // SENSOR_DEF
+ dev->reg.init_reg(0x98, 0x01); // SENSOR_DEF
+ dev->reg.init_reg(0x99, 0x1f);
+ dev->reg.init_reg(0x9a, 0x00);
+ dev->reg.init_reg(0x9b, 0x80);
+ dev->reg.init_reg(0x9c, 0x80);
+ dev->reg.init_reg(0x9d, 0x3f);
+ dev->reg.init_reg(0x9e, 0x00);
+ dev->reg.init_reg(0x9f, 0x00);
+ dev->reg.init_reg(0xa0, 0x20);
+ dev->reg.init_reg(0xa1, 0x30);
+ dev->reg.init_reg(0xa2, 0x00);
+ dev->reg.init_reg(0xa3, 0x20);
+ dev->reg.init_reg(0xa4, 0x01);
+ dev->reg.init_reg(0xa5, 0x00);
+ dev->reg.init_reg(0xa6, 0x00);
+ dev->reg.init_reg(0xa7, 0x08);
+ dev->reg.init_reg(0xa8, 0x00);
+ dev->reg.init_reg(0xa9, 0x08);
+ dev->reg.init_reg(0xaa, 0x01);
+ dev->reg.init_reg(0xab, 0x00);
+ dev->reg.init_reg(0xac, 0x00);
+ dev->reg.init_reg(0xad, 0x40);
+ dev->reg.init_reg(0xae, 0x01);
+ dev->reg.init_reg(0xaf, 0x00);
+ dev->reg.init_reg(0xb0, 0x00);
+ dev->reg.init_reg(0xb1, 0x40);
+ dev->reg.init_reg(0xb2, 0x00);
+ dev->reg.init_reg(0xb3, 0x09);
+ dev->reg.init_reg(0xb4, 0x5b);
+ dev->reg.init_reg(0xb5, 0x00);
+ dev->reg.init_reg(0xb6, 0x10);
+ dev->reg.init_reg(0xb7, 0x3f);
+ dev->reg.init_reg(0xb8, 0x00);
+ dev->reg.init_reg(0xbb, 0x00);
+ dev->reg.init_reg(0xbc, 0xff);
+ dev->reg.init_reg(0xbd, 0x00);
+ dev->reg.init_reg(0xbe, 0x07);
+ dev->reg.init_reg(0xc3, 0x00);
+ dev->reg.init_reg(0xc4, 0x00);
+
+ /* gamma
+ dev->reg.init_reg(0xc5, 0x00);
+ dev->reg.init_reg(0xc6, 0x00);
+ dev->reg.init_reg(0xc7, 0x00);
+ dev->reg.init_reg(0xc8, 0x00);
+ dev->reg.init_reg(0xc9, 0x00);
+ dev->reg.init_reg(0xca, 0x00);
+ dev->reg.init_reg(0xcb, 0x00);
+ dev->reg.init_reg(0xcc, 0x00);
+ dev->reg.init_reg(0xcd, 0x00);
+ dev->reg.init_reg(0xce, 0x00);
+ */
+
+ if (dev->model->sensor_id == SensorId::CIS_CANON_LIDE_120) {
+ dev->reg.init_reg(0xc5, 0x20);
+ dev->reg.init_reg(0xc6, 0xeb);
+ dev->reg.init_reg(0xc7, 0x20);
+ dev->reg.init_reg(0xc8, 0xeb);
+ dev->reg.init_reg(0xc9, 0x20);
+ dev->reg.init_reg(0xca, 0xeb);
+ }
+
+ // memory layout
+ /*
+ dev->reg.init_reg(0xd0, 0x0a);
+ dev->reg.init_reg(0xd1, 0x1f);
+ dev->reg.init_reg(0xd2, 0x34);
+ */
+ dev->reg.init_reg(0xd3, 0x00);
+ dev->reg.init_reg(0xd4, 0x00);
+ dev->reg.init_reg(0xd5, 0x00);
+ dev->reg.init_reg(0xd6, 0x00);
+ dev->reg.init_reg(0xd7, 0x00);
+ dev->reg.init_reg(0xd8, 0x00);
+ dev->reg.init_reg(0xd9, 0x00);
+
+ // memory layout
+ /*
+ dev->reg.init_reg(0xe0, 0x00);
+ dev->reg.init_reg(0xe1, 0x48);
+ dev->reg.init_reg(0xe2, 0x15);
+ dev->reg.init_reg(0xe3, 0x90);
+ dev->reg.init_reg(0xe4, 0x15);
+ dev->reg.init_reg(0xe5, 0x91);
+ dev->reg.init_reg(0xe6, 0x2a);
+ dev->reg.init_reg(0xe7, 0xd9);
+ dev->reg.init_reg(0xe8, 0x2a);
+ dev->reg.init_reg(0xe9, 0xad);
+ dev->reg.init_reg(0xea, 0x40);
+ dev->reg.init_reg(0xeb, 0x22);
+ dev->reg.init_reg(0xec, 0x40);
+ dev->reg.init_reg(0xed, 0x23);
+ dev->reg.init_reg(0xee, 0x55);
+ dev->reg.init_reg(0xef, 0x6b);
+ dev->reg.init_reg(0xf0, 0x55);
+ dev->reg.init_reg(0xf1, 0x6c);
+ dev->reg.init_reg(0xf2, 0x6a);
+ dev->reg.init_reg(0xf3, 0xb4);
+ dev->reg.init_reg(0xf4, 0x6a);
+ dev->reg.init_reg(0xf5, 0xb5);
+ dev->reg.init_reg(0xf6, 0x7f);
+ dev->reg.init_reg(0xf7, 0xfd);
+ */
+
+ dev->reg.init_reg(0xf8, 0x01); // other value is 0x05
+ dev->reg.init_reg(0xf9, 0x00);
+ dev->reg.init_reg(0xfa, 0x00);
+ dev->reg.init_reg(0xfb, 0x00);
+ dev->reg.init_reg(0xfc, 0x00);
+ dev->reg.init_reg(0xff, 0x00);
+
+ // fine tune upon device description
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+ sanei_genesys_set_dpihw(dev->reg, sensor, sensor.optical_res);
+
+ dev->calib_reg = dev->reg;
+}
+
+/**@brief send slope table for motor movement
+ * Send slope_table in machine byte order
+ * @param dev device to send slope table
+ * @param table_nr index of the slope table in ASIC memory
+ * Must be in the [0-4] range.
+ * @param slope_table pointer to 16 bit values array of the slope table
+ * @param steps number of elemnts in the slope table
+ */
+static void gl124_send_slope_table(Genesys_Device* dev, int table_nr,
+ const std::vector<uint16_t>& slope_table,
+ int steps)
+{
+ DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d", table_nr, steps);
+ int i;
+ char msg[10000];
+
+ /* sanity check */
+ if(table_nr<0 || table_nr>4)
+ {
+ throw SaneException("invalid table number");
+ }
+
+ std::vector<uint8_t> table(steps * 2);
+ for (i = 0; i < steps; i++)
+ {
+ table[i * 2] = slope_table[i] & 0xff;
+ table[i * 2 + 1] = slope_table[i] >> 8;
+ }
+
+ if (DBG_LEVEL >= DBG_io)
+ {
+ std::sprintf(msg, "write slope %d (%d)=", table_nr, steps);
+ for (i = 0; i < steps; i++) {
+ std::sprintf(msg + std::strlen(msg), ",%d", slope_table[i]);
+ }
+ DBG (DBG_io, "%s: %s\n", __func__, msg);
+ }
+
+ if (dev->interface->is_mock()) {
+ dev->interface->record_slope_table(table_nr, slope_table);
+ }
+ // slope table addresses are fixed
+ dev->interface->write_ahb(0x10000000 + 0x4000 * table_nr, steps * 2, table.data());
+}
+
+/** @brief * Set register values of 'special' ti type frontend
+ * Registers value are taken from the frontend register data
+ * set.
+ * @param dev device owning the AFE
+ * @param set flag AFE_INIT to specify the AFE must be reset before writing data
+ * */
+static void gl124_set_ti_fe(Genesys_Device* dev, uint8_t set)
+{
+ DBG_HELPER(dbg);
+ int i;
+
+ if (set == AFE_INIT)
+ {
+ DBG(DBG_proc, "%s: setting DAC %u\n", __func__,
+ static_cast<unsigned>(dev->model->adc_id));
+
+ dev->frontend = dev->frontend_initial;
+ }
+
+ // start writing to DAC
+ dev->interface->write_fe_register(0x00, 0x80);
+
+ /* write values to analog frontend */
+ for (uint16_t addr = 0x01; addr < 0x04; addr++)
+ {
+ dev->interface->write_fe_register(addr, dev->frontend.regs.get_value(addr));
+ }
+
+ dev->interface->write_fe_register(0x04, 0x00);
+
+ /* these are not really sign for this AFE */
+ for (i = 0; i < 3; i++)
+ {
+ dev->interface->write_fe_register(0x05 + i, dev->frontend.regs.get_value(0x24 + i));
+ }
+
+ if (dev->model->adc_id == AdcId::CANON_LIDE_120) {
+ dev->interface->write_fe_register(0x00, 0x01);
+ }
+ else
+ {
+ dev->interface->write_fe_register(0x00, 0x11);
+ }
+}
+
+
+// Set values of analog frontend
+void CommandSetGl124::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const
+{
+ DBG_HELPER_ARGS(dbg, "%s", set == AFE_INIT ? "init" :
+ set == AFE_SET ? "set" :
+ set == AFE_POWER_SAVE ? "powersave" : "huh?");
+ (void) sensor;
+ uint8_t val;
+
+ if (set == AFE_INIT)
+ {
+ DBG(DBG_proc, "%s(): setting DAC %u\n", __func__,
+ static_cast<unsigned>(dev->model->adc_id));
+ dev->frontend = dev->frontend_initial;
+ }
+
+ val = dev->interface->read_register(REG_0x0A);
+
+ /* route to correct analog FE */
+ switch ((val & REG_0x0A_SIFSEL) >> REG_0x0AS_SIFSEL) {
+ case 3:
+ gl124_set_ti_fe(dev, set);
+ break;
+ case 0:
+ case 1:
+ case 2:
+ default:
+ throw SaneException("unsupported analog FE 0x%02x", val);
+ }
+}
+
+static void gl124_init_motor_regs_scan(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const Motor_Profile& motor_profile,
+ unsigned int scan_exposure_time,
+ unsigned scan_yres,
+ unsigned int scan_lines,
+ unsigned int scan_dummy,
+ unsigned int feed_steps,
+ ScanColorMode scan_mode,
+ MotorFlag flags)
+{
+ DBG_HELPER(dbg);
+ int use_fast_fed;
+ unsigned int lincnt, fast_dpi;
+ unsigned int feedl,dist;
+ uint32_t z1, z2;
+ unsigned yres;
+ unsigned min_speed;
+ unsigned int linesel;
+
+ DBG(DBG_info, "%s : scan_exposure_time=%d, scan_yres=%d, step_type=%d, scan_lines=%d, "
+ "scan_dummy=%d, feed_steps=%d, scan_mode=%d, flags=%x\n", __func__, scan_exposure_time,
+ scan_yres, static_cast<unsigned>(motor_profile.step_type), scan_lines, scan_dummy,
+ feed_steps, static_cast<unsigned>(scan_mode),
+ static_cast<unsigned>(flags));
+
+ /* we never use fast fed since we do manual feed for the scans */
+ use_fast_fed=0;
+
+ /* enforce motor minimal scan speed
+ * @TODO extend motor struct for this value */
+ if (scan_mode == ScanColorMode::COLOR_SINGLE_PASS)
+ {
+ min_speed = 900;
+ }
+ else
+ {
+ switch(dev->model->motor_id)
+ {
+ case MotorId::CANON_LIDE_110:
+ min_speed = 600;
+ break;
+ case MotorId::CANON_LIDE_120:
+ min_speed = 900;
+ break;
+ default:
+ min_speed = 900;
+ break;
+ }
+ }
+
+ /* compute min_speed and linesel */
+ if(scan_yres<min_speed)
+ {
+ yres=min_speed;
+ linesel = yres / scan_yres - 1;
+ /* limit case, we need a linesel > 0 */
+ if(linesel==0)
+ {
+ linesel=1;
+ yres=scan_yres*2;
+ }
+ }
+ else
+ {
+ yres=scan_yres;
+ linesel=0;
+ }
+
+ DBG(DBG_io2, "%s: final yres=%d, linesel=%d\n", __func__, yres, linesel);
+
+ lincnt=scan_lines*(linesel+1);
+ reg->set24(REG_LINCNT, lincnt);
+ DBG (DBG_io, "%s: lincnt=%d\n", __func__, lincnt);
+
+ /* compute register 02 value */
+ uint8_t r02 = REG_0x02_NOTHOME;
+
+ if (use_fast_fed) {
+ r02 |= REG_0x02_FASTFED;
+ } else {
+ r02 &= ~REG_0x02_FASTFED;
+ }
+
+ if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) {
+ r02 |= REG_0x02_AGOHOME;
+ }
+
+ if (has_flag(flags, MotorFlag::DISABLE_BUFFER_FULL_MOVE) || (yres >= sensor.optical_res))
+ {
+ r02 |= REG_0x02_ACDCDIS;
+ }
+ if (has_flag(flags, MotorFlag::REVERSE)) {
+ r02 |= REG_0x02_MTRREV;
+ }
+
+ reg->set8(REG_0x02, r02);
+ sanei_genesys_set_motor_power(*reg, true);
+
+ reg->set16(REG_SCANFED, 4);
+
+ /* scan and backtracking slope table */
+ auto scan_table = sanei_genesys_slope_table(dev->model->asic_type, yres, scan_exposure_time,
+ dev->motor.base_ydpi, 1,
+ motor_profile);
+ gl124_send_slope_table(dev, SCAN_TABLE, scan_table.table, scan_table.steps_count);
+ gl124_send_slope_table(dev, BACKTRACK_TABLE, scan_table.table, scan_table.steps_count);
+
+ reg->set16(REG_STEPNO, scan_table.steps_count);
+
+ /* fast table */
+ fast_dpi=yres;
+
+ /*
+ if (scan_mode != ScanColorMode::COLOR_SINGLE_PASS)
+ {
+ fast_dpi*=3;
+ }
+ */
+ auto fast_table = sanei_genesys_slope_table(dev->model->asic_type, fast_dpi,
+ scan_exposure_time, dev->motor.base_ydpi,
+ 1, motor_profile);
+ gl124_send_slope_table(dev, STOP_TABLE, fast_table.table, fast_table.steps_count);
+ gl124_send_slope_table(dev, FAST_TABLE, fast_table.table, fast_table.steps_count);
+
+ reg->set16(REG_FASTNO, fast_table.steps_count);
+ reg->set16(REG_FSHDEC, fast_table.steps_count);
+ reg->set16(REG_FMOVNO, fast_table.steps_count);
+
+ /* substract acceleration distance from feedl */
+ feedl=feed_steps;
+ feedl <<= static_cast<unsigned>(motor_profile.step_type);
+
+ dist = scan_table.steps_count;
+ if (has_flag(flags, MotorFlag::FEED)) {
+ dist *= 2;
+ }
+ if (use_fast_fed) {
+ dist += fast_table.steps_count * 2;
+ }
+ DBG (DBG_io2, "%s: acceleration distance=%d\n", __func__, dist);
+
+ /* get sure we don't use insane value */
+ if (dist < feedl) {
+ feedl -= dist;
+ } else {
+ feedl = 0;
+ }
+
+ reg->set24(REG_FEEDL, feedl);
+ DBG (DBG_io, "%s: feedl=%d\n", __func__, feedl);
+
+ /* doesn't seem to matter that much */
+ sanei_genesys_calculate_zmod(use_fast_fed,
+ scan_exposure_time,
+ scan_table.table,
+ scan_table.steps_count,
+ feedl,
+ scan_table.steps_count,
+ &z1,
+ &z2);
+
+ reg->set24(REG_Z1MOD, z1);
+ DBG(DBG_info, "%s: z1 = %d\n", __func__, z1);
+
+ reg->set24(REG_Z2MOD, z2);
+ DBG(DBG_info, "%s: z2 = %d\n", __func__, z2);
+
+ /* LINESEL */
+ reg->set8_mask(REG_0x1D, linesel, REG_0x1D_LINESEL);
+ reg->set8(REG_0xA0, (static_cast<unsigned>(motor_profile.step_type) << REG_0xA0S_STEPSEL) |
+ (static_cast<unsigned>(motor_profile.step_type) << REG_0xA0S_FSTPSEL));
+
+ reg->set16(REG_FMOVDEC, fast_table.steps_count);
+}
+
+
+/** @brief copy sensor specific settings
+ * Set up register set for the given sensor resolution. Values are from the device table
+ * in genesys_devices.c for registers:
+ * [0x16 ... 0x1d]
+ * [0x52 ... 0x5e]
+ * Other come from the specific device sensor table in genesys_gl124.h:
+ * 0x18, 0x20, 0x61, 0x98 and
+ * @param dev device to set up
+ * @param regs register set to modify
+ * @param dpi resolution of the sensor during scan
+ * @param ccd_size_divisor flag for half ccd mode
+ * */
+static void gl124_setup_sensor(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs)
+{
+ DBG_HELPER(dbg);
+
+ for (const auto& reg : sensor.custom_regs) {
+ regs->set8(reg.address, reg.value);
+ }
+
+ regs->set24(REG_EXPR, sensor.exposure.red);
+ regs->set24(REG_EXPG, sensor.exposure.green);
+ regs->set24(REG_EXPB, sensor.exposure.blue);
+
+ dev->segment_order = sensor.segment_order;
+}
+
+/** @brief setup optical related registers
+ * start and pixels are expressed in optical sensor resolution coordinate
+ * space.
+ * @param dev scanner device to use
+ * @param reg registers to set up
+ * @param exposure_time exposure time to use
+ * @param used_res scanning resolution used, may differ from
+ * scan's one
+ * @param start logical start pixel coordinate
+ * @param pixels logical number of pixels to use
+ * @param channels number of color channels (currently 1 or 3)
+ * @param depth bit depth of the scan (1, 8 or 16)
+ * @param ccd_size_divisor whether sensor's timings are such that x coordinates must be halved
+ * @param color_filter color channel to use as gray data
+ * @param flags optical flags (@see )
+ */
+static void gl124_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, unsigned int exposure_time,
+ const ScanSession& session)
+{
+ DBG_HELPER_ARGS(dbg, "exposure_time=%d", exposure_time);
+ unsigned int dpihw;
+ GenesysRegister *r;
+ uint32_t expmax;
+
+ // resolution is divided according to ccd_pixels_per_system_pixel
+ unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel();
+ DBG(DBG_io2, "%s: ccd_pixels_per_system_pixel=%d\n", __func__, ccd_pixels_per_system_pixel);
+
+ // to manage high resolution device while keeping good low resolution scanning speed, we
+ // make hardware dpi vary
+ dpihw = sensor.get_register_hwdpi(session.output_resolution * ccd_pixels_per_system_pixel);
+ DBG(DBG_io2, "%s: dpihw=%d\n", __func__, dpihw);
+
+ gl124_setup_sensor(dev, sensor, reg);
+
+ dev->cmd_set->set_fe(dev, sensor, AFE_SET);
+
+ /* enable shading */
+ regs_set_optical_off(dev->model->asic_type, *reg);
+ r = sanei_genesys_get_address (reg, REG_0x01);
+ if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) ||
+ (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION))
+ {
+ r->value &= ~REG_0x01_DVDSET;
+ } else {
+ r->value |= REG_0x01_DVDSET;
+ }
+
+ r = sanei_genesys_get_address(reg, REG_0x03);
+ if ((dev->model->sensor_id != SensorId::CIS_CANON_LIDE_120) && (session.params.xres>=600)) {
+ r->value &= ~REG_0x03_AVEENB;
+ DBG (DBG_io, "%s: disabling AVEENB\n", __func__);
+ }
+ else
+ {
+ r->value |= ~REG_0x03_AVEENB;
+ DBG (DBG_io, "%s: enabling AVEENB\n", __func__);
+ }
+
+ sanei_genesys_set_lamp_power(dev, sensor, *reg,
+ !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP));
+
+ // BW threshold
+ dev->interface->write_register(REG_0x114, dev->settings.threshold);
+ dev->interface->write_register(REG_0x115, dev->settings.threshold);
+
+ /* monochrome / color scan */
+ r = sanei_genesys_get_address (reg, REG_0x04);
+ switch (session.params.depth) {
+ case 8:
+ r->value &= ~(REG_0x04_LINEART | REG_0x04_BITSET);
+ break;
+ case 16:
+ r->value &= ~REG_0x04_LINEART;
+ r->value |= REG_0x04_BITSET;
+ break;
+ }
+
+ r->value &= ~REG_0x04_FILTER;
+ if (session.params.channels == 1)
+ {
+ switch (session.params.color_filter)
+ {
+ case ColorFilter::RED:
+ r->value |= 0x10;
+ break;
+ case ColorFilter::BLUE:
+ r->value |= 0x30;
+ break;
+ case ColorFilter::GREEN:
+ r->value |= 0x20;
+ break;
+ default:
+ break; // should not happen
+ }
+ }
+
+ sanei_genesys_set_dpihw(*reg, sensor, dpihw);
+
+ if (should_enable_gamma(session, sensor)) {
+ reg->find_reg(REG_0x05).value |= REG_0x05_GMMENB;
+ } else {
+ reg->find_reg(REG_0x05).value &= ~REG_0x05_GMMENB;
+ }
+
+ unsigned dpiset_reg = session.output_resolution * ccd_pixels_per_system_pixel *
+ session.ccd_size_divisor;
+ if (sensor.dpiset_override != 0) {
+ dpiset_reg = sensor.dpiset_override;
+ }
+
+ reg->set16(REG_DPISET, dpiset_reg);
+ DBG (DBG_io2, "%s: dpiset used=%d\n", __func__, dpiset_reg);
+
+ r = sanei_genesys_get_address(reg, REG_0x06);
+ r->value |= REG_0x06_GAIN4;
+
+ /* CIS scanners can do true gray by setting LEDADD */
+ /* we set up LEDADD only when asked */
+ if (dev->model->is_cis) {
+ r = sanei_genesys_get_address (reg, REG_0x60);
+ r->value &= ~REG_0x60_LEDADD;
+ if (session.enable_ledadd) {
+ r->value |= REG_0x60_LEDADD;
+ expmax = reg->get24(REG_EXPR);
+ expmax = std::max(expmax, reg->get24(REG_EXPG));
+ expmax = std::max(expmax, reg->get24(REG_EXPB));
+
+ dev->reg.set24(REG_EXPR, expmax);
+ dev->reg.set24(REG_EXPG, expmax);
+ dev->reg.set24(REG_EXPB, expmax);
+ }
+ /* RGB weighting, REG_TRUER,G and B are to be set */
+ r = sanei_genesys_get_address (reg, 0x01);
+ r->value &= ~REG_0x01_TRUEGRAY;
+ if (session.enable_ledadd) {
+ r->value |= REG_0x01_TRUEGRAY;
+ dev->interface->write_register(REG_TRUER, 0x80);
+ dev->interface->write_register(REG_TRUEG, 0x80);
+ dev->interface->write_register(REG_TRUEB, 0x80);
+ }
+ }
+
+ reg->set24(REG_STRPIXEL, session.pixel_startx);
+ reg->set24(REG_ENDPIXEL, session.pixel_endx);
+
+ dev->line_count = 0;
+
+ build_image_pipeline(dev, session);
+
+ // MAXWD is expressed in 2 words unit
+
+ // BUG: we shouldn't multiply by channels here
+ reg->set24(REG_MAXWD, session.output_line_bytes_raw / session.ccd_size_divisor * session.params.channels);
+
+ reg->set24(REG_LPERIOD, exposure_time);
+ DBG (DBG_io2, "%s: exposure_time used=%d\n", __func__, exposure_time);
+
+ reg->set16(REG_DUMMY, sensor.dummy_pixel);
+}
+
+void CommandSetGl124::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const
+{
+ DBG_HELPER(dbg);
+ session.assert_computed();
+
+ int move;
+ int exposure_time;
+
+ int dummy = 0;
+ int slope_dpi = 0;
+
+ /* cis color scan is effectively a gray scan with 3 gray lines per color line and a FILTER of 0 */
+ if (dev->model->is_cis) {
+ slope_dpi = session.params.yres * session.params.channels;
+ } else {
+ slope_dpi = session.params.yres;
+ }
+
+ if (has_flag(session.params.flags, ScanFlag::FEEDING)) {
+ exposure_time = 2304;
+ } else {
+ exposure_time = sensor.exposure_lperiod;
+ }
+ const auto& motor_profile = sanei_genesys_get_motor_profile(*gl124_motor_profiles,
+ dev->model->motor_id,
+ exposure_time);
+
+ DBG(DBG_info, "%s : exposure_time=%d pixels\n", __func__, exposure_time);
+ DBG(DBG_info, "%s : scan_step_type=%d\n", __func__, static_cast<unsigned>(motor_profile.step_type));
+
+ /* we enable true gray for cis scanners only, and just when doing
+ * scan since color calibration is OK for this mode
+ */
+
+ // now _LOGICAL_ optical values used are known, setup registers
+ gl124_init_optical_regs_scan(dev, sensor, reg, exposure_time, session);
+
+ /* add tl_y to base movement */
+ move = session.params.starty;
+ DBG(DBG_info, "%s: move=%d steps\n", __func__, move);
+
+ MotorFlag mflags = MotorFlag::NONE;
+ if (has_flag(session.params.flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE)) {
+ mflags |= MotorFlag::DISABLE_BUFFER_FULL_MOVE;
+ }
+ if (has_flag(session.params.flags, ScanFlag::FEEDING)) {
+ mflags |= MotorFlag::FEED;
+ }
+ if (has_flag(session.params.flags, ScanFlag::REVERSE)) {
+ mflags |= MotorFlag::REVERSE;
+ }
+ gl124_init_motor_regs_scan(dev, sensor, reg, motor_profile, exposure_time, slope_dpi,
+ dev->model->is_cis ? session.output_line_count * session.params.channels :
+ session.output_line_count,
+ dummy, move, session.params.scan_mode, mflags);
+
+ /*** prepares data reordering ***/
+
+ dev->read_buffer.clear();
+ dev->read_buffer.alloc(session.buffer_size_read);
+
+ dev->read_active = true;
+
+ dev->session = session;
+
+ dev->total_bytes_read = 0;
+ dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines;
+
+ DBG(DBG_info, "%s: total bytes to send to frontend = %zu\n", __func__,
+ dev->total_bytes_to_read);
+}
+
+ScanSession CommandSetGl124::calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const
+{
+ int start;
+
+ DBG(DBG_info, "%s ", __func__);
+ debug_dump(DBG_info, settings);
+
+ /* start */
+ start = static_cast<int>(dev->model->x_offset);
+ start += static_cast<int>(settings.tl_x);
+ start = static_cast<int>((start * sensor.optical_res) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = settings.xres;
+ session.params.yres = settings.yres;
+ session.params.startx = start;
+ session.params.starty = 0; // not used
+ session.params.pixels = settings.pixels;
+ session.params.requested_pixels = settings.requested_pixels;
+ session.params.lines = settings.lines;
+ session.params.depth = settings.depth;
+ session.params.channels = settings.get_channels();
+ session.params.scan_method = settings.scan_method;
+ session.params.scan_mode = settings.scan_mode;
+ session.params.color_filter = settings.color_filter;
+ session.params.flags = ScanFlag::NONE;
+
+ compute_session(dev, session, sensor);
+
+ return session;
+}
+
+/**
+ * for fast power saving methods only, like disabling certain amplifiers
+ * @param dev device to use
+ * @param enable true to set inot powersaving
+ * */
+void CommandSetGl124::save_power(Genesys_Device* dev, bool enable) const
+{
+ (void) dev;
+ DBG_HELPER_ARGS(dbg, "enable = %d", enable);
+}
+
+void CommandSetGl124::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const
+{
+ DBG_HELPER_ARGS(dbg, "delay = %d", delay);
+ GenesysRegister *r;
+
+ r = sanei_genesys_get_address(&dev->reg, REG_0x03);
+ r->value &= ~0xf0;
+ if(delay<15)
+ {
+ r->value |= delay;
+ }
+ else
+ {
+ r->value |= 0x0f;
+ }
+}
+
+/** @brief setup GPIOs for scan
+ * Setup GPIO values to drive motor (or light) needed for the
+ * target resolution
+ * @param *dev device to set up
+ * @param resolution dpi of the target scan
+ */
+void gl124_setup_scan_gpio(Genesys_Device* dev, int resolution)
+{
+ DBG_HELPER(dbg);
+
+ uint8_t val = dev->interface->read_register(REG_0x32);
+
+ /* LiDE 110, 210 and 220 cases */
+ if(dev->model->gpio_id != GpioId::CANON_LIDE_120) {
+ if(resolution>=dev->motor.base_ydpi/2)
+ {
+ val &= 0xf7;
+ }
+ else if(resolution>=dev->motor.base_ydpi/4)
+ {
+ val &= 0xef;
+ }
+ else
+ {
+ val |= 0x10;
+ }
+ }
+ /* 120 : <=300 => 0x53 */
+ else
+ { /* base_ydpi is 4800 */
+ if(resolution<=300)
+ {
+ val &= 0xf7;
+ }
+ else if(resolution<=600)
+ {
+ val |= 0x08;
+ }
+ else if(resolution<=1200)
+ {
+ val &= 0xef;
+ val |= 0x08;
+ }
+ else
+ {
+ val &= 0xf7;
+ }
+ }
+ val |= 0x02;
+ dev->interface->write_register(REG_0x32, val);
+}
+
+// Send the low-level scan command
+// todo: is this that useful ?
+void CommandSetGl124::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, bool start_motor) const
+{
+ DBG_HELPER(dbg);
+ (void) sensor;
+ (void) reg;
+
+ // set up GPIO for scan
+ gl124_setup_scan_gpio(dev,dev->settings.yres);
+
+ // clear scan and feed count
+ dev->interface->write_register(REG_0x0D, REG_0x0D_CLRLNCNT | REG_0x0D_CLRMCNT);
+
+ // enable scan and motor
+ uint8_t val = dev->interface->read_register(REG_0x01);
+ val |= REG_0x01_SCAN;
+ dev->interface->write_register(REG_0x01, val);
+
+ scanner_start_action(*dev, start_motor);
+
+ dev->advance_head_pos_by_session(ScanHeadId::PRIMARY);
+}
+
+
+// Send the stop scan command
+void CommandSetGl124::end_scan(Genesys_Device* dev, Genesys_Register_Set* reg,
+ bool check_stop) const
+{
+ (void) reg;
+ DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop);
+
+ if (!dev->model->is_sheetfed) {
+ scanner_stop_action(*dev);
+ }
+}
+
+
+/** Park head
+ * Moves the slider to the home (top) position slowly
+ * @param dev device to park
+ * @param wait_until_home true to make the function waiting for head
+ * to be home before returning, if fals returne immediately
+ */
+void CommandSetGl124::move_back_home(Genesys_Device* dev, bool wait_until_home) const
+{
+ scanner_move_back_home(*dev, wait_until_home);
+}
+
+// Automatically set top-left edge of the scan area by scanning a 200x200 pixels area at 600 dpi
+// from very top of scanner
+void CommandSetGl124::search_start_position(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ int size;
+ Genesys_Register_Set local_reg = dev->reg;
+
+ int pixels = 600;
+ int dpi = 300;
+
+ /* sets for a 200 lines * 600 pixels */
+ /* normal scan with no shading */
+
+ // FIXME: the current approach of doing search only for one resolution does not work on scanners
+ // whith employ different sensors with potentially different settings.
+ const auto& sensor = sanei_genesys_find_sensor(dev, dpi, 1, ScanMethod::FLATBED);
+
+ ScanSession session;
+ session.params.xres = dpi;
+ session.params.yres = dpi;
+ session.params.startx = 0;
+ session.params.starty = 0; /*we should give a small offset here~60 steps */
+ session.params.pixels = 600;
+ session.params.lines = dev->model->search_lines;
+ session.params.depth = 8;
+ session.params.channels = 1;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::GRAY;
+ session.params.color_filter = ColorFilter::GREEN;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::IGNORE_LINE_DISTANCE |
+ ScanFlag::DISABLE_BUFFER_FULL_MOVE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &local_reg, session);
+
+ // send to scanner
+ dev->interface->write_registers(local_reg);
+
+ size = pixels * dev->model->search_lines;
+
+ std::vector<uint8_t> data(size);
+
+ begin_scan(dev, sensor, &local_reg, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("search_start_position");
+ end_scan(dev, &local_reg, true);
+ dev->reg = local_reg;
+ return;
+ }
+
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ sanei_genesys_read_data_from_scanner(dev, data.data(), size);
+
+ if (DBG_LEVEL >= DBG_data) {
+ sanei_genesys_write_pnm_file("gl124_search_position.pnm", data.data(), 8, 1, pixels,
+ dev->model->search_lines);
+ }
+
+ end_scan(dev, &local_reg, true);
+
+ /* update regs to copy ASIC internal state */
+ dev->reg = local_reg;
+
+ for (auto& sensor_update :
+ sanei_genesys_find_sensors_all_for_write(dev, dev->model->default_method))
+ {
+ sanei_genesys_search_reference_point(dev, sensor_update, data.data(), 0, dpi, pixels,
+ dev->model->search_lines);
+ }
+}
+
+// sets up register for coarse gain calibration
+// todo: check it for scanners using it
+void CommandSetGl124::init_regs_for_coarse_calibration(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+
+ ScanSession session;
+ session.params.xres = dev->settings.xres;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = sensor.optical_res / sensor.ccd_pixels_per_system_pixel();
+ session.params.lines = 20;
+ session.params.depth = 16;
+ session.params.channels = dev->settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = dev->settings.scan_mode;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::FEEDING |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &regs, session);
+
+ sanei_genesys_set_motor_power(regs, false);
+
+ DBG(DBG_info, "%s: optical sensor res: %d dpi, actual res: %d\n", __func__,
+ sensor.optical_res / sensor.ccd_pixels_per_system_pixel(), dev->settings.xres);
+
+ dev->interface->write_registers(regs);
+}
+
+
+// init registers for shading calibration shading calibration is done at dpihw
+void CommandSetGl124::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ int move, resolution, dpihw, factor;
+
+ /* initial calibration reg values */
+ regs = dev->reg;
+
+ dev->calib_channels = 3;
+ dev->calib_lines = dev->model->shading_lines;
+ dpihw = sensor.get_register_hwdpi(dev->settings.xres);
+ if(dpihw>=2400)
+ {
+ dev->calib_lines *= 2;
+ }
+ resolution=dpihw;
+
+ unsigned ccd_size_divisor = sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres);
+
+ resolution /= ccd_size_divisor;
+ dev->calib_lines /= ccd_size_divisor; // reducing just because we reduced the resolution
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution,
+ dev->calib_channels,
+ dev->settings.scan_method);
+ dev->calib_resolution = resolution;
+ dev->calib_total_bytes_to_read = 0;
+ factor = calib_sensor.optical_res / resolution;
+ dev->calib_pixels = calib_sensor.sensor_pixels / factor;
+
+ /* distance to move to reach white target at high resolution */
+ move=0;
+ if (dev->settings.yres >= 1200) {
+ move = static_cast<int>(dev->model->y_offset_calib_white);
+ move = static_cast<int>((move * (dev->motor.base_ydpi/4)) / MM_PER_INCH);
+ }
+ DBG (DBG_io, "%s: move=%d steps\n", __func__, move);
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = resolution;
+ session.params.startx = 0;
+ session.params.starty = move;
+ session.params.pixels = dev->calib_pixels;
+ session.params.lines = dev->calib_lines;
+ session.params.depth = 16;
+ session.params.channels = dev->calib_channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = ColorFilter::RED;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::DISABLE_BUFFER_FULL_MOVE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ try {
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); });
+ throw;
+ }
+ sanei_genesys_set_motor_power(regs, false);
+
+ dev->interface->write_registers(regs);
+}
+
+void CommandSetGl124::wait_for_motor_stop(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+
+ auto status = scanner_read_status(*dev);
+ uint8_t val40 = dev->interface->read_register(REG_0x100);
+
+ if (!status.is_motor_enabled && (val40 & REG_0x100_MOTMFLG) == 0) {
+ return;
+ }
+
+ do {
+ dev->interface->sleep_ms(10);
+ status = scanner_read_status(*dev);
+ val40 = dev->interface->read_register(REG_0x100);
+ } while (status.is_motor_enabled ||(val40 & REG_0x100_MOTMFLG));
+ dev->interface->sleep_ms(50);
+}
+
+/** @brief set up registers for the actual scan
+ */
+void CommandSetGl124::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ DBG_HELPER(dbg);
+ float move;
+ int move_dpi;
+ float start;
+
+ debug_dump(DBG_info, dev->settings);
+
+ /* y (motor) distance to move to reach scanned area */
+ move_dpi = dev->motor.base_ydpi/4;
+ move = static_cast<float>(dev->model->y_offset);
+ move += static_cast<float>(dev->settings.tl_y);
+ move = static_cast<float>((move * move_dpi) / MM_PER_INCH);
+ DBG (DBG_info, "%s: move=%f steps\n", __func__, move);
+
+ if (dev->settings.get_channels() * dev->settings.yres >= 600 && move > 700) {
+ scanner_move(*dev, dev->model->default_method, static_cast<unsigned>(move - 500),
+ Direction::FORWARD);
+ move=500;
+ }
+ DBG(DBG_info, "%s: move=%f steps\n", __func__, move);
+
+ /* start */
+ start = static_cast<float>(dev->model->x_offset);
+ start += static_cast<float>(dev->settings.tl_x);
+ start /= sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres);
+ start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = dev->settings.xres;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = static_cast<unsigned>(start);
+ session.params.starty = static_cast<unsigned>(move);
+ session.params.pixels = dev->settings.pixels;
+ session.params.requested_pixels = dev->settings.requested_pixels;
+ session.params.lines = dev->settings.lines;
+ session.params.depth = dev->settings.depth;
+ session.params.channels = dev->settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = dev->settings.scan_mode;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::NONE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &dev->reg, session);
+}
+
+/**
+ * Send shading calibration data. The buffer is considered to always hold values
+ * for all the channels.
+ */
+void CommandSetGl124::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ std::uint8_t* data, int size) const
+{
+ DBG_HELPER_ARGS(dbg, "writing %d bytes of shading data", size);
+ uint32_t addr, length, x, factor, segcnt, pixels, i;
+ uint16_t dpiset,dpihw;
+ uint8_t *ptr, *src;
+
+ /* logical size of a color as seen by generic code of the frontend */
+ length = size / 3;
+ std::uint32_t strpixel = dev->session.pixel_startx;
+ std::uint32_t endpixel = dev->session.pixel_endx;
+ segcnt = dev->reg.get24(REG_SEGCNT);
+ if(endpixel==0)
+ {
+ endpixel=segcnt;
+ }
+
+ /* compute deletion factor */
+ dpiset = dev->reg.get16(REG_DPISET);
+ dpihw = sensor.get_register_hwdpi(dpiset);
+ factor=dpihw/dpiset;
+ DBG( DBG_io2, "%s: factor=%d\n",__func__,factor);
+
+ /* turn pixel value into bytes 2x16 bits words */
+ strpixel*=2*2; /* 2 words of 2 bytes */
+ endpixel*=2*2;
+ segcnt*=2*2;
+ pixels=endpixel-strpixel;
+
+ dev->interface->record_key_value("shading_start_pixel", std::to_string(strpixel));
+ dev->interface->record_key_value("shading_pixels", std::to_string(pixels));
+ dev->interface->record_key_value("shading_length", std::to_string(length));
+ dev->interface->record_key_value("shading_factor", std::to_string(factor));
+ dev->interface->record_key_value("shading_segcnt", std::to_string(segcnt));
+ dev->interface->record_key_value("shading_segment_count",
+ std::to_string(dev->session.segment_count));
+
+ DBG( DBG_io2, "%s: using chunks of %d bytes (%d shading data pixels)\n",__func__,length, length/4);
+ std::vector<uint8_t> buffer(pixels * dev->session.segment_count, 0);
+
+ /* write actual red data */
+ for(i=0;i<3;i++)
+ {
+ /* copy data to work buffer and process it */
+ /* coefficent destination */
+ ptr = buffer.data();
+
+ /* iterate on both sensor segment */
+ for(x=0;x<pixels;x+=4*factor)
+ {
+ /* coefficient source */
+ src=data+x+strpixel+i*length;
+
+ /* iterate over all the segments */
+ switch (dev->session.segment_count) {
+ case 1:
+ ptr[0+pixels*0]=src[0+segcnt*0];
+ ptr[1+pixels*0]=src[1+segcnt*0];
+ ptr[2+pixels*0]=src[2+segcnt*0];
+ ptr[3+pixels*0]=src[3+segcnt*0];
+ break;
+ case 2:
+ ptr[0+pixels*0]=src[0+segcnt*0];
+ ptr[1+pixels*0]=src[1+segcnt*0];
+ ptr[2+pixels*0]=src[2+segcnt*0];
+ ptr[3+pixels*0]=src[3+segcnt*0];
+ ptr[0+pixels*1]=src[0+segcnt*1];
+ ptr[1+pixels*1]=src[1+segcnt*1];
+ ptr[2+pixels*1]=src[2+segcnt*1];
+ ptr[3+pixels*1]=src[3+segcnt*1];
+ break;
+ case 4:
+ ptr[0+pixels*0]=src[0+segcnt*0];
+ ptr[1+pixels*0]=src[1+segcnt*0];
+ ptr[2+pixels*0]=src[2+segcnt*0];
+ ptr[3+pixels*0]=src[3+segcnt*0];
+ ptr[0+pixels*1]=src[0+segcnt*2];
+ ptr[1+pixels*1]=src[1+segcnt*2];
+ ptr[2+pixels*1]=src[2+segcnt*2];
+ ptr[3+pixels*1]=src[3+segcnt*2];
+ ptr[0+pixels*2]=src[0+segcnt*1];
+ ptr[1+pixels*2]=src[1+segcnt*1];
+ ptr[2+pixels*2]=src[2+segcnt*1];
+ ptr[3+pixels*2]=src[3+segcnt*1];
+ ptr[0+pixels*3]=src[0+segcnt*3];
+ ptr[1+pixels*3]=src[1+segcnt*3];
+ ptr[2+pixels*3]=src[2+segcnt*3];
+ ptr[3+pixels*3]=src[3+segcnt*3];
+ break;
+ }
+
+ /* next shading coefficient */
+ ptr+=4;
+ }
+ uint8_t val = dev->interface->read_register(0xd0+i);
+ addr = val * 8192 + 0x10000000;
+ dev->interface->write_ahb(addr, pixels * dev->session.segment_count, buffer.data());
+ }
+}
+
+
+/** @brief move to calibration area
+ * This functions moves scanning head to calibration area
+ * by doing a 600 dpi scan
+ * @param dev scanner device
+ */
+static void move_to_calibration_area(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs)
+{
+ (void) sensor;
+
+ DBG_HELPER(dbg);
+ int pixels;
+ int size;
+
+ unsigned resolution = 600;
+ unsigned channels = 3;
+ const auto& move_sensor = sanei_genesys_find_sensor(dev, resolution, channels,
+ dev->settings.scan_method);
+ pixels = (move_sensor.sensor_pixels * 600) / move_sensor.optical_res;
+
+ /* initial calibration reg values */
+ regs = dev->reg;
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = resolution;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = pixels;
+ session.params.lines = 1;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, move_sensor);
+
+ dev->cmd_set->init_regs_for_scan_session(dev, move_sensor, &regs, session);
+
+ size = pixels * 3;
+ std::vector<uint8_t> line(size);
+
+ // write registers and scan data
+ dev->interface->write_registers(regs);
+
+ DBG (DBG_info, "%s: starting line reading\n", __func__);
+ dev->cmd_set->begin_scan(dev, move_sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("move_to_calibration_area");
+ scanner_stop_action(*dev);
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, line.data(), size);
+
+ // stop scanning
+ scanner_stop_action(*dev);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ sanei_genesys_write_pnm_file("gl124_movetocalarea.pnm", line.data(), 8, 3, pixels, 1);
+ }
+}
+
+/* this function does the led calibration by scanning one line of the calibration
+ area below scanner's top on white strip.
+
+-needs working coarse/gain
+*/
+SensorExposure CommandSetGl124::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ int num_pixels;
+ int total_size;
+ int resolution;
+ int dpihw;
+ int i, j;
+ int val;
+ int channels;
+ int avg[3];
+ int turn;
+ uint16_t exp[3],target;
+
+ /* move to calibration area */
+ move_to_calibration_area(dev, sensor, regs);
+
+ /* offset calibration is always done in 16 bit depth color mode */
+ channels = 3;
+ dpihw = sensor.get_register_hwdpi(dev->settings.xres);
+ resolution = dpihw;
+ unsigned ccd_size_divisor = sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres);
+ resolution /= ccd_size_divisor;
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels,
+ dev->settings.scan_method);
+ num_pixels = (calib_sensor.sensor_pixels * resolution) / calib_sensor.optical_res;
+
+ /* initial calibration reg values */
+ regs = dev->reg;
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = resolution;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = num_pixels;
+ session.params.lines = 1;
+ session.params.depth = 16;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ total_size = num_pixels * channels * (session.params.depth / 8) * 1;
+ std::vector<uint8_t> line(total_size);
+
+ // initial loop values and boundaries
+ exp[0] = calib_sensor.exposure.red;
+ exp[1] = calib_sensor.exposure.green;
+ exp[2] = calib_sensor.exposure.blue;
+ target=sensor.gain_white_ref*256;
+
+ turn = 0;
+
+ /* no move during led calibration */
+ sanei_genesys_set_motor_power(regs, false);
+ bool acceptable = false;
+ do
+ {
+ // set up exposure
+ regs.set24(REG_EXPR, exp[0]);
+ regs.set24(REG_EXPG, exp[1]);
+ regs.set24(REG_EXPB, exp[2]);
+
+ // write registers and scan data
+ dev->interface->write_registers(regs);
+
+ DBG(DBG_info, "%s: starting line reading\n", __func__);
+ begin_scan(dev, calib_sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("led_calibration");
+ scanner_stop_action(*dev);
+ return calib_sensor.exposure;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, line.data(), total_size);
+
+ // stop scanning
+ scanner_stop_action(*dev);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[30];
+ std::snprintf(fn, 30, "gl124_led_%02d.pnm", turn);
+ sanei_genesys_write_pnm_file(fn, line.data(), session.params.depth, channels, num_pixels,
+ 1);
+ }
+
+ /* compute average */
+ for (j = 0; j < channels; j++)
+ {
+ avg[j] = 0;
+ for (i = 0; i < num_pixels; i++)
+ {
+ if (dev->model->is_cis)
+ val =
+ line[i * 2 + j * 2 * num_pixels + 1] * 256 +
+ line[i * 2 + j * 2 * num_pixels];
+ else
+ val =
+ line[i * 2 * channels + 2 * j + 1] * 256 +
+ line[i * 2 * channels + 2 * j];
+ avg[j] += val;
+ }
+
+ avg[j] /= num_pixels;
+ }
+
+ DBG(DBG_info, "%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]);
+
+ /* check if exposure gives average within the boundaries */
+ acceptable = true;
+ for(i=0;i<3;i++)
+ {
+ /* we accept +- 2% delta from target */
+ if(abs(avg[i]-target)>target/50)
+ {
+ float prev_weight = 0.5;
+ exp[i] = exp[i] * prev_weight + ((exp[i] * target) / avg[i]) * (1 - prev_weight);
+ acceptable = false;
+ }
+ }
+
+ turn++;
+ }
+ while (!acceptable && turn < 100);
+
+ DBG(DBG_info, "%s: acceptable exposure: %d,%d,%d\n", __func__, exp[0], exp[1], exp[2]);
+
+ // set these values as final ones for scan
+ dev->reg.set24(REG_EXPR, exp[0]);
+ dev->reg.set24(REG_EXPG, exp[1]);
+ dev->reg.set24(REG_EXPB, exp[2]);
+
+ return { exp[0], exp[1], exp[2] };
+}
+
+/**
+ * average dark pixels of a 8 bits scan
+ */
+static int
+dark_average (uint8_t * data, unsigned int pixels, unsigned int lines,
+ unsigned int channels, unsigned int black)
+{
+ unsigned int i, j, k, average, count;
+ unsigned int avg[3];
+ uint8_t val;
+
+ /* computes average value on black margin */
+ for (k = 0; k < channels; k++)
+ {
+ avg[k] = 0;
+ count = 0;
+ for (i = 0; i < lines; i++)
+ {
+ for (j = 0; j < black; j++)
+ {
+ val = data[i * channels * pixels + j + k];
+ avg[k] += val;
+ count++;
+ }
+ }
+ if (count)
+ avg[k] /= count;
+ DBG(DBG_info, "%s: avg[%d] = %d\n", __func__, k, avg[k]);
+ }
+ average = 0;
+ for (i = 0; i < channels; i++)
+ average += avg[i];
+ average /= channels;
+ DBG(DBG_info, "%s: average = %d\n", __func__, average);
+ return average;
+}
+
+
+void CommandSetGl124::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ unsigned channels;
+ int pass = 0, avg, total_size;
+ int topavg, bottomavg, lines;
+ int top, bottom, black_pixels, pixels;
+
+ // no gain nor offset for TI AFE
+ uint8_t reg0a = dev->interface->read_register(REG_0x0A);
+ if (((reg0a & REG_0x0A_SIFSEL) >> REG_0x0AS_SIFSEL) == 3) {
+ return;
+ }
+
+ /* offset calibration is always done in color mode */
+ channels = 3;
+ dev->calib_pixels = sensor.sensor_pixels;
+ lines=1;
+ pixels = (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res;
+ black_pixels = (sensor.black_pixels * sensor.optical_res) / sensor.optical_res;
+ DBG(DBG_io2, "%s: black_pixels=%d\n", __func__, black_pixels);
+
+ ScanSession session;
+ session.params.xres = sensor.optical_res;
+ session.params.yres = sensor.optical_res;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &regs, session);
+
+ sanei_genesys_set_motor_power(regs, false);
+
+ /* allocate memory for scans */
+ total_size = pixels * channels * lines * (session.params.depth / 8);
+
+ std::vector<uint8_t> first_line(total_size);
+ std::vector<uint8_t> second_line(total_size);
+
+ /* init gain */
+ dev->frontend.set_gain(0, 0);
+ dev->frontend.set_gain(1, 0);
+ dev->frontend.set_gain(2, 0);
+
+ /* scan with no move */
+ bottom = 10;
+ dev->frontend.set_offset(0, bottom);
+ dev->frontend.set_offset(1, bottom);
+ dev->frontend.set_offset(2, bottom);
+
+ set_fe(dev, sensor, AFE_SET);
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting first line reading\n", __func__);
+ begin_scan(dev, sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("offset_calibration");
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size);
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char title[30];
+ std::snprintf(title, 30, "gl124_offset%03d.pnm", bottom);
+ sanei_genesys_write_pnm_file(title, first_line.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ bottomavg = dark_average(first_line.data(), pixels, lines, channels, black_pixels);
+ DBG(DBG_io2, "%s: bottom avg=%d\n", __func__, bottomavg);
+
+ /* now top value */
+ top = 255;
+ dev->frontend.set_offset(0, top);
+ dev->frontend.set_offset(1, top);
+ dev->frontend.set_offset(2, top);
+ set_fe(dev, sensor, AFE_SET);
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting second line reading\n", __func__);
+ begin_scan(dev, sensor, &regs, true);
+ sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size);
+
+ topavg = dark_average(second_line.data(), pixels, lines, channels, black_pixels);
+ DBG(DBG_io2, "%s: top avg=%d\n", __func__, topavg);
+
+ /* loop until acceptable level */
+ while ((pass < 32) && (top - bottom > 1))
+ {
+ pass++;
+
+ /* settings for new scan */
+ dev->frontend.set_offset(0, (top + bottom) / 2);
+ dev->frontend.set_offset(1, (top + bottom) / 2);
+ dev->frontend.set_offset(2, (top + bottom) / 2);
+
+ // scan with no move
+ set_fe(dev, sensor, AFE_SET);
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting second line reading\n", __func__);
+ begin_scan(dev, sensor, &regs, true);
+ sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char title[30];
+ std::snprintf(title, 30, "gl124_offset%03d.pnm", dev->frontend.get_offset(1));
+ sanei_genesys_write_pnm_file(title, second_line.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ avg = dark_average(second_line.data(), pixels, lines, channels, black_pixels);
+ DBG(DBG_info, "%s: avg=%d offset=%d\n", __func__, avg, dev->frontend.get_offset(1));
+
+ /* compute new boundaries */
+ if (topavg == avg)
+ {
+ topavg = avg;
+ top = dev->frontend.get_offset(1);
+ }
+ else
+ {
+ bottomavg = avg;
+ bottom = dev->frontend.get_offset(1);
+ }
+ }
+ DBG(DBG_info, "%s: offset=(%d,%d,%d)\n", __func__,
+ dev->frontend.get_offset(0),
+ dev->frontend.get_offset(1),
+ dev->frontend.get_offset(2));
+}
+
+
+/* alternative coarse gain calibration
+ this on uses the settings from offset_calibration and
+ uses only one scanline
+ */
+/*
+ with offset and coarse calibration we only want to get our input range into
+ a reasonable shape. the fine calibration of the upper and lower bounds will
+ be done with shading.
+ */
+void CommandSetGl124::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const
+{
+ DBG_HELPER_ARGS(dbg, "dpi = %d", dpi);
+ int pixels;
+ int total_size;
+ int i, j, channels;
+ int max[3];
+ float gain[3],coeff;
+ int val, code, lines;
+
+ // no gain nor offset for TI AFE
+ uint8_t reg0a = dev->interface->read_register(REG_0x0A);
+ if (((reg0a & REG_0x0A_SIFSEL) >> REG_0x0AS_SIFSEL) == 3) {
+ return;
+ }
+
+ /* coarse gain calibration is always done in color mode */
+ channels = 3;
+
+ if(dev->settings.xres<sensor.optical_res)
+ {
+ coeff = 0.9f;
+ } else {
+ coeff = 1.0f;
+ }
+ lines=10;
+ pixels = (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res;
+
+ ScanSession session;
+ session.params.xres = sensor.optical_res;
+ session.params.yres = sensor.optical_res;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ try {
+ init_regs_for_scan_session(dev, sensor, &regs, session);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); });
+ throw;
+ }
+
+ sanei_genesys_set_motor_power(regs, false);
+
+ dev->interface->write_registers(regs);
+
+ total_size = pixels * channels * (16 / session.params.depth) * lines;
+
+ std::vector<uint8_t> line(total_size);
+
+ set_fe(dev, sensor, AFE_SET);
+ begin_scan(dev, sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("coarse_gain_calibration");
+ scanner_stop_action(*dev);
+ move_back_home(dev, true);
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, line.data(), total_size);
+
+ if (DBG_LEVEL >= DBG_data) {
+ sanei_genesys_write_pnm_file("gl124_gain.pnm", line.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ /* average value on each channel */
+ for (j = 0; j < channels; j++)
+ {
+ max[j] = 0;
+ for (i = pixels/4; i < (pixels*3/4); i++)
+ {
+ if (dev->model->is_cis) {
+ val = line[i + j * pixels];
+ } else {
+ val = line[i * channels + j];
+ }
+
+ max[j] += val;
+ }
+ max[j] = max[j] / (pixels/2);
+
+ gain[j] = (static_cast<float>(sensor.gain_white_ref) * coeff) / max[j];
+
+ /* turn logical gain value into gain code, checking for overflow */
+ code = static_cast<int>(283 - 208 / gain[j]);
+ if (code > 255)
+ code = 255;
+ else if (code < 0)
+ code = 0;
+ dev->frontend.set_gain(j, code);
+
+ DBG(DBG_proc, "%s: channel %d, max=%d, gain = %f, setting:%d\n", __func__, j, max[j],
+ gain[j], dev->frontend.get_gain(j));
+ }
+
+ if (dev->model->is_cis) {
+ uint8_t gain0 = dev->frontend.get_gain(0);
+ if (gain0 > dev->frontend.get_gain(1)) {
+ gain0 = dev->frontend.get_gain(1);
+ }
+ if (gain0 > dev->frontend.get_gain(2)) {
+ gain0 = dev->frontend.get_gain(2);
+ }
+ dev->frontend.set_gain(0, gain0);
+ dev->frontend.set_gain(1, gain0);
+ dev->frontend.set_gain(2, gain0);
+ }
+
+ if (channels == 1) {
+ dev->frontend.set_gain(0, dev->frontend.get_gain(1));
+ dev->frontend.set_gain(2, dev->frontend.get_gain(1));
+ }
+
+ scanner_stop_action(*dev);
+
+ move_back_home(dev, true);
+}
+
+// wait for lamp warmup by scanning the same line until difference
+// between 2 scans is below a threshold
+void CommandSetGl124::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, int* channels,
+ int* total_size) const
+{
+ DBG_HELPER(dbg);
+ int num_pixels;
+
+ *channels=3;
+
+ *reg = dev->reg;
+
+ ScanSession session;
+ session.params.xres = sensor.optical_res;
+ session.params.yres = dev->motor.base_ydpi;
+ session.params.startx = sensor.sensor_pixels / 4;
+ session.params.starty = 0;
+ session.params.pixels = sensor.sensor_pixels / 2;
+ session.params.lines = 1;
+ session.params.depth = 8;
+ session.params.channels = *channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, reg, session);
+
+ num_pixels = session.output_pixels;
+
+ *total_size = num_pixels * 3 * 1; /* colors * bytes_per_color * scan lines */
+
+ sanei_genesys_set_motor_power(*reg, false);
+ dev->interface->write_registers(*reg);
+}
+
+/** @brief default GPIO values
+ * set up GPIO/GPOE for idle state
+ * @param dev device to set up
+ */
+static void gl124_init_gpio(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ int idx;
+
+ /* per model GPIO layout */
+ if (dev->model->model_id == ModelId::CANON_LIDE_110) {
+ idx = 0;
+ } else if (dev->model->model_id == ModelId::CANON_LIDE_120) {
+ idx = 2;
+ }
+ else
+ { /* canon LiDE 210 and 220 case */
+ idx = 1;
+ }
+
+ dev->interface->write_register(REG_0x31, gpios[idx].r31);
+ dev->interface->write_register(REG_0x32, gpios[idx].r32);
+ dev->interface->write_register(REG_0x33, gpios[idx].r33);
+ dev->interface->write_register(REG_0x34, gpios[idx].r34);
+ dev->interface->write_register(REG_0x35, gpios[idx].r35);
+ dev->interface->write_register(REG_0x36, gpios[idx].r36);
+ dev->interface->write_register(REG_0x38, gpios[idx].r38);
+}
+
+/**
+ * set memory layout by filling values in dedicated registers
+ */
+static void gl124_init_memory_layout(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ int idx = 0;
+
+ /* point to per model memory layout */
+ if (dev->model->model_id == ModelId::CANON_LIDE_110 ||
+ dev->model->model_id == ModelId::CANON_LIDE_120)
+ {
+ idx = 0;
+ }
+ else
+ { /* canon LiDE 210 and 220 case */
+ idx = 1;
+ }
+
+ /* setup base address for shading data. */
+ /* values must be multiplied by 8192=0x4000 to give address on AHB */
+ /* R-Channel shading bank0 address setting for CIS */
+ dev->interface->write_register(0xd0, layouts[idx].rd0);
+ /* G-Channel shading bank0 address setting for CIS */
+ dev->interface->write_register(0xd1, layouts[idx].rd1);
+ /* B-Channel shading bank0 address setting for CIS */
+ dev->interface->write_register(0xd2, layouts[idx].rd2);
+
+ /* setup base address for scanned data. */
+ /* values must be multiplied by 1024*2=0x0800 to give address on AHB */
+ /* R-Channel ODD image buffer 0x0124->0x92000 */
+ /* size for each buffer is 0x16d*1k word */
+ dev->interface->write_register(0xe0, layouts[idx].re0);
+ dev->interface->write_register(0xe1, layouts[idx].re1);
+ /* R-Channel ODD image buffer end-address 0x0291->0x148800 => size=0xB6800*/
+ dev->interface->write_register(0xe2, layouts[idx].re2);
+ dev->interface->write_register(0xe3, layouts[idx].re3);
+
+ /* R-Channel EVEN image buffer 0x0292 */
+ dev->interface->write_register(0xe4, layouts[idx].re4);
+ dev->interface->write_register(0xe5, layouts[idx].re5);
+ /* R-Channel EVEN image buffer end-address 0x03ff*/
+ dev->interface->write_register(0xe6, layouts[idx].re6);
+ dev->interface->write_register(0xe7, layouts[idx].re7);
+
+ /* same for green, since CIS, same addresses */
+ dev->interface->write_register(0xe8, layouts[idx].re0);
+ dev->interface->write_register(0xe9, layouts[idx].re1);
+ dev->interface->write_register(0xea, layouts[idx].re2);
+ dev->interface->write_register(0xeb, layouts[idx].re3);
+ dev->interface->write_register(0xec, layouts[idx].re4);
+ dev->interface->write_register(0xed, layouts[idx].re5);
+ dev->interface->write_register(0xee, layouts[idx].re6);
+ dev->interface->write_register(0xef, layouts[idx].re7);
+
+/* same for blue, since CIS, same addresses */
+ dev->interface->write_register(0xf0, layouts[idx].re0);
+ dev->interface->write_register(0xf1, layouts[idx].re1);
+ dev->interface->write_register(0xf2, layouts[idx].re2);
+ dev->interface->write_register(0xf3, layouts[idx].re3);
+ dev->interface->write_register(0xf4, layouts[idx].re4);
+ dev->interface->write_register(0xf5, layouts[idx].re5);
+ dev->interface->write_register(0xf6, layouts[idx].re6);
+ dev->interface->write_register(0xf7, layouts[idx].re7);
+}
+
+/**
+ * initialize backend and ASIC : registers, motor tables, and gamma tables
+ * then ensure scanner's head is at home
+ */
+void CommandSetGl124::init(Genesys_Device* dev) const
+{
+ DBG_INIT ();
+ DBG_HELPER(dbg);
+
+ sanei_genesys_asic_init(dev, 0);
+}
+
+
+/* *
+ * initialize ASIC from power on condition
+ */
+void CommandSetGl124::asic_boot(Genesys_Device* dev, bool cold) const
+{
+ DBG_HELPER(dbg);
+
+ // reset ASIC in case of cold boot
+ if (cold) {
+ dev->interface->write_register(0x0e, 0x01);
+ dev->interface->write_register(0x0e, 0x00);
+ }
+
+ // enable GPOE 17
+ dev->interface->write_register(0x36, 0x01);
+
+ // set GPIO 17
+ uint8_t val = dev->interface->read_register(0x33);
+ val |= 0x01;
+ dev->interface->write_register(0x33, val);
+
+ // test CHKVER
+ val = dev->interface->read_register(REG_0x100);
+ if (val & REG_0x100_CHKVER) {
+ val = dev->interface->read_register(0x00);
+ DBG(DBG_info, "%s: reported version for genesys chip is 0x%02x\n", __func__, val);
+ }
+
+ /* Set default values for registers */
+ gl124_init_registers (dev);
+
+ // Write initial registers
+ dev->interface->write_registers(dev->reg);
+
+ // tune reg 0B
+ dev->interface->write_register(REG_0x0B, REG_0x0B_30MHZ | REG_0x0B_ENBDRAM | REG_0x0B_64M);
+ dev->reg.remove_reg(0x0b);
+
+ //set up end access
+ dev->interface->write_0x8c(0x10, 0x0b);
+ dev->interface->write_0x8c(0x13, 0x0e);
+
+ /* CIS_LINE */
+ dev->reg.init_reg(0x08, REG_0x08_CIS_LINE);
+ dev->interface->write_register(0x08, dev->reg.find_reg(0x08).value);
+
+ // setup gpio
+ gl124_init_gpio(dev);
+
+ // setup internal memory layout
+ gl124_init_memory_layout(dev);
+}
+
+
+void CommandSetGl124::update_hardware_sensors(Genesys_Scanner* s) const
+{
+ /* do what is needed to get a new set of events, but try to not loose
+ any of them.
+ */
+ DBG_HELPER(dbg);
+ uint8_t val = s->dev->interface->read_register(REG_0x31);
+
+ /* TODO : for the next scanner special case,
+ * add another per scanner button profile struct to avoid growing
+ * hard-coded button mapping here.
+ */
+ if ((s->dev->model->gpio_id == GpioId::CANON_LIDE_110) ||
+ (s->dev->model->gpio_id == GpioId::CANON_LIDE_120))
+ {
+ s->buttons[BUTTON_SCAN_SW].write((val & 0x01) == 0);
+ s->buttons[BUTTON_FILE_SW].write((val & 0x08) == 0);
+ s->buttons[BUTTON_EMAIL_SW].write((val & 0x04) == 0);
+ s->buttons[BUTTON_COPY_SW].write((val & 0x02) == 0);
+ }
+ else
+ { /* LiDE 210 case */
+ s->buttons[BUTTON_EXTRA_SW].write((val & 0x01) == 0);
+ s->buttons[BUTTON_SCAN_SW].write((val & 0x02) == 0);
+ s->buttons[BUTTON_COPY_SW].write((val & 0x04) == 0);
+ s->buttons[BUTTON_EMAIL_SW].write((val & 0x08) == 0);
+ s->buttons[BUTTON_FILE_SW].write((val & 0x10) == 0);
+ }
+}
+
+void CommandSetGl124::update_home_sensor_gpio(Genesys_Device& dev) const
+{
+ DBG_HELPER(dbg);
+
+ std::uint8_t val = dev.interface->read_register(REG_0x32);
+ val &= ~REG_0x32_GPIO10;
+ dev.interface->write_register(REG_0x32, val);
+}
+
+bool CommandSetGl124::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const
+{
+ (void) dev;
+ return true;
+}
+
+void CommandSetGl124::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ sanei_genesys_send_gamma_table(dev, sensor);
+}
+
+void CommandSetGl124::load_document(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl124::detect_document_end(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl124::eject_document(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl124::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ bool forward, bool black) const
+{
+ (void) dev;
+ (void) sensor;
+ (void) forward;
+ (void) black;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl124::move_to_ta(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+std::unique_ptr<CommandSet> create_gl124_cmd_set()
+{
+ return std::unique_ptr<CommandSet>(new CommandSetGl124{});
+}
+
+} // namespace gl124
+} // namespace genesys
diff --git a/backend/genesys/gl124.h b/backend/genesys/gl124.h
new file mode 100644
index 0000000..cdf8faf
--- /dev/null
+++ b/backend/genesys/gl124.h
@@ -0,0 +1,205 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2010-2016 Stéphane Voltz <stef.dev@free.fr>
+
+ 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_GL124_H
+#define BACKEND_GENESYS_GL124_H
+
+#include "genesys.h"
+#include "command_set.h"
+
+namespace genesys {
+namespace gl124 {
+
+typedef struct
+{
+ uint8_t r31;
+ uint8_t r32;
+ uint8_t r33;
+ uint8_t r34;
+ uint8_t r35;
+ uint8_t r36;
+ uint8_t r38;
+} Gpio_layout;
+
+/** @brief gpio layout
+ * describes initial gpio settings for a given model
+ * registers 0x31 to 0x38
+ */
+static Gpio_layout gpios[]={
+ /* LiDE 110 */
+ { /* 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x38 */
+ 0x9f, 0x59, 0x01, 0x80, 0x5f, 0x01, 0x00
+ },
+ /* LiDE 210 */
+ {
+ 0x9f, 0x59, 0x01, 0x80, 0x5f, 0x01, 0x00
+ },
+ /* LiDE 120 */
+ {
+ 0x9f, 0x53, 0x01, 0x80, 0x5f, 0x01, 0x00
+ },
+};
+
+typedef struct
+{
+ uint8_t rd0;
+ uint8_t rd1;
+ uint8_t rd2;
+ uint8_t re0;
+ uint8_t re1;
+ uint8_t re2;
+ uint8_t re3;
+ uint8_t re4;
+ uint8_t re5;
+ uint8_t re6;
+ uint8_t re7;
+} Memory_layout;
+
+static Memory_layout layouts[]={
+ /* LIDE 110, 120 */
+ { /* 0xd0 0xd1 0xd2 */
+ 0x0a, 0x15, 0x20,
+ /* 0xe0 0xe1 0xe2 0xe3 0xe4 0xe5 0xe6 0xe7 */
+ 0x00, 0xac, 0x08, 0x55, 0x08, 0x56, 0x0f, 0xff
+ },
+ /* LIDE 210, 220 */
+ {
+ 0x0a, 0x1f, 0x34,
+ 0x01, 0x24, 0x08, 0x91, 0x08, 0x92, 0x0f, 0xff
+ }
+};
+
+static void gl124_send_slope_table(Genesys_Device* dev, int table_nr,
+ const std::vector<uint16_t>& slope_table, int steps);
+
+class CommandSetGl124 : public CommandSet
+{
+public:
+ ~CommandSetGl124() override = default;
+
+ bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override;
+
+ void init(Genesys_Device* dev) const override;
+
+ void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, int* channels,
+ int* total_size) const override;
+
+ void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const override;
+
+ void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override;
+ void set_powersaving(Genesys_Device* dev, int delay) const override;
+ void save_power(Genesys_Device* dev, bool enable) const override;
+
+ void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, bool start_motor) const override;
+
+ void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override;
+
+ void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void search_start_position(Genesys_Device* dev) const override;
+
+ void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const override;
+
+ SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void wait_for_motor_stop(Genesys_Device* dev) const override;
+
+ void move_back_home(Genesys_Device* dev, bool wait_until_home) const override;
+
+ void update_hardware_sensors(struct Genesys_Scanner* s) const override;
+
+ bool needs_update_home_sensor_gpio() const override { return true; }
+
+ void update_home_sensor_gpio(Genesys_Device& dev) const override;
+
+ void load_document(Genesys_Device* dev) const override;
+
+ void detect_document_end(Genesys_Device* dev) const override;
+
+ void eject_document(Genesys_Device* dev) const override;
+
+ void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ bool forward, bool black) const override;
+
+ void move_to_ta(Genesys_Device* dev) const override;
+
+ void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data,
+ int size) const override;
+
+ ScanSession calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const override;
+
+ void asic_boot(Genesys_Device* dev, bool cold) const override;
+};
+
+enum SlopeTable
+{
+ SCAN_TABLE = 0, // table 1 at 0x4000
+ BACKTRACK_TABLE = 1, // table 2 at 0x4800
+ STOP_TABLE = 2, // table 3 at 0x5000
+ FAST_TABLE = 3, // table 4 at 0x5800
+ HOME_TABLE = 4, // table 5 at 0x6000
+};
+
+} // namespace gl124
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL124_H
diff --git a/backend/genesys/gl124_registers.h b/backend/genesys/gl124_registers.h
new file mode 100644
index 0000000..9b42084
--- /dev/null
+++ b/backend/genesys/gl124_registers.h
@@ -0,0 +1,316 @@
+/* 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_GL124_REGISTERS_H
+#define BACKEND_GENESYS_GL124_REGISTERS_H
+
+#include <cstdint>
+
+namespace genesys {
+namespace gl124 {
+
+using RegAddr = std::uint16_t;
+using RegMask = std::uint8_t;
+using RegShift = unsigned;
+
+static constexpr RegAddr REG_0x01 = 0x01;
+static constexpr RegMask REG_0x01_CISSET = 0x80;
+static constexpr RegMask REG_0x01_DOGENB = 0x40;
+static constexpr RegMask REG_0x01_DVDSET = 0x20;
+static constexpr RegMask REG_0x01_STAGGER = 0x10;
+static constexpr RegMask REG_0x01_COMPENB = 0x08;
+static constexpr RegMask REG_0x01_TRUEGRAY = 0x04;
+static constexpr RegMask REG_0x01_SHDAREA = 0x02;
+static constexpr RegMask REG_0x01_SCAN = 0x01;
+
+static constexpr RegAddr REG_0x02 = 0x02;
+static constexpr RegMask REG_0x02_NOTHOME = 0x80;
+static constexpr RegMask REG_0x02_ACDCDIS = 0x40;
+static constexpr RegMask REG_0x02_AGOHOME = 0x20;
+static constexpr RegMask REG_0x02_MTRPWR = 0x10;
+static constexpr RegMask REG_0x02_FASTFED = 0x08;
+static constexpr RegMask REG_0x02_MTRREV = 0x04;
+static constexpr RegMask REG_0x02_HOMENEG = 0x02;
+static constexpr RegMask REG_0x02_LONGCURV = 0x01;
+
+static constexpr RegAddr REG_0x03 = 0x03;
+static constexpr RegMask REG_0x03_LAMPDOG = 0x80;
+static constexpr RegMask REG_0x03_AVEENB = 0x40;
+static constexpr RegMask REG_0x03_XPASEL = 0x20;
+static constexpr RegMask REG_0x03_LAMPPWR = 0x10;
+static constexpr RegMask REG_0x03_LAMPTIM = 0x0f;
+
+static constexpr RegAddr REG_0x04 = 0x04;
+static constexpr RegMask REG_0x04_LINEART = 0x80;
+static constexpr RegMask REG_0x04_BITSET = 0x40;
+static constexpr RegMask REG_0x04_FILTER = 0x30;
+static constexpr RegMask REG_0x04_AFEMOD = 0x07;
+
+static constexpr RegAddr REG_0x05 = 0x05;
+static constexpr RegMask REG_0x05_DPIHW = 0xc0;
+static constexpr RegMask REG_0x05_DPIHW_600 = 0x00;
+static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40;
+static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80;
+static constexpr RegMask REG_0x05_DPIHW_4800 = 0xc0;
+static constexpr RegMask REG_0x05_MTLLAMP = 0x30;
+static constexpr RegMask REG_0x05_GMMENB = 0x08;
+static constexpr RegMask REG_0x05_ENB20M = 0x04;
+static constexpr RegMask REG_0x05_MTLBASE = 0x03;
+
+static constexpr RegAddr REG_0x06 = 0x06;
+static constexpr RegMask REG_0x06_SCANMOD = 0xe0;
+static constexpr RegMask REG_0x06S_SCANMOD = 5;
+static constexpr RegMask REG_0x06_PWRBIT = 0x10;
+static constexpr RegMask REG_0x06_GAIN4 = 0x08;
+static constexpr RegMask REG_0x06_OPTEST = 0x07;
+
+static constexpr RegMask REG_0x07_LAMPSIM = 0x80;
+
+static constexpr RegMask REG_0x08_DRAM2X = 0x80;
+static constexpr RegMask REG_0x08_MPENB = 0x20;
+static constexpr RegMask REG_0x08_CIS_LINE = 0x10;
+static constexpr RegMask REG_0x08_IR2_ENB = 0x08;
+static constexpr RegMask REG_0x08_IR1_ENB = 0x04;
+static constexpr RegMask REG_0x08_ENB24M = 0x01;
+
+static constexpr RegMask REG_0x09_MCNTSET = 0xc0;
+static constexpr RegMask REG_0x09_EVEN1ST = 0x20;
+static constexpr RegMask REG_0x09_BLINE1ST = 0x10;
+static constexpr RegMask REG_0x09_BACKSCAN = 0x08;
+static constexpr RegMask REG_0x09_OUTINV = 0x04;
+static constexpr RegMask REG_0x09_SHORTTG = 0x02;
+
+static constexpr RegShift REG_0x09S_MCNTSET = 6;
+static constexpr RegShift REG_0x09S_CLKSET = 4;
+
+static constexpr RegAddr REG_0x0A = 0x0a;
+static constexpr RegMask REG_0x0A_SIFSEL = 0xc0;
+static constexpr RegShift REG_0x0AS_SIFSEL = 6;
+static constexpr RegMask REG_0x0A_SHEETFED = 0x20;
+static constexpr RegMask REG_0x0A_LPWMEN = 0x10;
+
+static constexpr RegAddr REG_0x0B = 0x0b;
+static constexpr RegMask REG_0x0B_DRAMSEL = 0x07;
+static constexpr RegMask REG_0x0B_16M = 0x01;
+static constexpr RegMask REG_0x0B_64M = 0x02;
+static constexpr RegMask REG_0x0B_128M = 0x03;
+static constexpr RegMask REG_0x0B_256M = 0x04;
+static constexpr RegMask REG_0x0B_512M = 0x05;
+static constexpr RegMask REG_0x0B_1G = 0x06;
+static constexpr RegMask REG_0x0B_ENBDRAM = 0x08;
+static constexpr RegMask REG_0x0B_RFHDIS = 0x10;
+static constexpr RegMask REG_0x0B_CLKSET = 0xe0;
+static constexpr RegMask REG_0x0B_24MHZ = 0x00;
+static constexpr RegMask REG_0x0B_30MHZ = 0x20;
+static constexpr RegMask REG_0x0B_40MHZ = 0x40;
+static constexpr RegMask REG_0x0B_48MHZ = 0x60;
+static constexpr RegMask REG_0x0B_60MHZ = 0x80;
+
+static constexpr RegAddr REG_0x0D = 0x0d;
+static constexpr RegMask REG_0x0D_MTRP_RDY = 0x80;
+static constexpr RegMask REG_0x0D_FULLSTP = 0x10;
+static constexpr RegMask REG_0x0D_CLRMCNT = 0x04;
+static constexpr RegMask REG_0x0D_CLRDOCJM = 0x02;
+static constexpr RegMask REG_0x0D_CLRLNCNT = 0x01;
+
+static constexpr RegAddr REG_0x0F = 0x0f;
+
+static constexpr RegMask REG_0x16_CTRLHI = 0x80;
+static constexpr RegMask REG_0x16_TOSHIBA = 0x40;
+static constexpr RegMask REG_0x16_TGINV = 0x20;
+static constexpr RegMask REG_0x16_CK1INV = 0x10;
+static constexpr RegMask REG_0x16_CK2INV = 0x08;
+static constexpr RegMask REG_0x16_CTRLINV = 0x04;
+static constexpr RegMask REG_0x16_CKDIS = 0x02;
+static constexpr RegMask REG_0x16_CTRLDIS = 0x01;
+
+static constexpr RegMask REG_0x17_TGMODE = 0xc0;
+static constexpr RegMask REG_0x17_SNRSYN = 0x0f;
+
+static constexpr RegAddr REG_0x18 = 0x18;
+static constexpr RegMask REG_0x18_CNSET = 0x80;
+static constexpr RegMask REG_0x18_DCKSEL = 0x60;
+static constexpr RegMask REG_0x18_CKTOGGLE = 0x10;
+static constexpr RegMask REG_0x18_CKDELAY = 0x0c;
+static constexpr RegMask REG_0x18_CKSEL = 0x03;
+
+static constexpr RegMask REG_0x1A_SW2SET = 0x80;
+static constexpr RegMask REG_0x1A_SW1SET = 0x40;
+static constexpr RegMask REG_0x1A_MANUAL3 = 0x02;
+static constexpr RegMask REG_0x1A_MANUAL1 = 0x01;
+static constexpr RegMask REG_0x1A_CK4INV = 0x08;
+static constexpr RegMask REG_0x1A_CK3INV = 0x04;
+static constexpr RegMask REG_0x1A_LINECLP = 0x02;
+
+static constexpr RegMask REG_0x1C_TBTIME = 0x07;
+
+static constexpr RegAddr REG_0x1D = 0x1d;
+static constexpr RegMask REG_0x1D_CK4LOW = 0x80;
+static constexpr RegMask REG_0x1D_CK3LOW = 0x40;
+static constexpr RegMask REG_0x1D_CK1LOW = 0x20;
+static constexpr RegMask REG_0x1D_LINESEL = 0x1f;
+static constexpr RegShift REG_0x1DS_LINESEL = 0;
+
+static constexpr RegAddr REG_0x1E = 0x1e;
+static constexpr RegMask REG_0x1E_WDTIME = 0xf0;
+static constexpr RegShift REG_0x1ES_WDTIME = 4;
+
+static constexpr RegAddr REG_0x30 = 0x30;
+static constexpr RegAddr REG_0x31 = 0x31;
+static constexpr RegAddr REG_0x32 = 0x32;
+static constexpr RegMask REG_0x32_GPIO16 = 0x80;
+static constexpr RegMask REG_0x32_GPIO15 = 0x40;
+static constexpr RegMask REG_0x32_GPIO14 = 0x20;
+static constexpr RegMask REG_0x32_GPIO13 = 0x10;
+static constexpr RegMask REG_0x32_GPIO12 = 0x08;
+static constexpr RegMask REG_0x32_GPIO11 = 0x04;
+static constexpr RegMask REG_0x32_GPIO10 = 0x02;
+static constexpr RegMask REG_0x32_GPIO9 = 0x01;
+static constexpr RegAddr REG_0x33 = 0x33;
+static constexpr RegAddr REG_0x34 = 0x34;
+static constexpr RegAddr REG_0x35 = 0x35;
+static constexpr RegAddr REG_0x36 = 0x36;
+static constexpr RegAddr REG_0x37 = 0x37;
+static constexpr RegAddr REG_0x38 = 0x38;
+static constexpr RegAddr REG_0x39 = 0x39;
+
+static constexpr RegAddr REG_0x60 = 0x60;
+static constexpr RegMask REG_0x60_LED4TG = 0x80;
+static constexpr RegMask REG_0x60_YENB = 0x40;
+static constexpr RegMask REG_0x60_YBIT = 0x20;
+static constexpr RegMask REG_0x60_ACYNCNRLC = 0x10;
+static constexpr RegMask REG_0x60_ENOFFSET = 0x08;
+static constexpr RegMask REG_0x60_LEDADD = 0x04;
+static constexpr RegMask REG_0x60_CK4ADC = 0x02;
+static constexpr RegMask REG_0x60_AUTOCONF = 0x01;
+
+static constexpr RegAddr REG_0x80 = 0x80;
+static constexpr RegAddr REG_0x81 = 0x81;
+
+static constexpr RegAddr REG_0xA0 = 0xa0;
+static constexpr RegMask REG_0xA0_FSTPSEL = 0x38;
+static constexpr RegShift REG_0xA0S_FSTPSEL = 3;
+static constexpr RegMask REG_0xA0_STEPSEL = 0x07;
+static constexpr RegShift REG_0xA0S_STEPSEL = 0;
+
+static constexpr RegAddr REG_0xA1 = 0xa1;
+static constexpr RegAddr REG_0xA2 = 0xa2;
+static constexpr RegAddr REG_0xA3 = 0xa3;
+static constexpr RegAddr REG_0xA4 = 0xa4;
+static constexpr RegAddr REG_0xA5 = 0xa5;
+static constexpr RegAddr REG_0xA6 = 0xa6;
+static constexpr RegAddr REG_0xA7 = 0xa7;
+static constexpr RegAddr REG_0xA8 = 0xa8;
+static constexpr RegAddr REG_0xA9 = 0xa9;
+static constexpr RegAddr REG_0xAA = 0xaa;
+static constexpr RegAddr REG_0xAB = 0xab;
+static constexpr RegAddr REG_0xAC = 0xac;
+static constexpr RegAddr REG_0xAD = 0xad;
+static constexpr RegAddr REG_0xAE = 0xae;
+static constexpr RegAddr REG_0xAF = 0xaf;
+static constexpr RegAddr REG_0xB0 = 0xb0;
+static constexpr RegAddr REG_0xB1 = 0xb1;
+
+static constexpr RegAddr REG_0xB2 = 0xb2;
+static constexpr RegMask REG_0xB2_Z1MOD = 0x1f;
+static constexpr RegAddr REG_0xB3 = 0xb3;
+static constexpr RegMask REG_0xB3_Z1MOD = 0xff;
+static constexpr RegAddr REG_0xB4 = 0xb4;
+static constexpr RegMask REG_0xB4_Z1MOD = 0xff;
+
+static constexpr RegAddr REG_0xB5 = 0xb5;
+static constexpr RegMask REG_0xB5_Z2MOD = 0x1f;
+static constexpr RegAddr REG_0xB6 = 0xb6;
+static constexpr RegMask REG_0xB6_Z2MOD = 0xff;
+static constexpr RegAddr REG_0xB7 = 0xb7;
+static constexpr RegMask REG_0xB7_Z2MOD = 0xff;
+
+static constexpr RegAddr REG_0x100 = 0x100;
+static constexpr RegMask REG_0x100_DOCSNR = 0x80;
+static constexpr RegMask REG_0x100_ADFSNR = 0x40;
+static constexpr RegMask REG_0x100_COVERSNR = 0x20;
+static constexpr RegMask REG_0x100_CHKVER = 0x10;
+static constexpr RegMask REG_0x100_DOCJAM = 0x08;
+static constexpr RegMask REG_0x100_HISPDFLG = 0x04;
+static constexpr RegMask REG_0x100_MOTMFLG = 0x02;
+static constexpr RegMask REG_0x100_DATAENB = 0x01;
+
+static constexpr RegAddr REG_0x114 = 0x114;
+static constexpr RegAddr REG_0x115 = 0x115;
+
+static constexpr RegAddr REG_LINCNT = 0x25;
+static constexpr RegAddr REG_MAXWD = 0x28;
+static constexpr RegAddr REG_DPISET = 0x2c;
+static constexpr RegAddr REG_FEEDL = 0x3d;
+static constexpr RegAddr REG_CK1MAP = 0x74;
+static constexpr RegAddr REG_CK3MAP = 0x77;
+static constexpr RegAddr REG_CK4MAP = 0x7a;
+static constexpr RegAddr REG_LPERIOD = 0x7d;
+static constexpr RegAddr REG_DUMMY = 0x80;
+static constexpr RegAddr REG_STRPIXEL = 0x82;
+static constexpr RegAddr REG_ENDPIXEL = 0x85;
+static constexpr RegAddr REG_EXPDMY = 0x88;
+static constexpr RegAddr REG_EXPR = 0x8a;
+static constexpr RegAddr REG_EXPG = 0x8d;
+static constexpr RegAddr REG_EXPB = 0x90;
+static constexpr RegAddr REG_SEGCNT = 0x93;
+static constexpr RegAddr REG_TG0CNT = 0x96;
+static constexpr RegAddr REG_SCANFED = 0xa2;
+static constexpr RegAddr REG_STEPNO = 0xa4;
+static constexpr RegAddr REG_FWDSTEP = 0xa6;
+static constexpr RegAddr REG_BWDSTEP = 0xa8;
+static constexpr RegAddr REG_FASTNO = 0xaa;
+static constexpr RegAddr REG_FSHDEC = 0xac;
+static constexpr RegAddr REG_FMOVNO = 0xae;
+static constexpr RegAddr REG_FMOVDEC = 0xb0;
+static constexpr RegAddr REG_Z1MOD = 0xb2;
+static constexpr RegAddr REG_Z2MOD = 0xb5;
+
+static constexpr RegAddr REG_TRUER = 0x110;
+static constexpr RegAddr REG_TRUEG = 0x111;
+static constexpr RegAddr REG_TRUEB = 0x112;
+
+} // namespace gl124
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL843_REGISTERS_H
diff --git a/backend/genesys/gl646.cpp b/backend/genesys/gl646.cpp
new file mode 100644
index 0000000..04ee85e
--- /dev/null
+++ b/backend/genesys/gl646.cpp
@@ -0,0 +1,3436 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2003 Oliver Rauch
+ Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
+ Copyright (C) 2004 Gerhard Jaeger <gerhard@gjaeger.de>
+ Copyright (C) 2004-2013 Stéphane Voltz <stef.dev@free.fr>
+ Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
+ Copyright (C) 2007 Luke <iceyfor@gmail.com>
+ Copyright (C) 2011 Alexey Osipov <simba@lerlan.ru> for HP2400 description
+ and tuning
+
+ 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 "gl646.h"
+#include "gl646_registers.h"
+#include "test_settings.h"
+
+#include <vector>
+
+namespace genesys {
+namespace gl646 {
+
+namespace {
+constexpr unsigned CALIBRATION_LINES = 10;
+} // namespace
+
+static void gl646_send_slope_table(Genesys_Device* dev, int table_nr,
+ const std::vector<uint16_t>& slope_table,
+ int steps);
+
+/**
+ * reads value from gpio endpoint
+ */
+static void gl646_gpio_read(IUsbDevice& usb_dev, uint8_t* value)
+{
+ DBG_HELPER(dbg);
+ usb_dev.control_msg(REQUEST_TYPE_IN, REQUEST_REGISTER, GPIO_READ, INDEX, 1, value);
+}
+
+/**
+ * writes the given value to gpio endpoint
+ */
+static void gl646_gpio_write(IUsbDevice& usb_dev, uint8_t value)
+{
+ DBG_HELPER_ARGS(dbg, "(0x%02x)", value);
+ usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, GPIO_WRITE, INDEX, 1, &value);
+}
+
+/**
+ * writes the given value to gpio output enable endpoint
+ */
+static void gl646_gpio_output_enable(IUsbDevice& usb_dev, uint8_t value)
+{
+ DBG_HELPER_ARGS(dbg, "(0x%02x)", value);
+ usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, GPIO_OUTPUT_ENABLE, INDEX, 1, &value);
+}
+
+/**
+ * stop scanner's motor
+ * @param dev scanner's device
+ */
+static void gl646_stop_motor(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ dev->interface->write_register(0x0f, 0x00);
+}
+
+/**
+ * find the closest match in mode tables for the given resolution and scan mode.
+ * @param sensor id of the sensor
+ * @param required required resolution
+ * @param color true is color mode
+ * @return the closest resolution for the sensor and mode
+ */
+static unsigned get_closest_resolution(SensorId sensor_id, int required, unsigned channels)
+{
+ unsigned best_res = 0;
+ unsigned best_diff = 9600;
+
+ for (const auto& sensor : *s_sensors) {
+ if (sensor_id != sensor.sensor_id)
+ continue;
+
+ // exit on perfect match
+ if (sensor.resolutions.matches(required) && sensor.matches_channel_count(channels)) {
+ DBG(DBG_info, "%s: match found for %d\n", __func__, required);
+ return required;
+ }
+
+ // computes distance and keep mode if it is closer than previous
+ if (sensor.matches_channel_count(channels)) {
+ for (auto res : sensor.resolutions.resolutions()) {
+ unsigned curr_diff = std::abs(static_cast<int>(res) - static_cast<int>(required));
+ if (curr_diff < best_diff) {
+ best_res = res;
+ best_diff = curr_diff;
+ }
+ }
+ }
+ }
+
+ DBG(DBG_info, "%s: closest match for %d is %d\n", __func__, required, best_res);
+ return best_res;
+}
+
+/**
+ * Returns the cksel values used by the required scan mode.
+ * @param sensor id of the sensor
+ * @param required required resolution
+ * @param color true is color mode
+ * @return cksel value for mode
+ */
+static int get_cksel(SensorId sensor_id, int required, unsigned channels)
+{
+ for (const auto& sensor : *s_sensors) {
+ // exit on perfect match
+ if (sensor.sensor_id == sensor_id && sensor.resolutions.matches(required) &&
+ sensor.matches_channel_count(channels))
+ {
+ unsigned cksel = sensor.ccd_pixels_per_system_pixel();
+ DBG(DBG_io, "%s: match found for %d (cksel=%d)\n", __func__, required, cksel);
+ return cksel;
+ }
+ }
+ DBG(DBG_error, "%s: failed to find match for %d dpi\n", __func__, required);
+ /* fail safe fallback */
+ return 1;
+}
+
+void CommandSetGl646::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs,
+ const ScanSession& session) const
+{
+ DBG_HELPER(dbg);
+ session.assert_computed();
+
+ debug_dump(DBG_info, sensor);
+
+ uint32_t move = session.params.starty;
+
+ int i, nb;
+ Motor_Master *motor = nullptr;
+ uint32_t z1, z2;
+ int feedl;
+
+
+ /* for the given resolution, search for master
+ * motor mode setting */
+ i = 0;
+ nb = sizeof (motor_master) / sizeof (Motor_Master);
+ while (i < nb)
+ {
+ if (dev->model->motor_id == motor_master[i].motor_id
+ && motor_master[i].dpi == session.params.yres
+ && motor_master[i].channels == session.params.channels)
+ {
+ motor = &motor_master[i];
+ }
+ i++;
+ }
+ if (motor == nullptr)
+ {
+ throw SaneException("unable to find settings for motor %d at %d dpi, color=%d",
+ static_cast<unsigned>(dev->model->motor_id),
+ session.params.yres, session.params.channels);
+ }
+
+ /* now we can search for the specific sensor settings */
+ i = 0;
+
+ // now apply values from settings to registers
+ regs->set16(REG_EXPR, sensor.exposure.red);
+ regs->set16(REG_EXPG, sensor.exposure.green);
+ regs->set16(REG_EXPB, sensor.exposure.blue);
+
+ for (const auto& reg : sensor.custom_regs) {
+ regs->set8(reg.address, reg.value);
+ }
+
+ /* now generate slope tables : we are not using generate_slope_table3 yet */
+ auto slope_table1 = create_slope_table(motor->slope1, motor->slope1.max_speed_w, StepType::FULL,
+ 1, 4, get_slope_table_max_size(AsicType::GL646));
+ auto slope_table2 = create_slope_table(motor->slope2, motor->slope2.max_speed_w, StepType::FULL,
+ 1, 4, get_slope_table_max_size(AsicType::GL646));
+
+ /* R01 */
+ /* now setup other registers for final scan (ie with shading enabled) */
+ /* watch dog + shading + scan enable */
+ regs->find_reg(0x01).value |= REG_0x01_DOGENB | REG_0x01_DVDSET | REG_0x01_SCAN;
+ if (dev->model->is_cis) {
+ regs->find_reg(0x01).value |= REG_0x01_CISSET;
+ } else {
+ regs->find_reg(0x01).value &= ~REG_0x01_CISSET;
+ }
+
+ /* if device has no calibration, don't enable shading correction */
+ if (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)
+ {
+ regs->find_reg(0x01).value &= ~REG_0x01_DVDSET;
+ }
+
+ regs->find_reg(0x01).value &= ~REG_0x01_FASTMOD;
+ if (motor->fastmod) {
+ regs->find_reg(0x01).value |= REG_0x01_FASTMOD;
+ }
+
+ /* R02 */
+ /* allow moving when buffer full by default */
+ if (!dev->model->is_sheetfed) {
+ dev->reg.find_reg(0x02).value &= ~REG_0x02_ACDCDIS;
+ } else {
+ dev->reg.find_reg(0x02).value |= REG_0x02_ACDCDIS;
+ }
+
+ /* setup motor power and direction */
+ sanei_genesys_set_motor_power(*regs, true);
+
+ if (has_flag(session.params.flags, ScanFlag::REVERSE)) {
+ regs->find_reg(0x02).value |= REG_0x02_MTRREV;
+ } else {
+ regs->find_reg(0x02).value &= ~REG_0x02_MTRREV;
+ }
+
+ /* fastfed enabled (2 motor slope tables) */
+ if (motor->fastfed) {
+ regs->find_reg(0x02).value |= REG_0x02_FASTFED;
+ } else {
+ regs->find_reg(0x02).value &= ~REG_0x02_FASTFED;
+ }
+
+ /* step type */
+ regs->find_reg(0x02).value &= ~REG_0x02_STEPSEL;
+ switch (motor->steptype)
+ {
+ case StepType::FULL:
+ break;
+ case StepType::HALF:
+ regs->find_reg(0x02).value |= 1;
+ break;
+ case StepType::QUARTER:
+ regs->find_reg(0x02).value |= 2;
+ break;
+ default:
+ regs->find_reg(0x02).value |= 3;
+ break;
+ }
+
+ if (dev->model->is_sheetfed) {
+ regs->find_reg(0x02).value &= ~REG_0x02_AGOHOME;
+ } else {
+ regs->find_reg(0x02).value |= REG_0x02_AGOHOME;
+ }
+
+ /* R03 */
+ regs->find_reg(0x03).value &= ~REG_0x03_AVEENB;
+ // regs->find_reg(0x03).value |= REG_0x03_AVEENB;
+ regs->find_reg(0x03).value &= ~REG_0x03_LAMPDOG;
+
+ /* select XPA */
+ regs->find_reg(0x03).value &= ~REG_0x03_XPASEL;
+ if ((session.params.flags & ScanFlag::USE_XPA) != ScanFlag::NONE) {
+ regs->find_reg(0x03).value |= REG_0x03_XPASEL;
+ }
+ regs->state.is_xpa_on = (session.params.flags & ScanFlag::USE_XPA) != ScanFlag::NONE;
+
+ /* R04 */
+ /* monochrome / color scan */
+ switch (session.params.depth) {
+ case 8:
+ regs->find_reg(0x04).value &= ~(REG_0x04_LINEART | REG_0x04_BITSET);
+ break;
+ case 16:
+ regs->find_reg(0x04).value &= ~REG_0x04_LINEART;
+ regs->find_reg(0x04).value |= REG_0x04_BITSET;
+ break;
+ }
+
+ sanei_genesys_set_dpihw(*regs, sensor, sensor.optical_res);
+
+ /* gamma enable for scans */
+ if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) {
+ regs->find_reg(0x05).value |= REG_0x05_GMM14BIT;
+ }
+
+ regs->find_reg(0x05).value &= ~REG_0x05_GMMENB;
+
+ /* true CIS gray if needed */
+ if (dev->model->is_cis && session.params.channels == 1 && dev->settings.true_gray) {
+ regs->find_reg(0x05).value |= REG_0x05_LEDADD;
+ } else {
+ regs->find_reg(0x05).value &= ~REG_0x05_LEDADD;
+ }
+
+ /* HP2400 1200dpi mode tuning */
+
+ if (dev->model->sensor_id == SensorId::CCD_HP2400) {
+ /* reset count of dummy lines to zero */
+ regs->find_reg(0x1e).value &= ~REG_0x1E_LINESEL;
+ if (session.params.xres >= 1200) {
+ /* there must be one dummy line */
+ regs->find_reg(0x1e).value |= 1 & REG_0x1E_LINESEL;
+
+ /* GPO12 need to be set to zero */
+ regs->find_reg(0x66).value &= ~0x20;
+ }
+ else
+ {
+ /* set GPO12 back to one */
+ regs->find_reg(0x66).value |= 0x20;
+ }
+ }
+
+ /* motor steps used */
+ unsigned forward_steps = motor->fwdbwd;
+ unsigned backward_steps = motor->fwdbwd;
+
+ // the steps count must be different by at most 128, otherwise it's impossible to construct
+ // a proper backtracking curve. We're using slightly lower limit to allow at least a minimum
+ // distance between accelerations (forward_steps, backward_steps)
+ if (slope_table1.steps_count > slope_table2.steps_count + 100) {
+ slope_table2.steps_count += slope_table1.steps_count - 100;
+ }
+ if (slope_table2.steps_count > slope_table1.steps_count + 100) {
+ slope_table1.steps_count += slope_table2.steps_count - 100;
+ }
+
+ if (slope_table1.steps_count >= slope_table2.steps_count) {
+ backward_steps += (slope_table1.steps_count - slope_table2.steps_count) * 2;
+ } else {
+ forward_steps += (slope_table2.steps_count - slope_table1.steps_count) * 2;
+ }
+
+ if (forward_steps > 255) {
+ if (backward_steps < (forward_steps - 255)) {
+ throw SaneException("Can't set backtracking parameters without skipping image");
+ }
+ backward_steps -= forward_steps - 255;
+ }
+ if (backward_steps > 255) {
+ if (forward_steps < (backward_steps - 255)) {
+ throw SaneException("Can't set backtracking parameters without skipping image");
+ }
+ forward_steps -= backward_steps - 255;
+ }
+
+ regs->find_reg(0x21).value = slope_table1.steps_count;
+ regs->find_reg(0x24).value = slope_table2.steps_count;
+ regs->find_reg(0x22).value = forward_steps;
+ regs->find_reg(0x23).value = backward_steps;
+
+ /* CIS scanners read one line per color channel
+ * since gray mode use 'add' we also read 3 channels even not in
+ * color mode */
+ if (dev->model->is_cis) {
+ regs->set24(REG_LINCNT, session.output_line_count * 3);
+ } else {
+ regs->set24(REG_LINCNT, session.output_line_count);
+ }
+
+ regs->set16(REG_STRPIXEL, session.pixel_startx);
+ regs->set16(REG_ENDPIXEL, session.pixel_endx);
+
+ regs->set24(REG_MAXWD, session.output_line_bytes);
+
+ regs->set16(REG_DPISET, session.output_resolution * session.ccd_size_divisor *
+ sensor.ccd_pixels_per_system_pixel());
+ regs->set16(REG_LPERIOD, sensor.exposure_lperiod);
+
+ /* move distance must be adjusted to take into account the extra lines
+ * read to reorder data */
+ feedl = move;
+
+ if (session.num_staggered_lines + session.max_color_shift_lines > 0 && feedl != 0) {
+ int feed_offset = ((session.max_color_shift_lines + session.num_staggered_lines) * dev->motor.optical_ydpi) /
+ motor->dpi;
+ if (feedl > feed_offset) {
+ feedl = feedl - feed_offset;
+ }
+ }
+
+ /* we assume all scans are done with 2 tables */
+ /*
+ feedl = feed_steps - fast_slope_steps*2 -
+ (slow_slope_steps >> scan_step_type); */
+ /* but head has moved due to shading calibration => dev->scanhead_position_primary */
+ if (feedl > 0)
+ {
+ DBG(DBG_info, "%s: initial move=%d\n", __func__, feedl);
+
+ /* TODO clean up this when I'll fully understand.
+ * for now, special casing each motor */
+ switch (dev->model->motor_id) {
+ case MotorId::MD_5345:
+ switch (motor->dpi) {
+ case 200:
+ feedl -= 70;
+ break;
+ case 300:
+ feedl -= 70;
+ break;
+ case 400:
+ feedl += 130;
+ break;
+ case 600:
+ feedl += 160;
+ break;
+ case 1200:
+ feedl += 160;
+ break;
+ case 2400:
+ feedl += 180;
+ break;
+ default:
+ break;
+ }
+ break;
+ case MotorId::HP2300:
+ switch (motor->dpi) {
+ case 75:
+ feedl -= 180;
+ break;
+ case 150:
+ feedl += 0;
+ break;
+ case 300:
+ feedl += 30;
+ break;
+ case 600:
+ feedl += 35;
+ break;
+ case 1200:
+ feedl += 45;
+ break;
+ default:
+ break;
+ }
+ break;
+ case MotorId::HP2400:
+ switch (motor->dpi) {
+ case 150:
+ feedl += 150;
+ break;
+ case 300:
+ feedl += 220;
+ break;
+ case 600:
+ feedl += 260;
+ break;
+ case 1200:
+ feedl += 280; /* 300 */
+ break;
+ case 50:
+ feedl += 0;
+ break;
+ case 100:
+ feedl += 100;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ /* theorical value */
+ default: {
+ unsigned step_shift = static_cast<unsigned>(motor->steptype);
+
+ if (motor->fastfed)
+ {
+ feedl = feedl - 2 * slope_table2.steps_count -
+ (slope_table1.steps_count >> step_shift);
+ }
+ else
+ {
+ feedl = feedl - (slope_table1.steps_count >> step_shift);
+ }
+ break;
+ }
+ }
+ /* security */
+ if (feedl < 0)
+ feedl = 0;
+ }
+
+ DBG(DBG_info, "%s: final move=%d\n", __func__, feedl);
+ regs->set24(REG_FEEDL, feedl);
+
+ regs->find_reg(0x65).value = motor->mtrpwm;
+
+ sanei_genesys_calculate_zmod(regs->find_reg(0x02).value & REG_0x02_FASTFED,
+ sensor.exposure_lperiod,
+ slope_table1.table,
+ slope_table1.steps_count,
+ move, motor->fwdbwd, &z1, &z2);
+
+ /* no z1/z2 for sheetfed scanners */
+ if (dev->model->is_sheetfed) {
+ z1 = 0;
+ z2 = 0;
+ }
+ regs->set16(REG_Z1MOD, z1);
+ regs->set16(REG_Z2MOD, z2);
+ regs->find_reg(0x6b).value = slope_table2.steps_count;
+ regs->find_reg(0x6c).value =
+ (regs->find_reg(0x6c).value & REG_0x6C_TGTIME) | ((z1 >> 13) & 0x38) | ((z2 >> 16)
+ & 0x07);
+
+ write_control(dev, sensor, session.output_resolution);
+
+ // setup analog frontend
+ gl646_set_fe(dev, sensor, AFE_SET, session.output_resolution);
+
+ dev->read_buffer.clear();
+ dev->read_buffer.alloc(session.buffer_size_read);
+
+ build_image_pipeline(dev, session);
+
+ dev->read_active = true;
+
+ dev->session = session;
+
+ dev->total_bytes_read = 0;
+ dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines;
+
+ /* select color filter based on settings */
+ regs->find_reg(0x04).value &= ~REG_0x04_FILTER;
+ if (session.params.channels == 1) {
+ switch (session.params.color_filter) {
+ case ColorFilter::RED:
+ regs->find_reg(0x04).value |= 0x04;
+ break;
+ case ColorFilter::GREEN:
+ regs->find_reg(0x04).value |= 0x08;
+ break;
+ case ColorFilter::BLUE:
+ regs->find_reg(0x04).value |= 0x0c;
+ break;
+ default:
+ break;
+ }
+ }
+
+ gl646_send_slope_table(dev, 0, slope_table1.table, regs->get8(0x21));
+ gl646_send_slope_table(dev, 1, slope_table2.table, regs->get8(0x6b));
+}
+
+
+/** copy sensor specific settings */
+/* *dev : device infos
+ *regs : regiters to be set
+ extended : do extended set up
+ ccd_size_divisor: set up for half ccd resolution
+ all registers 08-0B, 10-1D, 52-5E are set up. They shouldn't
+ appear anywhere else but in register init
+*/
+static void
+gl646_setup_sensor (Genesys_Device * dev, const Genesys_Sensor& sensor, Genesys_Register_Set * regs)
+{
+ (void) dev;
+ DBG(DBG_proc, "%s: start\n", __func__);
+
+ for (const auto& reg_setting : sensor.custom_base_regs) {
+ regs->set8(reg_setting.address, reg_setting.value);
+ }
+ // FIXME: all other drivers don't set exposure here
+ regs_set_exposure(AsicType::GL646, *regs, sensor.exposure);
+
+ DBG(DBG_proc, "%s: end\n", __func__);
+}
+
+/**
+ * Set all registers to default values after init
+ * @param dev scannerr's device to set
+ */
+static void
+gl646_init_regs (Genesys_Device * dev)
+{
+ int addr;
+
+ DBG(DBG_proc, "%s\n", __func__);
+
+ dev->reg.clear();
+
+ for (addr = 1; addr <= 0x0b; addr++)
+ dev->reg.init_reg(addr, 0);
+ for (addr = 0x10; addr <= 0x29; addr++)
+ dev->reg.init_reg(addr, 0);
+ for (addr = 0x2c; addr <= 0x39; addr++)
+ dev->reg.init_reg(addr, 0);
+ for (addr = 0x3d; addr <= 0x3f; addr++)
+ dev->reg.init_reg(addr, 0);
+ for (addr = 0x52; addr <= 0x5e; addr++)
+ dev->reg.init_reg(addr, 0);
+ for (addr = 0x60; addr <= 0x6d; addr++)
+ dev->reg.init_reg(addr, 0);
+
+ dev->reg.find_reg(0x01).value = 0x20 /*0x22 */ ; /* enable shading, CCD, color, 1M */
+ dev->reg.find_reg(0x02).value = 0x30 /*0x38 */ ; /* auto home, one-table-move, full step */
+ if (dev->model->motor_id == MotorId::MD_5345) {
+ dev->reg.find_reg(0x02).value |= 0x01; // half-step
+ }
+ switch (dev->model->motor_id) {
+ case MotorId::MD_5345:
+ dev->reg.find_reg(0x02).value |= 0x01; /* half-step */
+ break;
+ case MotorId::XP200:
+ /* for this sheetfed scanner, no AGOHOME, nor backtracking */
+ dev->reg.find_reg(0x02).value = 0x50;
+ break;
+ default:
+ break;
+ }
+ dev->reg.find_reg(0x03).value = 0x1f /*0x17 */ ; /* lamp on */
+ dev->reg.find_reg(0x04).value = 0x13 /*0x03 */ ; /* 8 bits data, 16 bits A/D, color, Wolfson fe *//* todo: according to spec, 0x0 is reserved? */
+ switch (dev->model->adc_id)
+ {
+ case AdcId::AD_XP200:
+ dev->reg.find_reg(0x04).value = 0x12;
+ break;
+ default:
+ /* Wolfson frontend */
+ dev->reg.find_reg(0x04).value = 0x13;
+ break;
+ }
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+
+ dev->reg.find_reg(0x05).value = 0x00; /* 12 bits gamma, disable gamma, 24 clocks/pixel */
+ sanei_genesys_set_dpihw(dev->reg, sensor, sensor.optical_res);
+
+ if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) {
+ dev->reg.find_reg(0x05).value |= REG_0x05_GMM14BIT;
+ }
+ if (dev->model->adc_id == AdcId::AD_XP200) {
+ dev->reg.find_reg(0x05).value |= 0x01; /* 12 clocks/pixel */
+ }
+
+ if (dev->model->sensor_id == SensorId::CCD_HP2300) {
+ dev->reg.find_reg(0x06).value = 0x00; // PWRBIT off, shading gain=4, normal AFE image capture
+ } else {
+ dev->reg.find_reg(0x06).value = 0x18; // PWRBIT on, shading gain=8, normal AFE image capture
+ }
+
+
+ gl646_setup_sensor(dev, sensor, &dev->reg);
+
+ dev->reg.find_reg(0x1e).value = 0xf0; /* watch-dog time */
+
+ switch (dev->model->sensor_id)
+ {
+ case SensorId::CCD_HP2300:
+ dev->reg.find_reg(0x1e).value = 0xf0;
+ dev->reg.find_reg(0x1f).value = 0x10;
+ dev->reg.find_reg(0x20).value = 0x20;
+ break;
+ case SensorId::CCD_HP2400:
+ dev->reg.find_reg(0x1e).value = 0x80;
+ dev->reg.find_reg(0x1f).value = 0x10;
+ dev->reg.find_reg(0x20).value = 0x20;
+ break;
+ case SensorId::CCD_HP3670:
+ dev->reg.find_reg(0x19).value = 0x2a;
+ dev->reg.find_reg(0x1e).value = 0x80;
+ dev->reg.find_reg(0x1f).value = 0x10;
+ dev->reg.find_reg(0x20).value = 0x20;
+ break;
+ case SensorId::CIS_XP200:
+ dev->reg.find_reg(0x1e).value = 0x10;
+ dev->reg.find_reg(0x1f).value = 0x01;
+ dev->reg.find_reg(0x20).value = 0x50;
+ break;
+ default:
+ dev->reg.find_reg(0x1f).value = 0x01;
+ dev->reg.find_reg(0x20).value = 0x50;
+ break;
+ }
+
+ dev->reg.find_reg(0x21).value = 0x08 /*0x20 */ ; /* table one steps number for forward slope curve of the acc/dec */
+ dev->reg.find_reg(0x22).value = 0x10 /*0x08 */ ; /* steps number of the forward steps for start/stop */
+ dev->reg.find_reg(0x23).value = 0x10 /*0x08 */ ; /* steps number of the backward steps for start/stop */
+ dev->reg.find_reg(0x24).value = 0x08 /*0x20 */ ; /* table one steps number backward slope curve of the acc/dec */
+ dev->reg.find_reg(0x25).value = 0x00; /* scan line numbers (7000) */
+ dev->reg.find_reg(0x26).value = 0x00 /*0x1b */ ;
+ dev->reg.find_reg(0x27).value = 0xd4 /*0x58 */ ;
+ dev->reg.find_reg(0x28).value = 0x01; /* PWM duty for lamp control */
+ dev->reg.find_reg(0x29).value = 0xff;
+
+ dev->reg.find_reg(0x2c).value = 0x02; /* set resolution (600 DPI) */
+ dev->reg.find_reg(0x2d).value = 0x58;
+ dev->reg.find_reg(0x2e).value = 0x78; /* set black&white threshold high level */
+ dev->reg.find_reg(0x2f).value = 0x7f; /* set black&white threshold low level */
+
+ dev->reg.find_reg(0x30).value = 0x00; /* begin pixel position (16) */
+ dev->reg.find_reg(0x31).value = sensor.dummy_pixel /*0x10 */ ; /* TGW + 2*TG_SHLD + x */
+ dev->reg.find_reg(0x32).value = 0x2a /*0x15 */ ; /* end pixel position (5390) */
+ dev->reg.find_reg(0x33).value = 0xf8 /*0x0e */ ; /* TGW + 2*TG_SHLD + y */
+ dev->reg.find_reg(0x34).value = sensor.dummy_pixel;
+ dev->reg.find_reg(0x35).value = 0x01 /*0x00 */ ; /* set maximum word size per line, for buffer full control (10800) */
+ dev->reg.find_reg(0x36).value = 0x00 /*0x2a */ ;
+ dev->reg.find_reg(0x37).value = 0x00 /*0x30 */ ;
+ dev->reg.find_reg(0x38).value = 0x2a; // line period (exposure time = 11000 pixels) */
+ dev->reg.find_reg(0x39).value = 0xf8;
+ dev->reg.find_reg(0x3d).value = 0x00; /* set feed steps number of motor move */
+ dev->reg.find_reg(0x3e).value = 0x00;
+ dev->reg.find_reg(0x3f).value = 0x01 /*0x00 */ ;
+
+ dev->reg.find_reg(0x60).value = 0x00; /* Z1MOD, 60h:61h:(6D b5:b3), remainder for start/stop */
+ dev->reg.find_reg(0x61).value = 0x00; /* (21h+22h)/LPeriod */
+ dev->reg.find_reg(0x62).value = 0x00; /* Z2MODE, 62h:63h:(6D b2:b0), remainder for start scan */
+ dev->reg.find_reg(0x63).value = 0x00; /* (3Dh+3Eh+3Fh)/LPeriod for one-table mode,(21h+1Fh)/LPeriod */
+ dev->reg.find_reg(0x64).value = 0x00; /* motor PWM frequency */
+ dev->reg.find_reg(0x65).value = 0x00; /* PWM duty cycle for table one motor phase (63 = max) */
+ if (dev->model->motor_id == MotorId::MD_5345) {
+ // PWM duty cycle for table one motor phase (63 = max)
+ dev->reg.find_reg(0x65).value = 0x02;
+ }
+
+ for (const auto& reg : dev->gpo.regs) {
+ dev->reg.set8(reg.address, reg.value);
+ }
+
+ switch (dev->model->motor_id) {
+ case MotorId::HP2300:
+ case MotorId::HP2400:
+ dev->reg.find_reg(0x6a).value = 0x7f; /* table two steps number for acc/dec */
+ dev->reg.find_reg(0x6b).value = 0x78; /* table two steps number for acc/dec */
+ dev->reg.find_reg(0x6d).value = 0x7f;
+ break;
+ case MotorId::MD_5345:
+ dev->reg.find_reg(0x6a).value = 0x42; /* table two fast moving step type, PWM duty for table two */
+ dev->reg.find_reg(0x6b).value = 0xff; /* table two steps number for acc/dec */
+ dev->reg.find_reg(0x6d).value = 0x41; /* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */
+ break;
+ case MotorId::XP200:
+ dev->reg.find_reg(0x6a).value = 0x7f; /* table two fast moving step type, PWM duty for table two */
+ dev->reg.find_reg(0x6b).value = 0x08; /* table two steps number for acc/dec */
+ dev->reg.find_reg(0x6d).value = 0x01; /* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */
+ break;
+ case MotorId::HP3670:
+ dev->reg.find_reg(0x6a).value = 0x41; /* table two steps number for acc/dec */
+ dev->reg.find_reg(0x6b).value = 0xc8; /* table two steps number for acc/dec */
+ dev->reg.find_reg(0x6d).value = 0x7f;
+ break;
+ default:
+ dev->reg.find_reg(0x6a).value = 0x40; /* table two fast moving step type, PWM duty for table two */
+ dev->reg.find_reg(0x6b).value = 0xff; /* table two steps number for acc/dec */
+ dev->reg.find_reg(0x6d).value = 0x01; /* select deceleration steps whenever go home (0), accel/decel stop time (31 * LPeriod) */
+ break;
+ }
+ dev->reg.find_reg(0x6c).value = 0x00; /* peroid times for LPeriod, expR,expG,expB, Z1MODE, Z2MODE (one period time) */
+}
+
+
+// Send slope table for motor movement slope_table in machine byte order
+static void gl646_send_slope_table(Genesys_Device* dev, int table_nr,
+ const std::vector<uint16_t>& slope_table,
+ int steps)
+{
+ DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d)=%d .. %d", table_nr, steps, slope_table[0],
+ slope_table[steps - 1]);
+ int dpihw;
+ int start_address;
+
+ dpihw = dev->reg.find_reg(0x05).value >> 6;
+
+ if (dpihw == 0) /* 600 dpi */
+ start_address = 0x08000;
+ else if (dpihw == 1) /* 1200 dpi */
+ start_address = 0x10000;
+ else if (dpihw == 2) /* 2400 dpi */
+ start_address = 0x1f800;
+ else {
+ throw SaneException("Unexpected dpihw");
+ }
+
+ std::vector<uint8_t> table(steps * 2);
+ for (int i = 0; i < steps; i++)
+ {
+ table[i * 2] = slope_table[i] & 0xff;
+ table[i * 2 + 1] = slope_table[i] >> 8;
+ }
+
+ if (dev->interface->is_mock()) {
+ dev->interface->record_slope_table(table_nr, slope_table);
+ }
+ dev->interface->write_buffer(0x3c, start_address + table_nr * 0x100, table.data(), steps * 2);
+}
+
+// Set values of Analog Device type frontend
+static void gl646_set_ad_fe(Genesys_Device* dev, uint8_t set)
+{
+ DBG_HELPER(dbg);
+ int i;
+
+ if (set == AFE_INIT)
+ {
+ DBG(DBG_proc, "%s(): setting DAC %u\n", __func__,
+ static_cast<unsigned>(dev->model->adc_id));
+
+ dev->frontend = dev->frontend_initial;
+
+ // write them to analog frontend
+ dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
+ dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01));
+ }
+ if (set == AFE_SET)
+ {
+ for (i = 0; i < 3; i++) {
+ dev->interface->write_fe_register(0x02 + i, dev->frontend.get_gain(i));
+ }
+ for (i = 0; i < 3; i++) {
+ dev->interface->write_fe_register(0x05 + i, dev->frontend.get_offset(i));
+ }
+ }
+ /*
+ if (set == AFE_POWER_SAVE)
+ {
+ dev->interface->write_fe_register(0x00, dev->frontend.reg[0] | 0x04);
+ } */
+}
+
+/** set up analog frontend
+ * set up analog frontend
+ * @param dev device to set up
+ * @param set action from AFE_SET, AFE_INIT and AFE_POWERSAVE
+ * @param dpi resolution of the scan since it affects settings
+ */
+static void gl646_wm_hp3670(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set,
+ unsigned dpi)
+{
+ DBG_HELPER(dbg);
+ int i;
+
+ switch (set)
+ {
+ case AFE_INIT:
+ dev->interface->write_fe_register(0x04, 0x80);
+ dev->interface->sleep_ms(200);
+ dev->interface->write_register(0x50, 0x00);
+ dev->frontend = dev->frontend_initial;
+ dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01));
+ dev->interface->write_fe_register(0x02, dev->frontend.regs.get_value(0x02));
+ gl646_gpio_output_enable(dev->interface->get_usb_device(), 0x07);
+ break;
+ case AFE_POWER_SAVE:
+ dev->interface->write_fe_register(0x01, 0x06);
+ dev->interface->write_fe_register(0x06, 0x0f);
+ return;
+ break;
+ default: /* AFE_SET */
+ /* mode setup */
+ i = dev->frontend.regs.get_value(0x03);
+ if (dpi > sensor.optical_res / 2)
+ {
+ /* fe_reg_0x03 must be 0x12 for 1200 dpi in WOLFSON_HP3670.
+ * WOLFSON_HP2400 in 1200 dpi mode works well with
+ * fe_reg_0x03 set to 0x32 or 0x12 but not to 0x02 */
+ i = 0x12;
+ }
+ dev->interface->write_fe_register(0x03, i);
+ /* offset and sign (or msb/lsb ?) */
+ for (i = 0; i < 3; i++) {
+ dev->interface->write_fe_register(0x20 + i, dev->frontend.get_offset(i));
+ dev->interface->write_fe_register(0x24 + i, dev->frontend.regs.get_value(0x24 + i));
+ }
+
+ // gain
+ for (i = 0; i < 3; i++) {
+ dev->interface->write_fe_register(0x28 + i, dev->frontend.get_gain(i));
+ }
+ }
+}
+
+/** Set values of analog frontend
+ * @param dev device to set
+ * @param set action to execute
+ * @param dpi dpi to setup the AFE
+ */
+static void gl646_set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set, int dpi)
+{
+ DBG_HELPER_ARGS(dbg, "%s,%d", set == AFE_INIT ? "init" :
+ set == AFE_SET ? "set" :
+ set == AFE_POWER_SAVE ? "powersave" : "huh?", dpi);
+ int i;
+ uint8_t val;
+
+ /* Analog Device type frontend */
+ uint8_t frontend_type = dev->reg.find_reg(0x04).value & REG_0x04_FESET;
+ if (frontend_type == 0x02) {
+ gl646_set_ad_fe(dev, set);
+ return;
+ }
+
+ /* Wolfson type frontend */
+ if (frontend_type != 0x03) {
+ throw SaneException("unsupported frontend type %d", frontend_type);
+ }
+
+ /* per frontend function to keep code clean */
+ switch (dev->model->adc_id)
+ {
+ case AdcId::WOLFSON_HP3670:
+ case AdcId::WOLFSON_HP2400:
+ gl646_wm_hp3670(dev, sensor, set, dpi);
+ return;
+ default:
+ DBG(DBG_proc, "%s(): using old method\n", __func__);
+ break;
+ }
+
+ /* initialize analog frontend */
+ if (set == AFE_INIT)
+ {
+ DBG(DBG_proc, "%s(): setting DAC %u\n", __func__,
+ static_cast<unsigned>(dev->model->adc_id));
+ dev->frontend = dev->frontend_initial;
+
+ // reset only done on init
+ dev->interface->write_fe_register(0x04, 0x80);
+
+ /* enable GPIO for some models */
+ if (dev->model->sensor_id == SensorId::CCD_HP2300) {
+ val = 0x07;
+ gl646_gpio_output_enable(dev->interface->get_usb_device(), val);
+ }
+ return;
+ }
+
+ // set fontend to power saving mode
+ if (set == AFE_POWER_SAVE) {
+ dev->interface->write_fe_register(0x01, 0x02);
+ return;
+ }
+
+ /* here starts AFE_SET */
+ /* TODO : base this test on cfg reg3 or a CCD family flag to be created */
+ /* if (dev->model->ccd_type != SensorId::CCD_HP2300
+ && dev->model->ccd_type != SensorId::CCD_HP3670
+ && dev->model->ccd_type != SensorId::CCD_HP2400) */
+ {
+ dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
+ dev->interface->write_fe_register(0x02, dev->frontend.regs.get_value(0x02));
+ }
+
+ // start with reg3
+ dev->interface->write_fe_register(0x03, dev->frontend.regs.get_value(0x03));
+
+ switch (dev->model->sensor_id)
+ {
+ default:
+ for (i = 0; i < 3; i++) {
+ dev->interface->write_fe_register(0x24 + i, dev->frontend.regs.get_value(0x24 + i));
+ dev->interface->write_fe_register(0x28 + i, dev->frontend.get_gain(i));
+ dev->interface->write_fe_register(0x20 + i, dev->frontend.get_offset(i));
+ }
+ break;
+ /* just can't have it to work ....
+ case SensorId::CCD_HP2300:
+ case SensorId::CCD_HP2400:
+ case SensorId::CCD_HP3670:
+
+ dev->interface->write_fe_register(0x23, dev->frontend.get_offset(1));
+ dev->interface->write_fe_register(0x28, dev->frontend.get_gain(1));
+ break; */
+ }
+
+ // end with reg1
+ dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01));
+}
+
+/** Set values of analog frontend
+ * this this the public interface, the gl646 as to use one more
+ * parameter to work effectively, hence the redirection
+ * @param dev device to set
+ * @param set action to execute
+ */
+void CommandSetGl646::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const
+{
+ gl646_set_fe(dev, sensor, set, dev->settings.yres);
+}
+
+/**
+ * enters or leaves power saving mode
+ * limited to AFE for now.
+ * @param dev scanner's device
+ * @param enable true to enable power saving, false to leave it
+ */
+void CommandSetGl646::save_power(Genesys_Device* dev, bool enable) const
+{
+ DBG_HELPER_ARGS(dbg, "enable = %d", enable);
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+
+ if (enable)
+ {
+ // gl646_set_fe(dev, sensor, AFE_POWER_SAVE);
+ }
+ else
+ {
+ gl646_set_fe(dev, sensor, AFE_INIT, 0);
+ }
+}
+
+void CommandSetGl646::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const
+{
+ DBG_HELPER_ARGS(dbg, "delay = %d", delay);
+ Genesys_Register_Set local_reg(Genesys_Register_Set::SEQUENTIAL);
+ int rate, exposure_time, tgtime, time;
+
+ local_reg.init_reg(0x01, dev->reg.get8(0x01)); // disable fastmode
+ local_reg.init_reg(0x03, dev->reg.get8(0x03)); // Lamp power control
+ local_reg.init_reg(0x05, dev->reg.get8(0x05) & ~REG_0x05_BASESEL); // 24 clocks/pixel
+ local_reg.init_reg(0x38, 0x00); // line period low
+ local_reg.init_reg(0x39, 0x00); //line period high
+ local_reg.init_reg(0x6c, 0x00); // period times for LPeriod, expR,expG,expB, Z1MODE, Z2MODE
+
+ if (!delay)
+ local_reg.find_reg(0x03).value &= 0xf0; /* disable lampdog and set lamptime = 0 */
+ else if (delay < 20)
+ local_reg.find_reg(0x03).value = (local_reg.get8(0x03) & 0xf0) | 0x09; /* enable lampdog and set lamptime = 1 */
+ else
+ local_reg.find_reg(0x03).value = (local_reg.get8(0x03) & 0xf0) | 0x0f; /* enable lampdog and set lamptime = 7 */
+
+ time = delay * 1000 * 60; /* -> msec */
+ exposure_time = static_cast<std::uint32_t>((time * 32000.0 /
+ (24.0 * 64.0 * (local_reg.get8(0x03) & REG_0x03_LAMPTIM) *
+ 1024.0) + 0.5));
+ /* 32000 = system clock, 24 = clocks per pixel */
+ rate = (exposure_time + 65536) / 65536;
+ if (rate > 4)
+ {
+ rate = 8;
+ tgtime = 3;
+ }
+ else if (rate > 2)
+ {
+ rate = 4;
+ tgtime = 2;
+ }
+ else if (rate > 1)
+ {
+ rate = 2;
+ tgtime = 1;
+ }
+ else
+ {
+ rate = 1;
+ tgtime = 0;
+ }
+
+ local_reg.find_reg(0x6c).value |= tgtime << 6;
+ exposure_time /= rate;
+
+ if (exposure_time > 65535)
+ exposure_time = 65535;
+
+ local_reg.find_reg(0x38).value = exposure_time / 256;
+ local_reg.find_reg(0x39).value = exposure_time & 255;
+
+ dev->interface->write_registers(local_reg);
+}
+
+
+/**
+ * loads document into scanner
+ * currently only used by XP200
+ * bit2 (0x04) of gpio is paper event (document in/out) on XP200
+ * HOMESNR is set if no document in front of sensor, the sequence of events is
+ * paper event -> document is in the sheet feeder
+ * HOMESNR becomes 0 -> document reach sensor
+ * HOMESNR becomes 1 ->document left sensor
+ * paper event -> document is out
+ */
+void CommandSetGl646::load_document(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+
+ // FIXME: sequential not really needed in this case
+ Genesys_Register_Set regs(Genesys_Register_Set::SEQUENTIAL);
+ unsigned count;
+
+ /* no need to load document is flatbed scanner */
+ if (!dev->model->is_sheetfed) {
+ DBG(DBG_proc, "%s: nothing to load\n", __func__);
+ DBG(DBG_proc, "%s: end\n", __func__);
+ return;
+ }
+
+ auto status = scanner_read_status(*dev);
+
+ // home sensor is set if a document is inserted
+ if (status.is_at_home) {
+ /* if no document, waits for a paper event to start loading */
+ /* with a 60 seconde minutes timeout */
+ count = 0;
+ std::uint8_t val = 0;
+ do {
+ gl646_gpio_read(dev->interface->get_usb_device(), &val);
+
+ DBG(DBG_info, "%s: GPIO=0x%02x\n", __func__, val);
+ if ((val & 0x04) != 0x04)
+ {
+ DBG(DBG_warn, "%s: no paper detected\n", __func__);
+ }
+ dev->interface->sleep_ms(200);
+ count++;
+ }
+ while (((val & 0x04) != 0x04) && (count < 300)); /* 1 min time out */
+ if (count == 300)
+ {
+ throw SaneException(SANE_STATUS_NO_DOCS, "timeout waiting for document");
+ }
+ }
+
+ /* set up to fast move before scan then move until document is detected */
+ regs.init_reg(0x01, 0x90);
+
+ /* AGOME, 2 slopes motor moving */
+ regs.init_reg(0x02, 0x79);
+
+ /* motor feeding steps to 0 */
+ regs.init_reg(0x3d, 0);
+ regs.init_reg(0x3e, 0);
+ regs.init_reg(0x3f, 0);
+
+ /* 50 fast moving steps */
+ regs.init_reg(0x6b, 50);
+
+ /* set GPO */
+ regs.init_reg(0x66, 0x30);
+
+ /* stesp NO */
+ regs.init_reg(0x21, 4);
+ regs.init_reg(0x22, 1);
+ regs.init_reg(0x23, 1);
+ regs.init_reg(0x24, 4);
+
+ /* generate slope table 2 */
+ auto slope_table = create_slope_table(MotorSlope::create_from_steps(6000, 2400, 50), 2400,
+ StepType::FULL, 1, 4,
+ get_slope_table_max_size(AsicType::GL646));
+ // document loading:
+ // send regs
+ // start motor
+ // wait e1 status to become e0
+ gl646_send_slope_table(dev, 1, slope_table.table, slope_table.steps_count);
+
+ dev->interface->write_registers(regs);
+
+ scanner_start_action(*dev, true);
+
+ count = 0;
+ do
+ {
+ status = scanner_read_status(*dev);
+ dev->interface->sleep_ms(200);
+ count++;
+ } while (status.is_motor_enabled && (count < 300));
+
+ if (count == 300)
+ {
+ throw SaneException(SANE_STATUS_JAMMED, "can't load document");
+ }
+
+ /* when loading OK, document is here */
+ dev->document = true;
+
+ /* set up to idle */
+ regs.set8(0x02, 0x71);
+ regs.set8(0x3f, 1);
+ regs.set8(0x6b, 8);
+ dev->interface->write_registers(regs);
+}
+
+/**
+ * detects end of document and adjust current scan
+ * to take it into account
+ * used by sheetfed scanners
+ */
+void CommandSetGl646::detect_document_end(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ std::uint8_t gpio;
+ unsigned int bytes_left;
+
+ // test for document presence
+ scanner_read_print_status(*dev);
+
+ gl646_gpio_read(dev->interface->get_usb_device(), &gpio);
+ DBG(DBG_info, "%s: GPIO=0x%02x\n", __func__, gpio);
+
+ /* detect document event. There one event when the document go in,
+ * then another when it leaves */
+ if (dev->document && (gpio & 0x04) && (dev->total_bytes_read > 0)) {
+ DBG(DBG_info, "%s: no more document\n", __func__);
+ dev->document = false;
+
+ /* adjust number of bytes to read:
+ * total_bytes_to_read is the number of byte to send to frontend
+ * total_bytes_read is the number of bytes sent to frontend
+ * read_bytes_left is the number of bytes to read from the scanner
+ */
+ DBG(DBG_io, "%s: total_bytes_to_read=%zu\n", __func__, dev->total_bytes_to_read);
+ DBG(DBG_io, "%s: total_bytes_read =%zu\n", __func__, dev->total_bytes_read);
+
+ // amount of data available from scanner is what to scan
+ sanei_genesys_read_valid_words(dev, &bytes_left);
+
+ unsigned lines_in_buffer = bytes_left / dev->session.output_line_bytes_raw;
+
+ // we add the number of lines needed to read the last part of the document in
+ unsigned lines_offset = static_cast<unsigned>(
+ (dev->model->y_offset * dev->session.params.yres) / MM_PER_INCH);
+
+ unsigned remaining_lines = lines_in_buffer + lines_offset;
+
+ bytes_left = remaining_lines * dev->session.output_line_bytes_raw;
+
+ if (bytes_left < dev->get_pipeline_source().remaining_bytes()) {
+ dev->get_pipeline_source().set_remaining_bytes(bytes_left);
+ dev->total_bytes_to_read = dev->total_bytes_read + bytes_left;
+ }
+ DBG(DBG_io, "%s: total_bytes_to_read=%zu\n", __func__, dev->total_bytes_to_read);
+ DBG(DBG_io, "%s: total_bytes_read =%zu\n", __func__, dev->total_bytes_read);
+ }
+}
+
+/**
+ * eject document from the feeder
+ * currently only used by XP200
+ * TODO we currently rely on AGOHOME not being set for sheetfed scanners,
+ * maybe check this flag in eject to let the document being eject automaticaly
+ */
+void CommandSetGl646::eject_document(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+
+ // FIXME: SEQUENTIAL not really needed in this case
+ Genesys_Register_Set regs((Genesys_Register_Set::SEQUENTIAL));
+ unsigned count;
+ std::uint8_t gpio;
+
+ /* at the end there will be noe more document */
+ dev->document = false;
+
+ // first check for document event
+ gl646_gpio_read(dev->interface->get_usb_device(), &gpio);
+
+ DBG(DBG_info, "%s: GPIO=0x%02x\n", __func__, gpio);
+
+ // test status : paper event + HOMESNR -> no more doc ?
+ auto status = scanner_read_status(*dev);
+
+ // home sensor is set when document is inserted
+ if (status.is_at_home) {
+ dev->document = false;
+ DBG(DBG_info, "%s: no more document to eject\n", __func__);
+ DBG(DBG_proc, "%s: end\n", __func__);
+ return;
+ }
+
+ // there is a document inserted, eject it
+ dev->interface->write_register(0x01, 0xb0);
+
+ /* wait for motor to stop */
+ do {
+ dev->interface->sleep_ms(200);
+ status = scanner_read_status(*dev);
+ }
+ while (status.is_motor_enabled);
+
+ /* set up to fast move before scan then move until document is detected */
+ regs.init_reg(0x01, 0xb0);
+
+ /* AGOME, 2 slopes motor moving , eject 'backward' */
+ regs.init_reg(0x02, 0x5d);
+
+ /* motor feeding steps to 119880 */
+ regs.init_reg(0x3d, 1);
+ regs.init_reg(0x3e, 0xd4);
+ regs.init_reg(0x3f, 0x48);
+
+ /* 60 fast moving steps */
+ regs.init_reg(0x6b, 60);
+
+ /* set GPO */
+ regs.init_reg(0x66, 0x30);
+
+ /* stesp NO */
+ regs.init_reg(0x21, 4);
+ regs.init_reg(0x22, 1);
+ regs.init_reg(0x23, 1);
+ regs.init_reg(0x24, 4);
+
+ /* generate slope table 2 */
+ auto slope_table = create_slope_table(MotorSlope::create_from_steps(10000, 1600, 60), 1600,
+ StepType::FULL, 1, 4,
+ get_slope_table_max_size(AsicType::GL646));
+ // document eject:
+ // send regs
+ // start motor
+ // wait c1 status to become c8 : HOMESNR and ~MOTFLAG
+ gl646_send_slope_table(dev, 1, slope_table.table, slope_table.steps_count);
+
+ dev->interface->write_registers(regs);
+
+ scanner_start_action(*dev, true);
+
+ /* loop until paper sensor tells paper is out, and till motor is running */
+ /* use a 30 timeout */
+ count = 0;
+ do {
+ status = scanner_read_status(*dev);
+
+ dev->interface->sleep_ms(200);
+ count++;
+ } while (!status.is_at_home && (count < 150));
+
+ // read GPIO on exit
+ gl646_gpio_read(dev->interface->get_usb_device(), &gpio);
+
+ DBG(DBG_info, "%s: GPIO=0x%02x\n", __func__, gpio);
+}
+
+// Send the low-level scan command
+void CommandSetGl646::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, bool start_motor) const
+{
+ DBG_HELPER(dbg);
+ (void) sensor;
+ // FIXME: SEQUENTIAL not really needed in this case
+ Genesys_Register_Set local_reg(Genesys_Register_Set::SEQUENTIAL);
+
+ local_reg.init_reg(0x03, reg->get8(0x03));
+ local_reg.init_reg(0x01, reg->get8(0x01) | REG_0x01_SCAN);
+
+ if (start_motor) {
+ local_reg.init_reg(0x0f, 0x01);
+ } else {
+ local_reg.init_reg(0x0f, 0x00); // do not start motor yet
+ }
+
+ dev->interface->write_registers(local_reg);
+
+ dev->advance_head_pos_by_session(ScanHeadId::PRIMARY);
+}
+
+
+// Send the stop scan command
+static void end_scan_impl(Genesys_Device* dev, Genesys_Register_Set* reg, bool check_stop,
+ bool eject)
+{
+ DBG_HELPER_ARGS(dbg, "check_stop = %d, eject = %d", check_stop, eject);
+
+ scanner_stop_action_no_move(*dev, *reg);
+
+ unsigned wait_limit_seconds = 30;
+
+ /* for sheetfed scanners, we may have to eject document */
+ if (dev->model->is_sheetfed) {
+ if (eject && dev->document) {
+ dev->cmd_set->eject_document(dev);
+ }
+ wait_limit_seconds = 3;
+ }
+
+ if (is_testing_mode()) {
+ return;
+ }
+
+ dev->interface->sleep_ms(100);
+
+ if (check_stop) {
+ for (unsigned i = 0; i < wait_limit_seconds * 10; i++) {
+ if (scanner_is_motor_stopped(*dev)) {
+ return;
+ }
+
+ dev->interface->sleep_ms(100);
+ }
+ throw SaneException(SANE_STATUS_IO_ERROR, "could not stop motor");
+ }
+}
+
+// Send the stop scan command
+void CommandSetGl646::end_scan(Genesys_Device* dev, Genesys_Register_Set* reg,
+ bool check_stop) const
+{
+ end_scan_impl(dev, reg, check_stop, false);
+}
+
+/**
+ * parks head
+ * @param dev scanner's device
+ * @param wait_until_home true if the function waits until head parked
+ */
+void CommandSetGl646::move_back_home(Genesys_Device* dev, bool wait_until_home) const
+{
+ DBG_HELPER_ARGS(dbg, "wait_until_home = %d\n", wait_until_home);
+ int i;
+ int loop = 0;
+
+ auto status = scanner_read_status(*dev);
+
+ if (status.is_at_home) {
+ DBG(DBG_info, "%s: end since already at home\n", __func__);
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+ return;
+ }
+
+ /* stop motor if needed */
+ if (status.is_motor_enabled) {
+ gl646_stop_motor(dev);
+ dev->interface->sleep_ms(200);
+ }
+
+ /* when scanhead is moving then wait until scanhead stops or timeout */
+ DBG(DBG_info, "%s: ensuring that motor is off\n", __func__);
+ for (i = 400; i > 0; i--) {
+ // do not wait longer than 40 seconds, count down to get i = 0 when busy
+
+ status = scanner_read_status(*dev);
+
+ if (!status.is_motor_enabled && status.is_at_home) {
+ DBG(DBG_info, "%s: already at home and not moving\n", __func__);
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+ return;
+ }
+ if (!status.is_motor_enabled) {
+ break;
+ }
+
+ dev->interface->sleep_ms(100);
+ }
+
+ if (!i) /* the loop counted down to 0, scanner still is busy */
+ {
+ dev->set_head_pos_unknown();
+ throw SaneException(SANE_STATUS_DEVICE_BUSY, "motor is still on: device busy");
+ }
+
+ // setup for a backward scan of 65535 steps, with no actual data reading
+ auto resolution = sanei_genesys_get_lowest_dpi(dev);
+
+ const auto& sensor = sanei_genesys_find_sensor(dev, resolution, 3,
+ dev->model->default_method);
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = resolution;
+ session.params.startx = 0;
+ session.params.starty = 65535;
+ session.params.pixels = 600;
+ session.params.requested_pixels = 600;
+ session.params.lines = 1;
+ session.params.depth = 8;
+ session.params.channels = 3;
+ session.params.scan_method = dev->model->default_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = ColorFilter::RED;
+ session.params.flags = ScanFlag::USE_XCORRECTION |
+ ScanFlag::REVERSE;
+ if (dev->model->default_method == ScanMethod::TRANSPARENCY) {
+ session.params.flags |= ScanFlag::USE_XPA;
+ }
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &dev->reg, session);
+
+ /* backward , no actual data scanned TODO more setup flags to avoid this register manipulations ? */
+ regs_set_optical_off(dev->model->asic_type, dev->reg);
+
+ // sets frontend
+ gl646_set_fe(dev, sensor, AFE_SET, resolution);
+
+ /* write scan registers */
+ try {
+ dev->interface->write_registers(dev->reg);
+ } catch (...) {
+ DBG(DBG_error, "%s: failed to bulk write registers\n", __func__);
+ }
+
+ /* registers are restored to an iddl state, give up if no head to park */
+ if (dev->model->is_sheetfed) {
+ DBG(DBG_proc, "%s: end \n", __func__);
+ return;
+ }
+
+ // starts scan
+ {
+ // this is effectively the same as dev->cmd_set->begin_scan(dev, sensor, &dev->reg, true);
+ // except that we don't modify the head position calculations
+
+ // FIXME: SEQUENTIAL not really needed in this case
+ Genesys_Register_Set scan_local_reg(Genesys_Register_Set::SEQUENTIAL);
+
+ scan_local_reg.init_reg(0x03, dev->reg.get8(0x03));
+ scan_local_reg.init_reg(0x01, dev->reg.get8(0x01) | REG_0x01_SCAN);
+ scan_local_reg.init_reg(0x0f, 0x01);
+
+ dev->interface->write_registers(scan_local_reg);
+ }
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("move_back_home");
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+ return;
+ }
+
+ /* loop until head parked */
+ if (wait_until_home)
+ {
+ while (loop < 300) /* do not wait longer then 30 seconds */
+ {
+ auto status = scanner_read_status(*dev);
+
+ if (status.is_at_home) {
+ DBG(DBG_info, "%s: reached home position\n", __func__);
+ DBG(DBG_proc, "%s: end\n", __func__);
+ dev->interface->sleep_ms(500);
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+ return;
+ }
+ dev->interface->sleep_ms(100);
+ ++loop;
+ }
+
+ // when we come here then the scanner needed too much time for this, so we better
+ // stop the motor
+ catch_all_exceptions(__func__, [&](){ gl646_stop_motor (dev); });
+ catch_all_exceptions(__func__, [&](){ end_scan_impl(dev, &dev->reg, true, false); });
+ dev->set_head_pos_unknown();
+ throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for scanhead to go home");
+ }
+
+
+ DBG(DBG_info, "%s: scanhead is still moving\n", __func__);
+}
+
+/**
+ * Automatically set top-left edge of the scan area by scanning an
+ * area at 300 dpi from very top of scanner
+ * @param dev device stucture describing the scanner
+ */
+void CommandSetGl646::search_start_position(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ Genesys_Settings settings;
+ unsigned int resolution, x, y;
+
+ /* we scan at 300 dpi */
+ resolution = get_closest_resolution(dev->model->sensor_id, 300, 1);
+
+ // FIXME: the current approach of doing search only for one resolution does not work on scanners
+ // whith employ different sensors with potentially different settings.
+ const auto& sensor = sanei_genesys_find_sensor(dev, resolution, 1,
+ dev->model->default_method);
+
+ /* fill settings for a gray level scan */
+ settings.scan_method = dev->model->default_method;
+ settings.scan_mode = ScanColorMode::GRAY;
+ settings.xres = resolution;
+ settings.yres = resolution;
+ settings.tl_x = 0;
+ settings.tl_y = 0;
+ settings.pixels = 600;
+ settings.requested_pixels = settings.pixels;
+ settings.lines = dev->model->search_lines;
+ settings.depth = 8;
+ settings.color_filter = ColorFilter::RED;
+
+ settings.disable_interpolation = 0;
+ settings.threshold = 0;
+
+ // scan the desired area
+ std::vector<uint8_t> data;
+ simple_scan(dev, sensor, settings, true, true, false, data, "search_start_position");
+
+ // handle stagger case : reorder gray data and thus loose some lines
+ auto staggered_lines = dev->session.num_staggered_lines;
+ if (staggered_lines > 0) {
+ DBG(DBG_proc, "%s: 'un-staggering'\n", __func__);
+ for (y = 0; y < settings.lines - staggered_lines; y++) {
+ /* one point out of 2 is 'unaligned' */
+ for (x = 0; x < settings.pixels; x += 2)
+ {
+ data[y * settings.pixels + x] = data[(y + staggered_lines) * settings.pixels + x];
+ }
+ }
+ /* correct line number */
+ settings.lines -= staggered_lines;
+ }
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ sanei_genesys_write_pnm_file("gl646_search_position.pnm", data.data(), settings.depth, 1,
+ settings.pixels, settings.lines);
+ }
+
+ // now search reference points on the data
+ for (auto& sensor_update :
+ sanei_genesys_find_sensors_all_for_write(dev, dev->model->default_method))
+ {
+ sanei_genesys_search_reference_point(dev, sensor_update, data.data(), 0,
+ resolution, settings.pixels, settings.lines);
+ }
+}
+
+/**
+ * internally overriden during effective calibration
+ * sets up register for coarse gain calibration
+ */
+void CommandSetGl646::init_regs_for_coarse_calibration(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ (void) dev;
+ (void) sensor;
+ (void) regs;
+}
+
+
+/**
+ * init registers for shading calibration
+ * we assume that scanner's head is on an area suiting shading calibration.
+ * We scan a full scan width area by the shading line number for the device
+ * at either at full sensor's resolution or half depending upon ccd_size_divisor
+ * @param dev scanner's device
+ */
+void CommandSetGl646::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ (void) regs;
+ Genesys_Settings settings;
+ int cksel = 1;
+
+ /* fill settings for scan : always a color scan */
+ int channels = 3;
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, dev->settings.xres, channels,
+ dev->settings.scan_method);
+
+ unsigned ccd_size_divisor = calib_sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres);
+
+ settings.scan_method = dev->settings.scan_method;
+ settings.scan_mode = dev->settings.scan_mode;
+ if (!dev->model->is_cis) {
+ // FIXME: always a color scan, but why don't we set scan_mode to COLOR_SINGLE_PASS always?
+ settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ }
+ settings.xres = sensor.optical_res / ccd_size_divisor;
+ cksel = get_cksel(dev->model->sensor_id, dev->settings.xres, channels);
+ settings.xres = settings.xres / cksel;
+ settings.yres = settings.xres;
+ settings.tl_x = 0;
+ settings.tl_y = 0;
+ settings.pixels = (calib_sensor.sensor_pixels * settings.xres) / calib_sensor.optical_res;
+ settings.requested_pixels = settings.pixels;
+ dev->calib_lines = dev->model->shading_lines;
+ settings.lines = dev->calib_lines * (3 - ccd_size_divisor);
+ settings.depth = 16;
+ settings.color_filter = dev->settings.color_filter;
+
+ settings.disable_interpolation = dev->settings.disable_interpolation;
+ settings.threshold = dev->settings.threshold;
+
+ // we don't want top offset, but we need right margin to be the same than the one for the final
+ // scan
+ setup_for_scan(dev, calib_sensor, &dev->reg, settings, true, false, false, false);
+
+ /* used when sending shading calibration data */
+ dev->calib_pixels = settings.pixels;
+ dev->calib_channels = dev->session.params.channels;
+ if (!dev->model->is_cis) {
+ dev->calib_channels = 3;
+ }
+
+ /* no shading */
+ dev->reg.find_reg(0x01).value &= ~REG_0x01_DVDSET;
+ dev->reg.find_reg(0x02).value |= REG_0x02_ACDCDIS; /* ease backtracking */
+ dev->reg.find_reg(0x02).value &= ~(REG_0x02_FASTFED | REG_0x02_AGOHOME);
+ dev->reg.find_reg(0x05).value &= ~REG_0x05_GMMENB;
+ sanei_genesys_set_motor_power(dev->reg, false);
+
+ /* TODO another flag to setup regs ? */
+ /* enforce needed LINCNT, getting rid of extra lines for color reordering */
+ if (!dev->model->is_cis) {
+ dev->reg.set24(REG_LINCNT, dev->calib_lines);
+ } else {
+ dev->reg.set24(REG_LINCNT, dev->calib_lines * 3);
+ }
+
+ /* copy reg to calib_reg */
+ dev->calib_reg = dev->reg;
+
+ DBG(DBG_info, "%s:\n\tdev->settings.xres=%d\n\tdev->settings.yres=%d\n", __func__,
+ dev->settings.xres, dev->settings.yres);
+}
+
+bool CommandSetGl646::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const
+{
+ return dev->is_head_pos_known(ScanHeadId::PRIMARY) &&
+ dev->head_pos(ScanHeadId::PRIMARY) &&
+ dev->settings.scan_method == ScanMethod::FLATBED;
+}
+
+/**
+ * set up registers for the actual scan. The scan's parameters are given
+ * through the device settings. It allocates the scan buffers.
+ */
+void CommandSetGl646::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ DBG_HELPER(dbg);
+
+ debug_dump(DBG_info, dev->settings);
+
+ ScanSession session = calculate_scan_session(dev, sensor, dev->settings);
+
+ init_regs_for_scan_session(dev, sensor, &dev->reg, session);
+
+ /* gamma is only enabled at final scan time */
+ if (dev->settings.depth < 16) {
+ dev->reg.find_reg(0x05).value |= REG_0x05_GMMENB;
+ }
+}
+
+/**
+ * set up registers for the actual scan. The scan's parameters are given
+ * through the device settings. It allocates the scan buffers.
+ * @param dev scanner's device
+ * @param regs registers to set up
+ * @param settings settings of scan
+ * @param split true if move to scan area is split from scan, false is
+ * scan first moves to area
+ * @param xcorrection take x geometry correction into account (fixed and detected offsets)
+ * @param ycorrection take y geometry correction into account
+ */
+static void setup_for_scan(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set*regs,
+ Genesys_Settings settings,
+ bool split,
+ bool xcorrection,
+ bool ycorrection,
+ bool reverse)
+{
+ DBG_HELPER(dbg);
+
+ debug_dump(DBG_info, dev->settings);
+
+ // compute distance to move
+ float move = 0;
+ // XXX STEF XXX MD5345 -> optical_ydpi, other base_ydpi => half/full step ? */
+ if (!split) {
+ if (!dev->model->is_sheetfed) {
+ if (ycorrection) {
+ move = static_cast<float>(dev->model->y_offset);
+ }
+
+ // add tl_y to base movement
+ }
+ move += static_cast<float>(settings.tl_y);
+
+ if (move < 0) {
+ DBG(DBG_error, "%s: overriding negative move value %f\n", __func__, move);
+ move = 0;
+ }
+ }
+ move = static_cast<float>((move * dev->motor.optical_ydpi) / MM_PER_INCH);
+ DBG(DBG_info, "%s: move=%f steps\n", __func__, move);
+
+ float start = static_cast<float>(settings.tl_x);
+ if (xcorrection) {
+ if (settings.scan_method == ScanMethod::FLATBED) {
+ start += static_cast<float>(dev->model->x_offset);
+ } else {
+ start += static_cast<float>(dev->model->x_offset_ta);
+ }
+ }
+ start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = settings.xres;
+ session.params.yres = settings.yres;
+ session.params.startx = static_cast<unsigned>(start);
+ session.params.starty = static_cast<unsigned>(move);
+ session.params.pixels = settings.pixels;
+ session.params.requested_pixels = settings.requested_pixels;
+ session.params.lines = settings.lines;
+ session.params.depth = settings.depth;
+ session.params.channels = settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = settings.scan_mode;
+ session.params.color_filter = settings.color_filter;
+ session.params.flags = ScanFlag::NONE;
+ if (settings.scan_method == ScanMethod::TRANSPARENCY) {
+ session.params.flags |= ScanFlag::USE_XPA;
+ }
+ if (xcorrection) {
+ session.params.flags |= ScanFlag::USE_XCORRECTION;
+ }
+ if (reverse) {
+ session.params.flags |= ScanFlag::REVERSE;
+ }
+ compute_session(dev, session, sensor);
+
+ dev->cmd_set->init_regs_for_scan_session(dev, sensor, regs, session);
+}
+
+/**
+ * this function send gamma table to ASIC
+ */
+void CommandSetGl646::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ DBG_HELPER(dbg);
+ int size;
+ int address;
+ int bits;
+
+ /* gamma table size */
+ if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA)
+ {
+ size = 16384;
+ bits = 14;
+ }
+ else
+ {
+ size = 4096;
+ bits = 12;
+ }
+
+ /* allocate temporary gamma tables: 16 bits words, 3 channels */
+ std::vector<uint8_t> gamma(size * 2 * 3);
+
+ sanei_genesys_generate_gamma_buffer(dev, sensor, bits, size-1, size, gamma.data());
+
+ /* table address */
+ switch (dev->reg.find_reg(0x05).value >> 6)
+ {
+ case 0: /* 600 dpi */
+ address = 0x09000;
+ break;
+ case 1: /* 1200 dpi */
+ address = 0x11000;
+ break;
+ case 2: /* 2400 dpi */
+ address = 0x20000;
+ break;
+ default:
+ throw SaneException("invalid dpi");
+ }
+
+ dev->interface->write_buffer(0x3c, address, gamma.data(), size * 2 * 3);
+}
+
+/** @brief this function does the led calibration.
+ * this function does the led calibration by scanning one line of the calibration
+ * area below scanner's top on white strip. The scope of this function is
+ * currently limited to the XP200
+ */
+SensorExposure CommandSetGl646::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ (void) regs;
+ int total_size;
+ unsigned int i, j;
+ int val;
+ int avg[3], avga, avge;
+ int turn;
+ uint16_t expr, expg, expb;
+ Genesys_Settings settings;
+ SANE_Int resolution;
+
+ unsigned channels = dev->settings.get_channels();
+
+ /* get led calibration resolution */
+ if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS)
+ {
+ settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ }
+ else
+ {
+ settings.scan_mode = ScanColorMode::GRAY;
+ }
+ resolution = get_closest_resolution(dev->model->sensor_id, sensor.optical_res, channels);
+
+ /* offset calibration is always done in color mode */
+ settings.scan_method = dev->model->default_method;
+ settings.xres = resolution;
+ settings.yres = resolution;
+ settings.tl_x = 0;
+ settings.tl_y = 0;
+ settings.pixels = (sensor.sensor_pixels * resolution) / sensor.optical_res;
+ settings.requested_pixels = settings.pixels;
+ settings.lines = 1;
+ settings.depth = 16;
+ settings.color_filter = ColorFilter::RED;
+
+ settings.disable_interpolation = 0;
+ settings.threshold = 0;
+
+ /* colors * bytes_per_color * scan lines */
+ total_size = settings.pixels * channels * 2 * 1;
+
+ std::vector<uint8_t> line(total_size);
+
+/*
+ we try to get equal bright leds here:
+
+ loop:
+ average per color
+ adjust exposure times
+ */
+ expr = sensor.exposure.red;
+ expg = sensor.exposure.green;
+ expb = sensor.exposure.blue;
+
+ turn = 0;
+
+ auto calib_sensor = sensor;
+
+ bool acceptable = false;
+ do {
+ calib_sensor.exposure.red = expr;
+ calib_sensor.exposure.green = expg;
+ calib_sensor.exposure.blue = expb;
+
+ DBG(DBG_info, "%s: starting first line reading\n", __func__);
+
+ simple_scan(dev, calib_sensor, settings, false, true, false, line, "led_calibration");
+
+ if (is_testing_mode()) {
+ return calib_sensor.exposure;
+ }
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[30];
+ std::snprintf(fn, 30, "gl646_led_%02d.pnm", turn);
+ sanei_genesys_write_pnm_file(fn, line.data(), 16, channels, settings.pixels, 1);
+ }
+
+ acceptable = true;
+
+ for (j = 0; j < channels; j++)
+ {
+ avg[j] = 0;
+ for (i = 0; i < settings.pixels; i++)
+ {
+ if (dev->model->is_cis)
+ val =
+ line[i * 2 + j * 2 * settings.pixels + 1] * 256 +
+ line[i * 2 + j * 2 * settings.pixels];
+ else
+ val =
+ line[i * 2 * channels + 2 * j + 1] * 256 +
+ line[i * 2 * channels + 2 * j];
+ avg[j] += val;
+ }
+
+ avg[j] /= settings.pixels;
+ }
+
+ DBG(DBG_info, "%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]);
+
+ acceptable = true;
+
+ if (!acceptable)
+ {
+ avga = (avg[0] + avg[1] + avg[2]) / 3;
+ expr = (expr * avga) / avg[0];
+ expg = (expg * avga) / avg[1];
+ expb = (expb * avga) / avg[2];
+
+ /* keep exposure time in a working window */
+ avge = (expr + expg + expb) / 3;
+ if (avge > 0x2000)
+ {
+ expr = (expr * 0x2000) / avge;
+ expg = (expg * 0x2000) / avge;
+ expb = (expb * 0x2000) / avge;
+ }
+ if (avge < 0x400)
+ {
+ expr = (expr * 0x400) / avge;
+ expg = (expg * 0x400) / avge;
+ expb = (expb * 0x400) / avge;
+ }
+ }
+
+ turn++;
+
+ }
+ while (!acceptable && turn < 100);
+
+ DBG(DBG_info,"%s: acceptable exposure: 0x%04x,0x%04x,0x%04x\n", __func__, expr, expg, expb);
+ // BUG: we don't store the result of the last iteration to the sensor
+ return calib_sensor.exposure;
+}
+
+/**
+ * average dark pixels of a scan
+ */
+static int
+dark_average (uint8_t * data, unsigned int pixels, unsigned int lines,
+ unsigned int channels, unsigned int black)
+{
+ unsigned int i, j, k, average, count;
+ unsigned int avg[3];
+ uint8_t val;
+
+ /* computes average value on black margin */
+ for (k = 0; k < channels; k++)
+ {
+ avg[k] = 0;
+ count = 0;
+ for (i = 0; i < lines; i++)
+ {
+ for (j = 0; j < black; j++)
+ {
+ val = data[i * channels * pixels + j + k];
+ avg[k] += val;
+ count++;
+ }
+ }
+ if (count)
+ avg[k] /= count;
+ DBG(DBG_info, "%s: avg[%d] = %d\n", __func__, k, avg[k]);
+ }
+ average = 0;
+ for (i = 0; i < channels; i++)
+ average += avg[i];
+ average /= channels;
+ DBG(DBG_info, "%s: average = %d\n", __func__, average);
+ return average;
+}
+
+
+/** @brief calibration for AD frontend devices
+ * we do simple scan until all black_pixels are higher than 0,
+ * raising offset at each turn.
+ */
+static void ad_fe_offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+ (void) sensor;
+
+ unsigned int channels;
+ int pass = 0;
+ SANE_Int resolution;
+ Genesys_Settings settings;
+ unsigned int x, y, adr, min;
+ unsigned int bottom, black_pixels;
+
+ channels = 3;
+ resolution = get_closest_resolution(dev->model->sensor_id, sensor.optical_res, channels);
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, 3, ScanMethod::FLATBED);
+ black_pixels = (calib_sensor.black_pixels * resolution) / calib_sensor.optical_res;
+ DBG(DBG_io2, "%s: black_pixels=%d\n", __func__, black_pixels);
+
+ settings.scan_method = dev->model->default_method;
+ settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ settings.xres = resolution;
+ settings.yres = resolution;
+ settings.tl_x = 0;
+ settings.tl_y = 0;
+ settings.pixels = (calib_sensor.sensor_pixels * resolution) / calib_sensor.optical_res;
+ settings.requested_pixels = settings.pixels;
+ settings.lines = CALIBRATION_LINES;
+ settings.depth = 8;
+ settings.color_filter = ColorFilter::RED;
+
+ settings.disable_interpolation = 0;
+ settings.threshold = 0;
+
+ /* scan first line of data with no gain */
+ dev->frontend.set_gain(0, 0);
+ dev->frontend.set_gain(1, 0);
+ dev->frontend.set_gain(2, 0);
+
+ std::vector<uint8_t> line;
+
+ /* scan with no move */
+ bottom = 1;
+ do
+ {
+ pass++;
+ dev->frontend.set_offset(0, bottom);
+ dev->frontend.set_offset(1, bottom);
+ dev->frontend.set_offset(2, bottom);
+ simple_scan(dev, calib_sensor, settings, false, true, false, line,
+ "ad_fe_offset_calibration");
+
+ if (is_testing_mode()) {
+ return;
+ }
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char title[30];
+ std::snprintf(title, 30, "gl646_offset%03d.pnm", static_cast<int>(bottom));
+ sanei_genesys_write_pnm_file (title, line.data(), 8, channels,
+ settings.pixels, settings.lines);
+ }
+
+ min = 0;
+ for (y = 0; y < settings.lines; y++)
+ {
+ for (x = 0; x < black_pixels; x++)
+ {
+ adr = (x + y * settings.pixels) * channels;
+ if (line[adr] > min)
+ min = line[adr];
+ if (line[adr + 1] > min)
+ min = line[adr + 1];
+ if (line[adr + 2] > min)
+ min = line[adr + 2];
+ }
+ }
+
+ DBG(DBG_io2, "%s: pass=%d, min=%d\n", __func__, pass, min);
+ bottom++;
+ }
+ while (pass < 128 && min == 0);
+ if (pass == 128)
+ {
+ throw SaneException(SANE_STATUS_INVAL, "failed to find correct offset");
+ }
+
+ DBG(DBG_info, "%s: offset=(%d,%d,%d)\n", __func__,
+ dev->frontend.get_offset(0),
+ dev->frontend.get_offset(1),
+ dev->frontend.get_offset(2));
+}
+
+/**
+ * This function does the offset calibration by scanning one line of the calibration
+ * area below scanner's top. There is a black margin and the remaining is white.
+ * genesys_search_start() must have been called so that the offsets and margins
+ * are already known.
+ * @param dev scanner's device
+*/
+void CommandSetGl646::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ (void) regs;
+
+ unsigned int channels;
+ int pass = 0, avg;
+ Genesys_Settings settings;
+ int topavg, bottomavg;
+ int top, bottom, black_pixels;
+
+ if (dev->model->adc_id == AdcId::AD_XP200) {
+ ad_fe_offset_calibration(dev, sensor);
+ return;
+ }
+
+ DBG(DBG_proc, "%s: start\n", __func__); // TODO
+
+ /* setup for a RGB scan, one full sensor's width line */
+ /* resolution is the one from the final scan */
+ channels = 3;
+ int resolution = get_closest_resolution(dev->model->sensor_id, dev->settings.xres, channels);
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, 3, ScanMethod::FLATBED);
+ black_pixels = (calib_sensor.black_pixels * resolution) / calib_sensor.optical_res;
+
+ DBG(DBG_io2, "%s: black_pixels=%d\n", __func__, black_pixels);
+
+ settings.scan_method = dev->model->default_method;
+ settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ settings.xres = resolution;
+ settings.yres = resolution;
+ settings.tl_x = 0;
+ settings.tl_y = 0;
+ settings.pixels = (calib_sensor.sensor_pixels * resolution) / calib_sensor.optical_res;
+ settings.requested_pixels = settings.pixels;
+ settings.lines = CALIBRATION_LINES;
+ settings.depth = 8;
+ settings.color_filter = ColorFilter::RED;
+
+ settings.disable_interpolation = 0;
+ settings.threshold = 0;
+
+ /* scan first line of data with no gain, but with offset from
+ * last calibration */
+ dev->frontend.set_gain(0, 0);
+ dev->frontend.set_gain(1, 0);
+ dev->frontend.set_gain(2, 0);
+
+ /* scan with no move */
+ bottom = 90;
+ dev->frontend.set_offset(0, bottom);
+ dev->frontend.set_offset(1, bottom);
+ dev->frontend.set_offset(2, bottom);
+
+ std::vector<uint8_t> first_line, second_line;
+
+ simple_scan(dev, calib_sensor, settings, false, true, false, first_line,
+ "offset_first_line");
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char title[30];
+ std::snprintf(title, 30, "gl646_offset%03d.pnm", bottom);
+ sanei_genesys_write_pnm_file(title, first_line.data(), 8, channels,
+ settings.pixels, settings.lines);
+ }
+ bottomavg = dark_average(first_line.data(), settings.pixels, settings.lines, channels,
+ black_pixels);
+ DBG(DBG_io2, "%s: bottom avg=%d\n", __func__, bottomavg);
+
+ /* now top value */
+ top = 231;
+ dev->frontend.set_offset(0, top);
+ dev->frontend.set_offset(1, top);
+ dev->frontend.set_offset(2, top);
+ simple_scan(dev, calib_sensor, settings, false, true, false, second_line,
+ "offset_second_line");
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char title[30];
+ std::snprintf(title, 30, "gl646_offset%03d.pnm", top);
+ sanei_genesys_write_pnm_file (title, second_line.data(), 8, channels,
+ settings.pixels, settings.lines);
+ }
+ topavg = dark_average(second_line.data(), settings.pixels, settings.lines, channels,
+ black_pixels);
+ DBG(DBG_io2, "%s: top avg=%d\n", __func__, topavg);
+
+ if (is_testing_mode()) {
+ return;
+ }
+
+ /* loop until acceptable level */
+ while ((pass < 32) && (top - bottom > 1))
+ {
+ pass++;
+
+ /* settings for new scan */
+ dev->frontend.set_offset(0, (top + bottom) / 2);
+ dev->frontend.set_offset(1, (top + bottom) / 2);
+ dev->frontend.set_offset(2, (top + bottom) / 2);
+
+ // scan with no move
+ simple_scan(dev, calib_sensor, settings, false, true, false, second_line,
+ "offset_calibration_i");
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char title[30];
+ std::snprintf(title, 30, "gl646_offset%03d.pnm", dev->frontend.get_offset(1));
+ sanei_genesys_write_pnm_file (title, second_line.data(), 8, channels,
+ settings.pixels, settings.lines);
+ }
+
+ avg =
+ dark_average (second_line.data(), settings.pixels, settings.lines, channels,
+ black_pixels);
+ DBG(DBG_info, "%s: avg=%d offset=%d\n", __func__, avg, dev->frontend.get_offset(1));
+
+ /* compute new boundaries */
+ if (topavg == avg)
+ {
+ topavg = avg;
+ top = dev->frontend.get_offset(1);
+ }
+ else
+ {
+ bottomavg = avg;
+ bottom = dev->frontend.get_offset(1);
+ }
+ }
+
+ DBG(DBG_info, "%s: offset=(%d,%d,%d)\n", __func__,
+ dev->frontend.get_offset(0),
+ dev->frontend.get_offset(1),
+ dev->frontend.get_offset(2));
+}
+
+/** @brief gain calibration for Analog Device frontends
+ * Alternative coarse gain calibration
+ */
+static void ad_fe_coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi)
+{
+ DBG_HELPER(dbg);
+ (void) sensor;
+ (void) regs;
+
+ unsigned int i, channels, val;
+ unsigned int size, count, resolution, pass;
+ float average;
+ Genesys_Settings settings;
+ char title[32];
+
+ /* setup for a RGB scan, one full sensor's width line */
+ /* resolution is the one from the final scan */
+ channels = 3;
+ resolution = get_closest_resolution(dev->model->sensor_id, dpi, channels);
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, 3, ScanMethod::FLATBED);
+
+ settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+
+ settings.scan_method = dev->model->default_method;
+ settings.xres = resolution;
+ settings.yres = resolution;
+ settings.tl_x = 0;
+ settings.tl_y = 0;
+ settings.pixels = (calib_sensor.sensor_pixels * resolution) / calib_sensor.optical_res;
+ settings.requested_pixels = settings.pixels;
+ settings.lines = CALIBRATION_LINES;
+ settings.depth = 8;
+ settings.color_filter = ColorFilter::RED;
+
+ settings.disable_interpolation = 0;
+ settings.threshold = 0;
+
+ size = channels * settings.pixels * settings.lines;
+
+ /* start gain value */
+ dev->frontend.set_gain(0, 1);
+ dev->frontend.set_gain(1, 1);
+ dev->frontend.set_gain(2, 1);
+
+ average = 0;
+ pass = 0;
+
+ std::vector<uint8_t> line;
+
+ // loop until each channel raises to acceptable level
+ while ((average < calib_sensor.gain_white_ref) && (pass < 30)) {
+ // scan with no move
+ simple_scan(dev, calib_sensor, settings, false, true, false, line,
+ "ad_fe_coarse_gain_calibration");
+
+ /* log scanning data */
+ if (DBG_LEVEL >= DBG_data)
+ {
+ std::sprintf(title, "gl646_alternative_gain%02d.pnm", pass);
+ sanei_genesys_write_pnm_file(title, line.data(), 8, channels, settings.pixels,
+ settings.lines);
+ }
+ pass++;
+
+ /* computes white average */
+ average = 0;
+ count = 0;
+ for (i = 0; i < size; i++)
+ {
+ val = line[i];
+ average += val;
+ count++;
+ }
+ average = average / count;
+
+ uint8_t gain0 = dev->frontend.get_gain(0);
+ // adjusts gain for the channel
+ if (average < calib_sensor.gain_white_ref) {
+ gain0 += 1;
+ }
+
+ dev->frontend.set_gain(0, gain0);
+ dev->frontend.set_gain(1, gain0);
+ dev->frontend.set_gain(2, gain0);
+
+ DBG(DBG_proc, "%s: average = %.2f, gain = %d\n", __func__, average, gain0);
+ }
+
+ DBG(DBG_info, "%s: gains=(%d,%d,%d)\n", __func__,
+ dev->frontend.get_gain(0),
+ dev->frontend.get_gain(1),
+ dev->frontend.get_gain(2));
+}
+
+/**
+ * Alternative coarse gain calibration
+ * this on uses the settings from offset_calibration. First scan moves so
+ * we can go to calibration area for XPA.
+ * @param dev device for scan
+ * @param dpi resolutnio to calibrate at
+ */
+void CommandSetGl646::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const
+{
+ DBG_HELPER(dbg);
+ (void) dpi;
+
+ unsigned int i, j, k, channels, val, maximum, idx;
+ unsigned int count, resolution, pass;
+ float average[3];
+ Genesys_Settings settings;
+ char title[32];
+
+ if (dev->model->sensor_id == SensorId::CIS_XP200) {
+ return ad_fe_coarse_gain_calibration(dev, sensor, regs, sensor.optical_res);
+ }
+
+ /* setup for a RGB scan, one full sensor's width line */
+ /* resolution is the one from the final scan */
+ channels = 3;
+
+ /* we are searching a sensor resolution */
+ resolution = get_closest_resolution(dev->model->sensor_id, dev->settings.xres, channels);
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels,
+ ScanMethod::FLATBED);
+
+ settings.scan_method = dev->settings.scan_method;
+ settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ settings.xres = resolution;
+ settings.yres = resolution;
+ settings.tl_y = 0;
+ if (settings.scan_method == ScanMethod::FLATBED)
+ {
+ settings.tl_x = 0;
+ settings.pixels = (calib_sensor.sensor_pixels * resolution) / calib_sensor.optical_res;
+ }
+ else
+ {
+ settings.tl_x = dev->model->x_offset_ta;
+ settings.pixels = static_cast<unsigned>((dev->model->x_size_ta * resolution) / MM_PER_INCH);
+ }
+ settings.requested_pixels = settings.pixels;
+ settings.lines = CALIBRATION_LINES;
+ settings.depth = 8;
+ settings.color_filter = ColorFilter::RED;
+
+ settings.disable_interpolation = 0;
+ settings.threshold = 0;
+
+ /* start gain value */
+ dev->frontend.set_gain(0, 1);
+ dev->frontend.set_gain(1, 1);
+ dev->frontend.set_gain(2, 1);
+
+ if (channels > 1)
+ {
+ average[0] = 0;
+ average[1] = 0;
+ average[2] = 0;
+ idx = 0;
+ }
+ else
+ {
+ average[0] = 255;
+ average[1] = 255;
+ average[2] = 255;
+ switch (dev->settings.color_filter) {
+ case ColorFilter::RED: idx = 0; break;
+ case ColorFilter::GREEN: idx = 1; break;
+ case ColorFilter::BLUE: idx = 2; break;
+ default: idx = 0; break; // should not happen
+ }
+ average[idx] = 0;
+ }
+ pass = 0;
+
+ std::vector<uint8_t> line;
+
+ /* loop until each channel raises to acceptable level */
+ while (((average[0] < calib_sensor.gain_white_ref) ||
+ (average[1] < calib_sensor.gain_white_ref) ||
+ (average[2] < calib_sensor.gain_white_ref)) && (pass < 30))
+ {
+ // scan with no move
+ simple_scan(dev, calib_sensor, settings, false, true, false, line,
+ "coarse_gain_calibration");
+
+ /* log scanning data */
+ if (DBG_LEVEL >= DBG_data)
+ {
+ std::sprintf(title, "gl646_gain%02d.pnm", pass);
+ sanei_genesys_write_pnm_file(title, line.data(), 8, channels, settings.pixels,
+ settings.lines);
+ }
+ pass++;
+
+ /* average high level for each channel and compute gain
+ to reach the target code
+ we only use the central half of the CCD data */
+ for (k = idx; k < idx + channels; k++)
+ {
+ /* we find the maximum white value, so we can deduce a threshold
+ to average white values */
+ maximum = 0;
+ for (i = 0; i < settings.lines; i++)
+ {
+ for (j = 0; j < settings.pixels; j++)
+ {
+ val = line[i * channels * settings.pixels + j + k];
+ if (val > maximum)
+ maximum = val;
+ }
+ }
+
+ /* threshold */
+ maximum = static_cast<int>(maximum * 0.9);
+
+ /* computes white average */
+ average[k] = 0;
+ count = 0;
+ for (i = 0; i < settings.lines; i++)
+ {
+ for (j = 0; j < settings.pixels; j++)
+ {
+ /* averaging only white points allow us not to care about dark margins */
+ val = line[i * channels * settings.pixels + j + k];
+ if (val > maximum)
+ {
+ average[k] += val;
+ count++;
+ }
+ }
+ }
+ average[k] = average[k] / count;
+
+ /* adjusts gain for the channel */
+ if (average[k] < calib_sensor.gain_white_ref)
+ dev->frontend.set_gain(k, dev->frontend.get_gain(k) + 1);
+
+ DBG(DBG_proc, "%s: channel %d, average = %.2f, gain = %d\n", __func__, k, average[k],
+ dev->frontend.get_gain(k));
+ }
+ }
+
+ if (channels < 3) {
+ dev->frontend.set_gain(1, dev->frontend.get_gain(0));
+ dev->frontend.set_gain(2, dev->frontend.get_gain(0));
+ }
+
+ DBG(DBG_info, "%s: gains=(%d,%d,%d)\n", __func__,
+ dev->frontend.get_gain(0),
+ dev->frontend.get_gain(1),
+ dev->frontend.get_gain(2));
+}
+
+/**
+ * sets up the scanner's register for warming up. We scan 2 lines without moving.
+ *
+ */
+void CommandSetGl646::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* local_reg, int* channels,
+ int* total_size) const
+{
+ DBG_HELPER(dbg);
+ (void) sensor;
+
+ Genesys_Settings settings;
+ int resolution, lines;
+
+ dev->frontend = dev->frontend_initial;
+
+ resolution = get_closest_resolution(dev->model->sensor_id, 300, 1);
+
+ const auto& local_sensor = sanei_genesys_find_sensor(dev, resolution, 1,
+ dev->settings.scan_method);
+
+ /* set up for a half width 2 lines gray scan without moving */
+ settings.scan_method = dev->model->default_method;
+ settings.scan_mode = ScanColorMode::GRAY;
+ settings.xres = resolution;
+ settings.yres = resolution;
+ settings.tl_x = 0;
+ settings.tl_y = 0;
+ settings.pixels = (local_sensor.sensor_pixels * resolution) / local_sensor.optical_res;
+ settings.requested_pixels = settings.pixels;
+ settings.lines = 2;
+ settings.depth = 8;
+ settings.color_filter = ColorFilter::RED;
+
+ settings.disable_interpolation = 0;
+ settings.threshold = 0;
+
+ // setup for scan
+ setup_for_scan(dev, local_sensor, &dev->reg, settings, true, false, false, false);
+
+ /* we are not going to move, so clear these bits */
+ dev->reg.find_reg(0x02).value &= ~(REG_0x02_FASTFED | REG_0x02_AGOHOME);
+
+ /* don't enable any correction for this scan */
+ dev->reg.find_reg(0x01).value &= ~REG_0x01_DVDSET;
+
+ /* copy to local_reg */
+ *local_reg = dev->reg;
+
+ /* turn off motor during this scan */
+ sanei_genesys_set_motor_power(*local_reg, false);
+
+ /* returned value to higher level warmup function */
+ *channels = 1;
+ lines = local_reg->get24(REG_LINCNT) + 1;
+ *total_size = lines * settings.pixels;
+
+ // now registers are ok, write them to scanner
+ gl646_set_fe(dev, local_sensor, AFE_SET, settings.xres);
+ dev->interface->write_registers(*local_reg);
+}
+
+
+/*
+ * this function moves head without scanning, forward, then backward
+ * so that the head goes to park position.
+ * as a by-product, also check for lock
+ */
+static void gl646_repark_head(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ Genesys_Settings settings;
+ unsigned int expected, steps;
+
+ settings.scan_method = dev->model->default_method;
+ settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ settings.xres = get_closest_resolution(dev->model->sensor_id, 75, 1);
+ settings.yres = settings.xres;
+ settings.tl_x = 0;
+ settings.tl_y = 5;
+ settings.pixels = 600;
+ settings.requested_pixels = settings.pixels;
+ settings.lines = 4;
+ settings.depth = 8;
+ settings.color_filter = ColorFilter::RED;
+
+ settings.disable_interpolation = 0;
+ settings.threshold = 0;
+
+ const auto& sensor = sanei_genesys_find_sensor(dev, settings.xres, 3,
+ dev->model->default_method);
+
+ setup_for_scan(dev, sensor, &dev->reg, settings, false, false, false, false);
+
+ /* TODO seems wrong ... no effective scan */
+ regs_set_optical_off(dev->model->asic_type, dev->reg);
+
+ dev->interface->write_registers(dev->reg);
+
+ // start scan
+ dev->cmd_set->begin_scan(dev, sensor, &dev->reg, true);
+
+ expected = dev->reg.get24(REG_FEEDL);
+ do
+ {
+ dev->interface->sleep_ms(100);
+ sanei_genesys_read_feed_steps (dev, &steps);
+ }
+ while (steps < expected);
+
+ // toggle motor flag, put an huge step number and redo move backward
+ dev->cmd_set->move_back_home(dev, 1);
+}
+
+/* *
+ * initialize ASIC : registers, motor tables, and gamma tables
+ * then ensure scanner's head is at home
+ * @param dev device description of the scanner to initailize
+ */
+void CommandSetGl646::init(Genesys_Device* dev) const
+{
+ DBG_INIT();
+ DBG_HELPER(dbg);
+
+ uint8_t val = 0;
+ uint32_t addr = 0xdead;
+ size_t len;
+
+ // to detect real power up condition, we write to REG_0x41 with pwrbit set, then read it back.
+ // When scanner is cold (just replugged) PWRBIT will be set in the returned value
+ auto status = scanner_read_status(*dev);
+ if (status.is_replugged) {
+ DBG(DBG_info, "%s: device is cold\n", __func__);
+ } else {
+ DBG(DBG_info, "%s: device is hot\n", __func__);
+ }
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+
+ /* if scanning session hasn't been initialized, set it up */
+ if (!dev->already_initialized)
+ {
+ dev->dark_average_data.clear();
+ dev->white_average_data.clear();
+
+ dev->settings.color_filter = ColorFilter::GREEN;
+
+ /* Set default values for registers */
+ gl646_init_regs (dev);
+
+ // Init shading data
+ sanei_genesys_init_shading_data(dev, sensor, sensor.sensor_pixels);
+
+ /* initial calibration reg values */
+ dev->calib_reg = dev->reg;
+ }
+
+ // execute physical unit init only if cold
+ if (status.is_replugged)
+ {
+ DBG(DBG_info, "%s: device is cold\n", __func__);
+
+ val = 0x04;
+ dev->interface->get_usb_device().control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER,
+ VALUE_INIT, INDEX, 1, &val);
+
+ // ASIC reset
+ dev->interface->write_register(0x0e, 0x00);
+ dev->interface->sleep_ms(100);
+
+ // Write initial registers
+ dev->interface->write_registers(dev->reg);
+
+ // send gamma tables if needed
+ dev->cmd_set->send_gamma_table(dev, sensor);
+
+ // Set powersaving(default = 15 minutes)
+ dev->cmd_set->set_powersaving(dev, 15);
+ }
+
+ // Set analog frontend
+ gl646_set_fe(dev, sensor, AFE_INIT, 0);
+
+ /* GPO enabling for XP200 */
+ if (dev->model->sensor_id == SensorId::CIS_XP200) {
+ dev->interface->write_register(0x68, dev->gpo.regs.get_value(0x68));
+ dev->interface->write_register(0x69, dev->gpo.regs.get_value(0x69));
+
+ // enable GPIO
+ gl646_gpio_output_enable(dev->interface->get_usb_device(), 6);
+
+ // writes 0 to GPIO
+ gl646_gpio_write(dev->interface->get_usb_device(), 0);
+
+ // clear GPIO enable
+ gl646_gpio_output_enable(dev->interface->get_usb_device(), 0);
+
+ dev->interface->write_register(0x66, 0x10);
+ dev->interface->write_register(0x66, 0x00);
+ dev->interface->write_register(0x66, 0x10);
+ }
+
+ /* MD6471/G2410 and XP200 read/write data from an undocumented memory area which
+ * is after the second slope table */
+ if (dev->model->gpio_id != GpioId::HP3670 &&
+ dev->model->gpio_id != GpioId::HP2400)
+ {
+ switch (sensor.optical_res)
+ {
+ case 600:
+ addr = 0x08200;
+ break;
+ case 1200:
+ addr = 0x10200;
+ break;
+ case 2400:
+ addr = 0x1fa00;
+ break;
+ }
+ sanei_genesys_set_buffer_address(dev, addr);
+
+ sanei_usb_set_timeout (2 * 1000);
+ len = 6;
+ // for some reason, read fails here for MD6471, HP2300 and XP200 one time out of
+ // 2 scanimage launches
+ try {
+ dev->interface->bulk_read_data(0x45, dev->control, len);
+ } catch (...) {
+ dev->interface->bulk_read_data(0x45, dev->control, len);
+ }
+ DBG(DBG_info, "%s: control read=0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", __func__,
+ dev->control[0], dev->control[1], dev->control[2], dev->control[3], dev->control[4],
+ dev->control[5]);
+ sanei_usb_set_timeout (30 * 1000);
+ }
+ else
+ /* HP2400 and HP3670 case */
+ {
+ dev->control[0] = 0x00;
+ dev->control[1] = 0x00;
+ dev->control[2] = 0x01;
+ dev->control[3] = 0x00;
+ dev->control[4] = 0x00;
+ dev->control[5] = 0x00;
+ }
+
+ /* ensure head is correctly parked, and check lock */
+ if (!dev->model->is_sheetfed) {
+ if (dev->model->flags & GENESYS_FLAG_REPARK)
+ {
+ // FIXME: if repark fails, we should print an error message that the scanner is locked and
+ // the user should unlock the lock. We should also rethrow with SANE_STATUS_JAMMED
+ gl646_repark_head(dev);
+ }
+ else
+ {
+ move_back_home(dev, true);
+ }
+ }
+
+ /* here session and device are initialized */
+ dev->already_initialized = true;
+}
+
+void CommandSetGl646::move_to_ta(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+
+ simple_move(dev, static_cast<int>(dev->model->y_offset_sensor_to_ta));
+}
+
+
+/**
+ * Does a simple scan: ie no line reordering and avanced data buffering and
+ * shading correction. Memory for data is allocated in this function
+ * and must be freed by caller.
+ * @param dev device of the scanner
+ * @param settings parameters of the scan
+ * @param move true if moving during scan
+ * @param forward true if moving forward during scan
+ * @param shading true to enable shading correction
+ * @param data pointer for the data
+ */
+static void simple_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Settings settings, bool move, bool forward,
+ bool shading, std::vector<uint8_t>& data,
+ const char* scan_identifier)
+{
+ DBG_HELPER_ARGS(dbg, "move=%d, forward=%d, shading=%d", move, forward, shading);
+ unsigned int size, lines, x, y, bpp;
+ bool split;
+
+ /* round up to multiple of 3 in case of CIS scanner */
+ if (dev->model->is_cis) {
+ settings.lines = ((settings.lines + 2) / 3) * 3;
+ }
+
+ /* setup for move then scan */
+ split = !(move && settings.tl_y > 0);
+ setup_for_scan(dev, sensor, &dev->reg, settings, split, false, false, !forward);
+
+ /* allocate memory fo scan : LINCNT may have been adjusted for CCD reordering */
+ if (dev->model->is_cis) {
+ lines = dev->reg.get24(REG_LINCNT) / 3;
+ } else {
+ lines = dev->reg.get24(REG_LINCNT) + 1;
+ }
+ size = lines * settings.pixels;
+ if (settings.depth == 16) {
+ bpp = 2;
+ } else {
+ bpp = 1;
+ }
+ size *= bpp * settings.get_channels();
+ data.clear();
+ data.resize(size);
+
+ DBG(DBG_io, "%s: allocated %d bytes of memory for %d lines\n", __func__, size, lines);
+
+ /* put back real line number in settings */
+ settings.lines = lines;
+
+ // initialize frontend
+ gl646_set_fe(dev, sensor, AFE_SET, settings.xres);
+
+ /* no shading correction and not watch dog for simple scan */
+ dev->reg.find_reg(0x01).value &= ~(REG_0x01_DVDSET | REG_0x01_DOGENB);
+ if (shading) {
+ dev->reg.find_reg(0x01).value |= REG_0x01_DVDSET;
+ }
+
+ /* enable gamma table for the scan */
+ dev->reg.find_reg(0x05).value |= REG_0x05_GMMENB;
+
+ /* one table movement for simple scan */
+ dev->reg.find_reg(0x02).value &= ~REG_0x02_FASTFED;
+
+ if (!move) {
+ sanei_genesys_set_motor_power(dev->reg, false);
+
+ /* no automatic go home if no movement */
+ dev->reg.find_reg(0x02).value &= ~REG_0x02_AGOHOME;
+ }
+
+ /* no automatic go home when using XPA */
+ if (settings.scan_method == ScanMethod::TRANSPARENCY) {
+ dev->reg.find_reg(0x02).value &= ~REG_0x02_AGOHOME;
+ }
+
+ // write scan registers
+ dev->interface->write_registers(dev->reg);
+
+ // starts scan
+ dev->cmd_set->begin_scan(dev, sensor, &dev->reg, move);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint(scan_identifier);
+ return;
+ }
+
+ wait_until_buffer_non_empty(dev, true);
+
+ // now we're on target, we can read data
+ sanei_genesys_read_data_from_scanner(dev, data.data(), size);
+
+ /* in case of CIS scanner, we must reorder data */
+ if (dev->model->is_cis && settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS) {
+ /* alloc one line sized working buffer */
+ std::vector<uint8_t> buffer(settings.pixels * 3 * bpp);
+
+ /* reorder one line of data and put it back to buffer */
+ if (bpp == 1)
+ {
+ for (y = 0; y < lines; y++)
+ {
+ /* reorder line */
+ for (x = 0; x < settings.pixels; x++)
+ {
+ buffer[x * 3] = data[y * settings.pixels * 3 + x];
+ buffer[x * 3 + 1] = data[y * settings.pixels * 3 + settings.pixels + x];
+ buffer[x * 3 + 2] = data[y * settings.pixels * 3 + 2 * settings.pixels + x];
+ }
+ /* copy line back */
+ memcpy (data.data() + settings.pixels * 3 * y, buffer.data(),
+ settings.pixels * 3);
+ }
+ }
+ else
+ {
+ for (y = 0; y < lines; y++)
+ {
+ /* reorder line */
+ for (x = 0; x < settings.pixels; x++)
+ {
+ buffer[x * 6] = data[y * settings.pixels * 6 + x * 2];
+ buffer[x * 6 + 1] = data[y * settings.pixels * 6 + x * 2 + 1];
+ buffer[x * 6 + 2] = data[y * settings.pixels * 6 + 2 * settings.pixels + x * 2];
+ buffer[x * 6 + 3] = data[y * settings.pixels * 6 + 2 * settings.pixels + x * 2 + 1];
+ buffer[x * 6 + 4] = data[y * settings.pixels * 6 + 4 * settings.pixels + x * 2];
+ buffer[x * 6 + 5] = data[y * settings.pixels * 6 + 4 * settings.pixels + x * 2 + 1];
+ }
+ /* copy line back */
+ memcpy (data.data() + settings.pixels * 6 * y, buffer.data(),
+ settings.pixels * 6);
+ }
+ }
+ }
+
+ // end scan , waiting the motor to stop if needed (if moving), but without ejecting doc
+ end_scan_impl(dev, &dev->reg, true, false);
+}
+
+/**
+ * Does a simple move of the given distance by doing a scan at lowest resolution
+ * shading correction. Memory for data is allocated in this function
+ * and must be freed by caller.
+ * @param dev device of the scanner
+ * @param distance distance to move in MM
+ */
+static void simple_move(Genesys_Device* dev, SANE_Int distance)
+{
+ DBG_HELPER_ARGS(dbg, "%d mm", distance);
+ Genesys_Settings settings;
+
+ unsigned resolution = sanei_genesys_get_lowest_dpi(dev);
+
+ const auto& sensor = sanei_genesys_find_sensor(dev, resolution, 3, dev->model->default_method);
+
+ /* TODO give a no AGOHOME flag */
+ settings.scan_method = dev->model->default_method;
+ settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ settings.xres = resolution;
+ settings.yres = resolution;
+ settings.tl_y = 0;
+ settings.tl_x = 0;
+ settings.pixels = (sensor.sensor_pixels * settings.xres) / sensor.optical_res;
+ settings.requested_pixels = settings.pixels;
+ settings.lines = static_cast<unsigned>((distance * settings.xres) / MM_PER_INCH);
+ settings.depth = 8;
+ settings.color_filter = ColorFilter::RED;
+
+ settings.disable_interpolation = 0;
+ settings.threshold = 0;
+
+ std::vector<uint8_t> data;
+ simple_scan(dev, sensor, settings, true, true, false, data, "simple_move");
+}
+
+/**
+ * update the status of the required sensor in the scanner session
+ * the button fileds are used to make events 'sticky'
+ */
+void CommandSetGl646::update_hardware_sensors(Genesys_Scanner* session) const
+{
+ DBG_HELPER(dbg);
+ Genesys_Device *dev = session->dev;
+ uint8_t value;
+
+ // do what is needed to get a new set of events, but try to not loose any of them.
+ gl646_gpio_read(dev->interface->get_usb_device(), &value);
+ DBG(DBG_io, "%s: GPIO=0x%02x\n", __func__, value);
+
+ // scan button
+ if (dev->model->buttons & GENESYS_HAS_SCAN_SW) {
+ switch (dev->model->gpio_id) {
+ case GpioId::XP200:
+ session->buttons[BUTTON_SCAN_SW].write((value & 0x02) != 0);
+ break;
+ case GpioId::MD_5345:
+ session->buttons[BUTTON_SCAN_SW].write(value == 0x16);
+ break;
+ case GpioId::HP2300:
+ session->buttons[BUTTON_SCAN_SW].write(value == 0x6c);
+ break;
+ case GpioId::HP3670:
+ case GpioId::HP2400:
+ session->buttons[BUTTON_SCAN_SW].write((value & 0x20) == 0);
+ break;
+ default:
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type");
+ }
+ }
+
+ // email button
+ if (dev->model->buttons & GENESYS_HAS_EMAIL_SW) {
+ switch (dev->model->gpio_id) {
+ case GpioId::MD_5345:
+ session->buttons[BUTTON_EMAIL_SW].write(value == 0x12);
+ break;
+ case GpioId::HP3670:
+ case GpioId::HP2400:
+ session->buttons[BUTTON_EMAIL_SW].write((value & 0x08) == 0);
+ break;
+ default:
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type");
+ }
+ }
+
+ // copy button
+ if (dev->model->buttons & GENESYS_HAS_COPY_SW) {
+ switch (dev->model->gpio_id) {
+ case GpioId::MD_5345:
+ session->buttons[BUTTON_COPY_SW].write(value == 0x11);
+ break;
+ case GpioId::HP2300:
+ session->buttons[BUTTON_COPY_SW].write(value == 0x5c);
+ break;
+ case GpioId::HP3670:
+ case GpioId::HP2400:
+ session->buttons[BUTTON_COPY_SW].write((value & 0x10) == 0);
+ break;
+ default:
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type");
+ }
+ }
+
+ // power button
+ if (dev->model->buttons & GENESYS_HAS_POWER_SW) {
+ switch (dev->model->gpio_id) {
+ case GpioId::MD_5345:
+ session->buttons[BUTTON_POWER_SW].write(value == 0x14);
+ break;
+ default:
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type");
+ }
+ }
+
+ // ocr button
+ if (dev->model->buttons & GENESYS_HAS_OCR_SW) {
+ switch (dev->model->gpio_id) {
+ case GpioId::MD_5345:
+ session->buttons[BUTTON_OCR_SW].write(value == 0x13);
+ break;
+ default:
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type");
+ }
+ }
+
+ // document detection
+ if (dev->model->buttons & GENESYS_HAS_PAGE_LOADED_SW) {
+ switch (dev->model->gpio_id) {
+ case GpioId::XP200:
+ session->buttons[BUTTON_PAGE_LOADED_SW].write((value & 0x04) != 0);
+ break;
+ default:
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type");
+ }
+ }
+
+ /* XPA detection */
+ if (dev->model->flags & GENESYS_FLAG_XPA)
+ {
+ switch (dev->model->gpio_id) {
+ case GpioId::HP3670:
+ case GpioId::HP2400:
+ /* test if XPA is plugged-in */
+ if ((value & 0x40) == 0)
+ {
+ DBG(DBG_io, "%s: enabling XPA\n", __func__);
+ session->opt[OPT_SOURCE].cap &= ~SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ DBG(DBG_io, "%s: disabling XPA\n", __func__);
+ session->opt[OPT_SOURCE].cap |= SANE_CAP_INACTIVE;
+ }
+ break;
+ default:
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "unknown gpo type");
+ }
+ }
+}
+
+
+static void write_control(Genesys_Device* dev, const Genesys_Sensor& sensor, int resolution)
+{
+ DBG_HELPER(dbg);
+ uint8_t control[4];
+ uint32_t addr = 0xdead;
+
+ /* 2300 does not write to 'control' */
+ if (dev->model->motor_id == MotorId::HP2300) {
+ return;
+ }
+
+ /* MD6471/G2410/HP2300 and XP200 read/write data from an undocumented memory area which
+ * is after the second slope table */
+ switch (sensor.optical_res)
+ {
+ case 600:
+ addr = 0x08200;
+ break;
+ case 1200:
+ addr = 0x10200;
+ break;
+ case 2400:
+ addr = 0x1fa00;
+ break;
+ default:
+ throw SaneException("failed to compute control address");
+ }
+
+ /* XP200 sets dpi, what other scanner put is unknown yet */
+ switch (dev->model->motor_id)
+ {
+ case MotorId::XP200:
+ /* we put scan's dpi, not motor one */
+ control[0] = resolution & 0xff;
+ control[1] = (resolution >> 8) & 0xff;
+ control[2] = dev->control[4];
+ control[3] = dev->control[5];
+ break;
+ case MotorId::HP3670:
+ case MotorId::HP2400:
+ case MotorId::MD_5345:
+ default:
+ control[0] = dev->control[2];
+ control[1] = dev->control[3];
+ control[2] = dev->control[4];
+ control[3] = dev->control[5];
+ break;
+ }
+
+ DBG(DBG_info, "%s: control write=0x%02x 0x%02x 0x%02x 0x%02x\n", __func__, control[0], control[1],
+ control[2], control[3]);
+ dev->interface->write_buffer(0x3c, addr, control, 4);
+}
+
+/**
+ * search for a full width black or white strip.
+ * @param dev scanner device
+ * @param forward true if searching forward, false if searching backward
+ * @param black true if searching for a black strip, false for a white strip
+ */
+void CommandSetGl646::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, bool forward,
+ bool black) const
+{
+ DBG_HELPER(dbg);
+ (void) sensor;
+
+ Genesys_Settings settings;
+ int res = get_closest_resolution(dev->model->sensor_id, 75, 1);
+ unsigned int pass, count, found, x, y;
+ char title[80];
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, res, 1, ScanMethod::FLATBED);
+
+ /* we set up for a lowest available resolution color grey scan, full width */
+ settings.scan_method = dev->model->default_method;
+ settings.scan_mode = ScanColorMode::GRAY;
+ settings.xres = res;
+ settings.yres = res;
+ settings.tl_x = 0;
+ settings.tl_y = 0;
+ settings.pixels = static_cast<unsigned>((dev->model->x_size * res) / MM_PER_INCH);
+ settings.pixels /= calib_sensor.get_ccd_size_divisor_for_dpi(res);
+ settings.requested_pixels = settings.pixels;
+
+ /* 15 mm at at time */
+ settings.lines = static_cast<unsigned>((15 * settings.yres) / MM_PER_INCH);
+ settings.depth = 8;
+ settings.color_filter = ColorFilter::RED;
+
+ settings.disable_interpolation = 0;
+ settings.threshold = 0;
+
+ /* signals if a strip of the given color has been found */
+ found = 0;
+
+ /* detection pass done */
+ pass = 0;
+
+ std::vector<uint8_t> data;
+
+ /* loop until strip is found or maximum pass number done */
+ while (pass < 20 && !found)
+ {
+ // scan a full width strip
+ simple_scan(dev, calib_sensor, settings, true, forward, false, data, "search_strip");
+
+ if (is_testing_mode()) {
+ return;
+ }
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ std::sprintf(title, "gl646_search_strip_%s%02d.pnm", forward ? "fwd" : "bwd", pass);
+ sanei_genesys_write_pnm_file (title, data.data(), settings.depth, 1,
+ settings.pixels, settings.lines);
+ }
+
+ /* search data to find black strip */
+ /* when searching forward, we only need one line of the searched color since we
+ * will scan forward. But when doing backward search, we need all the area of the
+ * same color */
+ if (forward)
+ {
+ for (y = 0; y < settings.lines && !found; y++)
+ {
+ count = 0;
+ /* count of white/black pixels depending on the color searched */
+ for (x = 0; x < settings.pixels; x++)
+ {
+ /* when searching for black, detect white pixels */
+ if (black && data[y * settings.pixels + x] > 90)
+ {
+ count++;
+ }
+ /* when searching for white, detect black pixels */
+ if (!black && data[y * settings.pixels + x] < 60)
+ {
+ count++;
+ }
+ }
+
+ /* at end of line, if count >= 3%, line is not fully of the desired color
+ * so we must go to next line of the buffer */
+ /* count*100/pixels < 3 */
+ if ((count * 100) / settings.pixels < 3)
+ {
+ found = 1;
+ DBG(DBG_data, "%s: strip found forward during pass %d at line %d\n", __func__,
+ pass, y);
+ }
+ else
+ {
+ DBG(DBG_data, "%s: pixels=%d, count=%d\n", __func__, settings.pixels, count);
+ }
+ }
+ }
+ else /* since calibration scans are done forward, we need the whole area
+ to be of the required color when searching backward */
+ {
+ count = 0;
+ for (y = 0; y < settings.lines; y++)
+ {
+ /* count of white/black pixels depending on the color searched */
+ for (x = 0; x < settings.pixels; x++)
+ {
+ /* when searching for black, detect white pixels */
+ if (black && data[y * settings.pixels + x] > 60)
+ {
+ count++;
+ }
+ /* when searching for white, detect black pixels */
+ if (!black && data[y * settings.pixels + x] < 60)
+ {
+ count++;
+ }
+ }
+ }
+
+ /* at end of area, if count >= 3%, area is not fully of the desired color
+ * so we must go to next buffer */
+ if ((count * 100) / (settings.pixels * settings.lines) < 3)
+ {
+ found = 1;
+ DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass);
+ }
+ else
+ {
+ DBG(DBG_data, "%s: pixels=%d, count=%d\n", __func__, settings.pixels, count);
+ }
+ }
+ pass++;
+ }
+ if (found)
+ {
+ DBG(DBG_info, "%s: strip found\n", __func__);
+ }
+ else
+ {
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", black ? "black" : "white");
+ }
+}
+
+void CommandSetGl646::wait_for_motor_stop(Genesys_Device* dev) const
+{
+ (void) dev;
+}
+
+void CommandSetGl646::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ std::uint8_t* data, int size) const
+{
+ (void) dev;
+ (void) sensor;
+ (void) data;
+ (void) size;
+ throw SaneException("not implemented");
+}
+
+ScanSession CommandSetGl646::calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const
+{
+ // compute distance to move
+ float move = 0;
+ // XXX STEF XXX MD5345 -> optical_ydpi, other base_ydpi => half/full step ? */
+ if (!dev->model->is_sheetfed) {
+ move = static_cast<float>(dev->model->y_offset);
+ // add tl_y to base movement
+ }
+ move += static_cast<float>(settings.tl_y);
+
+ if (move < 0) {
+ DBG(DBG_error, "%s: overriding negative move value %f\n", __func__, move);
+ move = 0;
+ }
+
+ move = static_cast<float>((move * dev->motor.optical_ydpi) / MM_PER_INCH);
+ float start = static_cast<float>(settings.tl_x);
+ if (settings.scan_method == ScanMethod::FLATBED) {
+ start += static_cast<float>(dev->model->x_offset);
+ } else {
+ start += static_cast<float>(dev->model->x_offset_ta);
+ }
+ start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = settings.xres;
+ session.params.yres = settings.yres;
+ session.params.startx = static_cast<unsigned>(start);
+ session.params.starty = static_cast<unsigned>(move);
+ session.params.pixels = settings.pixels;
+ session.params.requested_pixels = settings.requested_pixels;
+ session.params.lines = settings.lines;
+ session.params.depth = settings.depth;
+ session.params.channels = settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = settings.scan_mode;
+ session.params.color_filter = settings.color_filter;
+ session.params.flags = ScanFlag::USE_XCORRECTION;
+ if (settings.scan_method == ScanMethod::TRANSPARENCY) {
+ session.params.flags |= ScanFlag::USE_XPA;
+ }
+ compute_session(dev, session, sensor);
+
+ return session;
+}
+
+void CommandSetGl646::asic_boot(Genesys_Device *dev, bool cold) const
+{
+ (void) dev;
+ (void) cold;
+ throw SaneException("not implemented");
+}
+
+std::unique_ptr<CommandSet> create_gl646_cmd_set()
+{
+ return std::unique_ptr<CommandSet>(new CommandSetGl646{});
+}
+
+} // namespace gl646
+} // namespace genesys
diff --git a/backend/genesys/gl646.h b/backend/genesys/gl646.h
new file mode 100644
index 0000000..afcfa05
--- /dev/null
+++ b/backend/genesys/gl646.h
@@ -0,0 +1,521 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2003-2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
+ Copyright (C) 2004-2005 Gerhard Jaeger <gerhard@gjaeger.de>
+ Copyright (C) 2004-2013 Stéphane Voltz <stef.dev@free.fr>
+ Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
+
+ 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_GL646_H
+#define BACKEND_GENESYS_GL646_H
+
+#include "genesys.h"
+#include "command_set.h"
+#include "motor.h"
+
+namespace genesys {
+namespace gl646 {
+
+static void gl646_set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set, int dpi);
+
+/**
+ * sets up the scanner for a scan, registers, gamma tables, shading tables
+ * and slope tables, based on the parameter struct.
+ * @param dev device to set up
+ * @param regs registers to set up
+ * @param settings settings of the scan
+ * @param split true if move before scan has to be done
+ * @param xcorrection true if scanner's X geometry must be taken into account to
+ * compute X, ie add left margins
+ * @param ycorrection true if scanner's Y geometry must be taken into account to
+ * compute Y, ie add top margins
+ */
+static void setup_for_scan(Genesys_Device* device,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set*regs,
+ Genesys_Settings settings,
+ bool split,
+ bool xcorrection,
+ bool ycorrection,
+ bool reverse);
+
+/**
+ * Does a simple move of the given distance by doing a scan at lowest resolution
+ * shading correction. Memory for data is allocated in this function
+ * and must be freed by caller.
+ * @param dev device of the scanner
+ * @param distance distance to move in MM
+ */
+static void simple_move(Genesys_Device* dev, SANE_Int distance);
+
+/**
+ * Does a simple scan of the area given by the settings. Scanned data
+ * it put in an allocated area which must be freed by the caller.
+ * and slope tables, based on the parameter struct. There is no shading
+ * correction while gamma correction is active.
+ * @param dev device to set up
+ * @param settings settings of the scan
+ * @param move flag to enable scanhead to move
+ * @param forward flag to tell movement direction
+ * @param shading flag to tell if shading correction should be done
+ * @param data pointer that will point to the scanned data
+ */
+static void simple_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Settings settings, bool move, bool forward,
+ bool shading, std::vector<uint8_t>& data, const char* test_identifier);
+
+/**
+ * Send the stop scan command
+ * */
+static void end_scan_impl(Genesys_Device* dev, Genesys_Register_Set* reg, bool check_stop,
+ bool eject);
+/**
+ * writes control data to an area behind the last motor table.
+ */
+static void write_control(Genesys_Device* dev, const Genesys_Sensor& sensor, int resolution);
+
+
+/**
+ * initialize scanner's registers at SANE init time
+ */
+static void gl646_init_regs (Genesys_Device * dev);
+
+/**
+ * master motor settings table entry
+ */
+typedef struct
+{
+ /* key */
+ MotorId motor_id;
+ unsigned dpi;
+ unsigned channels;
+
+ /* settings */
+ StepType steptype;
+ bool fastmod; // fast scanning
+ bool fastfed; // fast fed slope tables
+ SANE_Int mtrpwm;
+ MotorSlope slope1;
+ MotorSlope slope2;
+ SANE_Int fwdbwd; /* forward/backward steps */
+} Motor_Master;
+
+/**
+ * master motor settings, for a given motor and dpi,
+ * it gives steps and speed informations
+ */
+static Motor_Master motor_master[] = {
+ /* HP3670 motor settings */
+ {MotorId::HP3670, 50, 3, StepType::HALF, false, true, 1,
+ MotorSlope::create_from_steps(2329, 120, 229),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670, 75, 3, StepType::FULL, false, true, 1,
+ MotorSlope::create_from_steps(3429, 305, 200),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670, 100, 3, StepType::HALF, false, true, 1,
+ MotorSlope::create_from_steps(2905, 187, 143),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670, 150, 3, StepType::HALF, false, true, 1,
+ MotorSlope::create_from_steps(3429, 305, 73),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670, 300, 3, StepType::HALF, false, true, 1,
+ MotorSlope::create_from_steps(1055, 563, 11),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670, 600, 3, StepType::FULL, false, true, 0,
+ MotorSlope::create_from_steps(10687, 5126, 3),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670,1200, 3, StepType::HALF, false, true, 0,
+ MotorSlope::create_from_steps(15937, 6375, 3),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670, 50, 1, StepType::HALF, false, true, 1,
+ MotorSlope::create_from_steps(2329, 120, 229),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670, 75, 1, StepType::FULL, false, true, 1,
+ MotorSlope::create_from_steps(3429, 305, 200),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670, 100, 1, StepType::HALF, false, true, 1,
+ MotorSlope::create_from_steps(2905, 187, 143),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670, 150, 1, StepType::HALF, false, true, 1,
+ MotorSlope::create_from_steps(3429, 305, 73),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670, 300, 1, StepType::HALF, false, true, 1,
+ MotorSlope::create_from_steps(1055, 563, 11),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670, 600, 1, StepType::FULL, false, true, 0,
+ MotorSlope::create_from_steps(10687, 5126, 3),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ {MotorId::HP3670,1200, 1, StepType::HALF, false, true, 0,
+ MotorSlope::create_from_steps(15937, 6375, 3),
+ MotorSlope::create_from_steps(3399, 337, 192), 192},
+
+ /* HP2400/G2410 motor settings base motor dpi = 600 */
+ {MotorId::HP2400, 50, 3, StepType::FULL, false, true, 63,
+ MotorSlope::create_from_steps(8736, 601, 120),
+ MotorSlope::create_from_steps(4905, 337, 192), 192},
+
+ {MotorId::HP2400, 100, 3, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(8736, 601, 120),
+ MotorSlope::create_from_steps(4905, 337, 192), 192},
+
+ {MotorId::HP2400, 150, 3, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(15902, 902, 67),
+ MotorSlope::create_from_steps(4905, 337, 192), 192},
+
+ {MotorId::HP2400, 300, 3, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(16703, 2188, 32),
+ MotorSlope::create_from_steps(4905, 337, 192), 192},
+
+ {MotorId::HP2400, 600, 3, StepType::FULL, false, true, 63,
+ MotorSlope::create_from_steps(18761, 18761, 3),
+ MotorSlope::create_from_steps(4905, 627, 192), 192},
+
+ {MotorId::HP2400,1200, 3, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(43501, 43501, 3),
+ MotorSlope::create_from_steps(4905, 627, 192), 192},
+
+ {MotorId::HP2400, 50, 1, StepType::FULL, false, true, 63,
+ MotorSlope::create_from_steps(8736, 601, 120),
+ MotorSlope::create_from_steps(4905, 337, 192), 192},
+
+ {MotorId::HP2400, 100, 1, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(8736, 601, 120),
+ MotorSlope::create_from_steps(4905, 337, 192), 192},
+
+ {MotorId::HP2400, 150, 1, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(15902, 902, 67),
+ MotorSlope::create_from_steps(4905, 337, 192), 192},
+
+ {MotorId::HP2400, 300, 1, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(16703, 2188, 32),
+ MotorSlope::create_from_steps(4905, 337, 192), 192},
+
+ {MotorId::HP2400, 600, 1, StepType::FULL, false, true, 63,
+ MotorSlope::create_from_steps(18761, 18761, 3),
+ MotorSlope::create_from_steps(4905, 337, 192), 192},
+
+ {MotorId::HP2400,1200, 1, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(43501, 43501, 3),
+ MotorSlope::create_from_steps(4905, 337, 192), 192},
+
+ /* XP 200 motor settings */
+ {MotorId::XP200, 75, 3, StepType::HALF, true, false, 0,
+ MotorSlope::create_from_steps(6000, 2136, 4),
+ MotorSlope::create_from_steps(12000, 1200, 8), 1},
+
+ {MotorId::XP200, 100, 3, StepType::HALF, true, false, 0,
+ MotorSlope::create_from_steps(6000, 2850, 4),
+ MotorSlope::create_from_steps(12000, 1200, 8), 1},
+
+ {MotorId::XP200, 200, 3, StepType::HALF, true, false, 0,
+ MotorSlope::create_from_steps(6999, 5700, 4),
+ MotorSlope::create_from_steps(12000, 1200, 8), 1},
+
+ {MotorId::XP200, 250, 3, StepType::HALF, true, false, 0,
+ MotorSlope::create_from_steps(6999, 6999, 4),
+ MotorSlope::create_from_steps(12000, 1200, 8), 1},
+
+ {MotorId::XP200, 300, 3, StepType::HALF, true, false, 0,
+ MotorSlope::create_from_steps(13500, 13500, 4),
+ MotorSlope::create_from_steps(12000, 1200, 8), 1},
+
+ {MotorId::XP200, 600, 3, StepType::HALF, true, true, 0,
+ MotorSlope::create_from_steps(31998, 31998, 4),
+ MotorSlope::create_from_steps(12000, 1200, 2), 1},
+
+ {MotorId::XP200, 75, 1, StepType::HALF, true, false, 0,
+ MotorSlope::create_from_steps(6000, 2000, 4),
+ MotorSlope::create_from_steps(12000, 1200, 8), 1},
+
+ {MotorId::XP200, 100, 1, StepType::HALF, true, false, 0,
+ MotorSlope::create_from_steps(6000, 1300, 4),
+ MotorSlope::create_from_steps(12000, 1200, 8), 1},
+
+ {MotorId::XP200, 200, 1, StepType::HALF, true, true, 0,
+ MotorSlope::create_from_steps(6000, 3666, 4),
+ MotorSlope::create_from_steps(12000, 1200, 8), 1},
+
+ {MotorId::XP200, 300, 1, StepType::HALF, true, false, 0,
+ MotorSlope::create_from_steps(6500, 6500, 4),
+ MotorSlope::create_from_steps(12000, 1200, 8), 1},
+
+ {MotorId::XP200, 600, 1, StepType::HALF, true, true, 0,
+ MotorSlope::create_from_steps(24000, 24000, 4),
+ MotorSlope::create_from_steps(12000, 1200, 2), 1},
+
+ /* HP scanjet 2300c */
+ {MotorId::HP2300, 75, 3, StepType::FULL, false, true, 63,
+ MotorSlope::create_from_steps(8139, 560, 120),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+
+ {MotorId::HP2300, 150, 3, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(7903, 543, 67),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+
+ {MotorId::HP2300, 300, 3, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(2175, 1087, 3),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+
+ {MotorId::HP2300, 600, 3, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(8700, 4350, 3),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+
+ {MotorId::HP2300,1200, 3, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(17400, 8700, 3),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+
+ {MotorId::HP2300, 75, 1, StepType::FULL, false, true, 63,
+ MotorSlope::create_from_steps(8139, 560, 120),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+
+ {MotorId::HP2300, 150, 1, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(7903, 543, 67),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+
+ {MotorId::HP2300, 300, 1, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(2175, 1087, 3),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+
+ {MotorId::HP2300, 600, 1, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(8700, 4350, 3),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+
+ {MotorId::HP2300,1200, 1, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(17400, 8700, 3),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+
+ /* non half ccd settings for 300 dpi
+ {MotorId::HP2300, 300, 3, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(5386, 2175, 44),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+
+ {MotorId::HP2300, 300, 1, StepType::HALF, false, true, 63,
+ MotorSlope::create_from_steps(5386, 2175, 44),
+ MotorSlope::create_from_steps(4905, 337, 120), 16},
+ */
+
+ /* MD5345/6471 motor settings */
+ /* vfinal=(exposure/(1200/dpi))/step_type */
+ {MotorId::MD_5345, 50, 3, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 250, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 75, 3, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 343, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 100, 3, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 458, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 150, 3, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 687, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 200, 3, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 916, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 300, 3, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 1375, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 400, 3, StepType::HALF, false, true, 0,
+ MotorSlope::create_from_steps(2000, 1833, 32),
+ MotorSlope::create_from_steps(2000, 300, 255), 32},
+
+ {MotorId::MD_5345, 500, 3, StepType::HALF, false, true, 0,
+ MotorSlope::create_from_steps(2291, 2291, 32),
+ MotorSlope::create_from_steps(2000, 300, 255), 32},
+
+ {MotorId::MD_5345, 600, 3, StepType::HALF, false, true, 0,
+ MotorSlope::create_from_steps(2750, 2750, 32),
+ MotorSlope::create_from_steps(2000, 300, 255), 32},
+
+ {MotorId::MD_5345, 1200, 3, StepType::QUARTER, false, true, 0,
+ MotorSlope::create_from_steps(2750, 2750, 16),
+ MotorSlope::create_from_steps(2000, 300, 255), 146},
+
+ {MotorId::MD_5345, 2400, 3, StepType::QUARTER, false, true, 0,
+ MotorSlope::create_from_steps(5500, 5500, 16),
+ MotorSlope::create_from_steps(2000, 300, 255), 146},
+
+ {MotorId::MD_5345, 50, 1, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 250, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 75, 1, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 343, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 100, 1, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 458, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 150, 1, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 687, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 200, 1, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 916, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 300, 1, StepType::HALF, false, true, 2,
+ MotorSlope::create_from_steps(2500, 1375, 255),
+ MotorSlope::create_from_steps(2000, 300, 255), 64},
+
+ {MotorId::MD_5345, 400, 1, StepType::HALF, false, true, 0,
+ MotorSlope::create_from_steps(2000, 1833, 32),
+ MotorSlope::create_from_steps(2000, 300, 255), 32},
+
+ {MotorId::MD_5345, 500, 1, StepType::HALF, false, true, 0,
+ MotorSlope::create_from_steps(2291, 2291, 32),
+ MotorSlope::create_from_steps(2000, 300, 255), 32},
+
+ {MotorId::MD_5345, 600, 1, StepType::HALF, false, true, 0,
+ MotorSlope::create_from_steps(2750, 2750, 32),
+ MotorSlope::create_from_steps(2000, 300, 255), 32},
+
+ {MotorId::MD_5345, 1200, 1, StepType::QUARTER, false, true, 0,
+ MotorSlope::create_from_steps(2750, 2750, 16),
+ MotorSlope::create_from_steps(2000, 300, 255), 146},
+
+ {MotorId::MD_5345, 2400, 1, StepType::QUARTER, false, true, 0,
+ MotorSlope::create_from_steps(5500, 5500, 16),
+ MotorSlope::create_from_steps(2000, 300, 255), 146}, /* 5500 guessed */
+};
+
+class CommandSetGl646 : public CommandSet
+{
+public:
+ ~CommandSetGl646() override = default;
+
+ bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override;
+
+ void init(Genesys_Device* dev) const override;
+
+ void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, int* channels,
+ int* total_size) const override;
+
+ void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const override;
+
+ void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override;
+ void set_powersaving(Genesys_Device* dev, int delay) const override;
+ void save_power(Genesys_Device* dev, bool enable) const override;
+
+ void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, bool start_motor) const override;
+
+ void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override;
+
+ void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void search_start_position(Genesys_Device* dev) const override;
+
+ void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const override;
+
+ SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void wait_for_motor_stop(Genesys_Device* dev) const override;
+
+ void move_back_home(Genesys_Device* dev, bool wait_until_home) const override;
+
+ void update_hardware_sensors(struct Genesys_Scanner* s) const override;
+
+ void load_document(Genesys_Device* dev) const override;
+
+ void detect_document_end(Genesys_Device* dev) const override;
+
+ void eject_document(Genesys_Device* dev) const override;
+
+ void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ bool forward, bool black) const override;
+
+ void move_to_ta(Genesys_Device* dev) const override;
+
+ void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data,
+ int size) const override;
+
+ bool has_send_shading_data() const override
+ {
+ return false;
+ }
+
+ ScanSession calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const override;
+
+ void asic_boot(Genesys_Device* dev, bool cold) const override;
+};
+
+} // namespace gl646
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL646_H
diff --git a/backend/genesys/gl646_registers.h b/backend/genesys/gl646_registers.h
new file mode 100644
index 0000000..2fe8f19
--- /dev/null
+++ b/backend/genesys/gl646_registers.h
@@ -0,0 +1,176 @@
+/* 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_GL646_REGISTERS_H
+#define BACKEND_GENESYS_GL646_REGISTERS_H
+
+#include <cstdint>
+
+namespace genesys {
+namespace gl646 {
+
+using RegAddr = std::uint16_t;
+using RegMask = std::uint8_t;
+using RegShift = unsigned;
+
+static constexpr RegAddr REG_0x01 = 0x01;
+static constexpr RegMask REG_0x01_CISSET = 0x80;
+static constexpr RegMask REG_0x01_DOGENB = 0x40;
+static constexpr RegMask REG_0x01_DVDSET = 0x20;
+static constexpr RegMask REG_0x01_FASTMOD = 0x10;
+static constexpr RegMask REG_0x01_COMPENB = 0x08;
+static constexpr RegMask REG_0x01_DRAMSEL = 0x04;
+static constexpr RegMask REG_0x01_SHDAREA = 0x02;
+static constexpr RegMask REG_0x01_SCAN = 0x01;
+
+static constexpr RegMask REG_0x02_NOTHOME = 0x80;
+static constexpr RegMask REG_0x02_ACDCDIS = 0x40;
+static constexpr RegMask REG_0x02_AGOHOME = 0x20;
+static constexpr RegMask REG_0x02_MTRPWR = 0x10;
+static constexpr RegMask REG_0x02_FASTFED = 0x08;
+static constexpr RegMask REG_0x02_MTRREV = 0x04;
+static constexpr RegMask REG_0x02_STEPSEL = 0x03;
+
+static constexpr RegMask REG_0x02_FULLSTEP = 0x00;
+static constexpr RegMask REG_0x02_HALFSTEP = 0x01;
+static constexpr RegMask REG_0x02_QUATERSTEP = 0x02;
+
+static constexpr RegMask REG_0x03_TG3 = 0x80;
+static constexpr RegMask REG_0x03_AVEENB = 0x40;
+static constexpr RegMask REG_0x03_XPASEL = 0x20;
+static constexpr RegMask REG_0x03_LAMPPWR = 0x10;
+static constexpr RegMask REG_0x03_LAMPDOG = 0x08;
+static constexpr RegMask REG_0x03_LAMPTIM = 0x07;
+
+static constexpr RegMask REG_0x04_LINEART = 0x80;
+static constexpr RegMask REG_0x04_BITSET = 0x40;
+static constexpr RegMask REG_0x04_ADTYPE = 0x30;
+static constexpr RegMask REG_0x04_FILTER = 0x0c;
+static constexpr RegMask REG_0x04_FESET = 0x03;
+
+static constexpr RegMask REG_0x05_DPIHW = 0xc0;
+static constexpr RegMask REG_0x05_DPIHW_600 = 0x00;
+static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40;
+static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80;
+static constexpr RegMask REG_0x05_DPIHW_4800 = 0xc0;
+static constexpr RegMask REG_0x05_GMMTYPE = 0x30;
+static constexpr RegMask REG_0x05_GMM14BIT = 0x10;
+static constexpr RegMask REG_0x05_GMMENB = 0x08;
+static constexpr RegMask REG_0x05_LEDADD = 0x04;
+static constexpr RegMask REG_0x05_BASESEL = 0x03;
+
+static constexpr RegAddr REG_0x06 = 0x06;
+static constexpr RegMask REG_0x06_PWRBIT = 0x10;
+static constexpr RegMask REG_0x06_GAIN4 = 0x08;
+static constexpr RegMask REG_0x06_OPTEST = 0x07;
+
+static constexpr RegMask REG_0x07_DMASEL = 0x02;
+static constexpr RegMask REG_0x07_DMARDWR = 0x01;
+
+static constexpr RegMask REG_0x16_CTRLHI = 0x80;
+static constexpr RegMask REG_0x16_SELINV = 0x40;
+static constexpr RegMask REG_0x16_TGINV = 0x20;
+static constexpr RegMask REG_0x16_CK1INV = 0x10;
+static constexpr RegMask REG_0x16_CK2INV = 0x08;
+static constexpr RegMask REG_0x16_CTRLINV = 0x04;
+static constexpr RegMask REG_0x16_CKDIS = 0x02;
+static constexpr RegMask REG_0x16_CTRLDIS = 0x01;
+
+static constexpr RegMask REG_0x17_TGMODE = 0xc0;
+static constexpr RegMask REG_0x17_TGMODE_NO_DUMMY = 0x00;
+static constexpr RegMask REG_0x17_TGMODE_REF = 0x40;
+static constexpr RegMask REG_0x17_TGMODE_XPA = 0x80;
+static constexpr RegMask REG_0x17_TGW = 0x3f;
+
+static constexpr RegMask REG_0x18_CNSET = 0x80;
+static constexpr RegMask REG_0x18_DCKSEL = 0x60;
+static constexpr RegMask REG_0x18_CKTOGGLE = 0x10;
+static constexpr RegMask REG_0x18_CKDELAY = 0x0c;
+static constexpr RegMask REG_0x18_CKSEL = 0x03;
+
+static constexpr RegMask REG_0x1D_CKMANUAL = 0x80;
+
+static constexpr RegMask REG_0x1E_WDTIME = 0xf0;
+static constexpr RegMask REG_0x1E_LINESEL = 0x0f;
+
+static constexpr RegMask REG_0x41_PWRBIT = 0x80;
+static constexpr RegMask REG_0x41_BUFEMPTY = 0x40;
+static constexpr RegMask REG_0x41_FEEDFSH = 0x20;
+static constexpr RegMask REG_0x41_SCANFSH = 0x10;
+static constexpr RegMask REG_0x41_HOMESNR = 0x08;
+static constexpr RegMask REG_0x41_LAMPSTS = 0x04;
+static constexpr RegMask REG_0x41_FEBUSY = 0x02;
+static constexpr RegMask REG_0x41_MOTMFLG = 0x01;
+
+static constexpr RegMask REG_0x66_LOW_CURRENT = 0x10;
+
+static constexpr RegMask REG_0x6A_FSTPSEL = 0xc0;
+static constexpr RegMask REG_0x6A_FASTPWM = 0x3f;
+
+static constexpr RegMask REG_0x6C_TGTIME = 0xc0;
+static constexpr RegMask REG_0x6C_Z1MOD = 0x38;
+static constexpr RegMask REG_0x6C_Z2MOD = 0x07;
+
+static constexpr RegAddr REG_EXPR = 0x10;
+static constexpr RegAddr REG_EXPG = 0x12;
+static constexpr RegAddr REG_EXPB = 0x14;
+static constexpr RegAddr REG_SCANFED = 0x1f;
+static constexpr RegAddr REG_BUFSEL = 0x20;
+static constexpr RegAddr REG_LINCNT = 0x25;
+static constexpr RegAddr REG_DPISET = 0x2c;
+static constexpr RegAddr REG_STRPIXEL = 0x30;
+static constexpr RegAddr REG_ENDPIXEL = 0x32;
+static constexpr RegAddr REG_DUMMY = 0x34;
+static constexpr RegAddr REG_MAXWD = 0x35;
+static constexpr RegAddr REG_LPERIOD = 0x38;
+static constexpr RegAddr REG_FEEDL = 0x3d;
+static constexpr RegAddr REG_VALIDWORD = 0x42;
+static constexpr RegAddr REG_FEDCNT = 0x48;
+static constexpr RegAddr REG_SCANCNT = 0x4b;
+static constexpr RegAddr REG_Z1MOD = 0x60;
+static constexpr RegAddr REG_Z2MOD = 0x62;
+
+} // namespace gl646
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL646_REGISTERS_H
diff --git a/backend/genesys/gl841.cpp b/backend/genesys/gl841.cpp
new file mode 100644
index 0000000..470f9ba
--- /dev/null
+++ b/backend/genesys/gl841.cpp
@@ -0,0 +1,4010 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2003 Oliver Rauch
+ Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
+ Copyright (C) 2004 Gerhard Jaeger <gerhard@gjaeger.de>
+ Copyright (C) 2004-2013 Stéphane Voltz <stef.dev@free.fr>
+ Copyright (C) 2005 Philipp Schmid <philipp8288@web.de>
+ Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
+ Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com>
+ Copyright (C) 2010 Chris Berry <s0457957@sms.ed.ac.uk> and Michael Rickmann <mrickma@gwdg.de>
+ for Plustek Opticbook 3600 support
+
+
+ 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 "gl841.h"
+#include "gl841_registers.h"
+#include "test_settings.h"
+
+#include <vector>
+
+namespace genesys {
+namespace gl841 {
+
+
+static int gl841_exposure_time(Genesys_Device *dev, const Genesys_Sensor& sensor,
+ float slope_dpi,
+ StepType scan_step_type,
+ int start,
+ int used_pixels);
+
+/** copy sensor specific settings */
+/* *dev : device infos
+ *regs : registers to be set
+ extended : do extended set up
+ ccd_size_divisor: set up for half ccd resolution
+ all registers 08-0B, 10-1D, 52-59 are set up. They shouldn't
+ appear anywhere else but in register_ini
+
+Responsible for signals to CCD/CIS:
+ CCD_CK1X (CK1INV(0x16),CKDIS(0x16),CKTOGGLE(0x18),CKDELAY(0x18),MANUAL1(0x1A),CK1MTGL(0x1C),CK1LOW(0x1D),CK1MAP(0x74,0x75,0x76),CK1NEG(0x7D))
+ CCD_CK2X (CK2INV(0x16),CKDIS(0x16),CKTOGGLE(0x18),CKDELAY(0x18),MANUAL1(0x1A),CK1LOW(0x1D),CK1NEG(0x7D))
+ CCD_CK3X (MANUAL3(0x1A),CK3INV(0x1A),CK3MTGL(0x1C),CK3LOW(0x1D),CK3MAP(0x77,0x78,0x79),CK3NEG(0x7D))
+ CCD_CK4X (MANUAL3(0x1A),CK4INV(0x1A),CK4MTGL(0x1C),CK4LOW(0x1D),CK4MAP(0x7A,0x7B,0x7C),CK4NEG(0x7D))
+ CCD_CPX (CTRLHI(0x16),CTRLINV(0x16),CTRLDIS(0x16),CPH(0x72),CPL(0x73),CPNEG(0x7D))
+ CCD_RSX (CTRLHI(0x16),CTRLINV(0x16),CTRLDIS(0x16),RSH(0x70),RSL(0x71),RSNEG(0x7D))
+ CCD_TGX (TGINV(0x16),TGMODE(0x17),TGW(0x17),EXPR(0x10,0x11),TGSHLD(0x1D))
+ CCD_TGG (TGINV(0x16),TGMODE(0x17),TGW(0x17),EXPG(0x12,0x13),TGSHLD(0x1D))
+ CCD_TGB (TGINV(0x16),TGMODE(0x17),TGW(0x17),EXPB(0x14,0x15),TGSHLD(0x1D))
+ LAMP_SW (EXPR(0x10,0x11),XPA_SEL(0x03),LAMP_PWR(0x03),LAMPTIM(0x03),MTLLAMP(0x04),LAMPPWM(0x29))
+ XPA_SW (EXPG(0x12,0x13),XPA_SEL(0x03),LAMP_PWR(0x03),LAMPTIM(0x03),MTLLAMP(0x04),LAMPPWM(0x29))
+ LAMP_B (EXPB(0x14,0x15),LAMP_PWR(0x03))
+
+other registers:
+ CISSET(0x01),CNSET(0x18),DCKSEL(0x18),SCANMOD(0x18),EXPDMY(0x19),LINECLP(0x1A),CKAREA(0x1C),TGTIME(0x1C),LINESEL(0x1E),DUMMY(0x34)
+
+Responsible for signals to AFE:
+ VSMP (VSMP(0x58),VSMPW(0x58))
+ BSMP (BSMP(0x59),BSMPW(0x59))
+
+other register settings depending on this:
+ RHI(0x52),RLOW(0x53),GHI(0x54),GLOW(0x55),BHI(0x56),BLOW(0x57),
+
+*/
+static void sanei_gl841_setup_sensor(Genesys_Device * dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set * regs,
+ bool extended, unsigned ccd_size_divisor)
+{
+ DBG(DBG_proc, "%s\n", __func__);
+
+ // that one is tricky at least
+ for (uint16_t addr = 0x08; addr <= 0x0b; ++addr) {
+ regs->set8(0x70 + addr - 0x08, sensor.custom_regs.get_value(addr));
+ }
+
+ // ignore registers in range [0x10..0x16)
+ for (uint16_t addr = 0x16; addr < 0x1e; ++addr) {
+ regs->set8(addr, sensor.custom_regs.get_value(addr));
+ }
+
+ // ignore registers in range [0x5b..0x5e]
+ for (uint16_t addr = 0x52; addr < 0x52 + 9; ++addr) {
+ regs->set8(addr, sensor.custom_regs.get_value(addr));
+ }
+
+ /* don't go any further if no extended setup */
+ if (!extended)
+ return;
+
+ /* todo : add more CCD types if needed */
+ /* we might want to expand the Sensor struct to have these
+ 2 kind of settings */
+ if (dev->model->sensor_id == SensorId::CCD_5345) {
+ if (ccd_size_divisor > 1) {
+ GenesysRegister* r;
+ /* settings for CCD used at half is max resolution */
+ r = sanei_genesys_get_address (regs, 0x70);
+ r->value = 0x00;
+ r = sanei_genesys_get_address (regs, 0x71);
+ r->value = 0x05;
+ r = sanei_genesys_get_address (regs, 0x72);
+ r->value = 0x06;
+ r = sanei_genesys_get_address (regs, 0x73);
+ r->value = 0x08;
+ r = sanei_genesys_get_address (regs, 0x18);
+ r->value = 0x28;
+ r = sanei_genesys_get_address (regs, 0x58);
+ r->value = 0x80 | (r->value & 0x03); /* VSMP=16 */
+ }
+ else
+ {
+ GenesysRegister* r;
+ /* swap latch times */
+ r = sanei_genesys_get_address (regs, 0x18);
+ r->value = 0x30;
+ regs->set8(0x52, sensor.custom_regs.get_value(0x55));
+ regs->set8(0x53, sensor.custom_regs.get_value(0x56));
+ regs->set8(0x54, sensor.custom_regs.get_value(0x57));
+ regs->set8(0x55, sensor.custom_regs.get_value(0x52));
+ regs->set8(0x56, sensor.custom_regs.get_value(0x53));
+ regs->set8(0x57, sensor.custom_regs.get_value(0x54));
+ r = sanei_genesys_get_address (regs, 0x58);
+ r->value = 0x20 | (r->value & 0x03); /* VSMP=4 */
+ }
+ return;
+ }
+
+ if (dev->model->sensor_id == SensorId::CCD_HP2300) {
+ /* settings for CCD used at half is max resolution */
+ GenesysRegister* r;
+ if (ccd_size_divisor > 1) {
+ r = sanei_genesys_get_address (regs, 0x70);
+ r->value = 0x16;
+ r = sanei_genesys_get_address (regs, 0x71);
+ r->value = 0x00;
+ r = sanei_genesys_get_address (regs, 0x72);
+ r->value = 0x01;
+ r = sanei_genesys_get_address (regs, 0x73);
+ r->value = 0x03;
+ /* manual clock programming */
+ r = sanei_genesys_get_address (regs, 0x1d);
+ r->value |= 0x80;
+ }
+ else
+ {
+ r = sanei_genesys_get_address (regs, 0x70);
+ r->value = 1;
+ r = sanei_genesys_get_address (regs, 0x71);
+ r->value = 3;
+ r = sanei_genesys_get_address (regs, 0x72);
+ r->value = 4;
+ r = sanei_genesys_get_address (regs, 0x73);
+ r->value = 6;
+ }
+ r = sanei_genesys_get_address (regs, 0x58);
+ r->value = 0x80 | (r->value & 0x03); /* VSMP=16 */
+ return;
+ }
+}
+
+/*
+ * Set all registers LiDE 80 to default values
+ * (function called only once at the beginning)
+ * we are doing a special case to ease development
+ */
+static void
+gl841_init_lide80 (Genesys_Device * dev)
+{
+ dev->reg.init_reg(0x01, 0x82); // 0x02 = SHDAREA and no CISSET !
+ dev->reg.init_reg(0x02, 0x10);
+ dev->reg.init_reg(0x03, 0x50);
+ dev->reg.init_reg(0x04, 0x02);
+ dev->reg.init_reg(0x05, 0x4c); // 1200 DPI
+ dev->reg.init_reg(0x06, 0x38); // 0x38 scanmod=1, pwrbit, GAIN4
+ dev->reg.init_reg(0x07, 0x00);
+ dev->reg.init_reg(0x08, 0x00);
+ dev->reg.init_reg(0x09, 0x11);
+ dev->reg.init_reg(0x0a, 0x00);
+
+ dev->reg.init_reg(0x10, 0x40);
+ dev->reg.init_reg(0x11, 0x00);
+ dev->reg.init_reg(0x12, 0x40);
+ dev->reg.init_reg(0x13, 0x00);
+ dev->reg.init_reg(0x14, 0x40);
+ dev->reg.init_reg(0x15, 0x00);
+ dev->reg.init_reg(0x16, 0x00);
+ dev->reg.init_reg(0x17, 0x01);
+ dev->reg.init_reg(0x18, 0x00);
+ dev->reg.init_reg(0x19, 0x06);
+ dev->reg.init_reg(0x1a, 0x00);
+ dev->reg.init_reg(0x1b, 0x00);
+ dev->reg.init_reg(0x1c, 0x00);
+ dev->reg.init_reg(0x1d, 0x04);
+ dev->reg.init_reg(0x1e, 0x10);
+ dev->reg.init_reg(0x1f, 0x04);
+ dev->reg.init_reg(0x20, 0x02);
+ dev->reg.init_reg(0x21, 0x10);
+ dev->reg.init_reg(0x22, 0x20);
+ dev->reg.init_reg(0x23, 0x20);
+ dev->reg.init_reg(0x24, 0x10);
+ dev->reg.init_reg(0x25, 0x00);
+ dev->reg.init_reg(0x26, 0x00);
+ dev->reg.init_reg(0x27, 0x00);
+
+ dev->reg.init_reg(0x29, 0xff);
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+ dev->reg.init_reg(0x2c, sensor.optical_res>>8);
+ dev->reg.init_reg(0x2d, sensor.optical_res & 0xff);
+ dev->reg.init_reg(0x2e, 0x80);
+ dev->reg.init_reg(0x2f, 0x80);
+ dev->reg.init_reg(0x30, 0x00);
+ dev->reg.init_reg(0x31, 0x10);
+ dev->reg.init_reg(0x32, 0x15);
+ dev->reg.init_reg(0x33, 0x0e);
+ dev->reg.init_reg(0x34, 0x40);
+ dev->reg.init_reg(0x35, 0x00);
+ dev->reg.init_reg(0x36, 0x2a);
+ dev->reg.init_reg(0x37, 0x30);
+ dev->reg.init_reg(0x38, 0x2a);
+ dev->reg.init_reg(0x39, 0xf8);
+
+ dev->reg.init_reg(0x3d, 0x00);
+ dev->reg.init_reg(0x3e, 0x00);
+ dev->reg.init_reg(0x3f, 0x00);
+
+ dev->reg.init_reg(0x52, 0x03);
+ dev->reg.init_reg(0x53, 0x07);
+ dev->reg.init_reg(0x54, 0x00);
+ dev->reg.init_reg(0x55, 0x00);
+ dev->reg.init_reg(0x56, 0x00);
+ dev->reg.init_reg(0x57, 0x00);
+ dev->reg.init_reg(0x58, 0x29);
+ dev->reg.init_reg(0x59, 0x69);
+ dev->reg.init_reg(0x5a, 0x55);
+
+ dev->reg.init_reg(0x5d, 0x20);
+ dev->reg.init_reg(0x5e, 0x41);
+ dev->reg.init_reg(0x5f, 0x40);
+ dev->reg.init_reg(0x60, 0x00);
+ dev->reg.init_reg(0x61, 0x00);
+ dev->reg.init_reg(0x62, 0x00);
+ dev->reg.init_reg(0x63, 0x00);
+ dev->reg.init_reg(0x64, 0x00);
+ dev->reg.init_reg(0x65, 0x00);
+ dev->reg.init_reg(0x66, 0x00);
+ dev->reg.init_reg(0x67, 0x40);
+ dev->reg.init_reg(0x68, 0x40);
+ dev->reg.init_reg(0x69, 0x20);
+ dev->reg.init_reg(0x6a, 0x20);
+ dev->reg.init_reg(0x6c, 0x00);
+ dev->reg.init_reg(0x6d, 0x00);
+ dev->reg.init_reg(0x6e, 0x00);
+ dev->reg.init_reg(0x6f, 0x00);
+ dev->reg.init_reg(0x70, 0x00);
+ dev->reg.init_reg(0x71, 0x05);
+ dev->reg.init_reg(0x72, 0x07);
+ dev->reg.init_reg(0x73, 0x09);
+ dev->reg.init_reg(0x74, 0x00);
+ dev->reg.init_reg(0x75, 0x01);
+ dev->reg.init_reg(0x76, 0xff);
+ dev->reg.init_reg(0x77, 0x00);
+ dev->reg.init_reg(0x78, 0x0f);
+ dev->reg.init_reg(0x79, 0xf0);
+ dev->reg.init_reg(0x7a, 0xf0);
+ dev->reg.init_reg(0x7b, 0x00);
+ dev->reg.init_reg(0x7c, 0x1e);
+ dev->reg.init_reg(0x7d, 0x11);
+ dev->reg.init_reg(0x7e, 0x00);
+ dev->reg.init_reg(0x7f, 0x50);
+ dev->reg.init_reg(0x80, 0x00);
+ dev->reg.init_reg(0x81, 0x00);
+ dev->reg.init_reg(0x82, 0x0f);
+ dev->reg.init_reg(0x83, 0x00);
+ dev->reg.init_reg(0x84, 0x0e);
+ dev->reg.init_reg(0x85, 0x00);
+ dev->reg.init_reg(0x86, 0x0d);
+ dev->reg.init_reg(0x87, 0x02);
+ dev->reg.init_reg(0x88, 0x00);
+ dev->reg.init_reg(0x89, 0x00);
+
+ for (const auto& reg : dev->gpo.regs) {
+ dev->reg.set8(reg.address, reg.value);
+ }
+
+ // specific scanner settings, clock and gpio first
+ // FIXME: remove the dummy reads as we don't use the values
+ if (!is_testing_mode()) {
+ dev->interface->read_register(REG_0x6B);
+ }
+ dev->interface->write_register(REG_0x6B, 0x0c);
+ dev->interface->write_register(0x06, 0x10);
+ dev->interface->write_register(REG_0x6E, 0x6d);
+ dev->interface->write_register(REG_0x6F, 0x80);
+ dev->interface->write_register(REG_0x6B, 0x0e);
+ if (!is_testing_mode()) {
+ dev->interface->read_register(REG_0x6C);
+ }
+ dev->interface->write_register(REG_0x6C, 0x00);
+ if (!is_testing_mode()) {
+ dev->interface->read_register(REG_0x6D);
+ }
+ dev->interface->write_register(REG_0x6D, 0x8f);
+ if (!is_testing_mode()) {
+ dev->interface->read_register(REG_0x6B);
+ }
+ dev->interface->write_register(REG_0x6B, 0x0e);
+ if (!is_testing_mode()) {
+ dev->interface->read_register(REG_0x6B);
+ }
+ dev->interface->write_register(REG_0x6B, 0x0e);
+ if (!is_testing_mode()) {
+ dev->interface->read_register(REG_0x6B);
+ }
+ dev->interface->write_register(REG_0x6B, 0x0a);
+ if (!is_testing_mode()) {
+ dev->interface->read_register(REG_0x6B);
+ }
+ dev->interface->write_register(REG_0x6B, 0x02);
+ if (!is_testing_mode()) {
+ dev->interface->read_register(REG_0x6B);
+ }
+ dev->interface->write_register(REG_0x6B, 0x06);
+
+ dev->interface->write_0x8c(0x10, 0x94);
+ dev->interface->write_register(0x09, 0x10);
+
+ // FIXME: the following code originally changed 0x6b, but due to bug the 0x6c register was
+ // effectively changed. The current behavior matches the old code, but should probably be fixed.
+ dev->reg.find_reg(0x6c).value |= REG_0x6B_GPO18;
+ dev->reg.find_reg(0x6c).value &= ~REG_0x6B_GPO17;
+
+ sanei_gl841_setup_sensor(dev, sensor, &dev->reg, 0, 1);
+}
+
+/*
+ * Set all registers to default values
+ * (function called only once at the beginning)
+ */
+static void
+gl841_init_registers (Genesys_Device * dev)
+{
+ int addr;
+
+ DBG(DBG_proc, "%s\n", __func__);
+
+ dev->reg.clear();
+ if (dev->model->model_id == ModelId::CANON_LIDE_80) {
+ gl841_init_lide80(dev);
+ return ;
+ }
+
+ for (addr = 1; addr <= 0x0a; addr++) {
+ dev->reg.init_reg(addr, 0);
+ }
+ for (addr = 0x10; addr <= 0x27; addr++) {
+ dev->reg.init_reg(addr, 0);
+ }
+ dev->reg.init_reg(0x29, 0);
+ for (addr = 0x2c; addr <= 0x39; addr++)
+ dev->reg.init_reg(addr, 0);
+ for (addr = 0x3d; addr <= 0x3f; addr++)
+ dev->reg.init_reg(addr, 0);
+ for (addr = 0x52; addr <= 0x5a; addr++)
+ dev->reg.init_reg(addr, 0);
+ for (addr = 0x5d; addr <= 0x87; addr++)
+ dev->reg.init_reg(addr, 0);
+
+
+ dev->reg.find_reg(0x01).value = 0x20; /* (enable shading), CCD, color, 1M */
+ if (dev->model->is_cis) {
+ dev->reg.find_reg(0x01).value |= REG_0x01_CISSET;
+ } else {
+ dev->reg.find_reg(0x01).value &= ~REG_0x01_CISSET;
+ }
+
+ dev->reg.find_reg(0x02).value = 0x30 /*0x38 */ ; /* auto home, one-table-move, full step */
+ dev->reg.find_reg(0x02).value |= REG_0x02_AGOHOME;
+ sanei_genesys_set_motor_power(dev->reg, true);
+ dev->reg.find_reg(0x02).value |= REG_0x02_FASTFED;
+
+ dev->reg.find_reg(0x03).value = 0x1f /*0x17 */ ; /* lamp on */
+ dev->reg.find_reg(0x03).value |= REG_0x03_AVEENB;
+
+ if (dev->model->sensor_id == SensorId::CCD_PLUSTEK_OPTICPRO_3600) {
+ // AD front end
+ dev->reg.find_reg(0x04).value = (2 << REG_0x04S_AFEMOD) | 0x02;
+ }
+ else /* Wolfson front end */
+ {
+ dev->reg.find_reg(0x04).value |= 1 << REG_0x04S_AFEMOD;
+ }
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+
+ dev->reg.find_reg(0x05).value = 0x00; /* disable gamma, 24 clocks/pixel */
+
+ unsigned dpihw = 0;
+ if (sensor.sensor_pixels < 0x1500) {
+ dpihw = 600;
+ } else if (sensor.sensor_pixels < 0x2a80) {
+ dpihw = 1200;
+ } else if (sensor.sensor_pixels < 0x5400) {
+ dpihw = 2400;
+ } else {
+ throw SaneException("Cannot handle sensor pixel count %d", sensor.sensor_pixels);
+ }
+ sanei_genesys_set_dpihw(dev->reg, sensor, dpihw);
+
+ dev->reg.find_reg(0x06).value |= REG_0x06_PWRBIT;
+ dev->reg.find_reg(0x06).value |= REG_0x06_GAIN4;
+
+ /* XP300 CCD needs different clock and clock/pixels values */
+ if (dev->model->sensor_id != SensorId::CCD_XP300 &&
+ dev->model->sensor_id != SensorId::CCD_DP685 &&
+ dev->model->sensor_id != SensorId::CCD_PLUSTEK_OPTICPRO_3600)
+ {
+ dev->reg.find_reg(0x06).value |= 0 << REG_0x06S_SCANMOD;
+ dev->reg.find_reg(0x09).value |= 1 << REG_0x09S_CLKSET;
+ }
+ else
+ {
+ dev->reg.find_reg(0x06).value |= 0x05 << REG_0x06S_SCANMOD; /* 15 clocks/pixel */
+ dev->reg.find_reg(0x09).value = 0; /* 24 MHz CLKSET */
+ }
+
+ dev->reg.find_reg(0x1e).value = 0xf0; /* watch-dog time */
+
+ dev->reg.find_reg(0x17).value |= 1 << REG_0x17S_TGW;
+
+ dev->reg.find_reg(0x19).value = 0x50;
+
+ dev->reg.find_reg(0x1d).value |= 1 << REG_0x1DS_TGSHLD;
+
+ dev->reg.find_reg(0x1e).value |= 1 << REG_0x1ES_WDTIME;
+
+/*SCANFED*/
+ dev->reg.find_reg(0x1f).value = 0x01;
+
+/*BUFSEL*/
+ dev->reg.find_reg(0x20).value = 0x20;
+
+/*LAMPPWM*/
+ dev->reg.find_reg(0x29).value = 0xff;
+
+/*BWHI*/
+ dev->reg.find_reg(0x2e).value = 0x80;
+
+/*BWLOW*/
+ dev->reg.find_reg(0x2f).value = 0x80;
+
+/*LPERIOD*/
+ dev->reg.find_reg(0x38).value = 0x4f;
+ dev->reg.find_reg(0x39).value = 0xc1;
+
+/*VSMPW*/
+ dev->reg.find_reg(0x58).value |= 3 << REG_0x58S_VSMPW;
+
+/*BSMPW*/
+ dev->reg.find_reg(0x59).value |= 3 << REG_0x59S_BSMPW;
+
+/*RLCSEL*/
+ dev->reg.find_reg(0x5a).value |= REG_0x5A_RLCSEL;
+
+/*STOPTIM*/
+ dev->reg.find_reg(0x5e).value |= 0x2 << REG_0x5ES_STOPTIM;
+
+ sanei_gl841_setup_sensor(dev, sensor, &dev->reg, 0, 1);
+
+ // set up GPIO
+ for (const auto& reg : dev->gpo.regs) {
+ dev->reg.set8(reg.address, reg.value);
+ }
+
+ /* TODO there is a switch calling to be written here */
+ if (dev->model->gpio_id == GpioId::CANON_LIDE_35) {
+ dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO18;
+ dev->reg.find_reg(0x6b).value &= ~REG_0x6B_GPO17;
+ }
+
+ if (dev->model->gpio_id == GpioId::XP300) {
+ dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17;
+ }
+
+ if (dev->model->gpio_id == GpioId::DP685) {
+ /* REG_0x6B_GPO18 lights on green led */
+ dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17|REG_0x6B_GPO18;
+ }
+
+ DBG(DBG_proc, "%s complete\n", __func__);
+}
+
+// Send slope table for motor movement slope_table in machine byte order
+static void gl841_send_slope_table(Genesys_Device* dev, int table_nr,
+ const std::vector<uint16_t>& slope_table,
+ int steps)
+{
+ DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d", table_nr, steps);
+ int dpihw;
+ int start_address;
+ char msg[4000];
+/*#ifdef WORDS_BIGENDIAN*/
+ int i;
+/*#endif*/
+
+ dpihw = dev->reg.find_reg(0x05).value >> 6;
+
+ if (dpihw == 0) /* 600 dpi */
+ start_address = 0x08000;
+ else if (dpihw == 1) /* 1200 dpi */
+ start_address = 0x10000;
+ else if (dpihw == 2) /* 2400 dpi */
+ start_address = 0x20000;
+ else {
+ throw SaneException("Unexpected dpihw");
+ }
+
+ std::vector<uint8_t> table(steps * 2);
+ for(i = 0; i < steps; i++) {
+ table[i * 2] = slope_table[i] & 0xff;
+ table[i * 2 + 1] = slope_table[i] >> 8;
+ }
+
+ if (DBG_LEVEL >= DBG_io)
+ {
+ std::sprintf(msg, "write slope %d (%d)=", table_nr, steps);
+ for (i = 0; i < steps; i++) {
+ std::sprintf (msg+strlen(msg), ",%d", slope_table[i]);
+ }
+ DBG(DBG_io, "%s: %s\n", __func__, msg);
+ }
+
+ if (dev->interface->is_mock()) {
+ dev->interface->record_slope_table(table_nr, slope_table);
+ }
+ dev->interface->write_buffer(0x3c, start_address + table_nr * 0x200, table.data(), steps * 2);
+}
+
+static void gl841_set_lide80_fe(Genesys_Device* dev, uint8_t set)
+{
+ DBG_HELPER(dbg);
+
+ if (set == AFE_INIT)
+ {
+ DBG(DBG_proc, "%s(): setting DAC %u\n", __func__,
+ static_cast<unsigned>(dev->model->adc_id));
+
+ dev->frontend = dev->frontend_initial;
+
+ // write them to analog frontend
+ dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
+ dev->interface->write_fe_register(0x03, dev->frontend.regs.get_value(0x01));
+ dev->interface->write_fe_register(0x06, dev->frontend.regs.get_value(0x02));
+ }
+
+ if (set == AFE_SET)
+ {
+ dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
+ dev->interface->write_fe_register(0x06, dev->frontend.regs.get_value(0x20));
+ dev->interface->write_fe_register(0x03, dev->frontend.regs.get_value(0x28));
+ }
+}
+
+// Set values of Analog Device type frontend
+static void gl841_set_ad_fe(Genesys_Device* dev, uint8_t set)
+{
+ DBG_HELPER(dbg);
+ int i;
+
+ if (dev->model->adc_id==AdcId::CANON_LIDE_80) {
+ gl841_set_lide80_fe(dev, set);
+ return;
+ }
+
+ if (set == AFE_INIT)
+ {
+ DBG(DBG_proc, "%s(): setting DAC %u\n", __func__,
+ static_cast<unsigned>(dev->model->adc_id));
+
+ dev->frontend = dev->frontend_initial;
+
+ // write them to analog frontend
+ dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
+
+ dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01));
+
+ for (i = 0; i < 6; i++) {
+ dev->interface->write_fe_register(0x02 + i, 0x00);
+ }
+ }
+ if (set == AFE_SET)
+ {
+ // write them to analog frontend
+ dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
+
+ dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01));
+
+ // Write fe 0x02 (red gain)
+ dev->interface->write_fe_register(0x02, dev->frontend.get_gain(0));
+
+ // Write fe 0x03 (green gain)
+ dev->interface->write_fe_register(0x03, dev->frontend.get_gain(1));
+
+ // Write fe 0x04 (blue gain)
+ dev->interface->write_fe_register(0x04, dev->frontend.get_gain(2));
+
+ // Write fe 0x05 (red offset)
+ dev->interface->write_fe_register(0x05, dev->frontend.get_offset(0));
+
+ // Write fe 0x06 (green offset)
+ dev->interface->write_fe_register(0x06, dev->frontend.get_offset(1));
+
+ // Write fe 0x07 (blue offset)
+ dev->interface->write_fe_register(0x07, dev->frontend.get_offset(2));
+ }
+}
+
+// Set values of analog frontend
+void CommandSetGl841::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const
+{
+ DBG_HELPER_ARGS(dbg, "%s", set == AFE_INIT ? "init" :
+ set == AFE_SET ? "set" :
+ set == AFE_POWER_SAVE ? "powersave" : "huh?");
+ (void) sensor;
+
+ /* Analog Device type frontend */
+ uint8_t frontend_type = dev->reg.find_reg(0x04).value & REG_0x04_FESET;
+
+ if (frontend_type == 0x02) {
+ gl841_set_ad_fe(dev, set);
+ return;
+ }
+
+ if (frontend_type != 0x00) {
+ throw SaneException("unsupported frontend type %d", frontend_type);
+ }
+
+ if (set == AFE_INIT)
+ {
+ DBG(DBG_proc, "%s(): setting DAC %u\n", __func__,
+ static_cast<unsigned>(dev->model->adc_id));
+ dev->frontend = dev->frontend_initial;
+
+ // reset only done on init
+ dev->interface->write_fe_register(0x04, 0x80);
+ DBG(DBG_proc, "%s(): frontend reset complete\n", __func__);
+ }
+
+
+ if (set == AFE_POWER_SAVE)
+ {
+ dev->interface->write_fe_register(0x01, 0x02);
+ return;
+ }
+
+ /* todo : base this test on cfg reg3 or a CCD family flag to be created */
+ /*if (dev->model->ccd_type!=SensorId::CCD_HP2300 && dev->model->ccd_type!=SensorId::CCD_HP2400) */
+ {
+ dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
+ dev->interface->write_fe_register(0x02, dev->frontend.regs.get_value(0x02));
+ }
+
+ dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01));
+ dev->interface->write_fe_register(0x03, dev->frontend.regs.get_value(0x03));
+ dev->interface->write_fe_register(0x06, dev->frontend.reg2[0]);
+ dev->interface->write_fe_register(0x08, dev->frontend.reg2[1]);
+ dev->interface->write_fe_register(0x09, dev->frontend.reg2[2]);
+
+ for (unsigned i = 0; i < 3; i++) {
+ dev->interface->write_fe_register(0x24 + i, dev->frontend.regs.get_value(0x24 + i));
+ dev->interface->write_fe_register(0x28 + i, dev->frontend.get_gain(i));
+ dev->interface->write_fe_register(0x20 + i, dev->frontend.get_offset(i));
+ }
+}
+
+enum MotorAction {
+ MOTOR_ACTION_FEED = 1,
+ MOTOR_ACTION_GO_HOME = 2,
+ MOTOR_ACTION_HOME_FREE = 3
+};
+
+// @brief turn off motor
+static void gl841_init_motor_regs_off(Genesys_Register_Set* reg, unsigned int scan_lines)
+{
+ DBG_HELPER_ARGS(dbg, "scan_lines=%d", scan_lines);
+ unsigned int feedl;
+ GenesysRegister* r;
+
+ feedl = 2;
+
+ r = sanei_genesys_get_address (reg, 0x3d);
+ r->value = (feedl >> 16) & 0xf;
+ r = sanei_genesys_get_address (reg, 0x3e);
+ r->value = (feedl >> 8) & 0xff;
+ r = sanei_genesys_get_address (reg, 0x3f);
+ r->value = feedl & 0xff;
+ r = sanei_genesys_get_address (reg, 0x5e);
+ r->value &= ~0xe0;
+
+ r = sanei_genesys_get_address (reg, 0x25);
+ r->value = (scan_lines >> 16) & 0xf;
+ r = sanei_genesys_get_address (reg, 0x26);
+ r->value = (scan_lines >> 8) & 0xff;
+ r = sanei_genesys_get_address (reg, 0x27);
+ r->value = scan_lines & 0xff;
+
+ r = sanei_genesys_get_address (reg, 0x02);
+ r->value &= ~0x01; /*LONGCURV OFF*/
+ r->value &= ~0x80; /*NOT_HOME OFF*/
+
+ r->value &= ~0x10;
+
+ r->value &= ~0x06;
+
+ r->value &= ~0x08;
+
+ r->value &= ~0x20;
+
+ r->value &= ~0x40;
+
+ r = sanei_genesys_get_address (reg, 0x67);
+ r->value = 0x3f;
+
+ r = sanei_genesys_get_address (reg, 0x68);
+ r->value = 0x3f;
+
+ r = sanei_genesys_get_address(reg, REG_STEPNO);
+ r->value = 0;
+
+ r = sanei_genesys_get_address(reg, REG_FASTNO);
+ r->value = 0;
+
+ r = sanei_genesys_get_address (reg, 0x69);
+ r->value = 0;
+
+ r = sanei_genesys_get_address (reg, 0x6a);
+ r->value = 0;
+
+ r = sanei_genesys_get_address (reg, 0x5f);
+ r->value = 0;
+}
+
+/** @brief write motor table frequency
+ * Write motor frequency data table.
+ * @param dev device to set up motor
+ * @param ydpi motor target resolution
+ */
+static void gl841_write_freq(Genesys_Device* dev, unsigned int ydpi)
+{
+ DBG_HELPER(dbg);
+/**< fast table */
+uint8_t tdefault[] = {0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0x36,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xb6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0xf6,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76,0x18,0x76};
+uint8_t t1200[] = {0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc7,0x31,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc0,0x11,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0xc7,0xb1,0x07,0xe0,0x07,0xe0,0x07,0xe0,0x07,0xe0,0x07,0xe0,0x07,0xe0,0x07,0xe0,0x07,0xe0,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc7,0xf1,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc0,0x51,0xc7,0x71,0xc7,0x71,0xc7,0x71,0xc7,0x71,0xc7,0x71,0xc7,0x71,0xc7,0x71,0xc7,0x71,0x07,0x20,0x07,0x20,0x07,0x20,0x07,0x20,0x07,0x20,0x07,0x20,0x07,0x20,0x07,0x20};
+uint8_t t300[] = {0x08,0x32,0x08,0x32,0x08,0x32,0x08,0x32,0x08,0x32,0x08,0x32,0x08,0x32,0x08,0x32,0x00,0x13,0x00,0x13,0x00,0x13,0x00,0x13,0x00,0x13,0x00,0x13,0x00,0x13,0x00,0x13,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x08,0xb2,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x0c,0xa0,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x08,0xf2,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x00,0xd3,0x08,0x72,0x08,0x72,0x08,0x72,0x08,0x72,0x08,0x72,0x08,0x72,0x08,0x72,0x08,0x72,0x0c,0x60,0x0c,0x60,0x0c,0x60,0x0c,0x60,0x0c,0x60,0x0c,0x60,0x0c,0x60,0x0c,0x60};
+uint8_t t150[] = {0x0c,0x33,0xcf,0x33,0xcf,0x33,0xcf,0x33,0xcf,0x33,0xcf,0x33,0xcf,0x33,0xcf,0x33,0x40,0x14,0x80,0x15,0x80,0x15,0x80,0x15,0x80,0x15,0x80,0x15,0x80,0x15,0x80,0x15,0x0c,0xb3,0xcf,0xb3,0xcf,0xb3,0xcf,0xb3,0xcf,0xb3,0xcf,0xb3,0xcf,0xb3,0xcf,0xb3,0x11,0xa0,0x16,0xa0,0x16,0xa0,0x16,0xa0,0x16,0xa0,0x16,0xa0,0x16,0xa0,0x16,0xa0,0x0c,0xf3,0xcf,0xf3,0xcf,0xf3,0xcf,0xf3,0xcf,0xf3,0xcf,0xf3,0xcf,0xf3,0xcf,0xf3,0x40,0xd4,0x80,0xd5,0x80,0xd5,0x80,0xd5,0x80,0xd5,0x80,0xd5,0x80,0xd5,0x80,0xd5,0x0c,0x73,0xcf,0x73,0xcf,0x73,0xcf,0x73,0xcf,0x73,0xcf,0x73,0xcf,0x73,0xcf,0x73,0x11,0x60,0x16,0x60,0x16,0x60,0x16,0x60,0x16,0x60,0x16,0x60,0x16,0x60,0x16,0x60};
+
+uint8_t *table;
+
+ if(dev->model->motor_id == MotorId::CANON_LIDE_80) {
+ switch(ydpi)
+ {
+ case 3600:
+ case 1200:
+ table=t1200;
+ break;
+ case 900:
+ case 300:
+ table=t300;
+ break;
+ case 450:
+ case 150:
+ table=t150;
+ break;
+ default:
+ table=tdefault;
+ }
+ dev->interface->write_register(0x66, 0x00);
+ dev->interface->write_gamma(0x28, 0xc000, table, 128,
+ ScannerInterface::FLAG_SWAP_REGISTERS);
+ dev->interface->write_register(0x5b, 0x00);
+ dev->interface->write_register(0x5c, 0x00);
+ }
+}
+
+
+static void gl841_init_motor_regs(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, unsigned int feed_steps,/*1/base_ydpi*/
+ /*maybe float for half/quarter step resolution?*/
+ unsigned int action, MotorFlag flags)
+{
+ DBG_HELPER_ARGS(dbg, "feed_steps=%d, action=%d, flags=%x", feed_steps, action,
+ static_cast<unsigned>(flags));
+ unsigned int fast_exposure = 0;
+ int use_fast_fed = 0;
+ unsigned int feedl;
+ GenesysRegister* r;
+/*number of scan lines to add in a scan_lines line*/
+
+ {
+ std::vector<uint16_t> table;
+ table.resize(256, 0xffff);
+
+ gl841_send_slope_table(dev, 0, table, 256);
+ gl841_send_slope_table(dev, 1, table, 256);
+ gl841_send_slope_table(dev, 2, table, 256);
+ gl841_send_slope_table(dev, 3, table, 256);
+ gl841_send_slope_table(dev, 4, table, 256);
+ }
+
+ gl841_write_freq(dev, dev->motor.base_ydpi / 4);
+
+ if (action == MOTOR_ACTION_FEED || action == MOTOR_ACTION_GO_HOME) {
+ /* FEED and GO_HOME can use fastest slopes available */
+ fast_exposure = gl841_exposure_time(dev, sensor,
+ dev->motor.base_ydpi / 4,
+ StepType::FULL,
+ 0,
+ 0);
+ DBG(DBG_info, "%s : fast_exposure=%d pixels\n", __func__, fast_exposure);
+ }
+
+ if (action == MOTOR_ACTION_HOME_FREE) {
+/* HOME_FREE must be able to stop in one step, so do not try to get faster */
+ fast_exposure = dev->motor.get_slope(StepType::FULL).max_speed_w;
+ }
+
+ auto fast_table = sanei_genesys_create_slope_table3(dev->model->asic_type, dev->motor,
+ StepType::FULL, fast_exposure,
+ dev->motor.base_ydpi / 4);
+
+ feedl = feed_steps - fast_table.steps_count * 2;
+ use_fast_fed = 1;
+
+/* all needed slopes available. we did even decide which mode to use.
+ what next?
+ - transfer slopes
+SCAN:
+flags \ use_fast_fed ! 0 1
+------------------------\--------------------
+ 0 ! 0,1,2 0,1,2,3
+MotorFlag::AUTO_GO_HOME ! 0,1,2,4 0,1,2,3,4
+OFF: none
+FEED: 3
+GO_HOME: 3
+HOME_FREE: 3
+ - setup registers
+ * slope specific registers (already done)
+ * DECSEL for HOME_FREE/GO_HOME/SCAN
+ * FEEDL
+ * MTRREV
+ * MTRPWR
+ * FASTFED
+ * STEPSEL
+ * MTRPWM
+ * FSTPSEL
+ * FASTPWM
+ * HOMENEG
+ * BWDSTEP
+ * FWDSTEP
+ * Z1
+ * Z2
+ */
+
+ r = sanei_genesys_get_address(reg, 0x3d);
+ r->value = (feedl >> 16) & 0xf;
+ r = sanei_genesys_get_address(reg, 0x3e);
+ r->value = (feedl >> 8) & 0xff;
+ r = sanei_genesys_get_address(reg, 0x3f);
+ r->value = feedl & 0xff;
+ r = sanei_genesys_get_address(reg, 0x5e);
+ r->value &= ~0xe0;
+
+ r = sanei_genesys_get_address(reg, 0x25);
+ r->value = 0;
+ r = sanei_genesys_get_address(reg, 0x26);
+ r->value = 0;
+ r = sanei_genesys_get_address(reg, 0x27);
+ r->value = 0;
+
+ r = sanei_genesys_get_address(reg, 0x02);
+ r->value &= ~0x01; /*LONGCURV OFF*/
+ r->value &= ~0x80; /*NOT_HOME OFF*/
+
+ r->value |= 0x10;
+
+ if (action == MOTOR_ACTION_GO_HOME)
+ r->value |= 0x06;
+ else
+ r->value &= ~0x06;
+
+ if (use_fast_fed)
+ r->value |= 0x08;
+ else
+ r->value &= ~0x08;
+
+ if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) {
+ r->value |= 0x20;
+ } else {
+ r->value &= ~0x20;
+ }
+
+ r->value &= ~0x40;
+
+ if (has_flag(flags, MotorFlag::REVERSE)) {
+ r->value |= REG_0x02_MTRREV;
+ }
+
+ gl841_send_slope_table(dev, 3, fast_table.table, 256);
+
+ r = sanei_genesys_get_address(reg, 0x67);
+ r->value = 0x3f;
+
+ r = sanei_genesys_get_address(reg, 0x68);
+ r->value = 0x3f;
+
+ r = sanei_genesys_get_address(reg, REG_STEPNO);
+ r->value = 0;
+
+ r = sanei_genesys_get_address(reg, REG_FASTNO);
+ r->value = 0;
+
+ r = sanei_genesys_get_address(reg, 0x69);
+ r->value = 0;
+
+ r = sanei_genesys_get_address(reg, 0x6a);
+ r->value = (fast_table.steps_count >> 1) + (fast_table.steps_count & 1);
+
+ r = sanei_genesys_get_address(reg, 0x5f);
+ r->value = (fast_table.steps_count >> 1) + (fast_table.steps_count & 1);
+}
+
+static void gl841_init_motor_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ unsigned int scan_exposure_time,/*pixel*/
+ unsigned scan_yres, // dpi, motor resolution
+ StepType scan_step_type,
+ unsigned int scan_lines,/*lines, scan resolution*/
+ unsigned int scan_dummy,
+ // number of scan lines to add in a scan_lines line
+ unsigned int feed_steps,/*1/base_ydpi*/
+ // maybe float for half/quarter step resolution?
+ MotorFlag flags)
+{
+ DBG_HELPER_ARGS(dbg, "scan_exposure_time=%d, scan_yres=%d, scan_step_type=%d, scan_lines=%d,"
+ " scan_dummy=%d, feed_steps=%d, flags=%x",
+ scan_exposure_time, scan_yres, static_cast<unsigned>(scan_step_type),
+ scan_lines, scan_dummy, feed_steps, static_cast<unsigned>(flags));
+ unsigned int fast_exposure;
+ int use_fast_fed = 0;
+ unsigned int fast_time;
+ unsigned int slow_time;
+ unsigned int feedl;
+ GenesysRegister* r;
+ unsigned int min_restep = 0x20;
+ uint32_t z1, z2;
+
+ fast_exposure = gl841_exposure_time(dev, sensor,
+ dev->motor.base_ydpi / 4,
+ StepType::FULL,
+ 0,
+ 0);
+
+ DBG(DBG_info, "%s : fast_exposure=%d pixels\n", __func__, fast_exposure);
+
+ {
+ std::vector<uint16_t> table;
+ table.resize(256, 0xffff);
+
+ gl841_send_slope_table(dev, 0, table, 256);
+ gl841_send_slope_table(dev, 1, table, 256);
+ gl841_send_slope_table(dev, 2, table, 256);
+ gl841_send_slope_table(dev, 3, table, 256);
+ gl841_send_slope_table(dev, 4, table, 256);
+ }
+
+
+ /* motor frequency table */
+ gl841_write_freq(dev, scan_yres);
+
+/*
+ we calculate both tables for SCAN. the fast slope step count depends on
+ how many steps we need for slow acceleration and how much steps we are
+ allowed to use.
+ */
+
+ auto slow_table = sanei_genesys_create_slope_table3(dev->model->asic_type, dev->motor,
+ scan_step_type, scan_exposure_time,
+ scan_yres);
+
+ auto back_table = sanei_genesys_create_slope_table3(dev->model->asic_type, dev->motor,
+ scan_step_type, 0, scan_yres);
+
+ if (feed_steps < (slow_table.steps_count >> static_cast<unsigned>(scan_step_type))) {
+ /*TODO: what should we do here?? go back to exposure calculation?*/
+ feed_steps = slow_table.steps_count >> static_cast<unsigned>(scan_step_type);
+ }
+
+ auto fast_table = sanei_genesys_create_slope_table3(dev->model->asic_type, dev->motor,
+ StepType::FULL, fast_exposure,
+ dev->motor.base_ydpi / 4);
+
+ unsigned max_fast_slope_steps_count = 1;
+ if (feed_steps > (slow_table.steps_count >> static_cast<unsigned>(scan_step_type)) + 2) {
+ max_fast_slope_steps_count = (feed_steps -
+ (slow_table.steps_count >> static_cast<unsigned>(scan_step_type))) / 2;
+ }
+
+ if (fast_table.steps_count > max_fast_slope_steps_count) {
+ fast_table.slice_steps(max_fast_slope_steps_count);
+ }
+
+ /* fast fed special cases handling */
+ if (dev->model->gpio_id == GpioId::XP300
+ || dev->model->gpio_id == GpioId::DP685)
+ {
+ /* quirk: looks like at least this scanner is unable to use
+ 2-feed mode */
+ use_fast_fed = 0;
+ }
+ else if (feed_steps < fast_table.steps_count * 2 +
+ (slow_table.steps_count >> static_cast<unsigned>(scan_step_type)))
+ {
+ use_fast_fed = 0;
+ DBG(DBG_info, "%s: feed too short, slow move forced.\n", __func__);
+ } else {
+/* for deciding whether we should use fast mode we need to check how long we
+ need for (fast)accelerating, moving, decelerating, (TODO: stopping?)
+ (slow)accelerating again versus (slow)accelerating and moving. we need
+ fast and slow tables here.
+*/
+/*NOTE: scan_exposure_time is per scan_yres*/
+/*NOTE: fast_exposure is per base_ydpi/4*/
+/*we use full steps as base unit here*/
+ fast_time =
+ fast_exposure / 4 *
+ (feed_steps - fast_table.steps_count*2 -
+ (slow_table.steps_count >> static_cast<unsigned>(scan_step_type)))
+ + fast_table.pixeltime_sum*2 + slow_table.pixeltime_sum;
+ slow_time =
+ (scan_exposure_time * scan_yres) / dev->motor.base_ydpi *
+ (feed_steps - (slow_table.steps_count >> static_cast<unsigned>(scan_step_type)))
+ + slow_table.pixeltime_sum;
+
+ DBG(DBG_info, "%s: Time for slow move: %d\n", __func__, slow_time);
+ DBG(DBG_info, "%s: Time for fast move: %d\n", __func__, fast_time);
+
+ use_fast_fed = fast_time < slow_time;
+ }
+
+ if (use_fast_fed) {
+ feedl = feed_steps - fast_table.steps_count * 2 -
+ (slow_table.steps_count >> static_cast<unsigned>(scan_step_type));
+ } else if ((feed_steps << static_cast<unsigned>(scan_step_type)) < slow_table.steps_count) {
+ feedl = 0;
+ } else {
+ feedl = (feed_steps << static_cast<unsigned>(scan_step_type)) - slow_table.steps_count;
+ }
+ DBG(DBG_info, "%s: Decided to use %s mode\n", __func__, use_fast_fed?"fast feed":"slow feed");
+
+/* all needed slopes available. we did even decide which mode to use.
+ what next?
+ - transfer slopes
+SCAN:
+flags \ use_fast_fed ! 0 1
+------------------------\--------------------
+ 0 ! 0,1,2 0,1,2,3
+MotorFlag::AUTO_GO_HOME ! 0,1,2,4 0,1,2,3,4
+OFF: none
+FEED: 3
+GO_HOME: 3
+HOME_FREE: 3
+ - setup registers
+ * slope specific registers (already done)
+ * DECSEL for HOME_FREE/GO_HOME/SCAN
+ * FEEDL
+ * MTRREV
+ * MTRPWR
+ * FASTFED
+ * STEPSEL
+ * MTRPWM
+ * FSTPSEL
+ * FASTPWM
+ * HOMENEG
+ * BWDSTEP
+ * FWDSTEP
+ * Z1
+ * Z2
+ */
+
+ r = sanei_genesys_get_address (reg, 0x3d);
+ r->value = (feedl >> 16) & 0xf;
+ r = sanei_genesys_get_address (reg, 0x3e);
+ r->value = (feedl >> 8) & 0xff;
+ r = sanei_genesys_get_address (reg, 0x3f);
+ r->value = feedl & 0xff;
+ r = sanei_genesys_get_address (reg, 0x5e);
+ r->value &= ~0xe0;
+
+ r = sanei_genesys_get_address (reg, 0x25);
+ r->value = (scan_lines >> 16) & 0xf;
+ r = sanei_genesys_get_address (reg, 0x26);
+ r->value = (scan_lines >> 8) & 0xff;
+ r = sanei_genesys_get_address (reg, 0x27);
+ r->value = scan_lines & 0xff;
+
+ r = sanei_genesys_get_address (reg, 0x02);
+ r->value &= ~0x01; /*LONGCURV OFF*/
+ r->value &= ~0x80; /*NOT_HOME OFF*/
+ r->value |= 0x10;
+
+ r->value &= ~0x06;
+
+ if (use_fast_fed)
+ r->value |= 0x08;
+ else
+ r->value &= ~0x08;
+
+ if (has_flag(flags, MotorFlag::AUTO_GO_HOME))
+ r->value |= 0x20;
+ else
+ r->value &= ~0x20;
+
+ if (has_flag(flags, MotorFlag::DISABLE_BUFFER_FULL_MOVE)) {
+ r->value |= 0x40;
+ } else {
+ r->value &= ~0x40;
+ }
+
+ gl841_send_slope_table(dev, 0, slow_table.table, 256);
+
+ gl841_send_slope_table(dev, 1, back_table.table, 256);
+
+ gl841_send_slope_table(dev, 2, slow_table.table, 256);
+
+ if (use_fast_fed) {
+ gl841_send_slope_table(dev, 3, fast_table.table, 256);
+ }
+
+ if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) {
+ gl841_send_slope_table(dev, 4, fast_table.table, 256);
+ }
+
+/* now reg 0x21 and 0x24 are available, we can calculate reg 0x22 and 0x23,
+ reg 0x60-0x62 and reg 0x63-0x65
+ rule:
+ 2*STEPNO+FWDSTEP=2*FASTNO+BWDSTEP
+*/
+/* steps of table 0*/
+ if (min_restep < slow_table.steps_count * 2 + 2) {
+ min_restep = slow_table.steps_count * 2 + 2;
+ }
+/* steps of table 1*/
+ if (min_restep < back_table.steps_count * 2 + 2) {
+ min_restep = back_table.steps_count * 2 + 2;
+ }
+/* steps of table 0*/
+ r = sanei_genesys_get_address(reg, REG_FWDSTEP);
+ r->value = min_restep - slow_table.steps_count*2;
+/* steps of table 1*/
+ r = sanei_genesys_get_address(reg, REG_BWDSTEP);
+ r->value = min_restep - back_table.steps_count*2;
+
+/*
+ for z1/z2:
+ in dokumentation mentioned variables a-d:
+ a = time needed for acceleration, table 1
+ b = time needed for reg 0x1f... wouldn't that be reg0x1f*exposure_time?
+ c = time needed for acceleration, table 1
+ d = time needed for reg 0x22... wouldn't that be reg0x22*exposure_time?
+ z1 = (c+d-1) % exposure_time
+ z2 = (a+b-1) % exposure_time
+*/
+/* i don't see any effect of this. i can only guess that this will enhance
+ sub-pixel accuracy
+ z1 = (slope_0_time-1) % exposure_time;
+ z2 = (slope_0_time-1) % exposure_time;
+*/
+ z1 = z2 = 0;
+
+ DBG(DBG_info, "%s: z1 = %d\n", __func__, z1);
+ DBG(DBG_info, "%s: z2 = %d\n", __func__, z2);
+ r = sanei_genesys_get_address (reg, 0x60);
+ r->value = ((z1 >> 16) & 0xff);
+ r = sanei_genesys_get_address (reg, 0x61);
+ r->value = ((z1 >> 8) & 0xff);
+ r = sanei_genesys_get_address (reg, 0x62);
+ r->value = (z1 & 0xff);
+ r = sanei_genesys_get_address (reg, 0x63);
+ r->value = ((z2 >> 16) & 0xff);
+ r = sanei_genesys_get_address (reg, 0x64);
+ r->value = ((z2 >> 8) & 0xff);
+ r = sanei_genesys_get_address (reg, 0x65);
+ r->value = (z2 & 0xff);
+
+ r = sanei_genesys_get_address(reg, REG_0x1E);
+ r->value &= REG_0x1E_WDTIME;
+ r->value |= scan_dummy;
+
+ r = sanei_genesys_get_address (reg, 0x67);
+ r->value = 0x3f | (static_cast<unsigned>(scan_step_type) << 6);
+
+ r = sanei_genesys_get_address (reg, 0x68);
+ r->value = 0x3f;
+
+ r = sanei_genesys_get_address(reg, REG_STEPNO);
+ r->value = (slow_table.steps_count >> 1) + (slow_table.steps_count & 1);
+
+ r = sanei_genesys_get_address(reg, REG_FASTNO);
+ r->value = (back_table.steps_count >> 1) + (back_table.steps_count & 1);
+
+ r = sanei_genesys_get_address (reg, 0x69);
+ r->value = (slow_table.steps_count >> 1) + (slow_table.steps_count & 1);
+
+ r = sanei_genesys_get_address (reg, 0x6a);
+ r->value = (fast_table.steps_count >> 1) + (fast_table.steps_count & 1);
+
+ r = sanei_genesys_get_address (reg, 0x5f);
+ r->value = (fast_table.steps_count >> 1) + (fast_table.steps_count & 1);
+}
+
+static int
+gl841_get_dpihw(Genesys_Device * dev)
+{
+ GenesysRegister* r;
+ r = sanei_genesys_get_address(&dev->reg, 0x05);
+ if ((r->value & REG_0x05_DPIHW) == REG_0x05_DPIHW_600) {
+ return 600;
+ }
+ if ((r->value & REG_0x05_DPIHW) == REG_0x05_DPIHW_1200) {
+ return 1200;
+ }
+ if ((r->value & REG_0x05_DPIHW) == REG_0x05_DPIHW_2400) {
+ return 2400;
+ }
+ return 0;
+}
+
+static void gl841_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, unsigned int exposure_time,
+ const ScanSession& session)
+{
+ DBG_HELPER_ARGS(dbg, "exposure_time=%d", exposure_time);
+ GenesysRegister* r;
+ uint16_t expavg, expr, expb, expg;
+
+ dev->cmd_set->set_fe(dev, sensor, AFE_SET);
+
+ /* gpio part.*/
+ if (dev->model->gpio_id == GpioId::CANON_LIDE_35) {
+ r = sanei_genesys_get_address(reg, REG_0x6C);
+ if (session.ccd_size_divisor > 1) {
+ r->value &= ~0x80;
+ } else {
+ r->value |= 0x80;
+ }
+ }
+ if (dev->model->gpio_id == GpioId::CANON_LIDE_80) {
+ r = sanei_genesys_get_address(reg, REG_0x6C);
+ if (session.ccd_size_divisor > 1) {
+ r->value &= ~0x40;
+ r->value |= 0x20;
+ } else {
+ r->value &= ~0x20;
+ r->value |= 0x40;
+ }
+ }
+
+ /* enable shading */
+ r = sanei_genesys_get_address (reg, 0x01);
+ r->value |= REG_0x01_SCAN;
+ if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) ||
+ (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)) {
+ r->value &= ~REG_0x01_DVDSET;
+ } else {
+ r->value |= REG_0x01_DVDSET;
+ }
+
+ /* average looks better than deletion, and we are already set up to
+ use one of the average enabled resolutions
+ */
+ r = sanei_genesys_get_address (reg, 0x03);
+ r->value |= REG_0x03_AVEENB;
+ sanei_genesys_set_lamp_power(dev, sensor, *reg,
+ !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP));
+
+ /* BW threshold */
+ r = sanei_genesys_get_address (reg, 0x2e);
+ r->value = dev->settings.threshold;
+ r = sanei_genesys_get_address (reg, 0x2f);
+ r->value = dev->settings.threshold;
+
+
+ /* monochrome / color scan */
+ r = sanei_genesys_get_address (reg, 0x04);
+ switch (session.params.depth) {
+ case 8:
+ r->value &= ~(REG_0x04_LINEART | REG_0x04_BITSET);
+ break;
+ case 16:
+ r->value &= ~REG_0x04_LINEART;
+ r->value |= REG_0x04_BITSET;
+ break;
+ }
+
+ /* AFEMOD should depend on FESET, and we should set these
+ * bits separately */
+ r->value &= ~(REG_0x04_FILTER | REG_0x04_AFEMOD);
+ if (has_flag(session.params.flags, ScanFlag::ENABLE_LEDADD)) {
+ r->value |= 0x10; /* no filter */
+ }
+ else if (session.params.channels == 1)
+ {
+ switch (session.params.color_filter)
+ {
+ case ColorFilter::RED:
+ r->value |= 0x14;
+ break;
+ case ColorFilter::GREEN:
+ r->value |= 0x18;
+ break;
+ case ColorFilter::BLUE:
+ r->value |= 0x1c;
+ break;
+ default:
+ r->value |= 0x10;
+ break;
+ }
+ }
+ else
+ {
+ if (dev->model->sensor_id == SensorId::CCD_PLUSTEK_OPTICPRO_3600) {
+ r->value |= 0x22; /* slow color pixel by pixel */
+ }
+ else
+ {
+ r->value |= 0x10; /* color pixel by pixel */
+ }
+ }
+
+ /* CIS scanners can do true gray by setting LEDADD */
+ r = sanei_genesys_get_address (reg, 0x87);
+ r->value &= ~REG_0x87_LEDADD;
+ if (has_flag(session.params.flags, ScanFlag::ENABLE_LEDADD)) {
+ r->value |= REG_0x87_LEDADD;
+ expr = reg->get16(REG_EXPR);
+ expg = reg->get16(REG_EXPG);
+ expb = reg->get16(REG_EXPB);
+
+ /* use minimal exposure for best image quality */
+ expavg = expg;
+ if (expr < expg)
+ expavg = expr;
+ if (expb < expavg)
+ expavg = expb;
+
+ dev->reg.set16(REG_EXPR, expavg);
+ dev->reg.set16(REG_EXPG, expavg);
+ dev->reg.set16(REG_EXPB, expavg);
+ }
+
+ // enable gamma tables
+ if (should_enable_gamma(session, sensor)) {
+ reg->find_reg(REG_0x05).value |= REG_0x05_GMMENB;
+ } else {
+ reg->find_reg(REG_0x05).value &= ~REG_0x05_GMMENB;
+ }
+
+ /* sensor parameters */
+ sanei_gl841_setup_sensor(dev, sensor, &dev->reg, 1, session.ccd_size_divisor);
+
+ r = sanei_genesys_get_address (reg, 0x29);
+ r->value = 255; /*<<<"magic" number, only suitable for cis*/
+
+ reg->set16(REG_DPISET, gl841_get_dpihw(dev) * session.output_resolution / session.optical_resolution);
+ reg->set16(REG_STRPIXEL, session.pixel_startx);
+ reg->set16(REG_ENDPIXEL, session.pixel_endx);
+
+ reg->set24(REG_MAXWD, session.output_line_bytes);
+
+ reg->set16(REG_LPERIOD, exposure_time);
+
+ r = sanei_genesys_get_address (reg, 0x34);
+ r->value = sensor.dummy_pixel;
+}
+
+static int
+gl841_get_led_exposure(Genesys_Device * dev, const Genesys_Sensor& sensor)
+{
+ int d,r,g,b,m;
+ if (!dev->model->is_cis)
+ return 0;
+ d = dev->reg.find_reg(0x19).value;
+
+ r = sensor.exposure.red;
+ g = sensor.exposure.green;
+ b = sensor.exposure.blue;
+
+ m = r;
+ if (m < g)
+ m = g;
+ if (m < b)
+ m = b;
+
+ return m + d;
+}
+
+/** @brief compute exposure time
+ * Compute exposure time for the device and the given scan resolution
+ */
+static int
+gl841_exposure_time(Genesys_Device *dev, const Genesys_Sensor& sensor,
+ float slope_dpi,
+ StepType scan_step_type,
+ int start,
+ int used_pixels)
+{
+int exposure_time = 0;
+int led_exposure;
+
+ led_exposure=gl841_get_led_exposure(dev, sensor);
+ exposure_time = sanei_genesys_exposure_time2(
+ dev,
+ slope_dpi,
+ scan_step_type,
+ start+used_pixels,/*+tgtime? currently done in sanei_genesys_exposure_time2 with tgtime = 32 pixel*/
+ led_exposure);
+
+ return exposure_time;
+}
+
+/**@brief compute scan_step_type
+ * Try to do at least 4 steps per line. if that is impossible we will have to
+ * live with that.
+ * @param dev device
+ * @param yres motor resolution
+ */
+static StepType gl841_scan_step_type(Genesys_Device *dev, int yres)
+{
+ StepType type = StepType::FULL;
+
+ /* TODO : check if there is a bug around the use of max_step_type */
+ /* should be <=1, need to chek all devices entry in genesys_devices */
+ if (yres * 4 < dev->motor.base_ydpi || dev->motor.max_step_type() == StepType::FULL) {
+ type = StepType::FULL;
+ } else if (yres * 4 < dev->motor.base_ydpi * 2 ||
+ dev->motor.max_step_type() <= StepType::HALF)
+ {
+ type = StepType::HALF;
+ } else {
+ type = StepType::QUARTER;
+ }
+
+ /* this motor behaves differently */
+ if (dev->model->motor_id==MotorId::CANON_LIDE_80) {
+ // driven by 'frequency' tables ?
+ type = StepType::FULL;
+ }
+
+ return type;
+}
+
+void CommandSetGl841::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const
+{
+ DBG_HELPER(dbg);
+ session.assert_computed();
+
+ int move;
+ int exposure_time;
+
+ int slope_dpi = 0;
+ int dummy = 0;
+
+/*
+results:
+
+for scanner:
+start
+end
+dpiset
+exposure_time
+dummy
+z1
+z2
+
+for ordered_read:
+ dev->words_per_line
+ dev->read_factor
+ dev->requested_buffer_size
+ dev->read_buffer_size
+ dev->read_pos
+ dev->read_bytes_in_buffer
+ dev->read_bytes_left
+ dev->max_shift
+ dev->stagger
+
+independent of our calculated values:
+ dev->total_bytes_read
+ dev->bytes_to_read
+ */
+
+/* dummy */
+ /* dummy lines: may not be usefull, for instance 250 dpi works with 0 or 1
+ dummy line. Maybe the dummy line adds correctness since the motor runs
+ slower (higher dpi)
+ */
+/* for cis this creates better aligned color lines:
+dummy \ scanned lines
+ 0: R G B R ...
+ 1: R G B - R ...
+ 2: R G B - - R ...
+ 3: R G B - - - R ...
+ 4: R G B - - - - R ...
+ 5: R G B - - - - - R ...
+ 6: R G B - - - - - - R ...
+ 7: R G B - - - - - - - R ...
+ 8: R G B - - - - - - - - R ...
+ 9: R G B - - - - - - - - - R ...
+ 10: R G B - - - - - - - - - - R ...
+ 11: R G B - - - - - - - - - - - R ...
+ 12: R G B - - - - - - - - - - - - R ...
+ 13: R G B - - - - - - - - - - - - - R ...
+ 14: R G B - - - - - - - - - - - - - - R ...
+ 15: R G B - - - - - - - - - - - - - - - R ...
+ -- pierre
+ */
+ dummy = 0;
+
+/* slope_dpi */
+/* cis color scan is effectively a gray scan with 3 gray lines per color
+ line and a FILTER of 0 */
+ if (dev->model->is_cis) {
+ slope_dpi = session.params.yres* session.params.channels;
+ } else {
+ slope_dpi = session.params.yres;
+ }
+
+ slope_dpi = slope_dpi * (1 + dummy);
+
+ StepType scan_step_type = gl841_scan_step_type(dev, session.params.yres);
+ exposure_time = gl841_exposure_time(dev, sensor,
+ slope_dpi,
+ scan_step_type,
+ session.pixel_startx,
+ session.optical_pixels);
+ DBG(DBG_info, "%s : exposure_time=%d pixels\n", __func__, exposure_time);
+
+ gl841_init_optical_regs_scan(dev, sensor, reg, exposure_time, session);
+
+ move = session.params.starty;
+ DBG(DBG_info, "%s: move=%d steps\n", __func__, move);
+
+ /* subtract current head position */
+ move -= (dev->head_pos(ScanHeadId::PRIMARY) * session.params.yres) / dev->motor.base_ydpi;
+ DBG(DBG_info, "%s: move=%d steps\n", __func__, move);
+
+ if (move < 0)
+ move = 0;
+
+ /* round it */
+/* the move is not affected by dummy -- pierre */
+/* move = ((move + dummy) / (dummy + 1)) * (dummy + 1);
+ DBG(DBG_info, "%s: move=%d steps\n", __func__, move);*/
+
+ if (has_flag(session.params.flags, ScanFlag::SINGLE_LINE)) {
+ gl841_init_motor_regs_off(reg, dev->model->is_cis ? session.output_line_count * session.params.channels
+ : session.output_line_count);
+ } else {
+ auto motor_flag = has_flag(session.params.flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE) ?
+ MotorFlag::DISABLE_BUFFER_FULL_MOVE : MotorFlag::NONE;
+
+ gl841_init_motor_regs_scan(dev, sensor, reg, exposure_time, slope_dpi, scan_step_type,
+ dev->model->is_cis ? session.output_line_count * session.params.channels
+ : session.output_line_count,
+ dummy, move, motor_flag);
+ }
+
+ dev->read_buffer.clear();
+ dev->read_buffer.alloc(session.buffer_size_read);
+
+ build_image_pipeline(dev, session);
+
+ dev->read_active = true;
+
+ dev->session = session;
+
+ dev->total_bytes_read = 0;
+ dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines;
+
+ DBG(DBG_info, "%s: total bytes to send = %zu\n", __func__, dev->total_bytes_to_read);
+}
+
+ScanSession CommandSetGl841::calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const
+{
+ int start;
+
+ DBG(DBG_info, "%s ", __func__);
+ debug_dump(DBG_info, settings);
+
+/* start */
+ start = static_cast<int>(dev->model->x_offset);
+ start += static_cast<int>(settings.tl_x);
+
+ start = static_cast<int>((start * sensor.optical_res) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = settings.xres;
+ session.params.yres = settings.yres;
+ session.params.startx = start;
+ session.params.starty = 0; // not used
+ session.params.pixels = settings.pixels;
+ session.params.requested_pixels = settings.requested_pixels;
+ session.params.lines = settings.lines;
+ session.params.depth = settings.depth;
+ session.params.channels = settings.get_channels();
+ session.params.scan_method = settings.scan_method;
+ session.params.scan_mode = settings.scan_mode;
+ session.params.color_filter = settings.color_filter;
+ session.params.flags = ScanFlag::NONE;
+
+ compute_session(dev, session, sensor);
+
+ return session;
+}
+
+// for fast power saving methods only, like disabling certain amplifiers
+void CommandSetGl841::save_power(Genesys_Device* dev, bool enable) const
+{
+ DBG_HELPER_ARGS(dbg, "enable = %d", enable);
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+
+ if (enable)
+ {
+ if (dev->model->gpio_id == GpioId::CANON_LIDE_35)
+ {
+/* expect GPIO17 to be enabled, and GPIO9 to be disabled,
+ while GPIO8 is disabled*/
+/* final state: GPIO8 disabled, GPIO9 enabled, GPIO17 disabled,
+ GPIO18 disabled*/
+
+ uint8_t val = dev->interface->read_register(REG_0x6D);
+ dev->interface->write_register(REG_0x6D, val | 0x80);
+
+ dev->interface->sleep_ms(1);
+
+ /*enable GPIO9*/
+ val = dev->interface->read_register(REG_0x6C);
+ dev->interface->write_register(REG_0x6C, val | 0x01);
+
+ /*disable GPO17*/
+ val = dev->interface->read_register(REG_0x6B);
+ dev->interface->write_register(REG_0x6B, val & ~REG_0x6B_GPO17);
+
+ /*disable GPO18*/
+ val = dev->interface->read_register(REG_0x6B);
+ dev->interface->write_register(REG_0x6B, val & ~REG_0x6B_GPO18);
+
+ dev->interface->sleep_ms(1);
+
+ val = dev->interface->read_register(REG_0x6D);
+ dev->interface->write_register(REG_0x6D, val & ~0x80);
+
+ }
+ if (dev->model->gpio_id == GpioId::DP685)
+ {
+ uint8_t val = dev->interface->read_register(REG_0x6B);
+ dev->interface->write_register(REG_0x6B, val & ~REG_0x6B_GPO17);
+ dev->reg.find_reg(0x6b).value &= ~REG_0x6B_GPO17;
+ dev->calib_reg.find_reg(0x6b).value &= ~REG_0x6B_GPO17;
+ }
+
+ set_fe(dev, sensor, AFE_POWER_SAVE);
+
+ }
+ else
+ {
+ if (dev->model->gpio_id == GpioId::CANON_LIDE_35)
+ {
+/* expect GPIO17 to be enabled, and GPIO9 to be disabled,
+ while GPIO8 is disabled*/
+/* final state: GPIO8 enabled, GPIO9 disabled, GPIO17 enabled,
+ GPIO18 enabled*/
+
+ uint8_t val = dev->interface->read_register(REG_0x6D);
+ dev->interface->write_register(REG_0x6D, val | 0x80);
+
+ dev->interface->sleep_ms(10);
+
+ /*disable GPIO9*/
+ val = dev->interface->read_register(REG_0x6C);
+ dev->interface->write_register(REG_0x6C, val & ~0x01);
+
+ /*enable GPIO10*/
+ val = dev->interface->read_register(REG_0x6C);
+ dev->interface->write_register(REG_0x6C, val | 0x02);
+
+ /*enable GPO17*/
+ val = dev->interface->read_register(REG_0x6B);
+ dev->interface->write_register(REG_0x6B, val | REG_0x6B_GPO17);
+ dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17;
+ dev->calib_reg.find_reg(0x6b).value |= REG_0x6B_GPO17;
+
+ /*enable GPO18*/
+ val = dev->interface->read_register(REG_0x6B);
+ dev->interface->write_register(REG_0x6B, val | REG_0x6B_GPO18);
+ dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO18;
+ dev->calib_reg.find_reg(0x6b).value |= REG_0x6B_GPO18;
+
+ }
+ if (dev->model->gpio_id == GpioId::DP665
+ || dev->model->gpio_id == GpioId::DP685)
+ {
+ uint8_t val = dev->interface->read_register(REG_0x6B);
+ dev->interface->write_register(REG_0x6B, val | REG_0x6B_GPO17);
+ dev->reg.find_reg(0x6b).value |= REG_0x6B_GPO17;
+ dev->calib_reg.find_reg(0x6b).value |= REG_0x6B_GPO17;
+ }
+
+ }
+}
+
+void CommandSetGl841::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const
+{
+ DBG_HELPER_ARGS(dbg, "delay = %d", delay);
+ // FIXME: SEQUENTIAL not really needed in this case
+ Genesys_Register_Set local_reg(Genesys_Register_Set::SEQUENTIAL);
+ int rate, exposure_time, tgtime, time;
+
+ local_reg.init_reg(0x01, dev->reg.get8(0x01)); /* disable fastmode */
+ local_reg.init_reg(0x03, dev->reg.get8(0x03)); /* Lamp power control */
+ local_reg.init_reg(0x05, dev->reg.get8(0x05)); /*& ~REG_0x05_BASESEL*/; /* 24 clocks/pixel */
+ local_reg.init_reg(0x18, 0x00); // Set CCD type
+ local_reg.init_reg(0x38, 0x00);
+ local_reg.init_reg(0x39, 0x00);
+
+ // period times for LPeriod, expR,expG,expB, Z1MODE, Z2MODE
+ local_reg.init_reg(0x1c, dev->reg.get8(0x05) & ~REG_0x1C_TGTIME);
+
+ if (!delay) {
+ local_reg.find_reg(0x03).value = local_reg.find_reg(0x03).value & 0xf0; /* disable lampdog and set lamptime = 0 */
+ } else if (delay < 20) {
+ local_reg.find_reg(0x03).value = (local_reg.find_reg(0x03).value & 0xf0) | 0x09; /* enable lampdog and set lamptime = 1 */
+ } else {
+ local_reg.find_reg(0x03).value = (local_reg.find_reg(0x03).value & 0xf0) | 0x0f; /* enable lampdog and set lamptime = 7 */
+ }
+
+ time = delay * 1000 * 60; /* -> msec */
+ exposure_time = static_cast<std::uint32_t>(time * 32000.0 /
+ (24.0 * 64.0 * (local_reg.find_reg(0x03).value & REG_0x03_LAMPTIM) *
+ 1024.0) + 0.5);
+ /* 32000 = system clock, 24 = clocks per pixel */
+ rate = (exposure_time + 65536) / 65536;
+ if (rate > 4)
+ {
+ rate = 8;
+ tgtime = 3;
+ }
+ else if (rate > 2)
+ {
+ rate = 4;
+ tgtime = 2;
+ }
+ else if (rate > 1)
+ {
+ rate = 2;
+ tgtime = 1;
+ }
+ else
+ {
+ rate = 1;
+ tgtime = 0;
+ }
+
+ local_reg.find_reg(0x1c).value |= tgtime;
+ exposure_time /= rate;
+
+ if (exposure_time > 65535)
+ exposure_time = 65535;
+
+ local_reg.set8(0x38, exposure_time >> 8);
+ local_reg.set8(0x39, exposure_time & 255); /* lowbyte */
+
+ dev->interface->write_registers(local_reg);
+}
+
+static void gl841_stop_action(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ Genesys_Register_Set local_reg;
+ unsigned int loop;
+
+ scanner_read_print_status(*dev);
+
+ if (scanner_is_motor_stopped(*dev)) {
+ DBG(DBG_info, "%s: already stopped\n", __func__);
+ return;
+ }
+
+ local_reg = dev->reg;
+
+ regs_set_optical_off(dev->model->asic_type, local_reg);
+
+ gl841_init_motor_regs_off(&local_reg,0);
+ dev->interface->write_registers(local_reg);
+
+ if (is_testing_mode()) {
+ return;
+ }
+
+ /* looks like writing the right registers to zero is enough to get the chip
+ out of scan mode into command mode, actually triggering(writing to
+ register 0x0f) seems to be unnecessary */
+
+ loop = 10;
+ while (loop > 0) {
+ if (scanner_is_motor_stopped(*dev)) {
+ return;
+ }
+
+ dev->interface->sleep_ms(100);
+ loop--;
+ }
+
+ throw SaneException(SANE_STATUS_IO_ERROR, "could not stop motor");
+}
+
+static bool gl841_get_paper_sensor(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+
+ uint8_t val = dev->interface->read_register(REG_0x6D);
+
+ return (val & 0x1) == 0;
+}
+
+void CommandSetGl841::eject_document(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ Genesys_Register_Set local_reg;
+ unsigned int init_steps;
+ float feed_mm;
+ int loop;
+
+ if (!dev->model->is_sheetfed) {
+ DBG(DBG_proc, "%s: there is no \"eject sheet\"-concept for non sheet fed\n", __func__);
+ DBG(DBG_proc, "%s: finished\n", __func__);
+ return;
+ }
+
+
+ local_reg.clear();
+
+ // FIXME: unused result
+ scanner_read_status(*dev);
+
+ gl841_stop_action(dev);
+
+ local_reg = dev->reg;
+
+ regs_set_optical_off(dev->model->asic_type, local_reg);
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+ gl841_init_motor_regs(dev, sensor, &local_reg, 65536, MOTOR_ACTION_FEED, MotorFlag::NONE);
+
+ dev->interface->write_registers(local_reg);
+
+ try {
+ scanner_start_action(*dev, true);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&]() { gl841_stop_action(dev); });
+ // restore original registers
+ catch_all_exceptions(__func__, [&]()
+ {
+ dev->interface->write_registers(dev->reg);
+ });
+ throw;
+ }
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("eject_document");
+ gl841_stop_action(dev);
+ return;
+ }
+
+ if (gl841_get_paper_sensor(dev)) {
+ DBG(DBG_info, "%s: paper still loaded\n", __func__);
+ /* force document TRUE, because it is definitely present */
+ dev->document = true;
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+
+ loop = 300;
+ while (loop > 0) /* do not wait longer then 30 seconds */
+ {
+
+ if (!gl841_get_paper_sensor(dev)) {
+ DBG(DBG_info, "%s: reached home position\n", __func__);
+ DBG(DBG_proc, "%s: finished\n", __func__);
+ break;
+ }
+ dev->interface->sleep_ms(100);
+ --loop;
+ }
+
+ if (loop == 0)
+ {
+ // when we come here then the scanner needed too much time for this, so we better stop
+ // the motor
+ catch_all_exceptions(__func__, [&](){ gl841_stop_action(dev); });
+ throw SaneException(SANE_STATUS_IO_ERROR,
+ "timeout while waiting for scanhead to go home");
+ }
+ }
+
+ feed_mm = static_cast<float>(dev->model->eject_feed);
+ if (dev->document)
+ {
+ feed_mm += static_cast<float>(dev->model->post_scan);
+ }
+
+ sanei_genesys_read_feed_steps(dev, &init_steps);
+
+ /* now feed for extra <number> steps */
+ loop = 0;
+ while (loop < 300) /* do not wait longer then 30 seconds */
+ {
+ unsigned int steps;
+
+ sanei_genesys_read_feed_steps(dev, &steps);
+
+ DBG(DBG_info, "%s: init_steps: %d, steps: %d\n", __func__, init_steps, steps);
+
+ if (steps > init_steps + (feed_mm * dev->motor.base_ydpi) / MM_PER_INCH)
+ {
+ break;
+ }
+
+ dev->interface->sleep_ms(100);
+ ++loop;
+ }
+
+ gl841_stop_action(dev);
+
+ dev->document = false;
+}
+
+
+void CommandSetGl841::load_document(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ int loop = 300;
+ while (loop > 0) /* do not wait longer then 30 seconds */
+ {
+ if (gl841_get_paper_sensor(dev)) {
+ DBG(DBG_info, "%s: document inserted\n", __func__);
+
+ /* when loading OK, document is here */
+ dev->document = true;
+
+ // give user some time to place document correctly
+ dev->interface->sleep_ms(1000);
+ break;
+ }
+ dev->interface->sleep_ms(100);
+ --loop;
+ }
+
+ if (loop == 0)
+ {
+ // when we come here then the user needed to much time for this
+ throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for document");
+ }
+}
+
+/**
+ * detects end of document and adjust current scan
+ * to take it into account
+ * used by sheetfed scanners
+ */
+void CommandSetGl841::detect_document_end(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ bool paper_loaded = gl841_get_paper_sensor(dev);
+
+ /* sheetfed scanner uses home sensor as paper present */
+ if (dev->document && !paper_loaded) {
+ DBG(DBG_info, "%s: no more document\n", __func__);
+ dev->document = false;
+
+ /* we can't rely on total_bytes_to_read since the frontend
+ * might have been slow to read data, so we re-evaluate the
+ * amount of data to scan form the hardware settings
+ */
+ unsigned scanned_lines = 0;
+ try {
+ sanei_genesys_read_scancnt(dev, &scanned_lines);
+ } catch (...) {
+ dev->total_bytes_to_read = dev->total_bytes_read;
+ throw;
+ }
+
+ if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS && dev->model->is_cis) {
+ scanned_lines /= 3;
+ }
+
+ std::size_t output_lines = dev->session.output_line_count;
+
+ std::size_t offset_lines = static_cast<std::size_t>(
+ (dev->model->post_scan / MM_PER_INCH) * dev->settings.yres);
+
+ std::size_t scan_end_lines = scanned_lines + offset_lines;
+
+ std::size_t remaining_lines = dev->get_pipeline_source().remaining_bytes() /
+ dev->session.output_line_bytes_raw;
+
+ DBG(DBG_io, "%s: scanned_lines=%u\n", __func__, scanned_lines);
+ DBG(DBG_io, "%s: scan_end_lines=%zu\n", __func__, scan_end_lines);
+ DBG(DBG_io, "%s: output_lines=%zu\n", __func__, output_lines);
+ DBG(DBG_io, "%s: remaining_lines=%zu\n", __func__, remaining_lines);
+
+ if (scan_end_lines > output_lines) {
+ auto skip_lines = scan_end_lines - output_lines;
+
+ if (remaining_lines > skip_lines) {
+ DBG(DBG_io, "%s: skip_lines=%zu\n", __func__, skip_lines);
+
+ remaining_lines -= skip_lines;
+ dev->get_pipeline_source().set_remaining_bytes(remaining_lines *
+ dev->session.output_line_bytes_raw);
+ dev->total_bytes_to_read -= skip_lines * dev->session.output_line_bytes_requested;
+ }
+ }
+ }
+}
+
+// Send the low-level scan command
+// todo : is this that useful ?
+void CommandSetGl841::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, bool start_motor) const
+{
+ DBG_HELPER(dbg);
+ (void) sensor;
+ // FIXME: SEQUENTIAL not really needed in this case
+ Genesys_Register_Set local_reg(Genesys_Register_Set::SEQUENTIAL);
+ uint8_t val;
+
+ if (dev->model->gpio_id == GpioId::CANON_LIDE_80) {
+ val = dev->interface->read_register(REG_0x6B);
+ val = REG_0x6B_GPO18;
+ dev->interface->write_register(REG_0x6B, val);
+ }
+
+ if (dev->model->sensor_id != SensorId::CCD_PLUSTEK_OPTICPRO_3600) {
+ local_reg.init_reg(0x03, reg->get8(0x03) | REG_0x03_LAMPPWR);
+ } else {
+ // TODO PLUSTEK_3600: why ??
+ local_reg.init_reg(0x03, reg->get8(0x03));
+ }
+
+ local_reg.init_reg(0x01, reg->get8(0x01) | REG_0x01_SCAN);
+ local_reg.init_reg(0x0d, 0x01);
+
+ // scanner_start_action(dev, start_motor)
+ if (start_motor) {
+ local_reg.init_reg(0x0f, 0x01);
+ } else {
+ // do not start motor yet
+ local_reg.init_reg(0x0f, 0x00);
+ }
+
+ dev->interface->write_registers(local_reg);
+
+ dev->advance_head_pos_by_session(ScanHeadId::PRIMARY);
+}
+
+
+// Send the stop scan command
+void CommandSetGl841::end_scan(Genesys_Device* dev, Genesys_Register_Set __sane_unused__* reg,
+ bool check_stop) const
+{
+ DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop);
+
+ if (!dev->model->is_sheetfed) {
+ gl841_stop_action(dev);
+ }
+}
+
+// Moves the slider to steps
+static void gl841_feed(Genesys_Device* dev, int steps)
+{
+ DBG_HELPER_ARGS(dbg, "steps = %d", steps);
+ Genesys_Register_Set local_reg;
+ int loop;
+
+ gl841_stop_action(dev);
+
+ // FIXME: we should pick sensor according to the resolution scanner is currently operating on
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+
+ local_reg = dev->reg;
+
+ regs_set_optical_off(dev->model->asic_type, local_reg);
+
+ gl841_init_motor_regs(dev, sensor, &local_reg, steps, MOTOR_ACTION_FEED, MotorFlag::NONE);
+
+ dev->interface->write_registers(local_reg);
+
+ try {
+ scanner_start_action(*dev, true);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&]() { gl841_stop_action (dev); });
+ // restore original registers
+ catch_all_exceptions(__func__, [&]()
+ {
+ dev->interface->write_registers(dev->reg);
+ });
+ throw;
+ }
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("feed");
+ dev->advance_head_pos_by_steps(ScanHeadId::PRIMARY, Direction::FORWARD, steps);
+ gl841_stop_action(dev);
+ return;
+ }
+
+ loop = 0;
+ while (loop < 300) /* do not wait longer then 30 seconds */
+ {
+ auto status = scanner_read_status(*dev);
+
+ if (!status.is_motor_enabled) {
+ DBG(DBG_proc, "%s: finished\n", __func__);
+ dev->advance_head_pos_by_steps(ScanHeadId::PRIMARY, Direction::FORWARD, steps);
+ return;
+ }
+ dev->interface->sleep_ms(100);
+ ++loop;
+ }
+
+ /* when we come here then the scanner needed too much time for this, so we better stop the motor */
+ gl841_stop_action (dev);
+
+ dev->set_head_pos_unknown();
+
+ throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for scanhead to go home");
+}
+
+// Moves the slider to the home (top) position slowly
+void CommandSetGl841::move_back_home(Genesys_Device* dev, bool wait_until_home) const
+{
+ DBG_HELPER_ARGS(dbg, "wait_until_home = %d", wait_until_home);
+ Genesys_Register_Set local_reg;
+ int loop = 0;
+
+ if (dev->model->is_sheetfed) {
+ DBG(DBG_proc, "%s: there is no \"home\"-concept for sheet fed\n", __func__);
+ DBG(DBG_proc, "%s: finished\n", __func__);
+ return;
+ }
+
+ // reset gpio pin
+ uint8_t val;
+ if (dev->model->gpio_id == GpioId::CANON_LIDE_35) {
+ val = dev->interface->read_register(REG_0x6C);
+ val = dev->gpo.regs.get_value(0x6c);
+ dev->interface->write_register(REG_0x6C, val);
+ }
+ if (dev->model->gpio_id == GpioId::CANON_LIDE_80) {
+ val = dev->interface->read_register(REG_0x6B);
+ val = REG_0x6B_GPO18 | REG_0x6B_GPO17;
+ dev->interface->write_register(REG_0x6B, val);
+ }
+ dev->cmd_set->save_power(dev, false);
+
+ // first read gives HOME_SENSOR true
+ auto status = scanner_read_reliable_status(*dev);
+
+
+ if (status.is_at_home) {
+ DBG(DBG_info, "%s: already at home, completed\n", __func__);
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+ return;
+ }
+
+ scanner_stop_action_no_move(*dev, dev->reg);
+
+ /* if motor is on, stop current action */
+ if (status.is_motor_enabled) {
+ gl841_stop_action(dev);
+ }
+
+ local_reg = dev->reg;
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+
+ gl841_init_motor_regs(dev, sensor, &local_reg, 65536, MOTOR_ACTION_GO_HOME, MotorFlag::REVERSE);
+
+ // set up for no scan
+ regs_set_optical_off(dev->model->asic_type, local_reg);
+
+ dev->interface->write_registers(local_reg);
+
+ try {
+ scanner_start_action(*dev, true);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&]() { gl841_stop_action(dev); });
+ // restore original registers
+ catch_all_exceptions(__func__, [&]()
+ {
+ dev->interface->write_registers(dev->reg);
+ });
+ throw;
+ }
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("move_back_home");
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+ return;
+ }
+
+ if (wait_until_home)
+ {
+ while (loop < 300) /* do not wait longer then 30 seconds */
+ {
+ auto status = scanner_read_status(*dev);
+ if (status.is_at_home) {
+ DBG(DBG_info, "%s: reached home position\n", __func__);
+ DBG(DBG_proc, "%s: finished\n", __func__);
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+ return;
+ }
+ dev->interface->sleep_ms(100);
+ ++loop;
+ }
+
+ // when we come here then the scanner needed too much time for this, so we better stop
+ // the motor
+ catch_all_exceptions(__func__, [&](){ gl841_stop_action(dev); });
+ dev->set_head_pos_unknown();
+ throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for scanhead to go home");
+ }
+
+ DBG(DBG_info, "%s: scanhead is still moving\n", __func__);
+}
+
+// Automatically set top-left edge of the scan area by scanning a 200x200 pixels area at 600 dpi
+// from very top of scanner
+void CommandSetGl841::search_start_position(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ int size;
+ Genesys_Register_Set local_reg;
+
+ int pixels = 600;
+ int dpi = 300;
+
+ local_reg = dev->reg;
+
+ /* sets for a 200 lines * 600 pixels */
+ /* normal scan with no shading */
+
+ // FIXME: the current approach of doing search only for one resolution does not work on scanners
+ // whith employ different sensors with potentially different settings.
+ const auto& sensor = sanei_genesys_find_sensor(dev, dpi, 1, dev->model->default_method);
+
+ ScanSession session;
+ session.params.xres = dpi;
+ session.params.yres = dpi;
+ session.params.startx = 0;
+ session.params.starty = 0; /*we should give a small offset here~60 steps*/
+ session.params.pixels = 600;
+ session.params.lines = dev->model->search_lines;
+ session.params.depth = 8;
+ session.params.channels = 1;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::GRAY;
+ session.params.color_filter = ColorFilter::GREEN;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::IGNORE_LINE_DISTANCE |
+ ScanFlag::DISABLE_BUFFER_FULL_MOVE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &local_reg, session);
+
+ // send to scanner
+ dev->interface->write_registers(local_reg);
+
+ size = pixels * dev->model->search_lines;
+
+ std::vector<uint8_t> data(size);
+
+ dev->cmd_set->begin_scan(dev, sensor, &local_reg, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("search_start_position");
+ dev->cmd_set->end_scan(dev, &local_reg, true);
+ dev->reg = local_reg;
+ return;
+ }
+
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ sanei_genesys_read_data_from_scanner(dev, data.data(), size);
+
+ if (DBG_LEVEL >= DBG_data) {
+ sanei_genesys_write_pnm_file("gl841_search_position.pnm", data.data(), 8, 1, pixels,
+ dev->model->search_lines);
+ }
+
+ dev->cmd_set->end_scan(dev, &local_reg, true);
+
+ /* update regs to copy ASIC internal state */
+ dev->reg = local_reg;
+
+ for (auto& sensor_update :
+ sanei_genesys_find_sensors_all_for_write(dev, dev->model->default_method))
+ {
+ sanei_genesys_search_reference_point(dev, sensor_update, data.data(), 0, dpi, pixels,
+ dev->model->search_lines);
+ }
+}
+
+// sets up register for coarse gain calibration
+// todo: check it for scanners using it
+void CommandSetGl841::init_regs_for_coarse_calibration(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+
+ ScanSession session;
+ session.params.xres = dev->settings.xres;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = sensor.optical_res / sensor.ccd_pixels_per_system_pixel();
+ session.params.lines = 20;
+ session.params.depth = 16;
+ session.params.channels = dev->settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = dev->settings.scan_mode;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &regs, session);
+
+ DBG(DBG_info, "%s: optical sensor res: %d dpi, actual res: %d\n", __func__,
+ sensor.optical_res / sensor.ccd_pixels_per_system_pixel(), dev->settings.xres);
+
+ dev->interface->write_registers(regs);
+
+/* if (DBG_LEVEL >= DBG_info)
+ sanei_gl841_print_registers (regs);*/
+}
+
+
+// init registers for shading calibration
+void CommandSetGl841::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER_ARGS(dbg, "lines = %zu", dev->calib_lines);
+ SANE_Int ydpi;
+ unsigned starty = 0;
+
+ /* initial calibration reg values */
+ regs = dev->reg;
+
+ ydpi = dev->motor.base_ydpi;
+ if (dev->model->motor_id == MotorId::PLUSTEK_OPTICPRO_3600) /* TODO PLUSTEK_3600: 1200dpi not yet working, produces dark bar */
+ {
+ ydpi = 600;
+ }
+ if (dev->model->motor_id == MotorId::CANON_LIDE_80) {
+ ydpi = gl841_get_dpihw(dev);
+ /* get over extra dark area for this model.
+ It looks like different devices have dark areas of different width
+ due to manufacturing variability. The initial value of starty was 140,
+ but it moves the sensor almost past the dark area completely in places
+ on certain devices.
+
+ On a particular device the black area starts at roughly position
+ 160 to 230 depending on location (the dark area is not completely
+ parallel to the frame).
+ */
+ starty = 70;
+ }
+
+ dev->calib_channels = 3;
+ dev->calib_lines = dev->model->shading_lines;
+
+ unsigned resolution = sensor.get_logical_hwdpi(dev->settings.xres);
+ unsigned factor = sensor.optical_res / resolution;
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, dev->calib_channels,
+ dev->settings.scan_method);
+
+ dev->calib_pixels = calib_sensor.sensor_pixels / factor;
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = ydpi;
+ session.params.startx = 0;
+ session.params.starty = starty;
+ session.params.pixels = dev->calib_pixels;
+ session.params.lines = dev->calib_lines;
+ session.params.depth = 16;
+ session.params.channels = dev->calib_channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ /*ScanFlag::DISABLE_BUFFER_FULL_MOVE |*/
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ dev->interface->write_registers(regs);
+}
+
+// set up registers for the actual scan
+void CommandSetGl841::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ DBG_HELPER(dbg);
+ float move;
+ int move_dpi;
+ float start;
+
+ debug_dump(DBG_info, dev->settings);
+
+ /* steps to move to reach scanning area:
+ - first we move to physical start of scanning
+ either by a fixed steps amount from the black strip
+ or by a fixed amount from parking position,
+ minus the steps done during shading calibration
+ - then we move by the needed offset whitin physical
+ scanning area
+
+ assumption: steps are expressed at maximum motor resolution
+
+ we need:
+ float y_offset;
+ float y_size;
+ float y_offset_calib;
+ mm_to_steps()=motor dpi / 2.54 / 10=motor dpi / MM_PER_INCH */
+
+ /* if scanner uses GENESYS_FLAG_SEARCH_START y_offset is
+ relative from origin, else, it is from parking position */
+
+ move_dpi = dev->motor.base_ydpi;
+
+ move = 0;
+ if (dev->model->flags & GENESYS_FLAG_SEARCH_START) {
+ move += static_cast<float>(dev->model->y_offset_calib_white);
+ }
+
+ DBG(DBG_info, "%s move=%f steps\n", __func__, move);
+
+ move += static_cast<float>(dev->model->y_offset);
+ DBG(DBG_info, "%s: move=%f steps\n", __func__, move);
+
+ move += static_cast<float>(dev->settings.tl_y);
+ DBG(DBG_info, "%s: move=%f steps\n", __func__, move);
+
+ move = static_cast<float>((move * move_dpi) / MM_PER_INCH);
+
+/* start */
+ start = static_cast<float>(dev->model->x_offset);
+
+ start += static_cast<float>(dev->settings.tl_x);
+
+ start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH);
+
+ /* we enable true gray for cis scanners only, and just when doing
+ * scan since color calibration is OK for this mode
+ */
+ ScanFlag flags = ScanFlag::NONE;
+
+ /* true gray (led add for cis scanners) */
+ if(dev->model->is_cis && dev->settings.true_gray
+ && dev->settings.scan_mode != ScanColorMode::COLOR_SINGLE_PASS
+ && dev->model->sensor_id != SensorId::CIS_CANON_LIDE_80)
+ {
+ // on Lide 80 the LEDADD bit results in only red LED array being lit
+ DBG(DBG_io, "%s: activating LEDADD\n", __func__);
+ flags |= ScanFlag::ENABLE_LEDADD;
+ }
+
+ ScanSession session;
+ session.params.xres = dev->settings.xres;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = static_cast<unsigned>(start);
+ session.params.starty = static_cast<unsigned>(move);
+ session.params.pixels = dev->settings.pixels;
+ session.params.requested_pixels = dev->settings.requested_pixels;
+ session.params.lines = dev->settings.lines;
+ session.params.depth = dev->settings.depth;
+ session.params.channels = dev->settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = dev->settings.scan_mode;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = flags;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &dev->reg, session);
+}
+
+
+// this function sends generic gamma table (ie linear ones) or the Sensor specific one if provided
+void CommandSetGl841::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ DBG_HELPER(dbg);
+ int size;
+
+ size = 256;
+
+ /* allocate temporary gamma tables: 16 bits words, 3 channels */
+ std::vector<uint8_t> gamma(size * 2 * 3);
+
+ sanei_genesys_generate_gamma_buffer(dev, sensor, 16, 65535, size, gamma.data());
+
+ dev->interface->write_gamma(0x28, 0x0000, gamma.data(), size * 2 * 3);
+}
+
+
+/* this function does the led calibration by scanning one line of the calibration
+ area below scanner's top on white strip.
+
+-needs working coarse/gain
+*/
+SensorExposure CommandSetGl841::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ int num_pixels;
+ int total_size;
+ int i, j;
+ int val;
+ int channels;
+ int avg[3], avga, avge;
+ int turn;
+ uint16_t exp[3], target;
+ int move;
+
+ /* these 2 boundaries should be per sensor */
+ uint16_t min_exposure=500;
+ uint16_t max_exposure;
+
+ /* feed to white strip if needed */
+ if (dev->model->y_offset_calib_white > 0) {
+ move = static_cast<int>(dev->model->y_offset_calib_white);
+ move = static_cast<int>((move * (dev->motor.base_ydpi)) / MM_PER_INCH);
+ DBG(DBG_io, "%s: move=%d lines\n", __func__, move);
+ gl841_feed(dev, move);
+ }
+
+ /* offset calibration is always done in color mode */
+ channels = 3;
+
+ unsigned resolution = sensor.get_logical_hwdpi(dev->settings.xres);
+ unsigned factor = sensor.optical_res / resolution;
+
+ const auto& calib_sensor_base = sanei_genesys_find_sensor(dev, resolution, channels,
+ dev->settings.scan_method);
+
+ num_pixels = calib_sensor_base.sensor_pixels / factor;
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = num_pixels;
+ session.params.lines = 1;
+ session.params.depth = 16;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor_base);
+
+ init_regs_for_scan_session(dev, calib_sensor_base, &regs, session);
+
+ dev->interface->write_registers(regs);
+
+
+ total_size = num_pixels * channels * 2 * 1; /* colors * bytes_per_color * scan lines */
+
+ std::vector<uint8_t> line(total_size);
+
+/*
+ we try to get equal bright leds here:
+
+ loop:
+ average per color
+ adjust exposure times
+ */
+
+ exp[0] = sensor.exposure.red;
+ exp[1] = sensor.exposure.green;
+ exp[2] = sensor.exposure.blue;
+
+ turn = 0;
+ /* max exposure is set to ~2 time initial average
+ * exposure, or 2 time last calibration exposure */
+ max_exposure=((exp[0]+exp[1]+exp[2])/3)*2;
+ target=sensor.gain_white_ref*256;
+
+ auto calib_sensor = calib_sensor_base;
+
+ bool acceptable = false;
+ do {
+ calib_sensor.exposure.red = exp[0];
+ calib_sensor.exposure.green = exp[1];
+ calib_sensor.exposure.blue = exp[2];
+
+ regs_set_exposure(dev->model->asic_type, regs, calib_sensor.exposure);
+ dev->interface->write_register(0x10, (calib_sensor.exposure.red >> 8) & 0xff);
+ dev->interface->write_register(0x11, calib_sensor.exposure.red & 0xff);
+ dev->interface->write_register(0x12, (calib_sensor.exposure.green >> 8) & 0xff);
+ dev->interface->write_register(0x13, calib_sensor.exposure.green & 0xff);
+ dev->interface->write_register(0x14, (calib_sensor.exposure.blue >> 8) & 0xff);
+ dev->interface->write_register(0x15, calib_sensor.exposure.blue & 0xff);
+
+ dev->interface->write_registers(regs);
+
+ DBG(DBG_info, "%s: starting line reading\n", __func__);
+ dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("led_calibration");
+ move_back_home(dev, true);
+ return calib_sensor.exposure;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, line.data(), total_size);
+
+ if (DBG_LEVEL >= DBG_data) {
+ char fn[30];
+ std::snprintf(fn, 30, "gl841_led_%d.pnm", turn);
+ sanei_genesys_write_pnm_file(fn, line.data(), 16, channels, num_pixels, 1);
+ }
+
+ /* compute average */
+ for (j = 0; j < channels; j++)
+ {
+ avg[j] = 0;
+ for (i = 0; i < num_pixels; i++)
+ {
+ if (dev->model->is_cis)
+ val =
+ line[i * 2 + j * 2 * num_pixels + 1] * 256 +
+ line[i * 2 + j * 2 * num_pixels];
+ else
+ val =
+ line[i * 2 * channels + 2 * j + 1] * 256 +
+ line[i * 2 * channels + 2 * j];
+ avg[j] += val;
+ }
+
+ avg[j] /= num_pixels;
+ }
+
+ DBG(DBG_info,"%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]);
+
+ acceptable = true;
+
+ /* exposure is acceptable if each color is in the %5 range
+ * of other color channels */
+ if (avg[0] < avg[1] * 0.95 || avg[1] < avg[0] * 0.95 ||
+ avg[0] < avg[2] * 0.95 || avg[2] < avg[0] * 0.95 ||
+ avg[1] < avg[2] * 0.95 || avg[2] < avg[1] * 0.95)
+ {
+ acceptable = false;
+ }
+
+ /* led exposure is not acceptable if white level is too low
+ * ~80 hardcoded value for white level */
+ if(avg[0]<20000 || avg[1]<20000 || avg[2]<20000)
+ {
+ acceptable = false;
+ }
+
+ /* for scanners using target value */
+ if(target>0)
+ {
+ acceptable = true;
+ for(i=0;i<3;i++)
+ {
+ /* we accept +- 2% delta from target */
+ if(abs(avg[i]-target)>target/50)
+ {
+ exp[i]=(exp[i]*target)/avg[i];
+ acceptable = false;
+ }
+ }
+ }
+ else
+ {
+ if (!acceptable)
+ {
+ avga = (avg[0]+avg[1]+avg[2])/3;
+ exp[0] = (exp[0] * avga) / avg[0];
+ exp[1] = (exp[1] * avga) / avg[1];
+ exp[2] = (exp[2] * avga) / avg[2];
+ /*
+ keep the resulting exposures below this value.
+ too long exposure drives the ccd into saturation.
+ we may fix this by relying on the fact that
+ we get a striped scan without shading, by means of
+ statistical calculation
+ */
+ avge = (exp[0] + exp[1] + exp[2]) / 3;
+
+ if (avge > max_exposure) {
+ exp[0] = (exp[0] * max_exposure) / avge;
+ exp[1] = (exp[1] * max_exposure) / avge;
+ exp[2] = (exp[2] * max_exposure) / avge;
+ }
+ if (avge < min_exposure) {
+ exp[0] = (exp[0] * min_exposure) / avge;
+ exp[1] = (exp[1] * min_exposure) / avge;
+ exp[2] = (exp[2] * min_exposure) / avge;
+ }
+
+ }
+ }
+
+ gl841_stop_action(dev);
+
+ turn++;
+
+ } while (!acceptable && turn < 100);
+
+ DBG(DBG_info,"%s: acceptable exposure: %d,%d,%d\n", __func__, exp[0], exp[1], exp[2]);
+
+ dev->cmd_set->move_back_home(dev, true);
+
+ return calib_sensor.exposure;
+}
+
+/** @brief calibration for AD frontend devices
+ * offset calibration assumes that the scanning head is on a black area
+ * For LiDE80 analog frontend
+ * 0x0003 : is gain and belongs to [0..63]
+ * 0x0006 : is offset
+ * We scan a line with no gain until average offset reaches the target
+ */
+static void ad_fe_offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs)
+{
+ DBG_HELPER(dbg);
+ int num_pixels;
+ int total_size;
+ int i;
+ int average;
+ int turn;
+ int top;
+ int bottom;
+ int target;
+
+ /* don't impact 3600 behavior since we can't test it */
+ if (dev->model->sensor_id == SensorId::CCD_PLUSTEK_OPTICPRO_3600) {
+ return;
+ }
+
+ unsigned resolution = sensor.get_logical_hwdpi(dev->settings.xres);
+ unsigned factor = sensor.optical_res / resolution;
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, 3,
+ dev->settings.scan_method);
+
+ num_pixels = calib_sensor.sensor_pixels / factor;
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = num_pixels;
+ session.params.lines = 1;
+ session.params.depth = 8;
+ session.params.channels = 3;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ dev->cmd_set->init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ total_size = num_pixels * 3 * 2 * 1;
+
+ std::vector<uint8_t> line(total_size);
+
+ dev->frontend.set_gain(0, 0);
+ dev->frontend.set_gain(1, 0);
+ dev->frontend.set_gain(2, 0);
+
+ /* loop on scan until target offset is reached */
+ turn=0;
+ target=24;
+ bottom=0;
+ top=255;
+ do {
+ /* set up offset mid range */
+ dev->frontend.set_offset(0, (top + bottom) / 2);
+ dev->frontend.set_offset(1, (top + bottom) / 2);
+ dev->frontend.set_offset(2, (top + bottom) / 2);
+
+ /* scan line */
+ DBG(DBG_info, "%s: starting line reading\n", __func__);
+ dev->interface->write_registers(regs);
+ dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET);
+ dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("ad_fe_offset_calibration");
+ gl841_stop_action(dev);
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, line.data(), total_size);
+ gl841_stop_action (dev);
+ if (DBG_LEVEL >= DBG_data) {
+ char fn[30];
+ std::snprintf(fn, 30, "gl841_offset_%02d.pnm", turn);
+ sanei_genesys_write_pnm_file(fn, line.data(), 8, 3, num_pixels, 1);
+ }
+
+ /* search for minimal value */
+ average=0;
+ for(i=0;i<total_size;i++)
+ {
+ average+=line[i];
+ }
+ average/=total_size;
+ DBG(DBG_data, "%s: average=%d\n", __func__, average);
+
+ /* if min value is above target, the current value becomes the new top
+ * else it is the new bottom */
+ if(average>target)
+ {
+ top=(top+bottom)/2;
+ }
+ else
+ {
+ bottom=(top+bottom)/2;
+ }
+ turn++;
+ } while ((top-bottom)>1 && turn < 100);
+
+ // FIXME: don't overwrite the calibrated values
+ dev->frontend.set_offset(0, 0);
+ dev->frontend.set_offset(1, 0);
+ dev->frontend.set_offset(2, 0);
+ DBG(DBG_info, "%s: offset=(%d,%d,%d)\n", __func__,
+ dev->frontend.get_offset(0),
+ dev->frontend.get_offset(1),
+ dev->frontend.get_offset(2));
+}
+
+/* this function does the offset calibration by scanning one line of the calibration
+ area below scanner's top. There is a black margin and the remaining is white.
+ sanei_genesys_search_start() must have been called so that the offsets and margins
+ are allready known.
+
+this function expects the slider to be where?
+*/
+void CommandSetGl841::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ int num_pixels;
+ int total_size;
+ int i, j;
+ int val;
+ int channels;
+ int off[3],offh[3],offl[3],off1[3],off2[3];
+ int min1[3],min2[3];
+ int cmin[3],cmax[3];
+ int turn;
+ int mintgt = 0x400;
+
+ /* Analog Device fronted have a different calibration */
+ if ((dev->reg.find_reg(0x04).value & REG_0x04_FESET) == 0x02) {
+ return ad_fe_offset_calibration(dev, sensor, regs);
+ }
+
+ /* offset calibration is always done in color mode */
+ channels = 3;
+
+ unsigned resolution = sensor.get_logical_hwdpi(dev->settings.xres);
+ unsigned factor = sensor.optical_res / resolution;
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels,
+ dev->settings.scan_method);
+
+ num_pixels = calib_sensor.sensor_pixels / factor;
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = num_pixels;
+ session.params.lines = 1;
+ session.params.depth = 16;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE |
+ ScanFlag::DISABLE_LAMP;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ total_size = num_pixels * channels * 2 * 1; /* colors * bytes_per_color * scan lines */
+
+ std::vector<uint8_t> first_line(total_size);
+ std::vector<uint8_t> second_line(total_size);
+
+ /* scan first line of data with no offset nor gain */
+/*WM8199: gain=0.73; offset=-260mV*/
+/*okay. the sensor black level is now at -260mV. we only get 0 from AFE...*/
+/* we should probably do real calibration here:
+ * -detect acceptable offset with binary search
+ * -calculate offset from this last version
+ *
+ * acceptable offset means
+ * - few completely black pixels(<10%?)
+ * - few completely white pixels(<10%?)
+ *
+ * final offset should map the minimum not completely black
+ * pixel to 0(16 bits)
+ *
+ * this does account for dummy pixels at the end of ccd
+ * this assumes slider is at black strip(which is not quite as black as "no
+ * signal").
+ *
+ */
+ dev->frontend.set_gain(0, 0);
+ dev->frontend.set_gain(1, 0);
+ dev->frontend.set_gain(2, 0);
+ offh[0] = 0xff;
+ offh[1] = 0xff;
+ offh[2] = 0xff;
+ offl[0] = 0x00;
+ offl[1] = 0x00;
+ offl[2] = 0x00;
+ turn = 0;
+
+ bool acceptable = false;
+ do {
+
+ dev->interface->write_registers(regs);
+
+ for (j=0; j < channels; j++) {
+ off[j] = (offh[j]+offl[j])/2;
+ dev->frontend.set_offset(j, off[j]);
+ }
+
+ dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET);
+
+ DBG(DBG_info, "%s: starting first line reading\n", __func__);
+ dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("offset_calibration");
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size);
+
+ if (DBG_LEVEL >= DBG_data) {
+ char fn[30];
+ std::snprintf(fn, 30, "gl841_offset1_%02d.pnm", turn);
+ sanei_genesys_write_pnm_file(fn, first_line.data(), 16, channels, num_pixels, 1);
+ }
+
+ acceptable = true;
+
+ for (j = 0; j < channels; j++)
+ {
+ cmin[j] = 0;
+ cmax[j] = 0;
+
+ for (i = 0; i < num_pixels; i++)
+ {
+ if (dev->model->is_cis)
+ val =
+ first_line[i * 2 + j * 2 * num_pixels + 1] * 256 +
+ first_line[i * 2 + j * 2 * num_pixels];
+ else
+ val =
+ first_line[i * 2 * channels + 2 * j + 1] * 256 +
+ first_line[i * 2 * channels + 2 * j];
+ if (val < 10)
+ cmin[j]++;
+ if (val > 65525)
+ cmax[j]++;
+ }
+
+ /* TODO the DP685 has a black strip in the middle of the sensor
+ * should be handled in a more elegant way , could be a bug */
+ if (dev->model->sensor_id == SensorId::CCD_DP685)
+ cmin[j] -= 20;
+
+ if (cmin[j] > num_pixels/100) {
+ acceptable = false;
+ if (dev->model->is_cis)
+ offl[0] = off[0];
+ else
+ offl[j] = off[j];
+ }
+ if (cmax[j] > num_pixels/100) {
+ acceptable = false;
+ if (dev->model->is_cis)
+ offh[0] = off[0];
+ else
+ offh[j] = off[j];
+ }
+ }
+
+ DBG(DBG_info,"%s: black/white pixels: %d/%d,%d/%d,%d/%d\n", __func__, cmin[0], cmax[0],
+ cmin[1], cmax[1], cmin[2], cmax[2]);
+
+ if (dev->model->is_cis) {
+ offh[2] = offh[1] = offh[0];
+ offl[2] = offl[1] = offl[0];
+ }
+
+ gl841_stop_action(dev);
+
+ turn++;
+ } while (!acceptable && turn < 100);
+
+ DBG(DBG_info,"%s: acceptable offsets: %d,%d,%d\n", __func__, off[0], off[1], off[2]);
+
+
+ for (j = 0; j < channels; j++)
+ {
+ off1[j] = off[j];
+
+ min1[j] = 65536;
+
+ for (i = 0; i < num_pixels; i++)
+ {
+ if (dev->model->is_cis)
+ val =
+ first_line[i * 2 + j * 2 * num_pixels + 1] * 256 +
+ first_line[i * 2 + j * 2 * num_pixels];
+ else
+ val =
+ first_line[i * 2 * channels + 2 * j + 1] * 256 +
+ first_line[i * 2 * channels + 2 * j];
+ if (min1[j] > val && val >= 10)
+ min1[j] = val;
+ }
+ }
+
+
+ offl[0] = off[0];
+ offl[1] = off[0];
+ offl[2] = off[0];
+ turn = 0;
+
+ do {
+
+ for (j=0; j < channels; j++) {
+ off[j] = (offh[j]+offl[j])/2;
+ dev->frontend.set_offset(j, off[j]);
+ }
+
+ dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET);
+
+ DBG(DBG_info, "%s: starting second line reading\n", __func__);
+ dev->interface->write_registers(regs);
+ dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
+ sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size);
+
+ if (DBG_LEVEL >= DBG_data) {
+ char fn[30];
+ std::snprintf(fn, 30, "gl841_offset2_%02d.pnm", turn);
+ sanei_genesys_write_pnm_file(fn, second_line.data(), 16, channels, num_pixels, 1);
+ }
+
+ acceptable = true;
+
+ for (j = 0; j < channels; j++)
+ {
+ cmin[j] = 0;
+ cmax[j] = 0;
+
+ for (i = 0; i < num_pixels; i++)
+ {
+ if (dev->model->is_cis)
+ val =
+ second_line[i * 2 + j * 2 * num_pixels + 1] * 256 +
+ second_line[i * 2 + j * 2 * num_pixels];
+ else
+ val =
+ second_line[i * 2 * channels + 2 * j + 1] * 256 +
+ second_line[i * 2 * channels + 2 * j];
+ if (val < 10)
+ cmin[j]++;
+ if (val > 65525)
+ cmax[j]++;
+ }
+
+ if (cmin[j] > num_pixels/100) {
+ acceptable = false;
+ if (dev->model->is_cis)
+ offl[0] = off[0];
+ else
+ offl[j] = off[j];
+ }
+ if (cmax[j] > num_pixels/100) {
+ acceptable = false;
+ if (dev->model->is_cis)
+ offh[0] = off[0];
+ else
+ offh[j] = off[j];
+ }
+ }
+
+ DBG(DBG_info, "%s: black/white pixels: %d/%d,%d/%d,%d/%d\n", __func__, cmin[0], cmax[0],
+ cmin[1], cmax[1], cmin[2], cmax[2]);
+
+ if (dev->model->is_cis) {
+ offh[2] = offh[1] = offh[0];
+ offl[2] = offl[1] = offl[0];
+ }
+
+ gl841_stop_action(dev);
+
+ turn++;
+
+ } while (!acceptable && turn < 100);
+
+ DBG(DBG_info, "%s: acceptable offsets: %d,%d,%d\n", __func__, off[0], off[1], off[2]);
+
+
+ for (j = 0; j < channels; j++)
+ {
+ off2[j] = off[j];
+
+ min2[j] = 65536;
+
+ for (i = 0; i < num_pixels; i++)
+ {
+ if (dev->model->is_cis)
+ val =
+ second_line[i * 2 + j * 2 * num_pixels + 1] * 256 +
+ second_line[i * 2 + j * 2 * num_pixels];
+ else
+ val =
+ second_line[i * 2 * channels + 2 * j + 1] * 256 +
+ second_line[i * 2 * channels + 2 * j];
+ if (min2[j] > val && val != 0)
+ min2[j] = val;
+ }
+ }
+
+ DBG(DBG_info, "%s: first set: %d/%d,%d/%d,%d/%d\n", __func__, off1[0], min1[0], off1[1], min1[1],
+ off1[2], min1[2]);
+
+ DBG(DBG_info, "%s: second set: %d/%d,%d/%d,%d/%d\n", __func__, off2[0], min2[0], off2[1], min2[1],
+ off2[2], min2[2]);
+
+/*
+ calculate offset for each channel
+ based on minimal pixel value min1 at offset off1 and minimal pixel value min2
+ at offset off2
+
+ to get min at off, values are linearly interpolated:
+ min=real+off*fact
+ min1=real+off1*fact
+ min2=real+off2*fact
+
+ fact=(min1-min2)/(off1-off2)
+ real=min1-off1*(min1-min2)/(off1-off2)
+
+ off=(min-min1+off1*(min1-min2)/(off1-off2))/((min1-min2)/(off1-off2))
+
+ off=(min*(off1-off2)+min1*off2-off1*min2)/(min1-min2)
+
+ */
+ for (j = 0; j < channels; j++)
+ {
+ if (min2[j]-min1[j] == 0) {
+/*TODO: try to avoid this*/
+ DBG(DBG_warn, "%s: difference too small\n", __func__);
+ if (mintgt * (off1[j] - off2[j]) + min1[j] * off2[j] - min2[j] * off1[j] >= 0)
+ off[j] = 0x0000;
+ else
+ off[j] = 0xffff;
+ } else
+ off[j] = (mintgt * (off1[j] - off2[j]) + min1[j] * off2[j] - min2[j] * off1[j])/(min1[j]-min2[j]);
+ if (off[j] > 255)
+ off[j] = 255;
+ if (off[j] < 0)
+ off[j] = 0;
+ dev->frontend.set_offset(j, off[j]);
+ }
+
+ DBG(DBG_info, "%s: final offsets: %d,%d,%d\n", __func__, off[0], off[1], off[2]);
+
+ if (dev->model->is_cis) {
+ if (off[0] < off[1])
+ off[0] = off[1];
+ if (off[0] < off[2])
+ off[0] = off[2];
+ dev->frontend.set_offset(0, off[0]);
+ dev->frontend.set_offset(1, off[0]);
+ dev->frontend.set_offset(2, off[0]);
+ }
+
+ if (channels == 1)
+ {
+ dev->frontend.set_offset(1, dev->frontend.get_offset(0));
+ dev->frontend.set_offset(2, dev->frontend.get_offset(0));
+ }
+}
+
+
+/* alternative coarse gain calibration
+ this on uses the settings from offset_calibration and
+ uses only one scanline
+ */
+/*
+ with offset and coarse calibration we only want to get our input range into
+ a reasonable shape. the fine calibration of the upper and lower bounds will
+ be done with shading.
+ */
+void CommandSetGl841::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const
+{
+ DBG_HELPER_ARGS(dbg, "dpi=%d", dpi);
+ int num_pixels;
+ int total_size;
+ int i, j, channels;
+ int max[3];
+ float gain[3];
+ int val;
+ int lines=1;
+ int move;
+
+ // feed to white strip if needed
+ if (dev->model->y_offset_calib_white > 0) {
+ move = static_cast<int>(dev->model->y_offset_calib_white);
+ move = static_cast<int>((move * (dev->motor.base_ydpi)) / MM_PER_INCH);
+ DBG(DBG_io, "%s: move=%d lines\n", __func__, move);
+ gl841_feed(dev, move);
+ }
+
+ /* coarse gain calibration is allways done in color mode */
+ channels = 3;
+
+ unsigned resolution = sensor.get_logical_hwdpi(dev->settings.xres);
+ unsigned factor = sensor.optical_res / resolution;
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels,
+ dev->settings.scan_method);
+
+ num_pixels = calib_sensor.sensor_pixels / factor;
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = num_pixels;
+ session.params.lines = lines;
+ session.params.depth = 16;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ dev->interface->write_registers(regs);
+
+ total_size = num_pixels * channels * 2 * lines; /* colors * bytes_per_color * scan lines */
+
+ std::vector<uint8_t> line(total_size);
+
+ dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("coarse_gain_calibration");
+ gl841_stop_action(dev);
+ move_back_home(dev, true);
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, line.data(), total_size);
+
+ if (DBG_LEVEL >= DBG_data)
+ sanei_genesys_write_pnm_file("gl841_gain.pnm", line.data(), 16, channels, num_pixels, lines);
+
+ /* average high level for each channel and compute gain
+ to reach the target code
+ we only use the central half of the CCD data */
+ for (j = 0; j < channels; j++)
+ {
+ max[j] = 0;
+ for (i = 0; i < num_pixels; i++)
+ {
+ if (dev->model->is_cis)
+ val =
+ line[i * 2 + j * 2 * num_pixels + 1] * 256 +
+ line[i * 2 + j * 2 * num_pixels];
+ else
+ val =
+ line[i * 2 * channels + 2 * j + 1] * 256 +
+ line[i * 2 * channels + 2 * j];
+
+ if (val > max[j])
+ max[j] = val;
+ }
+
+ gain[j] = 65535.0f / max[j];
+
+ uint8_t out_gain = 0;
+
+ if (dev->model->adc_id == AdcId::CANON_LIDE_35 ||
+ dev->model->adc_id == AdcId::WOLFSON_XP300 ||
+ dev->model->adc_id == AdcId::WOLFSON_DSM600)
+ {
+ gain[j] *= 0.69f; // seems we don't get the real maximum. empirically derived
+ if (283 - 208/gain[j] > 255)
+ out_gain = 255;
+ else if (283 - 208/gain[j] < 0)
+ out_gain = 0;
+ else
+ out_gain = static_cast<std::uint8_t>(283 - 208 / gain[j]);
+ } else if (dev->model->adc_id == AdcId::CANON_LIDE_80) {
+ out_gain = static_cast<std::uint8_t>(gain[j] * 12);
+ }
+ dev->frontend.set_gain(j, out_gain);
+
+ DBG(DBG_proc, "%s: channel %d, max=%d, gain = %f, setting:%d\n", __func__, j, max[j], gain[j],
+ out_gain);
+ }
+
+ for (j = 0; j < channels; j++)
+ {
+ if(gain[j] > 10)
+ {
+ DBG (DBG_error0, "**********************************************\n");
+ DBG (DBG_error0, "**********************************************\n");
+ DBG (DBG_error0, "**** ****\n");
+ DBG (DBG_error0, "**** Extremely low Brightness detected. ****\n");
+ DBG (DBG_error0, "**** Check the scanning head is ****\n");
+ DBG (DBG_error0, "**** unlocked and moving. ****\n");
+ DBG (DBG_error0, "**** ****\n");
+ DBG (DBG_error0, "**********************************************\n");
+ DBG (DBG_error0, "**********************************************\n");
+ throw SaneException(SANE_STATUS_JAMMED, "scanning head is locked");
+ }
+
+ }
+
+ if (dev->model->is_cis) {
+ uint8_t gain0 = dev->frontend.get_gain(0);
+ if (gain0 > dev->frontend.get_gain(1)) {
+ gain0 = dev->frontend.get_gain(1);
+ }
+ if (gain0 > dev->frontend.get_gain(2)) {
+ gain0 = dev->frontend.get_gain(2);
+ }
+ dev->frontend.set_gain(0, gain0);
+ dev->frontend.set_gain(1, gain0);
+ dev->frontend.set_gain(2, gain0);
+ }
+
+ if (channels == 1) {
+ dev->frontend.set_gain(0, dev->frontend.get_gain(1));
+ dev->frontend.set_gain(2, dev->frontend.get_gain(1));
+ }
+
+ DBG(DBG_info, "%s: gain=(%d,%d,%d)\n", __func__,
+ dev->frontend.get_gain(0),
+ dev->frontend.get_gain(1),
+ dev->frontend.get_gain(2));
+
+ gl841_stop_action(dev);
+
+ dev->cmd_set->move_back_home(dev, true);
+}
+
+// wait for lamp warmup by scanning the same line until difference
+// between 2 scans is below a threshold
+void CommandSetGl841::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* local_reg, int* channels,
+ int* total_size) const
+{
+ DBG_HELPER(dbg);
+ int num_pixels = 4 * 300;
+ *local_reg = dev->reg;
+
+/* okay.. these should be defaults stored somewhere */
+ dev->frontend.set_gain(0, 0);
+ dev->frontend.set_gain(1, 0);
+ dev->frontend.set_gain(2, 0);
+ dev->frontend.set_offset(0, 0x80);
+ dev->frontend.set_offset(1, 0x80);
+ dev->frontend.set_offset(2, 0x80);
+
+ ScanSession session;
+ session.params.xres = sensor.optical_res;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = sensor.dummy_pixel;
+ session.params.starty = 0;
+ session.params.pixels = num_pixels;
+ session.params.lines = 1;
+ session.params.depth = 16;
+ session.params.channels = *channels;
+ session.params.scan_method = dev->settings.scan_method;
+ if (*channels == 3) {
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ } else {
+ session.params.scan_mode = ScanColorMode::GRAY;
+ }
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, local_reg, session);
+
+ num_pixels = session.output_pixels;
+
+ *total_size = num_pixels * 3 * 2 * 1; /* colors * bytes_per_color * scan lines */
+
+ dev->interface->write_registers(*local_reg);
+}
+
+
+/*
+ * this function moves head without scanning, forward, then backward
+ * so that the head goes to park position.
+ * as a by-product, also check for lock
+ */
+static void sanei_gl841_repark_head(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+
+ gl841_feed(dev,232);
+
+ // toggle motor flag, put an huge step number and redo move backward
+ dev->cmd_set->move_back_home(dev, true);
+}
+
+/*
+ * initialize ASIC : registers, motor tables, and gamma tables
+ * then ensure scanner's head is at home
+ */
+void CommandSetGl841::init(Genesys_Device* dev) const
+{
+ size_t size;
+
+ DBG_INIT ();
+ DBG_HELPER(dbg);
+
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+
+ /* Check if the device has already been initialized and powered up */
+ if (dev->already_initialized)
+ {
+ auto status = scanner_read_status(*dev);
+ if (!status.is_replugged) {
+ DBG(DBG_info, "%s: already initialized\n", __func__);
+ return;
+ }
+ }
+
+ dev->dark_average_data.clear();
+ dev->white_average_data.clear();
+
+ dev->settings.color_filter = ColorFilter::RED;
+
+ // ASIC reset
+ dev->interface->write_register(0x0e, 0x01);
+ dev->interface->write_register(0x0e, 0x00);
+
+ /* Set default values for registers */
+ gl841_init_registers (dev);
+
+ // Write initial registers
+ dev->interface->write_registers(dev->reg);
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+
+ // Set analog frontend
+ dev->cmd_set->set_fe(dev, sensor, AFE_INIT);
+
+ // FIXME: move_back_home modifies dev->calib_reg and requires it to be filled
+ dev->calib_reg = dev->reg;
+
+ // Move home
+ dev->cmd_set->move_back_home(dev, true);
+
+ // Init shading data
+ sanei_genesys_init_shading_data(dev, sensor, sensor.sensor_pixels);
+
+ /* ensure head is correctly parked, and check lock */
+ if (dev->model->flags & GENESYS_FLAG_REPARK)
+ {
+ // FIXME: if repark fails, we should print an error message that the scanner is locked and
+ // the user should unlock the lock. We should also rethrow with SANE_STATUS_JAMMED
+ sanei_gl841_repark_head(dev);
+ }
+
+ // send gamma tables
+ dev->cmd_set->send_gamma_table(dev, sensor);
+
+ /* initial calibration reg values */
+ Genesys_Register_Set& regs = dev->calib_reg;
+ regs = dev->reg;
+
+ unsigned resolution = sensor.get_logical_hwdpi(300);
+ unsigned factor = sensor.optical_res / resolution;
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, 3,
+ dev->settings.scan_method);
+
+ unsigned num_pixels = 16 / factor;
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = 300;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = num_pixels;
+ session.params.lines = 1;
+ session.params.depth = 16;
+ session.params.channels = 3;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = ColorFilter::RED;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ dev->interface->write_registers(regs);
+
+ size = num_pixels * 3 * 2 * 1; // colors * bytes_per_color * scan lines
+
+ std::vector<uint8_t> line(size);
+
+ DBG(DBG_info, "%s: starting dummy data reading\n", __func__);
+ dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
+
+ sanei_usb_set_timeout(1000);/* 1 second*/
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("init");
+ } else {
+ // ignore errors. next read will succeed
+ catch_all_exceptions(__func__,
+ [&](){ sanei_genesys_read_data_from_scanner(dev, line.data(), size); });
+ }
+
+ sanei_usb_set_timeout(30 * 1000);/* 30 seconds*/
+
+ end_scan(dev, &regs, true);
+
+ regs = dev->reg;
+
+ // Set powersaving(default = 15 minutes)
+ set_powersaving(dev, 15);
+ dev->already_initialized = true;
+}
+
+void CommandSetGl841::update_hardware_sensors(Genesys_Scanner* s) const
+{
+ DBG_HELPER(dbg);
+ /* do what is needed to get a new set of events, but try to not lose
+ any of them.
+ */
+ uint8_t val;
+
+ if (s->dev->model->gpio_id == GpioId::CANON_LIDE_35
+ || s->dev->model->gpio_id == GpioId::CANON_LIDE_80)
+ {
+ val = s->dev->interface->read_register(REG_0x6D);
+ s->buttons[BUTTON_SCAN_SW].write((val & 0x01) == 0);
+ s->buttons[BUTTON_FILE_SW].write((val & 0x02) == 0);
+ s->buttons[BUTTON_EMAIL_SW].write((val & 0x04) == 0);
+ s->buttons[BUTTON_COPY_SW].write((val & 0x08) == 0);
+ }
+
+ if (s->dev->model->gpio_id == GpioId::XP300 ||
+ s->dev->model->gpio_id == GpioId::DP665 ||
+ s->dev->model->gpio_id == GpioId::DP685)
+ {
+ val = s->dev->interface->read_register(REG_0x6D);
+
+ s->buttons[BUTTON_PAGE_LOADED_SW].write((val & 0x01) == 0);
+ s->buttons[BUTTON_SCAN_SW].write((val & 0x02) == 0);
+ }
+}
+
+/** @brief search for a full width black or white strip.
+ * This function searches for a black or white stripe across the scanning area.
+ * When searching backward, the searched area must completely be of the desired
+ * color since this area will be used for calibration which scans forward.
+ * @param dev scanner device
+ * @param forward true if searching forward, false if searching backward
+ * @param black true if searching for a black strip, false for a white strip
+ */
+void CommandSetGl841::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, bool forward,
+ bool black) const
+{
+ DBG_HELPER_ARGS(dbg, "%s %s", black ? "black" : "white", forward ? "forward" : "reverse");
+ unsigned int pixels, lines, channels;
+ Genesys_Register_Set local_reg;
+ size_t size;
+ unsigned int pass, count, found, x, y, length;
+ char title[80];
+ GenesysRegister *r;
+ uint8_t white_level=90; /**< default white level to detect white dots */
+ uint8_t black_level=60; /**< default black level to detect black dots */
+
+ /* use maximum gain when doing forward white strip detection
+ * since we don't have calibrated the sensor yet */
+ if(!black && forward)
+ {
+ dev->frontend.set_gain(0, 0xff);
+ dev->frontend.set_gain(1, 0xff);
+ dev->frontend.set_gain(2, 0xff);
+ }
+
+ dev->cmd_set->set_fe(dev, sensor, AFE_SET);
+ gl841_stop_action(dev);
+
+ // set up for a gray scan at lowest dpi
+ const auto& resolution_settings = dev->model->get_resolution_settings(dev->settings.scan_method);
+ unsigned dpi = resolution_settings.get_min_resolution_x();
+ channels = 1;
+
+ /* shading calibation is done with dev->motor.base_ydpi */
+ /* lines = (dev->model->shading_lines * dpi) / dev->motor.base_ydpi; */
+ lines = static_cast<unsigned>((10 * dpi) / MM_PER_INCH);
+
+ pixels = (sensor.sensor_pixels * dpi) / sensor.optical_res;
+
+ /* 20 cm max length for calibration sheet */
+ length = static_cast<unsigned>(((200 * dpi) / MM_PER_INCH) / lines);
+
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+
+ local_reg = dev->reg;
+
+ ScanSession session;
+ session.params.xres = dpi;
+ session.params.yres = dpi;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::GRAY;
+ session.params.color_filter = ColorFilter::RED;
+ session.params.flags = ScanFlag::DISABLE_SHADING | ScanFlag::DISABLE_GAMMA;
+ compute_session(dev, session, sensor);
+
+ size = pixels * channels * lines * (session.params.depth / 8);
+ std::vector<uint8_t> data(size);
+
+ init_regs_for_scan_session(dev, sensor, &local_reg, session);
+
+ /* set up for reverse or forward */
+ r = sanei_genesys_get_address(&local_reg, 0x02);
+ if (forward) {
+ r->value &= ~4;
+ } else {
+ r->value |= 4;
+ }
+
+ dev->interface->write_registers(local_reg);
+
+ dev->cmd_set->begin_scan(dev, sensor, &local_reg, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("search_strip");
+ gl841_stop_action(dev);
+ return;
+ }
+
+ // waits for valid data
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ sanei_genesys_read_data_from_scanner(dev, data.data(), size);
+
+ gl841_stop_action(dev);
+
+ pass = 0;
+ if (DBG_LEVEL >= DBG_data)
+ {
+ std::sprintf(title, "gl841_search_strip_%s_%s%02u.pnm", black ? "black" : "white",
+ forward ? "fwd" : "bwd", pass);
+ sanei_genesys_write_pnm_file(title, data.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ /* loop until strip is found or maximum pass number done */
+ found = 0;
+ while (pass < length && !found)
+ {
+ dev->interface->write_registers(local_reg);
+
+ //now start scan
+ dev->cmd_set->begin_scan(dev, sensor, &local_reg, true);
+
+ // waits for valid data
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ sanei_genesys_read_data_from_scanner(dev, data.data(), size);
+
+ gl841_stop_action (dev);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ std::sprintf(title, "gl841_search_strip_%s_%s%02u.pnm",
+ black ? "black" : "white", forward ? "fwd" : "bwd", pass);
+ sanei_genesys_write_pnm_file(title, data.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ /* search data to find black strip */
+ /* when searching forward, we only need one line of the searched color since we
+ * will scan forward. But when doing backward search, we need all the area of the
+ * same color */
+ if (forward)
+ {
+ for (y = 0; y < lines && !found; y++)
+ {
+ count = 0;
+ /* count of white/black pixels depending on the color searched */
+ for (x = 0; x < pixels; x++)
+ {
+ /* when searching for black, detect white pixels */
+ if (black && data[y * pixels + x] > white_level)
+ {
+ count++;
+ }
+ /* when searching for white, detect black pixels */
+ if (!black && data[y * pixels + x] < black_level)
+ {
+ count++;
+ }
+ }
+
+ /* at end of line, if count >= 3%, line is not fully of the desired color
+ * so we must go to next line of the buffer */
+ /* count*100/pixels < 3 */
+ if ((count * 100) / pixels < 3)
+ {
+ found = 1;
+ DBG(DBG_data, "%s: strip found forward during pass %d at line %d\n", __func__,
+ pass, y);
+ }
+ else
+ {
+ DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count,
+ (100 * count) / pixels);
+ }
+ }
+ }
+ else /* since calibration scans are done forward, we need the whole area
+ to be of the required color when searching backward */
+ {
+ count = 0;
+ for (y = 0; y < lines; y++)
+ {
+ /* count of white/black pixels depending on the color searched */
+ for (x = 0; x < pixels; x++)
+ {
+ /* when searching for black, detect white pixels */
+ if (black && data[y * pixels + x] > white_level)
+ {
+ count++;
+ }
+ /* when searching for white, detect black pixels */
+ if (!black && data[y * pixels + x] < black_level)
+ {
+ count++;
+ }
+ }
+ }
+
+ /* at end of area, if count >= 3%, area is not fully of the desired color
+ * so we must go to next buffer */
+ if ((count * 100) / (pixels * lines) < 3)
+ {
+ found = 1;
+ DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass);
+ }
+ else
+ {
+ DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count,
+ (100 * count) / pixels);
+ }
+ }
+ pass++;
+ }
+
+ if (found)
+ {
+ DBG(DBG_info, "%s: %s strip found\n", __func__, black ? "black" : "white");
+ }
+ else
+ {
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", black ? "black" : "white");
+ }
+}
+
+/**
+ * Send shading calibration data. The buffer is considered to always hold values
+ * for all the channels.
+ */
+void CommandSetGl841::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ uint8_t* data, int size) const
+{
+ DBG_HELPER_ARGS(dbg, "writing %d bytes of shading data", size);
+ uint32_t length, x, factor, pixels, i;
+ uint16_t dpiset, dpihw, beginpixel;
+ uint8_t *ptr,*src;
+
+ /* old method if no SHDAREA */
+ if ((dev->reg.find_reg(0x01).value & REG_0x01_SHDAREA) == 0) {
+ dev->interface->write_buffer(0x3c, 0x0000, data, size);
+ return;
+ }
+
+ /* data is whole line, we extract only the part for the scanned area */
+ length = static_cast<std::uint32_t>(size / 3);
+ unsigned strpixel = dev->session.pixel_startx;
+ unsigned endpixel = dev->session.pixel_endx;
+
+ /* compute deletion/average factor */
+ dpiset = dev->reg.get16(REG_DPISET);
+ dpihw = gl841_get_dpihw(dev);
+ unsigned ccd_size_divisor = dev->session.ccd_size_divisor;
+ factor=dpihw/dpiset;
+ DBG(DBG_io2, "%s: dpihw=%d, dpiset=%d, ccd_size_divisor=%d, factor=%d\n", __func__, dpihw, dpiset,
+ ccd_size_divisor, factor);
+
+ /* turn pixel value into bytes 2x16 bits words */
+ strpixel*=2*2; /* 2 words of 2 bytes */
+ endpixel*=2*2;
+ pixels=endpixel-strpixel;
+
+ /* shading pixel begin is start pixel minus start pixel during shading
+ * calibration. Currently only cases handled are full and half ccd resolution.
+ */
+ beginpixel = sensor.ccd_start_xoffset / ccd_size_divisor;
+ beginpixel += sensor.dummy_pixel + 1;
+ DBG(DBG_io2, "%s: ORIGIN PIXEL=%d\n", __func__, beginpixel);
+ beginpixel = (strpixel-beginpixel*2*2)/factor;
+ DBG(DBG_io2, "%s: BEGIN PIXEL=%d\n", __func__, beginpixel/4);
+
+ dev->interface->record_key_value("shading_offset", std::to_string(beginpixel));
+ dev->interface->record_key_value("shading_pixels", std::to_string(pixels));
+ dev->interface->record_key_value("shading_length", std::to_string(length));
+
+ DBG(DBG_io2, "%s: using chunks of %d bytes (%d shading data pixels)\n", __func__, length,
+ length/4);
+ std::vector<uint8_t> buffer(pixels, 0);
+
+ /* write actual shading data contigously
+ * channel by channel, starting at addr 0x0000
+ * */
+ for(i=0;i<3;i++)
+ {
+ /* copy data to work buffer and process it */
+ /* coefficent destination */
+ ptr=buffer.data();
+
+ /* iterate on both sensor segment, data has been averaged,
+ * so is in the right order and we only have to copy it */
+ for(x=0;x<pixels;x+=4)
+ {
+ /* coefficient source */
+ src=data+x+beginpixel+i*length;
+ ptr[0]=src[0];
+ ptr[1]=src[1];
+ ptr[2]=src[2];
+ ptr[3]=src[3];
+
+ /* next shading coefficient */
+ ptr+=4;
+ }
+
+ // 0x5400 alignment for LIDE80 internal memory
+ dev->interface->write_buffer(0x3c, 0x5400 * i, buffer.data(), pixels);
+ }
+}
+
+bool CommandSetGl841::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const
+{
+ (void) dev;
+ return true;
+}
+
+void CommandSetGl841::wait_for_motor_stop(Genesys_Device* dev) const
+{
+ (void) dev;
+}
+
+void CommandSetGl841::move_to_ta(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl841::asic_boot(Genesys_Device *dev, bool cold) const
+{
+ (void) dev;
+ (void) cold;
+ throw SaneException("not implemented");
+}
+
+std::unique_ptr<CommandSet> create_gl841_cmd_set()
+{
+ return std::unique_ptr<CommandSet>(new CommandSetGl841{});
+}
+
+} // namespace gl841
+} // namespace genesys
diff --git a/backend/genesys/gl841.h b/backend/genesys/gl841.h
new file mode 100644
index 0000000..5e24249
--- /dev/null
+++ b/backend/genesys/gl841.h
@@ -0,0 +1,130 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2011-2013 Stéphane Voltz <stef.dev@free.fr>
+
+ 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.
+*/
+
+#include "genesys.h"
+#include "command_set.h"
+
+#ifndef BACKEND_GENESYS_GL841_H
+#define BACKEND_GENESYS_GL841_H
+
+namespace genesys {
+namespace gl841 {
+
+class CommandSetGl841 : public CommandSet
+{
+public:
+ ~CommandSetGl841() override = default;
+
+ bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override;
+
+ void init(Genesys_Device* dev) const override;
+
+ void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, int* channels,
+ int* total_size) const override;
+
+ void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const override;
+
+ void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override;
+ void set_powersaving(Genesys_Device* dev, int delay) const override;
+ void save_power(Genesys_Device* dev, bool enable) const override;
+
+ void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, bool start_motor) const override;
+
+ void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override;
+
+ void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void search_start_position(Genesys_Device* dev) const override;
+
+ void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const override;
+
+ SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void wait_for_motor_stop(Genesys_Device* dev) const override;
+
+ void move_back_home(Genesys_Device* dev, bool wait_until_home) const override;
+
+ void update_hardware_sensors(struct Genesys_Scanner* s) const override;
+
+ void load_document(Genesys_Device* dev) const override;
+
+ void detect_document_end(Genesys_Device* dev) const override;
+
+ void eject_document(Genesys_Device* dev) const override;
+
+ void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ bool forward, bool black) const override;
+
+ void move_to_ta(Genesys_Device* dev) const override;
+
+ void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data,
+ int size) const override;
+
+ ScanSession calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const override;
+
+ void asic_boot(Genesys_Device* dev, bool cold) const override;
+};
+
+} // namespace gl841
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL841_H
diff --git a/backend/genesys/gl841_registers.h b/backend/genesys/gl841_registers.h
new file mode 100644
index 0000000..8e0c204
--- /dev/null
+++ b/backend/genesys/gl841_registers.h
@@ -0,0 +1,269 @@
+/* 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_GL841_REGISTERS_H
+#define BACKEND_GENESYS_GL841_REGISTERS_H
+
+#include <cstdint>
+
+namespace genesys {
+namespace gl841 {
+
+using RegAddr = std::uint16_t;
+using RegMask = std::uint8_t;
+using RegShift = unsigned;
+
+static constexpr RegAddr REG_0x01 = 0x01;
+static constexpr RegMask REG_0x01_CISSET = 0x80;
+static constexpr RegMask REG_0x01_DOGENB = 0x40;
+static constexpr RegMask REG_0x01_DVDSET = 0x20;
+static constexpr RegMask REG_0x01_M16DRAM = 0x08;
+static constexpr RegMask REG_0x01_DRAMSEL = 0x04;
+static constexpr RegMask REG_0x01_SHDAREA = 0x02;
+static constexpr RegMask REG_0x01_SCAN = 0x01;
+
+static constexpr RegAddr REG_0x02 = 0x02;
+static constexpr RegMask REG_0x02_NOTHOME = 0x80;
+static constexpr RegMask REG_0x02_ACDCDIS = 0x40;
+static constexpr RegMask REG_0x02_AGOHOME = 0x20;
+static constexpr RegMask REG_0x02_MTRPWR = 0x10;
+static constexpr RegMask REG_0x02_FASTFED = 0x08;
+static constexpr RegMask REG_0x02_MTRREV = 0x04;
+static constexpr RegMask REG_0x02_HOMENEG = 0x02;
+static constexpr RegMask REG_0x02_LONGCURV = 0x01;
+
+static constexpr RegMask REG_0x03_LAMPDOG = 0x80;
+static constexpr RegMask REG_0x03_AVEENB = 0x40;
+static constexpr RegMask REG_0x03_XPASEL = 0x20;
+static constexpr RegMask REG_0x03_LAMPPWR = 0x10;
+static constexpr RegMask REG_0x03_LAMPTIM = 0x0f;
+
+static constexpr RegMask REG_0x04_LINEART = 0x80;
+static constexpr RegMask REG_0x04_BITSET = 0x40;
+static constexpr RegMask REG_0x04_AFEMOD = 0x30;
+static constexpr RegMask REG_0x04_FILTER = 0x0c;
+static constexpr RegMask REG_0x04_FESET = 0x03;
+
+static constexpr RegShift REG_0x04S_AFEMOD = 4;
+
+static constexpr RegAddr REG_0x05 = 0x05;
+static constexpr RegMask REG_0x05_DPIHW = 0xc0;
+static constexpr RegMask REG_0x05_DPIHW_600 = 0x00;
+static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40;
+static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80;
+static constexpr RegMask REG_0x05_MTLLAMP = 0x30;
+static constexpr RegMask REG_0x05_GMMENB = 0x08;
+static constexpr RegMask REG_0x05_MTLBASE = 0x03;
+
+static constexpr RegAddr REG_0x06 = 0x06;
+static constexpr RegMask REG_0x06_SCANMOD = 0xe0;
+static constexpr RegShift REG_0x06S_SCANMOD = 5;
+static constexpr RegMask REG_0x06_PWRBIT = 0x10;
+static constexpr RegMask REG_0x06_GAIN4 = 0x08;
+static constexpr RegMask REG_0x06_OPTEST = 0x07;
+
+static constexpr RegMask REG_0x07_SRAMSEL = 0x08;
+static constexpr RegMask REG_0x07_FASTDMA = 0x04;
+static constexpr RegMask REG_0x07_DMASEL = 0x02;
+static constexpr RegMask REG_0x07_DMARDWR = 0x01;
+
+static constexpr RegMask REG_0x08_DECFLAG = 0x40;
+static constexpr RegMask REG_0x08_GMMFFR = 0x20;
+static constexpr RegMask REG_0x08_GMMFFG = 0x10;
+static constexpr RegMask REG_0x08_GMMFFB = 0x08;
+static constexpr RegMask REG_0x08_GMMZR = 0x04;
+static constexpr RegMask REG_0x08_GMMZG = 0x02;
+static constexpr RegMask REG_0x08_GMMZB = 0x01;
+
+static constexpr RegMask REG_0x09_MCNTSET = 0xc0;
+static constexpr RegMask REG_0x09_CLKSET = 0x30;
+static constexpr RegMask REG_0x09_BACKSCAN = 0x08;
+static constexpr RegMask REG_0x09_ENHANCE = 0x04;
+static constexpr RegMask REG_0x09_SHORTTG = 0x02;
+static constexpr RegMask REG_0x09_NWAIT = 0x01;
+
+static constexpr RegShift REG_0x09S_MCNTSET = 6;
+static constexpr RegShift REG_0x09S_CLKSET = 4;
+
+
+static constexpr RegMask REG_0x0A_SRAMBUF = 0x01;
+
+static constexpr RegAddr REG_0x0D = 0x0d;
+static constexpr RegMask REG_0x0D_CLRLNCNT = 0x01;
+
+static constexpr RegMask REG_0x16_CTRLHI = 0x80;
+static constexpr RegMask REG_0x16_TOSHIBA = 0x40;
+static constexpr RegMask REG_0x16_TGINV = 0x20;
+static constexpr RegMask REG_0x16_CK1INV = 0x10;
+static constexpr RegMask REG_0x16_CK2INV = 0x08;
+static constexpr RegMask REG_0x16_CTRLINV = 0x04;
+static constexpr RegMask REG_0x16_CKDIS = 0x02;
+static constexpr RegMask REG_0x16_CTRLDIS = 0x01;
+
+static constexpr RegMask REG_0x17_TGMODE = 0xc0;
+static constexpr RegMask REG_0x17_TGMODE_NO_DUMMY = 0x00;
+static constexpr RegMask REG_0x17_TGMODE_REF = 0x40;
+static constexpr RegMask REG_0x17_TGMODE_XPA = 0x80;
+static constexpr RegMask REG_0x17_TGW = 0x3f;
+static constexpr RegShift REG_0x17S_TGW = 0;
+
+static constexpr RegMask REG_0x18_CNSET = 0x80;
+static constexpr RegMask REG_0x18_DCKSEL = 0x60;
+static constexpr RegMask REG_0x18_CKTOGGLE = 0x10;
+static constexpr RegMask REG_0x18_CKDELAY = 0x0c;
+static constexpr RegMask REG_0x18_CKSEL = 0x03;
+
+static constexpr RegMask REG_0x1A_MANUAL3 = 0x02;
+static constexpr RegMask REG_0x1A_MANUAL1 = 0x01;
+static constexpr RegMask REG_0x1A_CK4INV = 0x08;
+static constexpr RegMask REG_0x1A_CK3INV = 0x04;
+static constexpr RegMask REG_0x1A_LINECLP = 0x02;
+
+static constexpr RegMask REG_0x1C_TGTIME = 0x07;
+
+static constexpr RegMask REG_0x1D_CK4LOW = 0x80;
+static constexpr RegMask REG_0x1D_CK3LOW = 0x40;
+static constexpr RegMask REG_0x1D_CK1LOW = 0x20;
+static constexpr RegMask REG_0x1D_TGSHLD = 0x1f;
+static constexpr RegShift REG_0x1DS_TGSHLD = 0;
+
+
+static constexpr RegAddr REG_0x1E = 0x1e;
+static constexpr RegMask REG_0x1E_WDTIME = 0xf0;
+static constexpr RegShift REG_0x1ES_WDTIME = 4;
+static constexpr RegMask REG_0x1E_LINESEL = 0x0f;
+static constexpr RegShift REG_0x1ES_LINESEL = 0;
+
+static constexpr RegAddr REG_EXPR = 0x10;
+static constexpr RegAddr REG_EXPG = 0x12;
+static constexpr RegAddr REG_EXPB = 0x14;
+static constexpr RegAddr REG_STEPNO = 0x21;
+static constexpr RegAddr REG_FWDSTEP = 0x22;
+static constexpr RegAddr REG_BWDSTEP = 0x23;
+static constexpr RegAddr REG_FASTNO = 0x24;
+static constexpr RegAddr REG_LINCNT = 0x25;
+static constexpr RegAddr REG_DPISET = 0x2c;
+static constexpr RegAddr REG_STRPIXEL = 0x30;
+static constexpr RegAddr REG_ENDPIXEL = 0x32;
+static constexpr RegAddr REG_MAXWD = 0x35;
+static constexpr RegAddr REG_LPERIOD = 0x38;
+
+static constexpr RegAddr REG_0x40 = 0x40;
+static constexpr RegMask REG_0x40_HISPDFLG = 0x04;
+static constexpr RegMask REG_0x40_MOTMFLG = 0x02;
+static constexpr RegMask REG_0x40_DATAENB = 0x01;
+
+static constexpr RegMask REG_0x41_PWRBIT = 0x80;
+static constexpr RegMask REG_0x41_BUFEMPTY = 0x40;
+static constexpr RegMask REG_0x41_FEEDFSH = 0x20;
+static constexpr RegMask REG_0x41_SCANFSH = 0x10;
+static constexpr RegMask REG_0x41_HOMESNR = 0x08;
+static constexpr RegMask REG_0x41_LAMPSTS = 0x04;
+static constexpr RegMask REG_0x41_FEBUSY = 0x02;
+static constexpr RegMask REG_0x41_MOTORENB = 0x01;
+
+static constexpr RegMask REG_0x58_VSMP = 0xf8;
+static constexpr RegShift REG_0x58S_VSMP = 3;
+static constexpr RegMask REG_0x58_VSMPW = 0x07;
+static constexpr RegShift REG_0x58S_VSMPW = 0;
+
+static constexpr RegMask REG_0x59_BSMP = 0xf8;
+static constexpr RegShift REG_0x59S_BSMP = 3;
+static constexpr RegMask REG_0x59_BSMPW = 0x07;
+static constexpr RegShift REG_0x59S_BSMPW = 0;
+
+static constexpr RegMask REG_0x5A_ADCLKINV = 0x80;
+static constexpr RegMask REG_0x5A_RLCSEL = 0x40;
+static constexpr RegMask REG_0x5A_CDSREF = 0x30;
+static constexpr RegShift REG_0x5AS_CDSREF = 4;
+static constexpr RegMask REG_0x5A_RLC = 0x0f;
+static constexpr RegShift REG_0x5AS_RLC = 0;
+
+static constexpr RegMask REG_0x5E_DECSEL = 0xe0;
+static constexpr RegShift REG_0x5ES_DECSEL = 5;
+static constexpr RegMask REG_0x5E_STOPTIM = 0x1f;
+static constexpr RegShift REG_0x5ES_STOPTIM = 0;
+
+static constexpr RegMask REG_0x60_ZIMOD = 0x1f;
+static constexpr RegMask REG_0x61_Z1MOD = 0xff;
+static constexpr RegMask REG_0x62_Z1MOD = 0xff;
+
+static constexpr RegMask REG_0x63_Z2MOD = 0x1f;
+static constexpr RegMask REG_0x64_Z2MOD = 0xff;
+static constexpr RegMask REG_0x65_Z2MOD = 0xff;
+
+static constexpr RegMask REG_0x67_STEPSEL = 0xc0;
+static constexpr RegMask REG_0x67_FULLSTEP = 0x00;
+static constexpr RegMask REG_0x67_HALFSTEP = 0x40;
+static constexpr RegMask REG_0x67_QUATERSTEP = 0x80;
+static constexpr RegMask REG_0x67_MTRPWM = 0x3f;
+
+static constexpr RegMask REG_0x68_FSTPSEL = 0xc0;
+static constexpr RegMask REG_0x68_FULLSTEP = 0x00;
+static constexpr RegMask REG_0x68_HALFSTEP = 0x40;
+static constexpr RegMask REG_0x68_QUATERSTEP = 0x80;
+static constexpr RegMask REG_0x68_FASTPWM = 0x3f;
+
+static constexpr RegMask REG_0x6B_MULTFILM = 0x80;
+static constexpr RegMask REG_0x6B_GPOM13 = 0x40;
+static constexpr RegMask REG_0x6B_GPOM12 = 0x20;
+static constexpr RegMask REG_0x6B_GPOM11 = 0x10;
+static constexpr RegMask REG_0x6B_GPO18 = 0x02;
+static constexpr RegMask REG_0x6B_GPO17 = 0x01;
+
+static constexpr RegAddr REG_0x6B = 0x6b;
+
+static constexpr RegAddr REG_0x6C = 0x6c;
+static constexpr RegMask REG_0x6C_GPIOH = 0xff;
+static constexpr RegMask REG_0x6C_GPIOL = 0xff;
+
+static constexpr RegAddr REG_0x6D = 0x6d;
+static constexpr RegAddr REG_0x6E = 0x6e;
+static constexpr RegAddr REG_0x6F = 0x6f;
+
+static constexpr RegMask REG_0x87_LEDADD = 0x04;
+
+} // namespace gl841
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL841_REGISTERS_H
diff --git a/backend/genesys/gl843.cpp b/backend/genesys/gl843.cpp
new file mode 100644
index 0000000..f83ac8d
--- /dev/null
+++ b/backend/genesys/gl843.cpp
@@ -0,0 +1,3060 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr>
+
+
+ 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 "gl843_registers.h"
+#include "gl843.h"
+#include "test_settings.h"
+
+#include <string>
+#include <vector>
+
+namespace genesys {
+namespace gl843 {
+
+// Set address for writing data
+static void gl843_set_buffer_address(Genesys_Device* dev, uint32_t addr)
+{
+ DBG_HELPER_ARGS(dbg, "setting address to 0x%05x", addr & 0xffff);
+
+ dev->interface->write_register(0x5b, ((addr >> 8) & 0xff));
+ dev->interface->write_register(0x5c, (addr & 0xff));
+}
+
+/**
+ * compute the step multiplier used
+ */
+static int gl843_get_step_multiplier(Genesys_Register_Set* regs)
+{
+ GenesysRegister *r = sanei_genesys_get_address(regs, REG_0x9D);
+ int value = 1;
+ if (r != nullptr)
+ {
+ switch (r->value & 0x0c)
+ {
+ case 0x04:
+ value = 2;
+ break;
+ case 0x08:
+ value = 4;
+ break;
+ default:
+ value = 1;
+ }
+ }
+ DBG(DBG_io, "%s: step multiplier is %d\n", __func__, value);
+ return value;
+}
+
+/** copy sensor specific settings */
+static void gl843_setup_sensor(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs)
+{
+ DBG_HELPER(dbg);
+ for (const auto& custom_reg : sensor.custom_regs) {
+ regs->set8(custom_reg.address, custom_reg.value);
+ }
+ if (!(dev->model->flags & GENESYS_FLAG_FULL_HWDPI_MODE) &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300 &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7500I)
+ {
+ regs->set8(0x7d, 0x90);
+ }
+
+ dev->segment_order = sensor.segment_order;
+}
+
+
+/** @brief set all registers to default values .
+ * This function is called only once at the beginning and
+ * fills register startup values for registers reused across scans.
+ * Those that are rarely modified or not modified are written
+ * individually.
+ * @param dev device structure holding register set to initialize
+ */
+static void
+gl843_init_registers (Genesys_Device * dev)
+{
+ // Within this function SENSOR_DEF marker documents that a register is part
+ // of the sensors definition and the actual value is set in
+ // gl843_setup_sensor().
+
+ // 0x6c, 0x6d, 0x6e, 0x6f, 0xa6, 0xa7, 0xa8, 0xa9 are defined in the Gpo sensor struct
+
+ DBG_HELPER(dbg);
+
+ dev->reg.clear();
+
+ dev->reg.init_reg(0x01, 0x00);
+ dev->reg.init_reg(0x02, 0x78);
+ dev->reg.init_reg(0x03, 0x1f);
+ if (dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ dev->reg.init_reg(0x03, 0x1d);
+ }
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ dev->reg.init_reg(0x03, 0x1c);
+ }
+
+ dev->reg.init_reg(0x04, 0x10);
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I)
+ {
+ dev->reg.init_reg(0x04, 0x22);
+ }
+
+ // fine tune upon device description
+ dev->reg.init_reg(0x05, 0x80);
+ if (dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ dev->reg.init_reg(0x05, 0x08);
+ }
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+ sanei_genesys_set_dpihw(dev->reg, sensor, sensor.optical_res);
+
+ // TODO: on 8600F the windows driver turns off GAIN4 which is recommended
+ dev->reg.init_reg(0x06, 0xd8); /* SCANMOD=110, PWRBIT and GAIN4 */
+ if (dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ dev->reg.init_reg(0x06, 0xd8); /* SCANMOD=110, PWRBIT and GAIN4 */
+ }
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I) {
+ dev->reg.init_reg(0x06, 0xd0);
+ }
+ if (dev->model->model_id == ModelId::CANON_4400F ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I)
+ {
+ dev->reg.init_reg(0x06, 0xf0); /* SCANMOD=111, PWRBIT and no GAIN4 */
+ }
+
+ dev->reg.init_reg(0x08, 0x00);
+ dev->reg.init_reg(0x09, 0x00);
+ dev->reg.init_reg(0x0a, 0x00);
+ if (dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ dev->reg.init_reg(0x0a, 0x18);
+ }
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ dev->reg.init_reg(0x0a, 0x10);
+ }
+
+ // This register controls clock and RAM settings and is further modified in
+ // gl843_boot
+ dev->reg.init_reg(0x0b, 0x6a);
+
+ if (dev->model->model_id == ModelId::CANON_4400F) {
+ dev->reg.init_reg(0x0b, 0x69); // 16M only
+ }
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ dev->reg.init_reg(0x0b, 0x89);
+ }
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I) {
+ dev->reg.init_reg(0x0b, 0x2a);
+ }
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) {
+ dev->reg.init_reg(0x0b, 0x4a);
+ }
+ if (dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ dev->reg.init_reg(0x0b, 0x69);
+ }
+
+ if (dev->model->model_id != ModelId::CANON_8400F &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300)
+ {
+ dev->reg.init_reg(0x0c, 0x00);
+ }
+
+ // EXPR[0:15], EXPG[0:15], EXPB[0:15]: Exposure time settings.
+ dev->reg.init_reg(0x10, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x11, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x12, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x13, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x14, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x15, 0x00); // SENSOR_DEF
+ if (dev->model->model_id == ModelId::CANON_4400F ||
+ dev->model->model_id == ModelId::CANON_8600F)
+ {
+ dev->reg.set16(REG_EXPR, 0x9c40);
+ dev->reg.set16(REG_EXPG, 0x9c40);
+ dev->reg.set16(REG_EXPB, 0x9c40);
+ }
+ if (dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ dev->reg.set16(REG_EXPR, 0x2c09);
+ dev->reg.set16(REG_EXPG, 0x22b8);
+ dev->reg.set16(REG_EXPB, 0x10f0);
+ }
+
+ // CCD signal settings.
+ dev->reg.init_reg(0x16, 0x33); // SENSOR_DEF
+ dev->reg.init_reg(0x17, 0x1c); // SENSOR_DEF
+ dev->reg.init_reg(0x18, 0x10); // SENSOR_DEF
+
+ // EXPDMY[0:7]: Exposure time of dummy lines.
+ dev->reg.init_reg(0x19, 0x2a); // SENSOR_DEF
+
+ // Various CCD clock settings.
+ dev->reg.init_reg(0x1a, 0x04); // SENSOR_DEF
+ dev->reg.init_reg(0x1b, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x1c, 0x20); // SENSOR_DEF
+ dev->reg.init_reg(0x1d, 0x04); // SENSOR_DEF
+
+ dev->reg.init_reg(0x1e, 0x10);
+ if (dev->model->model_id == ModelId::CANON_4400F ||
+ dev->model->model_id == ModelId::CANON_8600F)
+ {
+ dev->reg.init_reg(0x1e, 0x20);
+ }
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ dev->reg.init_reg(0x1e, 0xa0);
+ }
+
+ dev->reg.init_reg(0x1f, 0x01);
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ dev->reg.init_reg(0x1f, 0xff);
+ }
+
+ dev->reg.init_reg(0x20, 0x10);
+ dev->reg.init_reg(0x21, 0x04);
+
+ dev->reg.init_reg(0x22, 0x10);
+ dev->reg.init_reg(0x23, 0x10);
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ dev->reg.init_reg(0x22, 0xc8);
+ dev->reg.init_reg(0x23, 0xc8);
+ }
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ dev->reg.init_reg(0x22, 0x50);
+ dev->reg.init_reg(0x23, 0x50);
+ }
+
+ dev->reg.init_reg(0x24, 0x04);
+ dev->reg.init_reg(0x25, 0x00);
+ dev->reg.init_reg(0x26, 0x00);
+ dev->reg.init_reg(0x27, 0x00);
+ dev->reg.init_reg(0x2c, 0x02);
+ dev->reg.init_reg(0x2d, 0x58);
+ // BWHI[0:7]: high level of black and white threshold
+ dev->reg.init_reg(0x2e, 0x80);
+ // BWLOW[0:7]: low level of black and white threshold
+ dev->reg.init_reg(0x2f, 0x80);
+ dev->reg.init_reg(0x30, 0x00);
+ dev->reg.init_reg(0x31, 0x14);
+ dev->reg.init_reg(0x32, 0x27);
+ dev->reg.init_reg(0x33, 0xec);
+
+ // DUMMY: CCD dummy and optically black pixel count
+ dev->reg.init_reg(0x34, 0x24);
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ dev->reg.init_reg(0x34, 0x14);
+ }
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I)
+ {
+ dev->reg.init_reg(0x34, 0x3c);
+ }
+
+ // MAXWD: If available buffer size is less than 2*MAXWD words, then
+ // "buffer full" state will be set.
+ dev->reg.init_reg(0x35, 0x00);
+ dev->reg.init_reg(0x36, 0xff);
+ dev->reg.init_reg(0x37, 0xff);
+
+ // LPERIOD: Line period or exposure time for CCD or CIS.
+ dev->reg.init_reg(0x38, 0x55); // SENSOR_DEF
+ dev->reg.init_reg(0x39, 0xf0); // SENSOR_DEF
+
+ // FEEDL[0:24]: The number of steps of motor movement.
+ dev->reg.init_reg(0x3d, 0x00);
+ dev->reg.init_reg(0x3e, 0x00);
+ dev->reg.init_reg(0x3f, 0x01);
+
+ // Latch points for high and low bytes of R, G and B channels of AFE. If
+ // multiple clocks per pixel are consumed, then the setting defines during
+ // which clock the corresponding value will be read.
+ // RHI[0:4]: The latch point for high byte of R channel.
+ // RLOW[0:4]: The latch point for low byte of R channel.
+ // GHI[0:4]: The latch point for high byte of G channel.
+ // GLOW[0:4]: The latch point for low byte of G channel.
+ // BHI[0:4]: The latch point for high byte of B channel.
+ // BLOW[0:4]: The latch point for low byte of B channel.
+ dev->reg.init_reg(0x52, 0x01); // SENSOR_DEF
+ dev->reg.init_reg(0x53, 0x04); // SENSOR_DEF
+ dev->reg.init_reg(0x54, 0x07); // SENSOR_DEF
+ dev->reg.init_reg(0x55, 0x0a); // SENSOR_DEF
+ dev->reg.init_reg(0x56, 0x0d); // SENSOR_DEF
+ dev->reg.init_reg(0x57, 0x10); // SENSOR_DEF
+
+ // VSMP[0:4]: The position of the image sampling pulse for AFE in cycles.
+ // VSMPW[0:2]: The length of the image sampling pulse for AFE in cycles.
+ dev->reg.init_reg(0x58, 0x1b); // SENSOR_DEF
+
+ dev->reg.init_reg(0x59, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x5a, 0x40); // SENSOR_DEF
+
+ // 0x5b-0x5c: GMMADDR[0:15] address for gamma or motor tables download
+ // SENSOR_DEF
+
+ // DECSEL[0:2]: The number of deceleration steps after touching home sensor
+ // STOPTIM[0:4]: The stop duration between change of directions in
+ // backtracking
+ dev->reg.init_reg(0x5e, 0x23);
+ if (dev->model->model_id == ModelId::CANON_4400F) {
+ dev->reg.init_reg(0x5e, 0x3f);
+ }
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ dev->reg.init_reg(0x5e, 0x85);
+ }
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ dev->reg.init_reg(0x5e, 0x1f);
+ }
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I)
+ {
+ dev->reg.init_reg(0x5e, 0x01);
+ }
+
+ //FMOVDEC: The number of deceleration steps in table 5 for auto-go-home
+ dev->reg.init_reg(0x5f, 0x01);
+ if (dev->model->model_id == ModelId::CANON_4400F) {
+ dev->reg.init_reg(0x5f, 0xf0);
+ }
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ dev->reg.init_reg(0x5f, 0xf0);
+ }
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I)
+ {
+ dev->reg.init_reg(0x5f, 0x01);
+ }
+
+ // Z1MOD[0:20]
+ dev->reg.init_reg(0x60, 0x00);
+ dev->reg.init_reg(0x61, 0x00);
+ dev->reg.init_reg(0x62, 0x00);
+
+ // Z2MOD[0:20]
+ dev->reg.init_reg(0x63, 0x00);
+ dev->reg.init_reg(0x64, 0x00);
+ dev->reg.init_reg(0x65, 0x00);
+
+ // STEPSEL[0:1]. Motor movement step mode selection for tables 1-3 in
+ // scanning mode.
+ // MTRPWM[0:5]. Motor phase PWM duty cycle setting for tables 1-3
+ dev->reg.init_reg(0x67, 0x7f);
+ // FSTPSEL[0:1]: Motor movement step mode selection for tables 4-5 in
+ // command mode.
+ // FASTPWM[5:0]: Motor phase PWM duty cycle setting for tables 4-5
+ dev->reg.init_reg(0x68, 0x7f);
+
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300) {
+ dev->reg.init_reg(0x67, 0x80);
+ dev->reg.init_reg(0x68, 0x80);
+ }
+
+ // FSHDEC[0:7]: The number of deceleration steps after scanning is finished
+ // (table 3)
+ dev->reg.init_reg(0x69, 0x01);
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ dev->reg.init_reg(0x69, 64);
+ }
+
+ // FMOVNO[0:7] The number of acceleration or deceleration steps for fast
+ // moving (table 4)
+ dev->reg.init_reg(0x6a, 0x04);
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ dev->reg.init_reg(0x69, 64);
+ }
+
+ // GPIO-related register bits
+ dev->reg.init_reg(0x6b, 0x30);
+ if (dev->model->model_id == ModelId::CANON_4400F ||
+ dev->model->model_id == ModelId::CANON_8600F)
+ {
+ dev->reg.init_reg(0x6b, 0x72);
+ }
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ dev->reg.init_reg(0x6b, 0xb1);
+ }
+ if (dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ dev->reg.init_reg(0x6b, 0xf4);
+ }
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I)
+ {
+ dev->reg.init_reg(0x6b, 0x31);
+ }
+
+ // 0x6c, 0x6d, 0x6e, 0x6f are set according to gpio tables. See
+ // gl843_init_gpio.
+
+ // RSH[0:4]: The position of rising edge of CCD RS signal in cycles
+ // RSL[0:4]: The position of falling edge of CCD RS signal in cycles
+ // CPH[0:4]: The position of rising edge of CCD CP signal in cycles.
+ // CPL[0:4]: The position of falling edge of CCD CP signal in cycles
+ dev->reg.init_reg(0x70, 0x01); // SENSOR_DEF
+ dev->reg.init_reg(0x71, 0x03); // SENSOR_DEF
+ dev->reg.init_reg(0x72, 0x04); // SENSOR_DEF
+ dev->reg.init_reg(0x73, 0x05); // SENSOR_DEF
+
+ if (dev->model->model_id == ModelId::CANON_4400F) {
+ dev->reg.init_reg(0x70, 0x01);
+ dev->reg.init_reg(0x71, 0x03);
+ dev->reg.init_reg(0x72, 0x01);
+ dev->reg.init_reg(0x73, 0x03);
+ }
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ dev->reg.init_reg(0x70, 0x01);
+ dev->reg.init_reg(0x71, 0x03);
+ dev->reg.init_reg(0x72, 0x03);
+ dev->reg.init_reg(0x73, 0x04);
+ }
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ dev->reg.init_reg(0x70, 0x00);
+ dev->reg.init_reg(0x71, 0x02);
+ dev->reg.init_reg(0x72, 0x02);
+ dev->reg.init_reg(0x73, 0x04);
+ }
+ if (dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ dev->reg.init_reg(0x70, 0x00);
+ dev->reg.init_reg(0x71, 0x02);
+ dev->reg.init_reg(0x72, 0x00);
+ dev->reg.init_reg(0x73, 0x00);
+ }
+
+ // CK1MAP[0:17], CK3MAP[0:17], CK4MAP[0:17]: CCD clock bit mapping setting.
+ dev->reg.init_reg(0x74, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x75, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x76, 0x3c); // SENSOR_DEF
+ dev->reg.init_reg(0x77, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x78, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x79, 0x9f); // SENSOR_DEF
+ dev->reg.init_reg(0x7a, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x7b, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x7c, 0x55); // SENSOR_DEF
+
+ // various AFE settings
+ dev->reg.init_reg(0x7d, 0x00);
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ dev->reg.init_reg(0x7d, 0x20);
+ }
+
+ // GPOLED[x]: LED vs GPIO settings
+ dev->reg.init_reg(0x7e, 0x00);
+
+ // BSMPDLY, VSMPDLY
+ // LEDCNT[0:1]: Controls led blinking and its period
+ dev->reg.init_reg(0x7f, 0x00);
+
+ // VRHOME, VRMOVE, VRBACK, VRSCAN: Vref settings of the motor driver IC for
+ // moving in various situations.
+ dev->reg.init_reg(0x80, 0x00);
+ if (dev->model->model_id == ModelId::CANON_4400F) {
+ dev->reg.init_reg(0x80, 0x0c);
+ }
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ dev->reg.init_reg(0x80, 0x28);
+ }
+ if (dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ dev->reg.init_reg(0x80, 0x50);
+ }
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I)
+ {
+ dev->reg.init_reg(0x80, 0x0f);
+ }
+
+ if (dev->model->model_id != ModelId::CANON_4400F) {
+ dev->reg.init_reg(0x81, 0x00);
+ dev->reg.init_reg(0x82, 0x00);
+ dev->reg.init_reg(0x83, 0x00);
+ dev->reg.init_reg(0x84, 0x00);
+ dev->reg.init_reg(0x85, 0x00);
+ dev->reg.init_reg(0x86, 0x00);
+ }
+
+ dev->reg.init_reg(0x87, 0x00);
+ if (dev->model->model_id == ModelId::CANON_4400F ||
+ dev->model->model_id == ModelId::CANON_8400F ||
+ dev->model->model_id == ModelId::CANON_8600F)
+ {
+ dev->reg.init_reg(0x87, 0x02);
+ }
+
+ // MTRPLS[0:7]: The width of the ADF motor trigger signal pulse.
+ if (dev->model->model_id != ModelId::CANON_8400F &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300)
+ {
+ dev->reg.init_reg(0x94, 0xff);
+ }
+
+ // 0x95-0x97: SCANLEN[0:19]: Controls when paper jam bit is set in sheetfed
+ // scanners.
+
+ // ONDUR[0:15]: The duration of PWM ON phase for LAMP control
+ // OFFDUR[0:15]: The duration of PWM OFF phase for LAMP control
+ // both of the above are in system clocks
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ dev->reg.init_reg(0x98, 0x00);
+ dev->reg.init_reg(0x99, 0x00);
+ dev->reg.init_reg(0x9a, 0x00);
+ dev->reg.init_reg(0x9b, 0x00);
+ }
+ if (dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ // TODO: move to set for scan
+ dev->reg.init_reg(0x98, 0x03);
+ dev->reg.init_reg(0x99, 0x30);
+ dev->reg.init_reg(0x9a, 0x01);
+ dev->reg.init_reg(0x9b, 0x80);
+ }
+
+ // RMADLY[0:1], MOTLAG, CMODE, STEPTIM, MULDMYLN, IFRS
+ dev->reg.init_reg(0x9d, 0x04);
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I)
+ {
+ dev->reg.init_reg(0x9d, 0x00);
+ }
+ if (dev->model->model_id == ModelId::CANON_4400F ||
+ dev->model->model_id == ModelId::CANON_8400F ||
+ dev->model->model_id == ModelId::CANON_8600F ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ dev->reg.init_reg(0x9d, 0x08); // sets the multiplier for slope tables
+ }
+
+
+ // SEL3INV, TGSTIME[0:2], TGWTIME[0:2]
+ if (dev->model->model_id != ModelId::CANON_8400F &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300)
+ {
+ dev->reg.init_reg(0x9e, 0x00); // SENSOR_DEF
+ }
+
+ if (dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) {
+ dev->reg.init_reg(0xa2, 0x0f);
+ }
+
+ // RFHSET[0:4]: Refresh time of SDRAM in units of 2us
+ if (dev->model->model_id == ModelId::CANON_4400F ||
+ dev->model->model_id == ModelId::CANON_8600F)
+ {
+ dev->reg.init_reg(0xa2, 0x1f);
+ }
+
+ // 0xa6-0xa9: controls gpio, see gl843_gpio_init
+
+ // not documented
+ if (dev->model->model_id != ModelId::CANON_4400F &&
+ dev->model->model_id != ModelId::CANON_8400F &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300)
+ {
+ dev->reg.init_reg(0xaa, 0x00);
+ }
+
+ // GPOM9, MULSTOP[0-2], NODECEL, TB3TB1, TB5TB2, FIX16CLK. Not documented
+ if (dev->model->model_id != ModelId::CANON_8400F &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7200I &&
+ dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7300) {
+ dev->reg.init_reg(0xab, 0x50);
+ }
+ if (dev->model->model_id == ModelId::CANON_4400F) {
+ dev->reg.init_reg(0xab, 0x00);
+ }
+ if (dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ // BUG: this should apply to ModelId::CANON_CANOSCAN_8600F too, but due to previous bug
+ // the 8400F case overwrote it
+ dev->reg.init_reg(0xab, 0x40);
+ }
+
+ // VRHOME[3:2], VRMOVE[3:2], VRBACK[3:2]: Vref setting of the motor driver IC
+ // for various situations.
+ if (dev->model->model_id == ModelId::CANON_8600F ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C)
+ {
+ dev->reg.init_reg(0xac, 0x00);
+ }
+
+ dev->calib_reg = dev->reg;
+
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I) {
+ uint8_t data[32] = {
+ 0x8c, 0x8f, 0xc9, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x6a, 0x73, 0x63, 0x68, 0x69, 0x65, 0x6e, 0x00,
+ };
+
+ dev->interface->write_buffer(0x3c, 0x3ff000, data, 32,
+ ScannerInterface::FLAG_SWAP_REGISTERS);
+ }
+}
+
+// Send slope table for motor movement slope_table in machine byte order
+static void gl843_send_slope_table(Genesys_Device* dev, int table_nr,
+ const std::vector<uint16_t>& slope_table,
+ int steps)
+{
+ DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d", table_nr, steps);
+
+ int i;
+ char msg[10000];
+
+ std::vector<uint8_t> table(steps * 2);
+ for (i = 0; i < steps; i++)
+ {
+ table[i * 2] = slope_table[i] & 0xff;
+ table[i * 2 + 1] = slope_table[i] >> 8;
+ }
+
+ if (DBG_LEVEL >= DBG_io)
+ {
+ std::sprintf(msg, "write slope %d (%d)=", table_nr, steps);
+ for (i = 0; i < steps; i++) {
+ std::sprintf (msg+strlen(msg), "%d", slope_table[i]);
+ }
+ DBG(DBG_io, "%s: %s\n", __func__, msg);
+ }
+
+ if (dev->interface->is_mock()) {
+ dev->interface->record_slope_table(table_nr, slope_table);
+ }
+
+ // slope table addresses are fixed : 0x40000, 0x48000, 0x50000, 0x58000, 0x60000
+ // XXX STEF XXX USB 1.1 ? sanei_genesys_write_0x8c (dev, 0x0f, 0x14);
+ dev->interface->write_gamma(0x28, 0x40000 + 0x8000 * table_nr, table.data(), steps * 2,
+ ScannerInterface::FLAG_SWAP_REGISTERS);
+
+ // FIXME: remove this when updating tests
+ gl843_set_buffer_address(dev, 0);
+}
+
+static void gl843_set_ad_fe(Genesys_Device* dev)
+{
+ for (const auto& reg : dev->frontend.regs) {
+ dev->interface->write_fe_register(reg.address, reg.value);
+ }
+}
+
+// Set values of analog frontend
+void CommandSetGl843::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const
+{
+ DBG_HELPER_ARGS(dbg, "%s", set == AFE_INIT ? "init" :
+ set == AFE_SET ? "set" :
+ set == AFE_POWER_SAVE ? "powersave" : "huh?");
+ (void) sensor;
+ int i;
+
+ if (set == AFE_INIT)
+ {
+ DBG(DBG_proc, "%s(): setting DAC %u\n", __func__,
+ static_cast<unsigned>(dev->model->adc_id));
+ dev->frontend = dev->frontend_initial;
+ dev->frontend_is_init = true;
+ }
+
+ // check analog frontend type
+ // FIXME: looks like we write to that register with initial data
+ uint8_t fe_type = dev->interface->read_register(REG_0x04) & REG_0x04_FESET;
+ if (fe_type == 2) {
+ gl843_set_ad_fe(dev);
+ return;
+ }
+ if (fe_type != 0) {
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "unsupported frontend type %d", fe_type);
+ }
+
+ DBG(DBG_proc, "%s(): frontend reset complete\n", __func__);
+
+ for (i = 1; i <= 3; i++)
+ {
+ // FIXME: the check below is just historical artifact, we can remove it when convenient
+ if (!dev->frontend_is_init) {
+ dev->interface->write_fe_register(i, 0x00);
+ } else {
+ dev->interface->write_fe_register(i, dev->frontend.regs.get_value(0x00 + i));
+ }
+ }
+ for (const auto& reg : sensor.custom_fe_regs) {
+ dev->interface->write_fe_register(reg.address, reg.value);
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ // FIXME: the check below is just historical artifact, we can remove it when convenient
+ if (!dev->frontend_is_init) {
+ dev->interface->write_fe_register(0x20 + i, 0x00);
+ } else {
+ dev->interface->write_fe_register(0x20 + i, dev->frontend.get_offset(i));
+ }
+ }
+
+ if (dev->model->sensor_id == SensorId::CCD_KVSS080) {
+ for (i = 0; i < 3; i++)
+ {
+ // FIXME: the check below is just historical artifact, we can remove it when convenient
+ if (!dev->frontend_is_init) {
+ dev->interface->write_fe_register(0x24 + i, 0x00);
+ } else {
+ dev->interface->write_fe_register(0x24 + i, dev->frontend.regs.get_value(0x24 + i));
+ }
+ }
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ // FIXME: the check below is just historical artifact, we can remove it when convenient
+ if (!dev->frontend_is_init) {
+ dev->interface->write_fe_register(0x28 + i, 0x00);
+ } else {
+ dev->interface->write_fe_register(0x28 + i, dev->frontend.get_gain(i));
+ }
+ }
+}
+
+
+static void gl843_init_motor_regs_scan(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const Motor_Profile& motor_profile,
+ unsigned int exposure,
+ unsigned scan_yres,
+ unsigned int scan_lines,
+ unsigned int scan_dummy,
+ unsigned int feed_steps,
+ MotorFlag flags)
+{
+ DBG_HELPER_ARGS(dbg, "exposure=%d, scan_yres=%d, step_type=%d, scan_lines=%d, scan_dummy=%d, "
+ "feed_steps=%d, flags=%x",
+ exposure, scan_yres, static_cast<unsigned>(motor_profile.step_type),
+ scan_lines, scan_dummy, feed_steps, static_cast<unsigned>(flags));
+
+ int use_fast_fed, coeff;
+ unsigned int lincnt;
+ unsigned feedl, dist;
+ GenesysRegister *r;
+ uint32_t z1, z2;
+
+ /* get step multiplier */
+ unsigned step_multiplier = gl843_get_step_multiplier (reg);
+
+ use_fast_fed = 0;
+
+ if ((scan_yres >= 300 && feed_steps > 900) || (has_flag(flags, MotorFlag::FEED))) {
+ use_fast_fed = 1;
+ }
+
+ lincnt=scan_lines;
+ reg->set24(REG_LINCNT, lincnt);
+ DBG(DBG_io, "%s: lincnt=%d\n", __func__, lincnt);
+
+ /* compute register 02 value */
+ r = sanei_genesys_get_address(reg, REG_0x02);
+ r->value = 0x00;
+ sanei_genesys_set_motor_power(*reg, true);
+
+ if (use_fast_fed) {
+ r->value |= REG_0x02_FASTFED;
+ } else {
+ r->value &= ~REG_0x02_FASTFED;
+ }
+
+ /* in case of automatic go home, move until home sensor */
+ if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) {
+ r->value |= REG_0x02_AGOHOME | REG_0x02_NOTHOME;
+ }
+
+ /* disable backtracking */
+ if (has_flag(flags, MotorFlag::DISABLE_BUFFER_FULL_MOVE)
+ ||(scan_yres>=2400 && dev->model->model_id != ModelId::CANON_4400F)
+ ||(scan_yres>=sensor.optical_res))
+ {
+ r->value |= REG_0x02_ACDCDIS;
+ }
+
+ if (has_flag(flags, MotorFlag::REVERSE)) {
+ r->value |= REG_0x02_MTRREV;
+ } else {
+ r->value &= ~REG_0x02_MTRREV;
+ }
+
+ /* scan and backtracking slope table */
+ auto scan_table = sanei_genesys_slope_table(dev->model->asic_type, scan_yres, exposure,
+ dev->motor.base_ydpi, step_multiplier,
+ motor_profile);
+
+ gl843_send_slope_table(dev, SCAN_TABLE, scan_table.table, scan_table.steps_count);
+ gl843_send_slope_table(dev, BACKTRACK_TABLE, scan_table.table, scan_table.steps_count);
+
+ reg->set8(REG_STEPNO, scan_table.steps_count / step_multiplier);
+ reg->set8(REG_FASTNO, scan_table.steps_count / step_multiplier);
+
+ // fast table
+ unsigned fast_yres = sanei_genesys_get_lowest_ydpi(dev);
+ auto fast_table = sanei_genesys_slope_table(dev->model->asic_type, fast_yres, exposure,
+ dev->motor.base_ydpi, step_multiplier,
+ motor_profile);
+ gl843_send_slope_table(dev, STOP_TABLE, fast_table.table, fast_table.steps_count);
+ gl843_send_slope_table(dev, FAST_TABLE, fast_table.table, fast_table.steps_count);
+ gl843_send_slope_table(dev, HOME_TABLE, fast_table.table, fast_table.steps_count);
+
+ reg->set8(REG_FSHDEC, fast_table.steps_count / step_multiplier);
+ reg->set8(REG_FMOVNO, fast_table.steps_count / step_multiplier);
+
+ /* substract acceleration distance from feedl */
+ feedl=feed_steps;
+ feedl <<= static_cast<unsigned>(motor_profile.step_type);
+
+ dist = scan_table.steps_count / step_multiplier;
+ if (use_fast_fed)
+ {
+ dist += (fast_table.steps_count / step_multiplier) * 2;
+ }
+ DBG(DBG_io2, "%s: acceleration distance=%d\n", __func__, dist);
+
+ /* get sure when don't insane value : XXX STEF XXX in this case we should
+ * fall back to single table move */
+ if (dist < feedl) {
+ feedl -= dist;
+ } else {
+ feedl = 1;
+ }
+
+ reg->set24(REG_FEEDL, feedl);
+ DBG(DBG_io, "%s: feedl=%d\n", __func__, feedl);
+
+ /* doesn't seem to matter that much */
+ sanei_genesys_calculate_zmod(use_fast_fed,
+ exposure,
+ scan_table.table,
+ scan_table.steps_count / step_multiplier,
+ feedl,
+ scan_table.steps_count / step_multiplier,
+ &z1,
+ &z2);
+ if(scan_yres>600)
+ {
+ z1=0;
+ z2=0;
+ }
+
+ reg->set24(REG_Z1MOD, z1);
+ DBG(DBG_info, "%s: z1 = %d\n", __func__, z1);
+
+ reg->set24(REG_Z2MOD, z2);
+ DBG(DBG_info, "%s: z2 = %d\n", __func__, z2);
+
+ r = sanei_genesys_get_address(reg, REG_0x1E);
+ r->value &= 0xf0; /* 0 dummy lines */
+ r->value |= scan_dummy; /* dummy lines */
+
+ reg->set8_mask(REG_0x67, static_cast<unsigned>(motor_profile.step_type) << REG_0x67S_STEPSEL, 0xc0);
+ reg->set8_mask(REG_0x68, static_cast<unsigned>(motor_profile.step_type) << REG_0x68S_FSTPSEL, 0xc0);
+
+ // steps for STOP table
+ reg->set8(REG_FMOVDEC, fast_table.steps_count / step_multiplier);
+
+ /* Vref XXX STEF XXX : optical divider or step type ? */
+ r = sanei_genesys_get_address (reg, 0x80);
+ if (!(dev->model->flags & GENESYS_FLAG_FULL_HWDPI_MODE))
+ {
+ r->value = 0x50;
+ coeff = sensor.get_hwdpi_divisor_for_dpi(scan_yres);
+ if (dev->model->motor_id == MotorId::KVSS080) {
+ if(coeff>=1)
+ {
+ r->value |= 0x05;
+ }
+ }
+ else {
+ switch(coeff)
+ {
+ case 4:
+ r->value |= 0x0a;
+ break;
+ case 2:
+ r->value |= 0x0f;
+ break;
+ case 1:
+ r->value |= 0x0f;
+ break;
+ }
+ }
+ }
+}
+
+
+/** @brief setup optical related registers
+ * start and pixels are expressed in optical sensor resolution coordinate
+ * space.
+ * @param dev device to use
+ * @param reg registers to set up
+ * @param exposure exposure time to use
+ * @param used_res scanning resolution used, may differ from
+ * scan's one
+ * @param start logical start pixel coordinate
+ * @param pixels logical number of pixels to use
+ * @param channels number of color channles used (1 or 3)
+ * @param depth bit depth of the scan (1, 8 or 16 bits)
+ * @param ccd_size_divisor true specifies how much x coordinates must be shrunk
+ * @param color_filter to choose the color channel used in gray scans
+ * @param flags to drive specific settings such no calibration, XPA use ...
+ */
+static void gl843_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, unsigned int exposure,
+ const ScanSession& session)
+{
+ DBG_HELPER_ARGS(dbg, "exposure=%d", exposure);
+ unsigned int dpihw;
+ unsigned int tgtime; /**> exposure time multiplier */
+ GenesysRegister *r;
+
+ /* tgtime */
+ tgtime = exposure / 65536 + 1;
+ DBG(DBG_io2, "%s: tgtime=%d\n", __func__, tgtime);
+
+ // to manage high resolution device while keeping good low resolution scanning speed, we make
+ // hardware dpi vary
+ dpihw = sensor.get_register_hwdpi(session.output_resolution);
+ DBG(DBG_io2, "%s: dpihw=%d\n", __func__, dpihw);
+
+ /* sensor parameters */
+ gl843_setup_sensor(dev, sensor, reg);
+
+ // resolution is divided according to CKSEL
+ unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel();
+ DBG(DBG_io2, "%s: ccd_pixels_per_system_pixel=%d\n", __func__, ccd_pixels_per_system_pixel);
+
+ dev->cmd_set->set_fe(dev, sensor, AFE_SET);
+
+ /* enable shading */
+ regs_set_optical_off(dev->model->asic_type, *reg);
+ r = sanei_genesys_get_address (reg, REG_0x01);
+ if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) ||
+ (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION ||
+ (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE)))
+ {
+ r->value &= ~REG_0x01_DVDSET;
+ } else {
+ r->value |= REG_0x01_DVDSET;
+ }
+
+ bool use_shdarea = dpihw > 600;
+ if (dev->model->model_id == ModelId::CANON_4400F) {
+ use_shdarea = session.params.xres <= 600;
+ } else if (dev->model->model_id == ModelId::CANON_8400F) {
+ use_shdarea = session.params.xres <= 400;
+ }
+ if (use_shdarea) {
+ r->value |= REG_0x01_SHDAREA;
+ } else {
+ r->value &= ~REG_0x01_SHDAREA;
+ }
+
+ r = sanei_genesys_get_address (reg, REG_0x03);
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ r->value |= REG_0x03_AVEENB;
+ } else {
+ r->value &= ~REG_0x03_AVEENB;
+ }
+
+ // FIXME: we probably don't need to set exposure to registers at this point. It was this way
+ // before a refactor.
+ sanei_genesys_set_lamp_power(dev, sensor, *reg,
+ !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP));
+
+ /* select XPA */
+ r->value &= ~REG_0x03_XPASEL;
+ if (has_flag(session.params.flags, ScanFlag::USE_XPA)) {
+ r->value |= REG_0x03_XPASEL;
+ }
+ reg->state.is_xpa_on = has_flag(session.params.flags, ScanFlag::USE_XPA);
+
+ /* BW threshold */
+ r = sanei_genesys_get_address(reg, REG_0x2E);
+ r->value = dev->settings.threshold;
+ r = sanei_genesys_get_address(reg, REG_0x2F);
+ r->value = dev->settings.threshold;
+
+ /* monochrome / color scan */
+ r = sanei_genesys_get_address(reg, REG_0x04);
+ switch (session.params.depth) {
+ case 8:
+ r->value &= ~(REG_0x04_LINEART | REG_0x04_BITSET);
+ break;
+ case 16:
+ r->value &= ~REG_0x04_LINEART;
+ r->value |= REG_0x04_BITSET;
+ break;
+ }
+
+ r->value &= ~(REG_0x04_FILTER | REG_0x04_AFEMOD);
+ if (session.params.channels == 1)
+ {
+ switch (session.params.color_filter)
+ {
+ case ColorFilter::RED:
+ r->value |= 0x14;
+ break;
+ case ColorFilter::BLUE:
+ r->value |= 0x1c;
+ break;
+ case ColorFilter::GREEN:
+ r->value |= 0x18;
+ break;
+ default:
+ break; // should not happen
+ }
+ } else {
+ switch (dev->frontend.layout.type) {
+ case FrontendType::WOLFSON:
+ r->value |= 0x10; // pixel by pixel
+ break;
+ case FrontendType::ANALOG_DEVICES:
+ r->value |= 0x20; // slow color pixel by pixel
+ break;
+ default:
+ throw SaneException("Invalid frontend type %d",
+ static_cast<unsigned>(dev->frontend.layout.type));
+ }
+ }
+
+ sanei_genesys_set_dpihw(*reg, sensor, dpihw);
+
+ if (should_enable_gamma(session, sensor)) {
+ reg->find_reg(REG_0x05).value |= REG_0x05_GMMENB;
+ } else {
+ reg->find_reg(REG_0x05).value &= ~REG_0x05_GMMENB;
+ }
+
+ unsigned dpiset = session.output_resolution * session.ccd_size_divisor *
+ ccd_pixels_per_system_pixel;
+
+ if (sensor.dpiset_override != 0) {
+ dpiset = sensor.dpiset_override;
+ }
+ reg->set16(REG_DPISET, dpiset);
+ DBG(DBG_io2, "%s: dpiset used=%d\n", __func__, dpiset);
+
+ reg->set16(REG_STRPIXEL, session.pixel_startx);
+ reg->set16(REG_ENDPIXEL, session.pixel_endx);
+
+ /* MAXWD is expressed in 2 words unit */
+ /* nousedspace = (mem_bank_range * 1024 / 256 -1 ) * 4; */
+ // BUG: the division by ccd_size_divisor likely does not make sense
+ reg->set24(REG_MAXWD, (session.output_line_bytes / session.ccd_size_divisor) >> 1);
+
+ reg->set16(REG_LPERIOD, exposure / tgtime);
+ DBG(DBG_io2, "%s: exposure used=%d\n", __func__, exposure/tgtime);
+
+ r = sanei_genesys_get_address (reg, REG_DUMMY);
+ r->value = sensor.dummy_pixel;
+}
+
+void CommandSetGl843::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const
+{
+ DBG_HELPER(dbg);
+ session.assert_computed();
+
+ int exposure;
+
+ int slope_dpi = 0;
+ int dummy = 0;
+
+ /* we enable true gray for cis scanners only, and just when doing
+ * scan since color calibration is OK for this mode
+ */
+
+ dummy = 0;
+ if (dev->model->model_id == ModelId::CANON_4400F && session.params.yres == 1200) {
+ dummy = 1;
+ }
+
+ /* slope_dpi */
+ /* cis color scan is effectively a gray scan with 3 gray lines per color line and a FILTER of 0 */
+ if (dev->model->is_cis)
+ slope_dpi = session.params.yres * session.params.channels;
+ else
+ slope_dpi = session.params.yres;
+ slope_dpi = slope_dpi * (1 + dummy);
+
+ /* scan_step_type */
+ exposure = sensor.exposure_lperiod;
+ if (exposure < 0) {
+ throw std::runtime_error("Exposure not defined in sensor definition");
+ }
+ const auto& motor_profile = sanei_genesys_get_motor_profile(*gl843_motor_profiles,
+ dev->model->motor_id,
+ exposure);
+
+ DBG(DBG_info, "%s : exposure=%d pixels\n", __func__, exposure);
+ DBG(DBG_info, "%s : scan_step_type=%d\n", __func__,
+ static_cast<unsigned>(motor_profile.step_type));
+
+ // now _LOGICAL_ optical values used are known, setup registers
+ gl843_init_optical_regs_scan(dev, sensor, reg, exposure, session);
+
+ /*** motor parameters ***/
+ MotorFlag mflags = MotorFlag::NONE;
+ if (has_flag(session.params.flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE)) {
+ mflags |= MotorFlag::DISABLE_BUFFER_FULL_MOVE;
+ }
+ if (has_flag(session.params.flags, ScanFlag::FEEDING)) {
+ mflags |= MotorFlag::FEED;
+ }
+ if (has_flag(session.params.flags, ScanFlag::USE_XPA)) {
+ mflags |= MotorFlag::USE_XPA;
+ }
+ if (has_flag(session.params.flags, ScanFlag::REVERSE)) {
+ mflags |= MotorFlag::REVERSE;
+ }
+
+ unsigned scan_lines = dev->model->is_cis ? session.output_line_count * session.params.channels
+ : session.output_line_count;
+
+ gl843_init_motor_regs_scan(dev, sensor, reg, motor_profile, exposure, slope_dpi,
+ scan_lines, dummy, session.params.starty, mflags);
+
+ dev->read_buffer.clear();
+ dev->read_buffer.alloc(session.buffer_size_read);
+
+ build_image_pipeline(dev, session);
+
+ dev->read_active = true;
+
+ dev->session = session;
+
+ dev->total_bytes_read = 0;
+ dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines;
+
+ DBG(DBG_info, "%s: total bytes to send = %zu\n", __func__, dev->total_bytes_to_read);
+}
+
+ScanSession CommandSetGl843::calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const
+{
+ DBG_HELPER(dbg);
+ debug_dump(DBG_info, settings);
+
+ int start;
+
+ /* we have 2 domains for ccd: xres below or above half ccd max dpi */
+ unsigned ccd_size_divisor = sensor.get_ccd_size_divisor_for_dpi(settings.xres);
+
+ if (settings.scan_method == ScanMethod::TRANSPARENCY ||
+ settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ start = static_cast<int>(dev->model->x_offset_ta);
+ } else {
+ start = static_cast<int>(dev->model->x_offset);
+ }
+
+ if (dev->model->model_id == ModelId::CANON_8600F)
+ {
+ // FIXME: this is probably just an artifact of a bug elsewhere
+ start /= ccd_size_divisor;
+ }
+
+ start += static_cast<int>(settings.tl_x);
+ start = static_cast<int>((start * sensor.optical_res) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = settings.xres;
+ session.params.yres = settings.yres;
+ session.params.startx = start; // not used
+ session.params.starty = 0; // not used
+ session.params.pixels = settings.pixels;
+ session.params.requested_pixels = settings.requested_pixels;
+ session.params.lines = settings.lines;
+ session.params.depth = settings.depth;
+ session.params.channels = settings.get_channels();
+ session.params.scan_method = settings.scan_method;
+ session.params.scan_mode = settings.scan_mode;
+ session.params.color_filter = settings.color_filter;
+ session.params.flags = ScanFlag::NONE;
+
+ compute_session(dev, session, sensor);
+
+ return session;
+}
+
+/**
+ * for fast power saving methods only, like disabling certain amplifiers
+ * @param dev device to use
+ * @param enable true to set inot powersaving
+ * */
+void CommandSetGl843::save_power(Genesys_Device* dev, bool enable) const
+{
+ DBG_HELPER_ARGS(dbg, "enable = %d", enable);
+
+ // switch KV-SS080 lamp off
+ if (dev->model->gpio_id == GpioId::KVSS080) {
+ uint8_t val = dev->interface->read_register(REG_0x6C);
+ if (enable) {
+ val &= 0xef;
+ } else {
+ val |= 0x10;
+ }
+ dev->interface->write_register(REG_0x6C, val);
+ }
+}
+
+void CommandSetGl843::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const
+{
+ (void) dev;
+ DBG_HELPER_ARGS(dbg, "delay = %d", delay);
+}
+
+static bool gl843_get_paper_sensor(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+
+ uint8_t val = dev->interface->read_register(REG_0x6D);
+
+ return (val & 0x1) == 0;
+}
+
+void CommandSetGl843::eject_document(Genesys_Device* dev) const
+{
+ (void) dev;
+ DBG_HELPER(dbg);
+}
+
+
+void CommandSetGl843::load_document(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ (void) dev;
+}
+
+/**
+ * detects end of document and adjust current scan
+ * to take it into account
+ * used by sheetfed scanners
+ */
+void CommandSetGl843::detect_document_end(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ bool paper_loaded = gl843_get_paper_sensor(dev);
+
+ /* sheetfed scanner uses home sensor as paper present */
+ if (dev->document && !paper_loaded) {
+ DBG(DBG_info, "%s: no more document\n", __func__);
+ dev->document = false;
+
+ unsigned scanned_lines = 0;
+ catch_all_exceptions(__func__, [&](){ sanei_genesys_read_scancnt(dev, &scanned_lines); });
+
+ std::size_t output_lines = dev->session.output_line_count;
+
+ std::size_t offset_lines = static_cast<std::size_t>(
+ (dev->model->post_scan * dev->session.params.yres) / MM_PER_INCH);
+
+ std::size_t scan_end_lines = scanned_lines + offset_lines;
+
+ std::size_t remaining_lines = dev->get_pipeline_source().remaining_bytes() /
+ dev->session.output_line_bytes_raw;
+
+ DBG(DBG_io, "%s: scanned_lines=%u\n", __func__, scanned_lines);
+ DBG(DBG_io, "%s: scan_end_lines=%zu\n", __func__, scan_end_lines);
+ DBG(DBG_io, "%s: output_lines=%zu\n", __func__, output_lines);
+ DBG(DBG_io, "%s: remaining_lines=%zu\n", __func__, remaining_lines);
+
+ if (scan_end_lines > output_lines) {
+ auto skip_lines = scan_end_lines - output_lines;
+
+ if (remaining_lines > skip_lines) {
+ DBG(DBG_io, "%s: skip_lines=%zu\n", __func__, skip_lines);
+
+ remaining_lines -= skip_lines;
+ dev->get_pipeline_source().set_remaining_bytes(remaining_lines *
+ dev->session.output_line_bytes_raw);
+ dev->total_bytes_to_read -= skip_lines * dev->session.output_line_bytes_requested;
+ }
+ }
+ }
+}
+
+// enables or disables XPA slider motor
+void gl843_set_xpa_motor_power(Genesys_Device* dev, Genesys_Register_Set& regs, bool set)
+{
+ DBG_HELPER(dbg);
+ uint8_t val;
+
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+
+ if (set) {
+ val = dev->interface->read_register(0x6c);
+ val &= ~(REG_0x6C_GPIO16 | REG_0x6C_GPIO13);
+ if (dev->session.output_resolution >= 2400) {
+ val &= ~REG_0x6C_GPIO10;
+ }
+ dev->interface->write_register(0x6c, val);
+
+ val = dev->interface->read_register(0xa9);
+ val |= REG_0xA9_GPO30;
+ val &= ~REG_0xA9_GPO29;
+ dev->interface->write_register(0xa9, val);
+ } else {
+ val = dev->interface->read_register(0x6c);
+ val |= REG_0x6C_GPIO16 | REG_0x6C_GPIO13;
+ dev->interface->write_register(0x6c, val);
+
+ val = dev->interface->read_register(0xa9);
+ val &= ~REG_0xA9_GPO30;
+ val |= REG_0xA9_GPO29;
+ dev->interface->write_register(0xa9, val);
+ }
+ } else if (dev->model->model_id == ModelId::CANON_8600F) {
+ if (set) {
+ val = dev->interface->read_register(REG_0x6C);
+ val &= ~REG_0x6C_GPIO14;
+ if (dev->session.output_resolution >= 2400) {
+ val |= REG_0x6C_GPIO10;
+ }
+ dev->interface->write_register(REG_0x6C, val);
+
+ val = dev->interface->read_register(REG_0xA6);
+ val |= REG_0xA6_GPIO17;
+ val &= ~REG_0xA6_GPIO23;
+ dev->interface->write_register(REG_0xA6, val);
+ } else {
+ val = dev->interface->read_register(REG_0x6C);
+ val |= REG_0x6C_GPIO14;
+ val &= ~REG_0x6C_GPIO10;
+ dev->interface->write_register(REG_0x6C, val);
+
+ val = dev->interface->read_register(REG_0xA6);
+ val &= ~REG_0xA6_GPIO17;
+ val &= ~REG_0xA6_GPIO23;
+ dev->interface->write_register(REG_0xA6, val);
+ }
+ } else if (dev->model->model_id == ModelId::HP_SCANJET_G4050) {
+ if (set) {
+ // set MULTFILM et GPOADF
+ val = dev->interface->read_register(REG_0x6B);
+ val |=REG_0x6B_MULTFILM|REG_0x6B_GPOADF;
+ dev->interface->write_register(REG_0x6B, val);
+
+ val = dev->interface->read_register(REG_0x6C);
+ val &= ~REG_0x6C_GPIO15;
+ dev->interface->write_register(REG_0x6C, val);
+
+ /* Motor power ? No move at all without this one */
+ val = dev->interface->read_register(REG_0xA6);
+ val |= REG_0xA6_GPIO20;
+ dev->interface->write_register(REG_0xA6, val);
+
+ val = dev->interface->read_register(REG_0xA8);
+ val &= ~REG_0xA8_GPO27;
+ dev->interface->write_register(REG_0xA8, val);
+
+ val = dev->interface->read_register(REG_0xA9);
+ val |= REG_0xA9_GPO32|REG_0xA9_GPO31;
+ dev->interface->write_register(REG_0xA9, val);
+ } else {
+ // unset GPOADF
+ val = dev->interface->read_register(REG_0x6B);
+ val &= ~REG_0x6B_GPOADF;
+ dev->interface->write_register(REG_0x6B, val);
+
+ val = dev->interface->read_register(REG_0xA8);
+ val |= REG_0xA8_GPO27;
+ dev->interface->write_register(REG_0xA8, val);
+
+ val = dev->interface->read_register(REG_0xA9);
+ val &= ~REG_0xA9_GPO31;
+ dev->interface->write_register(REG_0xA9, val);
+ }
+ }
+ regs.state.is_xpa_motor_on = set;
+}
+
+
+/** @brief light XPA lamp
+ * toggle gpios to switch off regular lamp and light on the
+ * XPA light
+ * @param dev device to set up
+ */
+static void gl843_set_xpa_lamp_power(Genesys_Device* dev, bool set)
+{
+ DBG_HELPER(dbg);
+
+ struct LampSettings {
+ ModelId model_id;
+ ScanMethod scan_method;
+ GenesysRegisterSettingSet regs_on;
+ GenesysRegisterSettingSet regs_off;
+ };
+
+ // FIXME: BUG: we're not clearing the registers to the previous state when returning back when
+ // turning off the lamp
+ LampSettings settings[] = {
+ { ModelId::CANON_8400F, ScanMethod::TRANSPARENCY, {
+ { 0xa6, 0x34, 0xf4 },
+ }, {
+ { 0xa6, 0x40, 0x70 },
+ }
+ },
+ { ModelId::CANON_8400F, ScanMethod::TRANSPARENCY_INFRARED, {
+ { 0x6c, 0x40, 0x40 },
+ { 0xa6, 0x01, 0xff },
+ }, {
+ { 0x6c, 0x00, 0x40 },
+ { 0xa6, 0x00, 0xff },
+ }
+ },
+ { ModelId::CANON_8600F, ScanMethod::TRANSPARENCY, {
+ { 0xa6, 0x34, 0xf4 },
+ { 0xa7, 0xe0, 0xe0 },
+ }, {
+ { 0xa6, 0x40, 0x70 },
+ }
+ },
+ { ModelId::CANON_8600F, ScanMethod::TRANSPARENCY_INFRARED, {
+ { 0xa6, 0x00, 0xc0 },
+ { 0xa7, 0xe0, 0xe0 },
+ { 0x6c, 0x80, 0x80 },
+ }, {
+ { 0xa6, 0x00, 0xc0 },
+ { 0x6c, 0x00, 0x80 },
+ }
+ },
+ { ModelId::PLUSTEK_OPTICFILM_7200I, ScanMethod::TRANSPARENCY, {
+ }, {
+ { 0xa6, 0x40, 0x70 }, // BUG: remove this cleanup write, it was enabled by accident
+ }
+ },
+ { ModelId::PLUSTEK_OPTICFILM_7200I, ScanMethod::TRANSPARENCY_INFRARED, {
+ { 0xa8, 0x07, 0x07 },
+ }, {
+ { 0xa8, 0x00, 0x07 },
+ }
+ },
+ { ModelId::PLUSTEK_OPTICFILM_7300, ScanMethod::TRANSPARENCY, {}, {} },
+ { ModelId::PLUSTEK_OPTICFILM_7500I, ScanMethod::TRANSPARENCY, {}, {} },
+ { ModelId::PLUSTEK_OPTICFILM_7500I, ScanMethod::TRANSPARENCY_INFRARED, {
+ { 0xa8, 0x07, 0x07 },
+ }, {
+ { 0xa8, 0x00, 0x07 },
+ }
+ },
+ };
+
+ for (const auto& setting : settings) {
+ if (setting.model_id == dev->model->model_id &&
+ setting.scan_method == dev->settings.scan_method)
+ {
+ apply_reg_settings_to_device(*dev, set ? setting.regs_on : setting.regs_off);
+ return;
+ }
+ }
+
+ // BUG: we're currently calling the function in shut down path of regular lamp
+ if (set) {
+ throw SaneException("Unexpected code path entered");
+ }
+
+ GenesysRegisterSettingSet regs = {
+ { 0xa6, 0x40, 0x70 },
+ };
+ apply_reg_settings_to_device(*dev, regs);
+ // TODO: throw exception when we're only calling this function in error return path
+ // throw SaneException("Could not find XPA lamp settings");
+}
+
+// Send the low-level scan command
+void CommandSetGl843::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, bool start_motor) const
+{
+ DBG_HELPER(dbg);
+ (void) sensor;
+
+ /* set up GPIO for scan */
+ switch(dev->model->gpio_id) {
+ /* KV case */
+ case GpioId::KVSS080:
+ dev->interface->write_register(REG_0xA9, 0x00);
+ dev->interface->write_register(REG_0xA6, 0xf6);
+ // blinking led
+ dev->interface->write_register(0x7e, 0x04);
+ break;
+ case GpioId::G4050:
+ dev->interface->write_register(REG_0xA7, 0xfe);
+ dev->interface->write_register(REG_0xA8, 0x3e);
+ dev->interface->write_register(REG_0xA9, 0x06);
+ if ((reg->get8(0x05) & REG_0x05_DPIHW) == REG_0x05_DPIHW_600) {
+ dev->interface->write_register(REG_0x6C, 0x20);
+ dev->interface->write_register(REG_0xA6, 0x44);
+ } else {
+ dev->interface->write_register(REG_0x6C, 0x60);
+ dev->interface->write_register(REG_0xA6, 0x46);
+ }
+
+ if (reg->state.is_xpa_on && reg->state.is_lamp_on) {
+ gl843_set_xpa_lamp_power(dev, true);
+ }
+
+ if (reg->state.is_xpa_on) {
+ gl843_set_xpa_motor_power(dev, *reg, true);
+ }
+
+ // blinking led
+ dev->interface->write_register(REG_0x7E, 0x01);
+ break;
+ case GpioId::CANON_8400F:
+ case GpioId::CANON_8600F:
+ if (reg->state.is_xpa_on && reg->state.is_lamp_on) {
+ gl843_set_xpa_lamp_power(dev, true);
+ }
+ if (reg->state.is_xpa_on) {
+ gl843_set_xpa_motor_power(dev, *reg, true);
+ }
+ break;
+ case GpioId::PLUSTEK_OPTICFILM_7200I:
+ case GpioId::PLUSTEK_OPTICFILM_7300:
+ case GpioId::PLUSTEK_OPTICFILM_7500I: {
+ if (reg->state.is_xpa_on && reg->state.is_lamp_on) {
+ gl843_set_xpa_lamp_power(dev, true);
+ }
+ break;
+ }
+ case GpioId::CANON_4400F:
+ default:
+ break;
+ }
+
+ // clear scan and feed count
+ dev->interface->write_register(REG_0x0D, REG_0x0D_CLRLNCNT | REG_0x0D_CLRMCNT);
+
+ // enable scan and motor
+ uint8_t val = dev->interface->read_register(REG_0x01);
+ val |= REG_0x01_SCAN;
+ dev->interface->write_register(REG_0x01, val);
+
+ scanner_start_action(*dev, start_motor);
+
+ if (reg->state.is_motor_on) {
+ dev->advance_head_pos_by_session(ScanHeadId::PRIMARY);
+ }
+ if (reg->state.is_xpa_motor_on) {
+ dev->advance_head_pos_by_session(ScanHeadId::SECONDARY);
+ }
+}
+
+
+// Send the stop scan command
+void CommandSetGl843::end_scan(Genesys_Device* dev, Genesys_Register_Set* reg,
+ bool check_stop) const
+{
+ DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop);
+
+ // post scan gpio
+ dev->interface->write_register(0x7e, 0x00);
+
+ // turn off XPA lamp if needed
+ // BUG: the if condition below probably shouldn't be enabled when XPA is off
+ if (reg->state.is_xpa_on || reg->state.is_lamp_on) {
+ gl843_set_xpa_lamp_power(dev, false);
+ }
+
+ if (!dev->model->is_sheetfed) {
+ scanner_stop_action(*dev);
+ }
+}
+
+/** @brief Moves the slider to the home (top) position slowly
+ * */
+void CommandSetGl843::move_back_home(Genesys_Device* dev, bool wait_until_home) const
+{
+ scanner_move_back_home(*dev, wait_until_home);
+}
+
+// Automatically set top-left edge of the scan area by scanning a 200x200 pixels area at 600 dpi
+// from very top of scanner
+void CommandSetGl843::search_start_position(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ Genesys_Register_Set local_reg;
+
+ int pixels = 600;
+ int dpi = 300;
+
+ local_reg = dev->reg;
+
+ /* sets for a 200 lines * 600 pixels */
+ /* normal scan with no shading */
+
+ // FIXME: the current approach of doing search only for one resolution does not work on scanners
+ // whith employ different sensors with potentially different settings.
+ const auto& sensor = sanei_genesys_find_sensor(dev, dpi, 1, dev->model->default_method);
+
+ ScanSession session;
+ session.params.xres = dpi;
+ session.params.yres = dpi;
+ session.params.startx = 0;
+ session.params.starty = 0; // we should give a small offset here - ~60 steps
+ session.params.pixels = 600;
+ session.params.lines = dev->model->search_lines;
+ session.params.depth = 8;
+ session.params.channels = 1;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::GRAY;
+ session.params.color_filter = ColorFilter::GREEN;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::IGNORE_LINE_DISTANCE |
+ ScanFlag::DISABLE_BUFFER_FULL_MOVE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &local_reg, session);
+
+ // send to scanner
+ dev->interface->write_registers(local_reg);
+
+ dev->cmd_set->begin_scan(dev, sensor, &local_reg, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("search_start_position");
+ end_scan(dev, &local_reg, true);
+ dev->reg = local_reg;
+ return;
+ }
+
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ Image image = read_unshuffled_image_from_scanner(dev, session, session.output_total_bytes_raw);
+
+ scanner_stop_action_no_move(*dev, local_reg);
+
+ if (DBG_LEVEL >= DBG_data) {
+ sanei_genesys_write_pnm_file("gl843_search_position.pnm", image);
+ }
+
+ dev->cmd_set->end_scan(dev, &local_reg, true);
+
+ /* update regs to copy ASIC internal state */
+ dev->reg = local_reg;
+
+ for (auto& sensor_update :
+ sanei_genesys_find_sensors_all_for_write(dev, dev->model->default_method))
+ {
+ sanei_genesys_search_reference_point(dev, sensor_update, image.get_row_ptr(0), 0, dpi,
+ pixels, dev->model->search_lines);
+ }
+}
+
+// sets up register for coarse gain calibration
+// todo: check it for scanners using it
+void CommandSetGl843::init_regs_for_coarse_calibration(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+
+ ScanFlag flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) {
+ flags |= ScanFlag::USE_XPA;
+ }
+
+ ScanSession session;
+ session.params.xres = dev->settings.xres;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = sensor.optical_res / sensor.ccd_pixels_per_system_pixel();
+ session.params.lines = 20;
+ session.params.depth = 16;
+ session.params.channels = dev->settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = dev->settings.scan_mode;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = flags;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &regs, session);
+
+ sanei_genesys_set_motor_power(regs, false);
+
+ DBG(DBG_info, "%s: optical sensor res: %d dpi, actual res: %d\n", __func__,
+ sensor.optical_res / sensor.ccd_pixels_per_system_pixel(), dev->settings.xres);
+
+ dev->interface->write_registers(regs);
+}
+
+// init registers for shading calibration shading calibration is done at dpihw
+void CommandSetGl843::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ int move, resolution, dpihw, factor;
+
+ /* initial calibration reg values */
+ regs = dev->reg;
+
+ dev->calib_channels = 3;
+
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ dev->calib_lines = dev->model->shading_ta_lines;
+ } else {
+ dev->calib_lines = dev->model->shading_lines;
+ }
+
+ dpihw = sensor.get_logical_hwdpi(dev->settings.xres);
+ factor=sensor.optical_res/dpihw;
+ resolution=dpihw;
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, dev->calib_channels,
+ dev->settings.scan_method);
+
+ if ((dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) &&
+ dev->model->model_id == ModelId::CANON_8600F &&
+ dev->settings.xres == 4800)
+ {
+ float offset = static_cast<float>(dev->model->x_offset_ta);
+ offset /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution);
+ offset = static_cast<float>((offset * calib_sensor.optical_res) / MM_PER_INCH);
+
+ float size = static_cast<float>(dev->model->x_size_ta);
+ size /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution);
+ size = static_cast<float>((size * calib_sensor.optical_res) / MM_PER_INCH);
+
+ dev->calib_pixels_offset = static_cast<std::size_t>(offset);
+ dev->calib_pixels = static_cast<std::size_t>(size);
+ }
+ else
+ {
+ dev->calib_pixels_offset = 0;
+ dev->calib_pixels = calib_sensor.sensor_pixels / factor;
+ }
+
+ dev->calib_resolution = resolution;
+
+ ScanFlag flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::DISABLE_BUFFER_FULL_MOVE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ // note: move_to_ta() function has already been called and the sensor is at the
+ // transparency adapter
+ move = static_cast<int>(dev->model->y_offset_calib_white_ta - dev->model->y_offset_sensor_to_ta);
+ flags |= ScanFlag::USE_XPA;
+ } else {
+ move = static_cast<int>(dev->model->y_offset_calib_white);
+ }
+
+ move = static_cast<int>((move * resolution) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = resolution;
+ session.params.startx = dev->calib_pixels_offset;
+ session.params.starty = move;
+ session.params.pixels = dev->calib_pixels;
+ session.params.lines = dev->calib_lines;
+ session.params.depth = 16;
+ session.params.channels = dev->calib_channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = dev->settings.scan_mode;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = flags;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ // the pixel number may be updated to conform to scanner constraints
+ dev->calib_pixels = session.output_pixels;
+
+ dev->calib_session = session;
+ dev->calib_total_bytes_to_read = session.output_total_bytes_raw;
+
+ dev->interface->write_registers(regs);
+}
+
+/** @brief set up registers for the actual scan
+ */
+void CommandSetGl843::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ DBG_HELPER(dbg);
+ float move;
+ int move_dpi;
+ float start;
+
+ debug_dump(DBG_info, dev->settings);
+
+ move_dpi = dev->motor.base_ydpi;
+
+ ScanFlag flags = ScanFlag::NONE;
+
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ // note: move_to_ta() function has already been called and the sensor is at the
+ // transparency adapter
+ if (dev->ignore_offsets) {
+ move = 0;
+ } else {
+ move = static_cast<float>(dev->model->y_offset_ta - dev->model->y_offset_sensor_to_ta);
+ }
+ flags |= ScanFlag::USE_XPA;
+ } else {
+ if (dev->ignore_offsets) {
+ move = 0;
+ } else {
+ move = static_cast<float>(dev->model->y_offset);
+ }
+ }
+
+ move += static_cast<float>(dev->settings.tl_y);
+ move = static_cast<float>((move * move_dpi) / MM_PER_INCH);
+ DBG(DBG_info, "%s: move=%f steps\n", __func__, move);
+
+ /* start */
+ if (dev->settings.scan_method==ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ start = static_cast<float>(dev->model->x_offset_ta);
+ } else {
+ start = static_cast<float>(dev->model->x_offset);
+ }
+
+ if (dev->model->model_id == ModelId::CANON_8400F ||
+ dev->model->model_id == ModelId::CANON_8600F)
+ {
+ // FIXME: this is probably just an artifact of a bug elsewhere
+ start /= sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres);
+ }
+
+ start = static_cast<float>(start + dev->settings.tl_x);
+ start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = dev->settings.xres;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = static_cast<unsigned>(start);
+ session.params.starty = static_cast<unsigned>(move);
+ session.params.pixels = dev->settings.pixels;
+ session.params.requested_pixels = dev->settings.requested_pixels;
+ session.params.lines = dev->settings.lines;
+ session.params.depth = dev->settings.depth;
+ session.params.channels = dev->settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = dev->settings.scan_mode;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = flags;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &dev->reg, session);
+}
+
+/**
+ * This function sends gamma tables to ASIC
+ */
+void CommandSetGl843::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ DBG_HELPER(dbg);
+ int size;
+ int i;
+
+ size = 256;
+
+ /* allocate temporary gamma tables: 16 bits words, 3 channels */
+ std::vector<uint8_t> gamma(size * 2 * 3);
+
+ std::vector<uint16_t> rgamma = get_gamma_table(dev, sensor, GENESYS_RED);
+ std::vector<uint16_t> ggamma = get_gamma_table(dev, sensor, GENESYS_GREEN);
+ std::vector<uint16_t> bgamma = get_gamma_table(dev, sensor, GENESYS_BLUE);
+
+ // copy sensor specific's gamma tables
+ for (i = 0; i < size; i++) {
+ gamma[i * 2 + size * 0 + 0] = rgamma[i] & 0xff;
+ gamma[i * 2 + size * 0 + 1] = (rgamma[i] >> 8) & 0xff;
+ gamma[i * 2 + size * 2 + 0] = ggamma[i] & 0xff;
+ gamma[i * 2 + size * 2 + 1] = (ggamma[i] >> 8) & 0xff;
+ gamma[i * 2 + size * 4 + 0] = bgamma[i] & 0xff;
+ gamma[i * 2 + size * 4 + 1] = (bgamma[i] >> 8) & 0xff;
+ }
+
+ dev->interface->write_gamma(0x28, 0x0000, gamma.data(), size * 2 * 3,
+ ScannerInterface::FLAG_SWAP_REGISTERS);
+}
+
+/* this function does the led calibration by scanning one line of the calibration
+ area below scanner's top on white strip.
+
+-needs working coarse/gain
+*/
+SensorExposure CommandSetGl843::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ int num_pixels;
+ int avg[3], avga, avge;
+ int turn;
+ uint16_t expr, expg, expb;
+
+ // offset calibration is always done in color mode
+ unsigned channels = 3;
+
+ // take a copy, as we're going to modify exposure
+ auto calib_sensor = sanei_genesys_find_sensor(dev, sensor.optical_res, channels,
+ dev->settings.scan_method);
+
+ num_pixels = (calib_sensor.sensor_pixels * calib_sensor.optical_res) / calib_sensor.optical_res;
+
+ /* initial calibration reg values */
+ regs = dev->reg;
+
+ ScanSession session;
+ session.params.xres = calib_sensor.sensor_pixels;
+ session.params.yres = dev->motor.base_ydpi;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = num_pixels;
+ session.params.lines = 1;
+ session.params.depth = 16;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ dev->interface->write_registers(regs);
+
+/*
+ we try to get equal bright leds here:
+
+ loop:
+ average per color
+ adjust exposure times
+ */
+
+ expr = calib_sensor.exposure.red;
+ expg = calib_sensor.exposure.green;
+ expb = calib_sensor.exposure.blue;
+
+ turn = 0;
+
+ bool acceptable = false;
+ do
+ {
+
+ calib_sensor.exposure.red = expr;
+ calib_sensor.exposure.green = expg;
+ calib_sensor.exposure.blue = expb;
+
+ regs_set_exposure(dev->model->asic_type, regs, calib_sensor.exposure);
+
+ dev->interface->write_registers(regs);
+
+ DBG(DBG_info, "%s: starting first line reading\n", __func__);
+ dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("led_calibration");
+ move_back_home(dev, true);
+ return calib_sensor.exposure;
+ }
+
+ auto image = read_unshuffled_image_from_scanner(dev, session,
+ session.output_total_bytes_raw);
+ scanner_stop_action_no_move(*dev, regs);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[30];
+ std::snprintf(fn, 30, "gl843_led_%02d.pnm", turn);
+ sanei_genesys_write_pnm_file(fn, image);
+ }
+
+ acceptable = true;
+
+ for (unsigned ch = 0; ch < channels; ch++) {
+ avg[ch] = 0;
+ for (std::size_t x = 0; x < image.get_width(); x++) {
+ avg[ch] += image.get_raw_channel(x, 0, ch);
+ }
+ avg[ch] /= image.get_width();
+ }
+
+ DBG(DBG_info, "%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]);
+
+ acceptable = true;
+
+ if (avg[0] < avg[1] * 0.95 || avg[1] < avg[0] * 0.95 ||
+ avg[0] < avg[2] * 0.95 || avg[2] < avg[0] * 0.95 ||
+ avg[1] < avg[2] * 0.95 || avg[2] < avg[1] * 0.95)
+ acceptable = false;
+
+ if (!acceptable)
+ {
+ avga = (avg[0] + avg[1] + avg[2]) / 3;
+ expr = (expr * avga) / avg[0];
+ expg = (expg * avga) / avg[1];
+ expb = (expb * avga) / avg[2];
+/*
+ keep the resulting exposures below this value.
+ too long exposure drives the ccd into saturation.
+ we may fix this by relying on the fact that
+ we get a striped scan without shading, by means of
+ statistical calculation
+*/
+ avge = (expr + expg + expb) / 3;
+
+ /* don't overflow max exposure */
+ if (avge > 3000)
+ {
+ expr = (expr * 2000) / avge;
+ expg = (expg * 2000) / avge;
+ expb = (expb * 2000) / avge;
+ }
+ if (avge < 50)
+ {
+ expr = (expr * 50) / avge;
+ expg = (expg * 50) / avge;
+ expb = (expb * 50) / avge;
+ }
+
+ }
+ scanner_stop_action(*dev);
+
+ turn++;
+
+ }
+ while (!acceptable && turn < 100);
+
+ DBG(DBG_info, "%s: acceptable exposure: %d,%d,%d\n", __func__, expr, expg, expb);
+
+ move_back_home(dev, true);
+
+ return calib_sensor.exposure;
+}
+
+
+
+/**
+ * average dark pixels of a 8 bits scan of a given channel
+ */
+static int dark_average_channel(const Image& image, unsigned black, unsigned channel)
+{
+ auto channels = get_pixel_channels(image.get_format());
+
+ unsigned avg[3];
+
+ // computes average values on black margin
+ for (unsigned ch = 0; ch < channels; ch++) {
+ avg[ch] = 0;
+ unsigned count = 0;
+ // FIXME: start with the second line because the black pixels often have noise on the first
+ // line; the cause is probably incorrectly cleaned up previous scan
+ for (std::size_t y = 1; y < image.get_height(); y++) {
+ for (unsigned j = 0; j < black; j++) {
+ avg[ch] += image.get_raw_channel(j, y, ch);
+ count++;
+ }
+ }
+ if (count > 0) {
+ avg[ch] /= count;
+ }
+ DBG(DBG_info, "%s: avg[%d] = %d\n", __func__, ch, avg[ch]);
+ }
+ DBG(DBG_info, "%s: average = %d\n", __func__, avg[channel]);
+ return avg[channel];
+}
+
+/** @brief calibrate AFE offset
+ * Iterate doing scans at target dpi until AFE offset if correct. One
+ * color line is scanned at a time. Scanning head doesn't move.
+ * @param dev device to calibrate
+ */
+void CommandSetGl843::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+
+ if (dev->frontend.layout.type != FrontendType::WOLFSON)
+ return;
+
+ unsigned channels;
+ int pass, resolution, lines;
+ int topavg[3], bottomavg[3], avg[3];
+ int top[3], bottom[3], black_pixels, pixels, factor, dpihw;
+
+ /* offset calibration is always done in color mode */
+ channels = 3;
+ lines = 8;
+
+ // compute divider factor to compute final pixels number
+ dpihw = sensor.get_logical_hwdpi(dev->settings.xres);
+ factor = sensor.optical_res / dpihw;
+ resolution = dpihw;
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels,
+ dev->settings.scan_method);
+
+ int target_pixels = calib_sensor.sensor_pixels / factor;
+ int start_pixel = 0;
+ black_pixels = calib_sensor.black_pixels / factor;
+
+ if ((dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) &&
+ dev->model->model_id == ModelId::CANON_8600F &&
+ dev->settings.xres == 4800)
+ {
+ start_pixel = static_cast<int>(dev->model->x_offset_ta);
+ start_pixel /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution);
+ start_pixel = static_cast<int>((start_pixel * calib_sensor.optical_res) / MM_PER_INCH);
+
+ target_pixels = static_cast<int>(dev->model->x_size_ta);
+ target_pixels /= calib_sensor.get_ccd_size_divisor_for_dpi(resolution);
+ target_pixels = static_cast<int>((target_pixels * calib_sensor.optical_res) / MM_PER_INCH);
+ }
+
+ ScanFlag flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ flags |= ScanFlag::USE_XPA;
+ }
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = resolution;
+ session.params.startx = start_pixel;
+ session.params.starty = 0;
+ session.params.pixels = target_pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = ColorFilter::RED;
+ session.params.flags = flags;
+ compute_session(dev, session, calib_sensor);
+ pixels = session.output_pixels;
+
+ DBG(DBG_io, "%s: dpihw =%d\n", __func__, dpihw);
+ DBG(DBG_io, "%s: factor =%d\n", __func__, factor);
+ DBG(DBG_io, "%s: resolution =%d\n", __func__, resolution);
+ DBG(DBG_io, "%s: pixels =%d\n", __func__, pixels);
+ DBG(DBG_io, "%s: black_pixels=%d\n", __func__, black_pixels);
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ sanei_genesys_set_motor_power(regs, false);
+
+ // init gain and offset
+ for (unsigned ch = 0; ch < 3; ch++)
+ {
+ bottom[ch] = 10;
+ dev->frontend.set_offset(ch, bottom[ch]);
+ dev->frontend.set_gain(ch, 0);
+ }
+ dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET);
+
+ // scan with bottom AFE settings
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting first line reading\n", __func__);
+
+ dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("offset_calibration");
+ scanner_stop_action_no_move(*dev, regs);
+ return;
+ }
+
+ auto first_line = read_unshuffled_image_from_scanner(dev, session,
+ session.output_total_bytes_raw);
+ scanner_stop_action_no_move(*dev, regs);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[40];
+ std::snprintf(fn, 40, "gl843_bottom_offset_%03d_%03d_%03d.pnm",
+ bottom[0], bottom[1], bottom[2]);
+ sanei_genesys_write_pnm_file(fn, first_line);
+ }
+
+ for (unsigned ch = 0; ch < 3; ch++) {
+ bottomavg[ch] = dark_average_channel(first_line, black_pixels, ch);
+ DBG(DBG_io2, "%s: bottom avg %d=%d\n", __func__, ch, bottomavg[ch]);
+ }
+
+ // now top value
+ for (unsigned ch = 0; ch < 3; ch++) {
+ top[ch] = 255;
+ dev->frontend.set_offset(ch, top[ch]);
+ }
+ dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET);
+
+ // scan with top AFE values
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting second line reading\n", __func__);
+
+ dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
+ auto second_line = read_unshuffled_image_from_scanner(dev, session,
+ session.output_total_bytes_raw);
+ scanner_stop_action_no_move(*dev, regs);
+
+ for (unsigned ch = 0; ch < 3; ch++){
+ topavg[ch] = dark_average_channel(second_line, black_pixels, ch);
+ DBG(DBG_io2, "%s: top avg %d=%d\n", __func__, ch, topavg[ch]);
+ }
+
+ pass = 0;
+
+ std::vector<uint8_t> debug_image;
+ size_t debug_image_lines = 0;
+ std::string debug_image_info;
+
+ /* loop until acceptable level */
+ while ((pass < 32)
+ && ((top[0] - bottom[0] > 1)
+ || (top[1] - bottom[1] > 1) || (top[2] - bottom[2] > 1)))
+ {
+ pass++;
+
+ // settings for new scan
+ for (unsigned ch = 0; ch < 3; ch++) {
+ if (top[ch] - bottom[ch] > 1) {
+ dev->frontend.set_offset(ch, (top[ch] + bottom[ch]) / 2);
+ }
+ }
+ dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET);
+
+ // scan with no move
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting second line reading\n", __func__);
+ dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
+ second_line = read_unshuffled_image_from_scanner(dev, session,
+ session.output_total_bytes_raw);
+ scanner_stop_action_no_move(*dev, regs);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char title[100];
+ std::snprintf(title, 100, "lines: %d pixels_per_line: %d offsets[0..2]: %d %d %d\n",
+ lines, pixels,
+ dev->frontend.get_offset(0),
+ dev->frontend.get_offset(1),
+ dev->frontend.get_offset(2));
+ debug_image_info += title;
+ std::copy(second_line.get_row_ptr(0),
+ second_line.get_row_ptr(0) + second_line.get_row_bytes() * second_line.get_height(),
+ std::back_inserter(debug_image));
+ debug_image_lines += lines;
+ }
+
+ for (unsigned ch = 0; ch < 3; ch++) {
+ avg[ch] = dark_average_channel(second_line, black_pixels, ch);
+ DBG(DBG_info, "%s: avg[%d]=%d offset=%d\n", __func__, ch, avg[ch],
+ dev->frontend.get_offset(ch));
+ }
+
+ // compute new boundaries
+ for (unsigned ch = 0; ch < 3; ch++) {
+ if (topavg[ch] >= avg[ch]) {
+ topavg[ch] = avg[ch];
+ top[ch] = dev->frontend.get_offset(ch);
+ } else {
+ bottomavg[ch] = avg[ch];
+ bottom[ch] = dev->frontend.get_offset(ch);
+ }
+ }
+ }
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ sanei_genesys_write_file("gl843_offset_all_desc.txt",
+ reinterpret_cast<const std::uint8_t*>(debug_image_info.data()),
+ debug_image_info.size());
+ sanei_genesys_write_pnm_file("gl843_offset_all.pnm",
+ debug_image.data(), session.params.depth, channels, pixels,
+ debug_image_lines);
+ }
+
+ DBG(DBG_info, "%s: offset=(%d,%d,%d)\n", __func__,
+ dev->frontend.get_offset(0),
+ dev->frontend.get_offset(1),
+ dev->frontend.get_offset(2));
+}
+
+
+/* alternative coarse gain calibration
+ this on uses the settings from offset_calibration and
+ uses only one scanline
+ */
+/*
+ with offset and coarse calibration we only want to get our input range into
+ a reasonable shape. the fine calibration of the upper and lower bounds will
+ be done with shading.
+ */
+void CommandSetGl843::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const
+{
+ DBG_HELPER_ARGS(dbg, "dpi = %d", dpi);
+ int factor, dpihw;
+ float coeff;
+ int lines;
+ int resolution;
+
+ if (dev->frontend.layout.type != FrontendType::WOLFSON)
+ return;
+
+ dpihw = sensor.get_logical_hwdpi(dpi);
+ factor=sensor.optical_res/dpihw;
+
+ // coarse gain calibration is always done in color mode
+ unsigned channels = 3;
+
+ /* follow CKSEL */
+ if (dev->model->sensor_id == SensorId::CCD_KVSS080) {
+ if(dev->settings.xres<sensor.optical_res)
+ {
+ coeff = 0.9f;
+ }
+ else
+ {
+ coeff=1.0;
+ }
+ }
+ else
+ {
+ coeff=1.0;
+ }
+ resolution=dpihw;
+ lines=10;
+ int target_pixels = sensor.sensor_pixels / factor;
+
+ ScanFlag flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+
+ if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ flags |= ScanFlag::USE_XPA;
+ }
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, channels,
+ dev->settings.scan_method);
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = resolution;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = target_pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = flags;
+ compute_session(dev, session, calib_sensor);
+ std::size_t pixels = session.output_pixels;
+
+ try {
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); });
+ throw;
+ }
+
+ sanei_genesys_set_motor_power(regs, false);
+
+ dev->interface->write_registers(regs);
+
+ dev->cmd_set->set_fe(dev, calib_sensor, AFE_SET);
+ dev->cmd_set->begin_scan(dev, calib_sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("coarse_gain_calibration");
+ scanner_stop_action(*dev);
+ move_back_home(dev, true);
+ return;
+ }
+
+ auto line = read_unshuffled_image_from_scanner(dev, session, session.output_total_bytes_raw);
+ scanner_stop_action_no_move(*dev, regs);
+
+ if (DBG_LEVEL >= DBG_data) {
+ sanei_genesys_write_pnm_file("gl843_gain.pnm", line);
+ }
+
+ // average value on each channel
+ for (unsigned ch = 0; ch < channels; ch++) {
+
+ std::vector<uint16_t> values;
+ // FIXME: start from the second line because the first line often has artifacts. Probably
+ // caused by unclean cleanup of previous scan
+ for (std::size_t x = pixels / 4; x < (pixels * 3 / 4); x++) {
+ values.push_back(line.get_raw_channel(x, 1, ch));
+ }
+
+ // pick target value at 95th percentile of all values. There may be a lot of black values
+ // in transparency scans for example
+ std::sort(values.begin(), values.end());
+ uint16_t curr_output = values[unsigned((values.size() - 1) * 0.95)];
+ float target_value = calib_sensor.gain_white_ref * coeff;
+
+ int code = compute_frontend_gain(curr_output, target_value, dev->frontend.layout.type);
+ dev->frontend.set_gain(ch, code);
+
+ DBG(DBG_proc, "%s: channel %d, max=%d, target=%d, setting:%d\n", __func__, ch, curr_output,
+ static_cast<int>(target_value), code);
+ }
+
+ if (dev->model->is_cis) {
+ uint8_t gain0 = dev->frontend.get_gain(0);
+ if (gain0 > dev->frontend.get_gain(1)) {
+ gain0 = dev->frontend.get_gain(1);
+ }
+ if (gain0 > dev->frontend.get_gain(2)) {
+ gain0 = dev->frontend.get_gain(2);
+ }
+ dev->frontend.set_gain(0, gain0);
+ dev->frontend.set_gain(1, gain0);
+ dev->frontend.set_gain(2, gain0);
+ }
+
+ if (channels == 1) {
+ dev->frontend.set_gain(0, dev->frontend.get_gain(1));
+ dev->frontend.set_gain(2, dev->frontend.get_gain(1));
+ }
+
+ scanner_stop_action(*dev);
+
+ move_back_home(dev, true);
+}
+
+// wait for lamp warmup by scanning the same line until difference
+// between 2 scans is below a threshold
+void CommandSetGl843::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, int* channels,
+ int* total_size) const
+{
+ DBG_HELPER(dbg);
+ int num_pixels;
+ int dpihw;
+ int resolution;
+ int factor;
+
+ /* setup scan */
+ *channels=3;
+ resolution=600;
+ dpihw = sensor.get_logical_hwdpi(resolution);
+ resolution=dpihw;
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, resolution, *channels,
+ dev->settings.scan_method);
+ factor = calib_sensor.optical_res/dpihw;
+ num_pixels = calib_sensor.sensor_pixels/(factor*2);
+ *total_size = num_pixels * 3 * 1;
+
+ *reg = dev->reg;
+
+ ScanSession session;
+ session.params.xres = resolution;
+ session.params.yres = resolution;
+ session.params.startx = num_pixels/2;
+ session.params.starty = 0;
+ session.params.pixels = num_pixels;
+ session.params.lines = 1;
+ session.params.depth = 8;
+ session.params.channels = *channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, reg, session);
+
+ sanei_genesys_set_motor_power(*reg, false);
+ dev->interface->write_registers(*reg);
+}
+
+/**
+ * set up GPIO/GPOE for idle state
+WRITE GPIO[17-21]= GPIO19
+WRITE GPOE[17-21]= GPOE21 GPOE20 GPOE19 GPOE18
+genesys_write_register(0xa8,0x3e)
+GPIO(0xa8)=0x3e
+ */
+static void gl843_init_gpio(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ apply_registers_ordered(dev->gpo.regs, { 0x6e, 0x6f }, [&](const GenesysRegisterSetting& reg)
+ {
+ dev->interface->write_register(reg.address, reg.value);
+ });
+}
+
+
+/* *
+ * initialize ASIC from power on condition
+ */
+void CommandSetGl843::asic_boot(Genesys_Device* dev, bool cold) const
+{
+ DBG_HELPER(dbg);
+ uint8_t val;
+
+ if (cold) {
+ dev->interface->write_register(0x0e, 0x01);
+ dev->interface->write_register(0x0e, 0x00);
+ }
+
+ if(dev->usb_mode == 1)
+ {
+ val = 0x14;
+ }
+ else
+ {
+ val = 0x11;
+ }
+ dev->interface->write_0x8c(0x0f, val);
+
+ // test CHKVER
+ val = dev->interface->read_register(REG_0x40);
+ if (val & REG_0x40_CHKVER) {
+ val = dev->interface->read_register(0x00);
+ DBG(DBG_info, "%s: reported version for genesys chip is 0x%02x\n", __func__, val);
+ }
+
+ /* Set default values for registers */
+ gl843_init_registers (dev);
+
+ if (dev->model->model_id == ModelId::CANON_8600F) {
+ // turns on vref control for maximum current of the motor driver
+ dev->interface->write_register(REG_0x6B, 0x72);
+ } else {
+ dev->interface->write_register(REG_0x6B, 0x02);
+ }
+
+ // Write initial registers
+ dev->interface->write_registers(dev->reg);
+
+ // Enable DRAM by setting a rising edge on bit 3 of reg 0x0b
+ val = dev->reg.find_reg(0x0b).value & REG_0x0B_DRAMSEL;
+ val = (val | REG_0x0B_ENBDRAM);
+ dev->interface->write_register(REG_0x0B, val);
+ dev->reg.find_reg(0x0b).value = val;
+
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ dev->interface->write_0x8c(0x1e, 0x01);
+ dev->interface->write_0x8c(0x10, 0xb4);
+ dev->interface->write_0x8c(0x0f, 0x02);
+ }
+ else if (dev->model->model_id == ModelId::CANON_8600F) {
+ dev->interface->write_0x8c(0x10, 0xc8);
+ } else if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I)
+ {
+ dev->interface->write_0x8c(0x10, 0xd4);
+ } else {
+ dev->interface->write_0x8c(0x10, 0xb4);
+ }
+
+ /* CLKSET */
+ int clock_freq = REG_0x0B_48MHZ;
+ switch (dev->model->model_id) {
+ case ModelId::CANON_8600F:
+ clock_freq = REG_0x0B_60MHZ;
+ break;
+ case ModelId::PLUSTEK_OPTICFILM_7200I:
+ clock_freq = REG_0x0B_30MHZ;
+ break;
+ case ModelId::PLUSTEK_OPTICFILM_7300:
+ case ModelId::PLUSTEK_OPTICFILM_7500I:
+ clock_freq = REG_0x0B_40MHZ;
+ break;
+ default:
+ break;
+ }
+
+ val = (dev->reg.find_reg(0x0b).value & ~REG_0x0B_CLKSET) | clock_freq;
+
+ dev->interface->write_register(REG_0x0B, val);
+ dev->reg.find_reg(0x0b).value = val;
+
+ /* prevent further writings by bulk write register */
+ dev->reg.remove_reg(0x0b);
+
+ if (dev->model->model_id != ModelId::CANON_8600F) {
+ // set up end access
+ // FIXME: this is overwritten in gl843_init_gpio
+ dev->interface->write_register(REG_0xA7, 0x04);
+ dev->interface->write_register(REG_0xA9, 0x00);
+ }
+
+ // set RAM read address
+ dev->interface->write_register(REG_0x29, 0x00);
+ dev->interface->write_register(REG_0x2A, 0x00);
+ dev->interface->write_register(REG_0x2B, 0x00);
+
+ // setup gpio
+ gl843_init_gpio(dev);
+
+ scanner_move(*dev, dev->model->default_method, 300, Direction::FORWARD);
+ dev->interface->sleep_ms(100);
+}
+
+/* *
+ * initialize backend and ASIC : registers, motor tables, and gamma tables
+ * then ensure scanner's head is at home
+ */
+void CommandSetGl843::init(Genesys_Device* dev) const
+{
+ DBG_INIT ();
+ DBG_HELPER(dbg);
+
+ sanei_genesys_asic_init(dev, 0);
+}
+
+void CommandSetGl843::update_hardware_sensors(Genesys_Scanner* s) const
+{
+ DBG_HELPER(dbg);
+ /* do what is needed to get a new set of events, but try to not lose
+ any of them.
+ */
+
+ uint8_t val = s->dev->interface->read_register(REG_0x6D);
+
+ switch (s->dev->model->gpio_id)
+ {
+ case GpioId::KVSS080:
+ s->buttons[BUTTON_SCAN_SW].write((val & 0x04) == 0);
+ break;
+ case GpioId::G4050:
+ s->buttons[BUTTON_SCAN_SW].write((val & 0x01) == 0);
+ s->buttons[BUTTON_FILE_SW].write((val & 0x02) == 0);
+ s->buttons[BUTTON_EMAIL_SW].write((val & 0x04) == 0);
+ s->buttons[BUTTON_COPY_SW].write((val & 0x08) == 0);
+ break;
+ case GpioId::CANON_4400F:
+ case GpioId::CANON_8400F:
+ default:
+ break;
+ }
+}
+
+/** @brief move sensor to transparency adaptor
+ * Move sensor to the calibration of the transparency adapator (XPA).
+ * @param dev device to use
+ */
+void CommandSetGl843::move_to_ta(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+
+ const auto& resolution_settings = dev->model->get_resolution_settings(dev->model->default_method);
+ float resolution = resolution_settings.get_min_resolution_y();
+
+ unsigned multiplier = 16;
+ if (dev->model->model_id == ModelId::CANON_8400F) {
+ multiplier = 4;
+ }
+ unsigned feed = static_cast<unsigned>(multiplier * (dev->model->y_offset_sensor_to_ta * resolution) /
+ MM_PER_INCH);
+ scanner_move(*dev, dev->model->default_method, feed, Direction::FORWARD);
+}
+
+
+/** @brief search for a full width black or white strip.
+ * This function searches for a black or white stripe across the scanning area.
+ * When searching backward, the searched area must completely be of the desired
+ * color since this area will be used for calibration which scans forward.
+ * @param dev scanner device
+ * @param forward true if searching forward, false if searching backward
+ * @param black true if searching for a black strip, false for a white strip
+ */
+void CommandSetGl843::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ bool forward, bool black) const
+{
+ DBG_HELPER_ARGS(dbg, "%s %s", black ? "black" : "white", forward ? "forward" : "reverse");
+ unsigned int pixels, lines, channels;
+ Genesys_Register_Set local_reg;
+ int dpi;
+ unsigned int pass, count, found, x, y;
+
+ dev->cmd_set->set_fe(dev, sensor, AFE_SET);
+ scanner_stop_action(*dev);
+
+ /* set up for a gray scan at lowest dpi */
+ dpi = sanei_genesys_get_lowest_dpi(dev);
+ channels = 1;
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, dpi, channels,
+ dev->settings.scan_method);
+
+ /* 10 MM */
+ /* lines = (10 * dpi) / MM_PER_INCH; */
+ /* shading calibation is done with dev->motor.base_ydpi */
+ lines = (dev->model->shading_lines * dpi) / dev->motor.base_ydpi;
+ pixels = (calib_sensor.sensor_pixels * dpi) / calib_sensor.optical_res;
+
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+
+ local_reg = dev->reg;
+
+ ScanSession session;
+ session.params.xres = dpi;
+ session.params.yres = dpi;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::GRAY;
+ session.params.color_filter = ColorFilter::RED;
+ session.params.flags = ScanFlag::DISABLE_SHADING | ScanFlag::DISABLE_SHADING;
+ if (!forward) {
+ session.params.flags = ScanFlag::REVERSE;
+ }
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &local_reg, session);
+
+ dev->interface->write_registers(local_reg);
+
+ dev->cmd_set->begin_scan(dev, calib_sensor, &local_reg, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("search_strip");
+ scanner_stop_action(*dev);
+ return;
+ }
+
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ auto data = read_unshuffled_image_from_scanner(dev, session,
+ session.output_total_bytes_raw);
+
+ scanner_stop_action(*dev);
+
+ pass = 0;
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[40];
+ std::snprintf(fn, 40, "gl843_search_strip_%s_%s%02d.pnm",
+ black ? "black" : "white", forward ? "fwd" : "bwd", pass);
+ sanei_genesys_write_pnm_file(fn, data);
+ }
+
+ /* loop until strip is found or maximum pass number done */
+ found = 0;
+ while (pass < 20 && !found)
+ {
+ dev->interface->write_registers(local_reg);
+
+ // now start scan
+ dev->cmd_set->begin_scan(dev, calib_sensor, &local_reg, true);
+
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ data = read_unshuffled_image_from_scanner(dev, session, session.output_total_bytes_raw);
+
+ scanner_stop_action(*dev);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[40];
+ std::snprintf(fn, 40, "gl843_search_strip_%s_%s%02d.pnm",
+ black ? "black" : "white", forward ? "fwd" : "bwd", pass);
+ sanei_genesys_write_pnm_file(fn, data);
+ }
+
+ /* search data to find black strip */
+ /* when searching forward, we only need one line of the searched color since we
+ * will scan forward. But when doing backward search, we need all the area of the
+ * same color */
+ if (forward)
+ {
+ for (y = 0; y < lines && !found; y++)
+ {
+ count = 0;
+ /* count of white/black pixels depending on the color searched */
+ for (x = 0; x < pixels; x++)
+ {
+ /* when searching for black, detect white pixels */
+ if (black && data.get_raw_channel(x, y, 0) > 90) {
+ count++;
+ }
+ /* when searching for white, detect black pixels */
+ if (!black && data.get_raw_channel(x, y, 0) < 60) {
+ count++;
+ }
+ }
+
+ /* at end of line, if count >= 3%, line is not fully of the desired color
+ * so we must go to next line of the buffer */
+ /* count*100/pixels < 3 */
+ if ((count * 100) / pixels < 3)
+ {
+ found = 1;
+ DBG(DBG_data, "%s: strip found forward during pass %d at line %d\n", __func__,
+ pass, y);
+ }
+ else
+ {
+ DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count,
+ (100 * count) / pixels);
+ }
+ }
+ }
+ else /* since calibration scans are done forward, we need the whole area
+ to be of the required color when searching backward */
+ {
+ count = 0;
+ for (y = 0; y < lines; y++)
+ {
+ /* count of white/black pixels depending on the color searched */
+ for (x = 0; x < pixels; x++)
+ {
+ // when searching for black, detect white pixels
+ if (black && data.get_raw_channel(x, y, 0) > 90) {
+ count++;
+ }
+ // when searching for white, detect black pixels
+ if (!black && data.get_raw_channel(x, y, 0) < 60) {
+ count++;
+ }
+ }
+ }
+
+ /* at end of area, if count >= 3%, area is not fully of the desired color
+ * so we must go to next buffer */
+ if ((count * 100) / (pixels * lines) < 3)
+ {
+ found = 1;
+ DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass);
+ }
+ else
+ {
+ DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count,
+ (100 * count) / pixels);
+ }
+ }
+ pass++;
+ }
+ if (found)
+ {
+ DBG(DBG_info, "%s: %s strip found\n", __func__, black ? "black" : "white");
+ }
+ else
+ {
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", black ? "black" : "white");
+ }
+}
+
+/**
+ * Send shading calibration data. The buffer is considered to always hold values
+ * for all the channels.
+ */
+void CommandSetGl843::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ uint8_t* data, int size) const
+{
+ DBG_HELPER(dbg);
+ uint32_t final_size, length, i;
+ uint8_t *buffer;
+ int count,offset;
+ GenesysRegister *r;
+ uint16_t strpixel, endpixel, startx;
+
+ offset=0;
+ length=size;
+ r = sanei_genesys_get_address(&dev->reg, REG_0x01);
+ if (r->value & REG_0x01_SHDAREA)
+ {
+ /* recompute STRPIXEL used shading calibration so we can
+ * compute offset within data for SHDAREA case */
+
+ // FIXME: the following is likely incorrect
+ // start coordinate in optical dpi coordinates
+ startx = (sensor.dummy_pixel / sensor.ccd_pixels_per_system_pixel()) / dev->session.hwdpi_divisor;
+ startx *= dev->session.pixel_count_multiplier;
+
+ /* current scan coordinates */
+ strpixel = dev->session.pixel_startx;
+ endpixel = dev->session.pixel_endx;
+
+ if (dev->model->model_id == ModelId::CANON_4400F ||
+ dev->model->model_id == ModelId::CANON_8600F)
+ {
+ int half_ccd_factor = dev->session.optical_resolution /
+ sensor.get_logical_hwdpi(dev->session.output_resolution);
+ strpixel /= half_ccd_factor * sensor.ccd_pixels_per_system_pixel();
+ endpixel /= half_ccd_factor * sensor.ccd_pixels_per_system_pixel();
+ }
+
+ /* 16 bit words, 2 words per color, 3 color channels */
+ offset=(strpixel-startx)*2*2*3;
+ length=(endpixel-strpixel)*2*2*3;
+ DBG(DBG_info, "%s: STRPIXEL=%d, ENDPIXEL=%d, startx=%d\n", __func__, strpixel, endpixel,
+ startx);
+ }
+
+ dev->interface->record_key_value("shading_offset", std::to_string(offset));
+ dev->interface->record_key_value("shading_length", std::to_string(length));
+
+ /* compute and allocate size for final data */
+ final_size = ((length+251) / 252) * 256;
+ DBG(DBG_io, "%s: final shading size=%04x (length=%d)\n", __func__, final_size, length);
+ std::vector<uint8_t> final_data(final_size, 0);
+
+ /* copy regular shading data to the expected layout */
+ buffer = final_data.data();
+ count = 0;
+
+ /* loop over calibration data */
+ for (i = 0; i < length; i++)
+ {
+ buffer[count] = data[offset+i];
+ count++;
+ if ((count % (256*2)) == (252*2))
+ {
+ count += 4*2;
+ }
+ }
+
+ dev->interface->write_buffer(0x3c, 0, final_data.data(), count,
+ ScannerInterface::FLAG_SMALL_ADDRESS);
+}
+
+bool CommandSetGl843::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const
+{
+ (void) dev;
+ return true;
+}
+
+void CommandSetGl843::wait_for_motor_stop(Genesys_Device* dev) const
+{
+ (void) dev;
+}
+
+std::unique_ptr<CommandSet> create_gl843_cmd_set()
+{
+ return std::unique_ptr<CommandSet>(new CommandSetGl843{});
+}
+
+} // namespace gl843
+} // namespace genesys
diff --git a/backend/genesys/gl843.h b/backend/genesys/gl843.h
new file mode 100644
index 0000000..9f0a9e9
--- /dev/null
+++ b/backend/genesys/gl843.h
@@ -0,0 +1,139 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr>
+
+ 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.
+*/
+
+#include "genesys.h"
+#include "command_set.h"
+
+#ifndef BACKEND_GENESYS_GL843_H
+#define BACKEND_GENESYS_GL843_H
+
+namespace genesys {
+namespace gl843 {
+
+class CommandSetGl843 : public CommandSet
+{
+public:
+ ~CommandSetGl843() override = default;
+
+ bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override;
+
+ void init(Genesys_Device* dev) const override;
+
+ void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, int* channels,
+ int* total_size) const override;
+
+ void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const override;
+
+ void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override;
+ void set_powersaving(Genesys_Device* dev, int delay) const override;
+ void save_power(Genesys_Device* dev, bool enable) const override;
+
+ void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, bool start_motor) const override;
+
+ void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override;
+
+ void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void search_start_position(Genesys_Device* dev) const override;
+
+ void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const override;
+
+ SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void wait_for_motor_stop(Genesys_Device* dev) const override;
+
+ void move_back_home(Genesys_Device* dev, bool wait_until_home) const override;
+
+ void update_hardware_sensors(struct Genesys_Scanner* s) const override;
+
+ void load_document(Genesys_Device* dev) const override;
+
+ void detect_document_end(Genesys_Device* dev) const override;
+
+ void eject_document(Genesys_Device* dev) const override;
+
+ void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ bool forward, bool black) const override;
+
+ void move_to_ta(Genesys_Device* dev) const override;
+
+ void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data,
+ int size) const override;
+
+ ScanSession calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const override;
+
+ void asic_boot(Genesys_Device* dev, bool cold) const override;
+};
+
+enum SlopeTable
+{
+ SCAN_TABLE = 0, // table 1 at 0x4000
+ BACKTRACK_TABLE = 1, // table 2 at 0x4800
+ STOP_TABLE = 2, // table 3 at 0x5000
+ FAST_TABLE = 3, // table 4 at 0x5800
+ HOME_TABLE = 4, // table 5 at 0x6000
+};
+
+} // namespace gl843
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL843_H
diff --git a/backend/genesys/gl843_registers.h b/backend/genesys/gl843_registers.h
new file mode 100644
index 0000000..8ecb0fc
--- /dev/null
+++ b/backend/genesys/gl843_registers.h
@@ -0,0 +1,382 @@
+/* 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_GL843_REGISTERS_H
+#define BACKEND_GENESYS_GL843_REGISTERS_H
+
+#include <cstdint>
+
+namespace genesys {
+namespace gl843 {
+
+using RegAddr = std::uint16_t;
+using RegMask = std::uint8_t;
+using RegShift = unsigned;
+
+static constexpr RegAddr REG_0x01 = 0x01;
+static constexpr RegMask REG_0x01_CISSET = 0x80;
+static constexpr RegMask REG_0x01_DOGENB = 0x40;
+static constexpr RegMask REG_0x01_DVDSET = 0x20;
+static constexpr RegMask REG_0x01_STAGGER = 0x10;
+static constexpr RegMask REG_0x01_COMPENB = 0x08;
+static constexpr RegMask REG_0x01_TRUEGRAY = 0x04;
+static constexpr RegMask REG_0x01_SHDAREA = 0x02;
+static constexpr RegMask REG_0x01_SCAN = 0x01;
+
+static constexpr RegAddr REG_0x02 = 0x02;
+static constexpr RegMask REG_0x02_NOTHOME = 0x80;
+static constexpr RegMask REG_0x02_ACDCDIS = 0x40;
+static constexpr RegMask REG_0x02_AGOHOME = 0x20;
+static constexpr RegMask REG_0x02_MTRPWR = 0x10;
+static constexpr RegMask REG_0x02_FASTFED = 0x08;
+static constexpr RegMask REG_0x02_MTRREV = 0x04;
+static constexpr RegMask REG_0x02_HOMENEG = 0x02;
+static constexpr RegMask REG_0x02_LONGCURV = 0x01;
+
+static constexpr RegAddr REG_0x03 = 0x03;
+static constexpr RegMask REG_0x03_LAMPDOG = 0x80;
+static constexpr RegMask REG_0x03_AVEENB = 0x40;
+static constexpr RegMask REG_0x03_XPASEL = 0x20;
+static constexpr RegMask REG_0x03_LAMPPWR = 0x10;
+static constexpr RegMask REG_0x03_LAMPTIM = 0x0f;
+
+static constexpr RegAddr REG_0x04 = 0x04;
+static constexpr RegMask REG_0x04_LINEART = 0x80;
+static constexpr RegMask REG_0x04_BITSET = 0x40;
+static constexpr RegMask REG_0x04_AFEMOD = 0x30;
+static constexpr RegMask REG_0x04_FILTER = 0x0c;
+static constexpr RegMask REG_0x04_FESET = 0x03;
+
+static constexpr RegShift REG_0x04S_AFEMOD = 4;
+
+static constexpr RegAddr REG_0x05 = 0x05;
+static constexpr RegMask REG_0x05_DPIHW = 0xc0;
+static constexpr RegMask REG_0x05_DPIHW_600 = 0x00;
+static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40;
+static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80;
+static constexpr RegMask REG_0x05_DPIHW_4800 = 0xc0;
+static constexpr RegMask REG_0x05_MTLLAMP = 0x30;
+static constexpr RegMask REG_0x05_GMMENB = 0x08;
+static constexpr RegMask REG_0x05_MTLBASE = 0x03;
+
+static constexpr RegAddr REG_0x06 = 0x06;
+static constexpr RegMask REG_0x06_SCANMOD = 0xe0;
+static constexpr RegShift REG_0x06S_SCANMOD = 5;
+static constexpr RegMask REG_0x06_PWRBIT = 0x10;
+static constexpr RegMask REG_0x06_GAIN4 = 0x08;
+static constexpr RegMask REG_0x06_OPTEST = 0x07;
+
+static constexpr RegMask REG_0x07_LAMPSIM = 0x80;
+
+static constexpr RegMask REG_0x08_DECFLAG = 0x40;
+static constexpr RegMask REG_0x08_GMMFFR = 0x20;
+static constexpr RegMask REG_0x08_GMMFFG = 0x10;
+static constexpr RegMask REG_0x08_GMMFFB = 0x08;
+static constexpr RegMask REG_0x08_GMMZR = 0x04;
+static constexpr RegMask REG_0x08_GMMZG = 0x02;
+static constexpr RegMask REG_0x08_GMMZB = 0x01;
+
+static constexpr RegMask REG_0x09_MCNTSET = 0xc0;
+static constexpr RegMask REG_0x09_EVEN1ST = 0x20;
+static constexpr RegMask REG_0x09_BLINE1ST = 0x10;
+static constexpr RegMask REG_0x09_BACKSCAN = 0x08;
+static constexpr RegMask REG_0x09_ENHANCE = 0x04;
+static constexpr RegMask REG_0x09_SHORTTG = 0x02;
+static constexpr RegMask REG_0x09_NWAIT = 0x01;
+
+static constexpr RegShift REG_0x09S_MCNTSET = 6;
+static constexpr RegShift REG_0x09S_CLKSET = 4;
+
+static constexpr RegAddr REG_0x0B = 0x0b;
+static constexpr RegMask REG_0x0B_DRAMSEL = 0x07;
+static constexpr RegMask REG_0x0B_ENBDRAM = 0x08;
+static constexpr RegMask REG_0x0B_RFHDIS = 0x10;
+static constexpr RegMask REG_0x0B_CLKSET = 0xe0;
+static constexpr RegMask REG_0x0B_24MHZ = 0x00;
+static constexpr RegMask REG_0x0B_30MHZ = 0x20;
+static constexpr RegMask REG_0x0B_40MHZ = 0x40;
+static constexpr RegMask REG_0x0B_48MHZ = 0x60;
+static constexpr RegMask REG_0x0B_60MHZ = 0x80;
+
+static constexpr RegAddr REG_0x0D = 0x0d;
+static constexpr RegMask REG_0x0D_JAMPCMD = 0x80;
+static constexpr RegMask REG_0x0D_DOCCMD = 0x40;
+static constexpr RegMask REG_0x0D_CCDCMD = 0x20;
+static constexpr RegMask REG_0x0D_FULLSTP = 0x10;
+static constexpr RegMask REG_0x0D_SEND = 0x08;
+static constexpr RegMask REG_0x0D_CLRMCNT = 0x04;
+static constexpr RegMask REG_0x0D_CLRDOCJM = 0x02;
+static constexpr RegMask REG_0x0D_CLRLNCNT = 0x01;
+
+static constexpr RegAddr REG_0x0F = 0x0f;
+
+static constexpr RegAddr REG_EXPR = 0x10;
+static constexpr RegAddr REG_EXPG = 0x12;
+static constexpr RegAddr REG_EXPB = 0x14;
+
+static constexpr RegMask REG_0x16_CTRLHI = 0x80;
+static constexpr RegMask REG_0x16_TOSHIBA = 0x40;
+static constexpr RegMask REG_0x16_TGINV = 0x20;
+static constexpr RegMask REG_0x16_CK1INV = 0x10;
+static constexpr RegMask REG_0x16_CK2INV = 0x08;
+static constexpr RegMask REG_0x16_CTRLINV = 0x04;
+static constexpr RegMask REG_0x16_CKDIS = 0x02;
+static constexpr RegMask REG_0x16_CTRLDIS = 0x01;
+
+static constexpr RegMask REG_0x17_TGMODE = 0xc0;
+static constexpr RegMask REG_0x17_TGMODE_NO_DUMMY = 0x00;
+static constexpr RegMask REG_0x17_TGMODE_REF = 0x40;
+static constexpr RegMask REG_0x17_TGMODE_XPA = 0x80;
+static constexpr RegMask REG_0x17_TGW = 0x3f;
+static constexpr RegShift REG_0x17S_TGW = 0;
+
+static constexpr RegAddr REG_0x18 = 0x18;
+static constexpr RegMask REG_0x18_CNSET = 0x80;
+static constexpr RegMask REG_0x18_DCKSEL = 0x60;
+static constexpr RegMask REG_0x18_CKTOGGLE = 0x10;
+static constexpr RegMask REG_0x18_CKDELAY = 0x0c;
+static constexpr RegMask REG_0x18_CKSEL = 0x03;
+
+static constexpr RegAddr REG_EXPDMY = 0x19;
+
+static constexpr RegMask REG_0x1A_TGLSW2 = 0x80;
+static constexpr RegMask REG_0x1A_TGLSW1 = 0x40;
+static constexpr RegMask REG_0x1A_MANUAL3 = 0x02;
+static constexpr RegMask REG_0x1A_MANUAL1 = 0x01;
+static constexpr RegMask REG_0x1A_CK4INV = 0x08;
+static constexpr RegMask REG_0x1A_CK3INV = 0x04;
+static constexpr RegMask REG_0x1A_LINECLP = 0x02;
+
+static constexpr RegAddr REG_0x1C = 0x1c;
+static constexpr RegMask REG_0x1C_TGTIME = 0x07;
+
+static constexpr RegMask REG_0x1D_CK4LOW = 0x80;
+static constexpr RegMask REG_0x1D_CK3LOW = 0x40;
+static constexpr RegMask REG_0x1D_CK1LOW = 0x20;
+static constexpr RegMask REG_0x1D_TGSHLD = 0x1f;
+static constexpr RegShift REG_0x1DS_TGSHLD = 0;
+
+
+static constexpr RegAddr REG_0x1E = 0x1e;
+static constexpr RegMask REG_0x1E_WDTIME = 0xf0;
+static constexpr RegShift REG_0x1ES_WDTIME = 4;
+static constexpr RegMask REG_0x1E_LINESEL = 0x0f;
+static constexpr RegShift REG_0x1ES_LINESEL = 0;
+
+static constexpr RegAddr REG_0x21 = 0x21;
+static constexpr RegAddr REG_STEPNO = 0x21;
+static constexpr RegAddr REG_FWDSTEP = 0x22;
+static constexpr RegAddr REG_BWDSTEP = 0x23;
+static constexpr RegAddr REG_FASTNO = 0x24;
+static constexpr RegAddr REG_LINCNT = 0x25;
+
+static constexpr RegAddr REG_0x29 = 0x29;
+static constexpr RegAddr REG_0x2A = 0x2a;
+static constexpr RegAddr REG_0x2B = 0x2b;
+static constexpr RegAddr REG_DPISET = 0x2c;
+static constexpr RegAddr REG_0x2E = 0x2e;
+static constexpr RegAddr REG_0x2F = 0x2f;
+
+static constexpr RegAddr REG_STRPIXEL = 0x30;
+static constexpr RegAddr REG_ENDPIXEL = 0x32;
+static constexpr RegAddr REG_DUMMY = 0x34;
+static constexpr RegAddr REG_MAXWD = 0x35;
+static constexpr RegAddr REG_LPERIOD = 0x38;
+static constexpr RegAddr REG_FEEDL = 0x3d;
+
+static constexpr RegAddr REG_0x40 = 0x40;
+static constexpr RegMask REG_0x40_DOCSNR = 0x80;
+static constexpr RegMask REG_0x40_ADFSNR = 0x40;
+static constexpr RegMask REG_0x40_COVERSNR = 0x20;
+static constexpr RegMask REG_0x40_CHKVER = 0x10;
+static constexpr RegMask REG_0x40_DOCJAM = 0x08;
+static constexpr RegMask REG_0x40_HISPDFLG = 0x04;
+static constexpr RegMask REG_0x40_MOTMFLG = 0x02;
+static constexpr RegMask REG_0x40_DATAENB = 0x01;
+
+static constexpr RegMask REG_0x41_PWRBIT = 0x80;
+static constexpr RegMask REG_0x41_BUFEMPTY = 0x40;
+static constexpr RegMask REG_0x41_FEEDFSH = 0x20;
+static constexpr RegMask REG_0x41_SCANFSH = 0x10;
+static constexpr RegMask REG_0x41_HOMESNR = 0x08;
+static constexpr RegMask REG_0x41_LAMPSTS = 0x04;
+static constexpr RegMask REG_0x41_FEBUSY = 0x02;
+static constexpr RegMask REG_0x41_MOTORENB = 0x01;
+
+static constexpr RegMask REG_0x58_VSMP = 0xf8;
+static constexpr RegShift REG_0x58S_VSMP = 3;
+static constexpr RegMask REG_0x58_VSMPW = 0x07;
+static constexpr RegShift REG_0x58S_VSMPW = 0;
+
+static constexpr RegMask REG_0x59_BSMP = 0xf8;
+static constexpr RegShift REG_0x59S_BSMP = 3;
+static constexpr RegMask REG_0x59_BSMPW = 0x07;
+static constexpr RegShift REG_0x59S_BSMPW = 0;
+
+static constexpr RegMask REG_0x5A_ADCLKINV = 0x80;
+static constexpr RegMask REG_0x5A_RLCSEL = 0x40;
+static constexpr RegMask REG_0x5A_CDSREF = 0x30;
+static constexpr RegShift REG_0x5AS_CDSREF = 4;
+static constexpr RegMask REG_0x5A_RLC = 0x0f;
+static constexpr RegShift REG_0x5AS_RLC = 0;
+
+static constexpr RegAddr REG_0x5E = 0x5e;
+static constexpr RegMask REG_0x5E_DECSEL = 0xe0;
+static constexpr RegShift REG_0x5ES_DECSEL = 5;
+static constexpr RegMask REG_0x5E_STOPTIM = 0x1f;
+static constexpr RegShift REG_0x5ES_STOPTIM = 0;
+
+static constexpr RegAddr REG_FMOVDEC = 0x5f;
+
+static constexpr RegAddr REG_0x60 = 0x60;
+static constexpr RegMask REG_0x60_Z1MOD = 0x1f;
+static constexpr RegAddr REG_0x61 = 0x61;
+static constexpr RegMask REG_0x61_Z1MOD = 0xff;
+static constexpr RegAddr REG_0x62 = 0x62;
+static constexpr RegMask REG_0x62_Z1MOD = 0xff;
+
+static constexpr RegAddr REG_0x63 = 0x63;
+static constexpr RegMask REG_0x63_Z2MOD = 0x1f;
+static constexpr RegAddr REG_0x64 = 0x64;
+static constexpr RegMask REG_0x64_Z2MOD = 0xff;
+static constexpr RegAddr REG_0x65 = 0x65;
+static constexpr RegMask REG_0x65_Z2MOD = 0xff;
+
+static constexpr RegAddr REG_0x67 = 0x67;
+
+static constexpr RegAddr REG_0x68 = 0x68;
+
+static constexpr RegShift REG_0x67S_STEPSEL = 6;
+static constexpr RegMask REG_0x67_STEPSEL = 0xc0;
+static constexpr RegMask REG_0x67_FULLSTEP = 0x00;
+static constexpr RegMask REG_0x67_HALFSTEP = 0x20;
+static constexpr RegMask REG_0x67_EIGHTHSTEP = 0x60;
+static constexpr RegMask REG_0x67_16THSTEP = 0x80;
+
+static constexpr RegShift REG_0x68S_FSTPSEL = 6;
+static constexpr RegMask REG_0x68_FSTPSEL = 0xc0;
+static constexpr RegMask REG_0x68_FULLSTEP = 0x00;
+static constexpr RegMask REG_0x68_HALFSTEP = 0x20;
+static constexpr RegMask REG_0x68_EIGHTHSTEP = 0x60;
+static constexpr RegMask REG_0x68_16THSTEP = 0x80;
+
+static constexpr RegAddr REG_FSHDEC = 0x69;
+static constexpr RegAddr REG_FMOVNO = 0x6a;
+
+static constexpr RegAddr REG_0x6B = 0x6b;
+static constexpr RegMask REG_0x6B_MULTFILM = 0x80;
+static constexpr RegMask REG_0x6B_GPOM13 = 0x40;
+static constexpr RegMask REG_0x6B_GPOM12 = 0x20;
+static constexpr RegMask REG_0x6B_GPOM11 = 0x10;
+static constexpr RegMask REG_0x6B_GPOCK4 = 0x08;
+static constexpr RegMask REG_0x6B_GPOCP = 0x04;
+static constexpr RegMask REG_0x6B_GPOLEDB = 0x02;
+static constexpr RegMask REG_0x6B_GPOADF = 0x01;
+
+static constexpr RegAddr REG_0x6C = 0x6c;
+static constexpr RegMask REG_0x6C_GPIO16 = 0x80;
+static constexpr RegMask REG_0x6C_GPIO15 = 0x40;
+static constexpr RegMask REG_0x6C_GPIO14 = 0x20;
+static constexpr RegMask REG_0x6C_GPIO13 = 0x10;
+static constexpr RegMask REG_0x6C_GPIO12 = 0x08;
+static constexpr RegMask REG_0x6C_GPIO11 = 0x04;
+static constexpr RegMask REG_0x6C_GPIO10 = 0x02;
+static constexpr RegMask REG_0x6C_GPIO9 = 0x01;
+static constexpr RegMask REG_0x6C_GPIOH = 0xff;
+static constexpr RegMask REG_0x6C_GPIOL = 0xff;
+
+static constexpr RegAddr REG_Z1MOD = 0x60;
+static constexpr RegAddr REG_Z2MOD = 0x63;
+
+static constexpr RegAddr REG_0x6D = 0x6d;
+static constexpr RegAddr REG_0x6E = 0x6e;
+static constexpr RegAddr REG_0x6F = 0x6f;
+
+static constexpr RegAddr REG_CK1MAP = 0x74;
+static constexpr RegAddr REG_CK3MAP = 0x77;
+static constexpr RegAddr REG_CK4MAP = 0x7a;
+
+static constexpr RegAddr REG_0x7E = 0x7e;
+
+static constexpr RegAddr REG_0x9D = 0x9d;
+static constexpr RegShift REG_0x9DS_STEPTIM = 2;
+
+static constexpr RegMask REG_0x87_LEDADD = 0x04;
+
+static constexpr RegAddr REG_0xA6 = 0xa6;
+static constexpr RegMask REG_0xA6_GPIO24 = 0x80;
+static constexpr RegMask REG_0xA6_GPIO23 = 0x40;
+static constexpr RegMask REG_0xA6_GPIO22 = 0x20;
+static constexpr RegMask REG_0xA6_GPIO21 = 0x10;
+static constexpr RegMask REG_0xA6_GPIO20 = 0x08;
+static constexpr RegMask REG_0xA6_GPIO19 = 0x04;
+static constexpr RegMask REG_0xA6_GPIO18 = 0x02;
+static constexpr RegMask REG_0xA6_GPIO17 = 0x01;
+static constexpr RegAddr REG_0xA7 = 0xa7;
+static constexpr RegMask REG_0xA7_GPOE24 = 0x80;
+static constexpr RegMask REG_0xA7_GPOE23 = 0x40;
+static constexpr RegMask REG_0xA7_GPOE22 = 0x20;
+static constexpr RegMask REG_0xA7_GPOE21 = 0x10;
+static constexpr RegMask REG_0xA7_GPOE20 = 0x08;
+static constexpr RegMask REG_0xA7_GPOE19 = 0x04;
+static constexpr RegMask REG_0xA7_GPOE18 = 0x02;
+static constexpr RegMask REG_0xA7_GPOE17 = 0x01;
+static constexpr RegAddr REG_0xA8 = 0xa8;
+static constexpr RegMask REG_0xA8_GPOE27 = 0x20;
+static constexpr RegMask REG_0xA8_GPOE26 = 0x10;
+static constexpr RegMask REG_0xA8_GPOE25 = 0x08;
+static constexpr RegMask REG_0xA8_GPO27 = 0x04;
+static constexpr RegMask REG_0xA8_GPO26 = 0x02;
+static constexpr RegMask REG_0xA8_GPO25 = 0x01;
+static constexpr RegAddr REG_0xA9 = 0xa9;
+static constexpr RegMask REG_0xA9_GPO33 = 0x20;
+static constexpr RegMask REG_0xA9_GPO32 = 0x10;
+static constexpr RegMask REG_0xA9_GPO31 = 0x08;
+static constexpr RegMask REG_0xA9_GPO30 = 0x04;
+static constexpr RegMask REG_0xA9_GPO29 = 0x02;
+static constexpr RegMask REG_0xA9_GPO28 = 0x01;
+
+} // namespace gl843
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL843_REGISTERS_H
diff --git a/backend/genesys/gl846.cpp b/backend/genesys/gl846.cpp
new file mode 100644
index 0000000..d309d29
--- /dev/null
+++ b/backend/genesys/gl846.cpp
@@ -0,0 +1,2098 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2012-2013 Stéphane Voltz <stef.dev@free.fr>
+
+
+ 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.
+*/
+
+/** @file
+ *
+ * This file handles GL846 and GL845 ASICs since they are really close to each other.
+ */
+
+#define DEBUG_DECLARE_ONLY
+
+#include "gl846.h"
+#include "gl846_registers.h"
+#include "test_settings.h"
+
+#include <vector>
+
+namespace genesys {
+namespace gl846 {
+
+/**
+ * compute the step multiplier used
+ */
+static int
+gl846_get_step_multiplier (Genesys_Register_Set * regs)
+{
+ GenesysRegister *r = sanei_genesys_get_address(regs, 0x9d);
+ int value = 1;
+ if (r != nullptr) {
+ value = (r->value & 0x0f)>>1;
+ value = 1 << value;
+ }
+ DBG (DBG_io, "%s: step multiplier is %d\n", __func__, value);
+ return value;
+}
+
+/** @brief sensor specific settings
+*/
+static void gl846_setup_sensor(Genesys_Device * dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs)
+{
+ DBG_HELPER(dbg);
+
+ for (const auto& reg : sensor.custom_regs) {
+ regs->set8(reg.address, reg.value);
+ }
+
+ regs->set16(REG_EXPR, sensor.exposure.red);
+ regs->set16(REG_EXPG, sensor.exposure.green);
+ regs->set16(REG_EXPB, sensor.exposure.blue);
+
+ dev->segment_order = sensor.segment_order;
+}
+
+
+/** @brief set all registers to default values .
+ * This function is called only once at the beginning and
+ * fills register startup values for registers reused across scans.
+ * Those that are rarely modified or not modified are written
+ * individually.
+ * @param dev device structure holding register set to initialize
+ */
+static void
+gl846_init_registers (Genesys_Device * dev)
+{
+ DBG_HELPER(dbg);
+
+ dev->reg.clear();
+
+ dev->reg.init_reg(0x01, 0x60);
+ dev->reg.init_reg(0x02, 0x38);
+ dev->reg.init_reg(0x03, 0x03);
+ dev->reg.init_reg(0x04, 0x22);
+ dev->reg.init_reg(0x05, 0x60);
+ dev->reg.init_reg(0x06, 0x10);
+ dev->reg.init_reg(0x08, 0x60);
+ dev->reg.init_reg(0x09, 0x00);
+ dev->reg.init_reg(0x0a, 0x00);
+ dev->reg.init_reg(0x0b, 0x8b);
+ dev->reg.init_reg(0x0c, 0x00);
+ dev->reg.init_reg(0x0d, 0x00);
+ dev->reg.init_reg(0x10, 0x00);
+ dev->reg.init_reg(0x11, 0x00);
+ dev->reg.init_reg(0x12, 0x00);
+ dev->reg.init_reg(0x13, 0x00);
+ dev->reg.init_reg(0x14, 0x00);
+ dev->reg.init_reg(0x15, 0x00);
+ dev->reg.init_reg(0x16, 0xbb); // SENSOR_DEF
+ dev->reg.init_reg(0x17, 0x13); // SENSOR_DEF
+ dev->reg.init_reg(0x18, 0x10); // SENSOR_DEF
+ dev->reg.init_reg(0x19, 0x2a); // SENSOR_DEF
+ dev->reg.init_reg(0x1a, 0x34); // SENSOR_DEF
+ dev->reg.init_reg(0x1b, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x1c, 0x20); // SENSOR_DEF
+ dev->reg.init_reg(0x1d, 0x06); // SENSOR_DEF
+ dev->reg.init_reg(0x1e, 0xf0);
+ dev->reg.init_reg(0x1f, 0x01);
+ dev->reg.init_reg(0x20, 0x03);
+ dev->reg.init_reg(0x21, 0x10);
+ dev->reg.init_reg(0x22, 0x60);
+ dev->reg.init_reg(0x23, 0x60);
+ dev->reg.init_reg(0x24, 0x60);
+ dev->reg.init_reg(0x25, 0x00);
+ dev->reg.init_reg(0x26, 0x00);
+ dev->reg.init_reg(0x27, 0x00);
+ dev->reg.init_reg(0x2c, 0x00);
+ dev->reg.init_reg(0x2d, 0x00);
+ dev->reg.init_reg(0x2e, 0x80);
+ dev->reg.init_reg(0x2f, 0x80);
+ dev->reg.init_reg(0x30, 0x00);
+ dev->reg.init_reg(0x31, 0x00);
+ dev->reg.init_reg(0x32, 0x00);
+ dev->reg.init_reg(0x33, 0x00);
+ dev->reg.init_reg(0x34, 0x1f);
+ dev->reg.init_reg(0x35, 0x00);
+ dev->reg.init_reg(0x36, 0x40);
+ dev->reg.init_reg(0x37, 0x00);
+ dev->reg.init_reg(0x38, 0x2a);
+ dev->reg.init_reg(0x39, 0xf8);
+ dev->reg.init_reg(0x3d, 0x00);
+ dev->reg.init_reg(0x3e, 0x00);
+ dev->reg.init_reg(0x3f, 0x01);
+ dev->reg.init_reg(0x52, 0x02); // SENSOR_DEF
+ dev->reg.init_reg(0x53, 0x04); // SENSOR_DEF
+ dev->reg.init_reg(0x54, 0x06); // SENSOR_DEF
+ dev->reg.init_reg(0x55, 0x08); // SENSOR_DEF
+ dev->reg.init_reg(0x56, 0x0a); // SENSOR_DEF
+ dev->reg.init_reg(0x57, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x58, 0x59); // SENSOR_DEF
+ dev->reg.init_reg(0x59, 0x31); // SENSOR_DEF
+ dev->reg.init_reg(0x5a, 0x40); // SENSOR_DEF
+ dev->reg.init_reg(0x5e, 0x1f);
+ dev->reg.init_reg(0x5f, 0x01);
+ dev->reg.init_reg(0x60, 0x00);
+ dev->reg.init_reg(0x61, 0x00);
+ dev->reg.init_reg(0x62, 0x00);
+ dev->reg.init_reg(0x63, 0x00);
+ dev->reg.init_reg(0x64, 0x00);
+ dev->reg.init_reg(0x65, 0x00);
+ dev->reg.init_reg(0x67, 0x7f);
+ dev->reg.init_reg(0x68, 0x7f);
+ dev->reg.init_reg(0x69, 0x01);
+ dev->reg.init_reg(0x6a, 0x01);
+ dev->reg.init_reg(0x70, 0x01);
+ dev->reg.init_reg(0x71, 0x00);
+ dev->reg.init_reg(0x72, 0x02);
+ dev->reg.init_reg(0x73, 0x01);
+ dev->reg.init_reg(0x74, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x75, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x76, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x77, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x78, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x79, 0x3f); // SENSOR_DEF
+ dev->reg.init_reg(0x7a, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x7b, 0x09); // SENSOR_DEF
+ dev->reg.init_reg(0x7c, 0x99); // SENSOR_DEF
+ dev->reg.init_reg(0x7d, 0x20);
+ dev->reg.init_reg(0x7f, 0x05);
+ dev->reg.init_reg(0x80, 0x4f);
+ dev->reg.init_reg(0x87, 0x02);
+ dev->reg.init_reg(0x94, 0xff);
+ dev->reg.init_reg(0x9d, 0x04);
+ dev->reg.init_reg(0x9e, 0x00);
+ dev->reg.init_reg(0xa1, 0xe0);
+ dev->reg.init_reg(0xa2, 0x1f);
+ dev->reg.init_reg(0xab, 0xc0);
+ dev->reg.init_reg(0xbb, 0x00);
+ dev->reg.init_reg(0xbc, 0x0f);
+ dev->reg.init_reg(0xdb, 0xff);
+ dev->reg.init_reg(0xfe, 0x08);
+ dev->reg.init_reg(0xff, 0x02);
+ dev->reg.init_reg(0x98, 0x20);
+ dev->reg.init_reg(0x99, 0x00);
+ dev->reg.init_reg(0x9a, 0x90);
+ dev->reg.init_reg(0x9b, 0x00);
+ dev->reg.init_reg(0xf8, 0x05);
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+ sanei_genesys_set_dpihw(dev->reg, sensor, sensor.optical_res);
+
+ /* initalize calibration reg */
+ dev->calib_reg = dev->reg;
+}
+
+/**@brief send slope table for motor movement
+ * Send slope_table in machine byte order
+ * @param dev device to send slope table
+ * @param table_nr index of the slope table in ASIC memory
+ * Must be in the [0-4] range.
+ * @param slope_table pointer to 16 bit values array of the slope table
+ * @param steps number of elements in the slope table
+ */
+static void gl846_send_slope_table(Genesys_Device* dev, int table_nr,
+ const std::vector<uint16_t>& slope_table,
+ int steps)
+{
+ DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d", table_nr, steps);
+ int i;
+ char msg[10000];
+
+ /* sanity check */
+ if(table_nr<0 || table_nr>4)
+ {
+ throw SaneException("invalid table number %d", table_nr);
+ }
+
+ std::vector<uint8_t> table(steps * 2);
+ for (i = 0; i < steps; i++)
+ {
+ table[i * 2] = slope_table[i] & 0xff;
+ table[i * 2 + 1] = slope_table[i] >> 8;
+ }
+
+ if (DBG_LEVEL >= DBG_io)
+ {
+ std::sprintf(msg, "write slope %d (%d)=", table_nr, steps);
+ for (i = 0; i < steps; i++)
+ {
+ std::sprintf(msg+strlen(msg), "%d", slope_table[i]);
+ }
+ DBG (DBG_io, "%s: %s\n", __func__, msg);
+ }
+
+ if (dev->interface->is_mock()) {
+ dev->interface->record_slope_table(table_nr, slope_table);
+ }
+ // slope table addresses are fixed
+ dev->interface->write_ahb(0x10000000 + 0x4000 * table_nr, steps * 2, table.data());
+}
+
+/**
+ * Set register values of Analog Device type frontend
+ * */
+static void gl846_set_adi_fe(Genesys_Device* dev, uint8_t set)
+{
+ DBG_HELPER(dbg);
+ int i;
+
+ // wait for FE to be ready
+ auto status = scanner_read_status(*dev);
+ while (status.is_front_end_busy) {
+ dev->interface->sleep_ms(10);
+ status = scanner_read_status(*dev);
+ };
+
+ if (set == AFE_INIT)
+ {
+ DBG(DBG_proc, "%s(): setting DAC %u\n", __func__,
+ static_cast<unsigned>(dev->model->adc_id));
+ dev->frontend = dev->frontend_initial;
+ }
+
+ // write them to analog frontend
+ dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
+
+ dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01));
+
+ for (i = 0; i < 3; i++) {
+ dev->interface->write_fe_register(0x02 + i, dev->frontend.get_gain(i));
+ }
+ for (i = 0; i < 3; i++) {
+ dev->interface->write_fe_register(0x05 + i, dev->frontend.get_offset(i));
+ }
+}
+
+// Set values of analog frontend
+void CommandSetGl846::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const
+{
+ DBG_HELPER_ARGS(dbg, "%s", set == AFE_INIT ? "init" :
+ set == AFE_SET ? "set" :
+ set == AFE_POWER_SAVE ? "powersave" : "huh?");
+ (void) sensor;
+
+ /* route to specific analog frontend setup */
+ uint8_t frontend_type = dev->reg.find_reg(0x04).value & REG_0x04_FESET;
+ switch (frontend_type) {
+ case 0x02: /* ADI FE */
+ gl846_set_adi_fe(dev, set);
+ break;
+ default:
+ throw SaneException("unsupported frontend type %d", frontend_type);
+ }
+}
+
+
+// @brief set up motor related register for scan
+static void gl846_init_motor_regs_scan(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const Motor_Profile& motor_profile,
+ unsigned int scan_exposure_time,
+ unsigned scan_yres,
+ unsigned int scan_lines,
+ unsigned int scan_dummy,
+ unsigned int feed_steps,
+ MotorFlag flags)
+{
+ DBG_HELPER_ARGS(dbg, "scan_exposure_time=%d, scan_yres=%d, step_type=%d, scan_lines=%d, "
+ "scan_dummy=%d, feed_steps=%d, flags=%x",
+ scan_exposure_time, scan_yres, static_cast<unsigned>(motor_profile.step_type),
+ scan_lines, scan_dummy, feed_steps, static_cast<unsigned>(flags));
+ int use_fast_fed;
+ unsigned int fast_dpi;
+ unsigned int feedl, dist;
+ GenesysRegister *r;
+ uint32_t z1, z2;
+ unsigned int min_restep = 0x20;
+ uint8_t val;
+ unsigned int ccdlmt,tgtime;
+
+ unsigned step_multiplier = gl846_get_step_multiplier(reg);
+
+ use_fast_fed=0;
+ /* no fast fed since feed works well */
+ if (dev->settings.yres == 4444 && feed_steps > 100 && !has_flag(flags, MotorFlag::FEED)) {
+ use_fast_fed = 1;
+ }
+ DBG (DBG_io, "%s: use_fast_fed=%d\n", __func__, use_fast_fed);
+
+ reg->set24(REG_LINCNT, scan_lines);
+ DBG (DBG_io, "%s: lincnt=%d\n", __func__, scan_lines);
+
+ /* compute register 02 value */
+ r = sanei_genesys_get_address(reg, REG_0x02);
+ r->value = 0x00;
+ sanei_genesys_set_motor_power(*reg, true);
+
+ if (use_fast_fed)
+ r->value |= REG_0x02_FASTFED;
+ else
+ r->value &= ~REG_0x02_FASTFED;
+
+ if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) {
+ r->value |= REG_0x02_AGOHOME | REG_0x02_NOTHOME;
+ }
+
+ if (has_flag(flags, MotorFlag::DISABLE_BUFFER_FULL_MOVE) ||(scan_yres>=sensor.optical_res)) {
+ r->value |= REG_0x02_ACDCDIS;
+ }
+ if (has_flag(flags, MotorFlag::REVERSE)) {
+ r->value |= REG_0x02_MTRREV;
+ } else {
+ r->value &= ~REG_0x02_MTRREV;
+ }
+
+ /* scan and backtracking slope table */
+ auto scan_table = sanei_genesys_slope_table(dev->model->asic_type, scan_yres,
+ scan_exposure_time, dev->motor.base_ydpi,
+ step_multiplier, motor_profile);
+
+ gl846_send_slope_table(dev, SCAN_TABLE, scan_table.table, scan_table.steps_count);
+ gl846_send_slope_table(dev, BACKTRACK_TABLE, scan_table.table, scan_table.steps_count);
+
+ /* fast table */
+ fast_dpi=sanei_genesys_get_lowest_ydpi(dev);
+
+ // BUG: looks like for fast moves we use inconsistent step type
+ StepType fast_step_type = motor_profile.step_type;
+ if (static_cast<unsigned>(motor_profile.step_type) >= static_cast<unsigned>(StepType::QUARTER)) {
+ fast_step_type = StepType::QUARTER;
+ }
+
+ Motor_Profile fast_motor_profile = motor_profile;
+ fast_motor_profile.step_type = fast_step_type;
+
+ auto fast_table = sanei_genesys_slope_table(dev->model->asic_type, fast_dpi,
+ scan_exposure_time, dev->motor.base_ydpi,
+ step_multiplier, fast_motor_profile);
+
+ gl846_send_slope_table(dev, STOP_TABLE, fast_table.table, fast_table.steps_count);
+ gl846_send_slope_table(dev, FAST_TABLE, fast_table.table, fast_table.steps_count);
+ gl846_send_slope_table(dev, HOME_TABLE, fast_table.table, fast_table.steps_count);
+
+ /* correct move distance by acceleration and deceleration amounts */
+ feedl=feed_steps;
+ if (use_fast_fed)
+ {
+ feedl <<= static_cast<unsigned>(fast_step_type);
+ dist = (scan_table.steps_count + 2 * fast_table.steps_count);
+ /* TODO read and decode REG_0xAB */
+ r = sanei_genesys_get_address (reg, 0x5e);
+ dist += (r->value & 31);
+ /* FEDCNT */
+ r = sanei_genesys_get_address(reg, REG_FEDCNT);
+ dist += r->value;
+ }
+ else
+ {
+ feedl <<= static_cast<unsigned>(motor_profile.step_type);
+ dist = scan_table.steps_count;
+ if (has_flag(flags, MotorFlag::FEED)) {
+ dist *= 2;
+ }
+ }
+ DBG (DBG_io2, "%s: acceleration distance=%d\n", __func__, dist);
+
+ /* check for overflow */
+ if (dist < feedl) {
+ feedl -= dist;
+ } else {
+ feedl = 0;
+ }
+
+ reg->set24(REG_FEEDL, feedl);
+ DBG (DBG_io ,"%s: feedl=%d\n",__func__,feedl);
+
+ r = sanei_genesys_get_address(reg, REG_0x0C);
+ ccdlmt = (r->value & REG_0x0C_CCDLMT) + 1;
+
+ r = sanei_genesys_get_address(reg, REG_0x1C);
+ tgtime = 1 << (r->value & REG_0x1C_TGTIME);
+
+ /* hi res motor speed GPIO */
+ /*
+ uint8_t effective = dev->interface->read_register(REG_0x6C);
+ */
+
+ /* if quarter step, bipolar Vref2 */
+ /* XXX STEF XXX GPIO
+ if (motor_profile.step_type > 1)
+ {
+ if (motor_profile.step_type < 3)
+ {
+ val = effective & ~REG_0x6C_GPIO13;
+ }
+ else
+ {
+ val = effective | REG_0x6C_GPIO13;
+ }
+ }
+ else
+ {
+ val = effective;
+ }
+ dev->interface->write_register(REG_0x6C, val);
+ */
+
+ /* effective scan */
+ /*
+ effective = dev->interface->read_register(REG_0x6C);
+ val = effective | REG_0x6C_GPIO10;
+ dev->interface->write_register(REG_0x6C, val);
+ */
+
+ if(dev->model->gpio_id == GpioId::IMG101) {
+ if (scan_yres == sensor.get_register_hwdpi(scan_yres)) {
+ val=1;
+ }
+ else
+ {
+ val=0;
+ }
+ dev->interface->write_register(REG_0x7E, val);
+ }
+
+ min_restep = (scan_table.steps_count / step_multiplier) / 2 - 1;
+ if (min_restep < 1) {
+ min_restep = 1;
+ }
+ r = sanei_genesys_get_address(reg, REG_FWDSTEP);
+ r->value = min_restep;
+ r = sanei_genesys_get_address(reg, REG_BWDSTEP);
+ r->value = min_restep;
+
+ sanei_genesys_calculate_zmod(use_fast_fed,
+ scan_exposure_time*ccdlmt*tgtime,
+ scan_table.table,
+ scan_table.steps_count,
+ feedl,
+ min_restep * step_multiplier,
+ &z1,
+ &z2);
+
+ DBG(DBG_info, "%s: z1 = %d\n", __func__, z1);
+ reg->set24(REG_0x60, z1 | (static_cast<unsigned>(motor_profile.step_type) << (16 + REG_0x60S_STEPSEL)));
+
+ DBG(DBG_info, "%s: z2 = %d\n", __func__, z2);
+ reg->set24(REG_0x63, z2 | (static_cast<unsigned>(motor_profile.step_type) << (16 + REG_0x63S_FSTPSEL)));
+
+ r = sanei_genesys_get_address (reg, 0x1e);
+ r->value &= 0xf0; /* 0 dummy lines */
+ r->value |= scan_dummy; /* dummy lines */
+
+ r = sanei_genesys_get_address(reg, REG_0x67);
+ r->value = 0x7f;
+
+ r = sanei_genesys_get_address(reg, REG_0x68);
+ r->value = 0x7f;
+
+ reg->set8(REG_STEPNO, scan_table.steps_count / step_multiplier);
+ reg->set8(REG_FASTNO, scan_table.steps_count / step_multiplier);
+ reg->set8(REG_FSHDEC, scan_table.steps_count / step_multiplier);
+ reg->set8(REG_FMOVNO, fast_table.steps_count / step_multiplier);
+ reg->set8(REG_FMOVDEC, fast_table.steps_count / step_multiplier);
+}
+
+
+/** @brief set up registers related to sensor
+ * Set up the following registers
+ 0x01
+ 0x03
+ 0x10-0x015 R/G/B exposures
+ 0x19 EXPDMY
+ 0x2e BWHI
+ 0x2f BWLO
+ 0x04
+ 0x87
+ 0x05
+ 0x2c,0x2d DPISET
+ 0x30,0x31 STRPIXEL
+ 0x32,0x33 ENDPIXEL
+ 0x35,0x36,0x37 MAXWD [25:2] (>>2)
+ 0x38,0x39 LPERIOD
+ 0x34 DUMMY
+ */
+static void gl846_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, unsigned int exposure_time,
+ const ScanSession& session)
+{
+ DBG_HELPER_ARGS(dbg, "exposure_time=%d", exposure_time);
+ unsigned int dpihw;
+ GenesysRegister *r;
+
+ // resolution is divided according to ccd_pixels_per_system_pixel()
+ unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel();
+ DBG(DBG_io2, "%s: ccd_pixels_per_system_pixel=%d\n", __func__, ccd_pixels_per_system_pixel);
+
+ // to manage high resolution device while keeping good low resolution scanning speed,
+ // we make hardware dpi vary
+ dpihw = sensor.get_register_hwdpi(session.params.xres * ccd_pixels_per_system_pixel);
+ DBG(DBG_io2, "%s: dpihw=%d\n", __func__, dpihw);
+
+ gl846_setup_sensor(dev, sensor, reg);
+
+ dev->cmd_set->set_fe(dev, sensor, AFE_SET);
+
+ /* enable shading */
+ regs_set_optical_off(dev->model->asic_type, *reg);
+ r = sanei_genesys_get_address(reg, REG_0x01);
+ r->value |= REG_0x01_SHDAREA;
+ if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) ||
+ (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION))
+ {
+ r->value &= ~REG_0x01_DVDSET;
+ }
+ else
+ {
+ r->value |= REG_0x01_DVDSET;
+ }
+
+ r = sanei_genesys_get_address(reg, REG_0x03);
+ r->value &= ~REG_0x03_AVEENB;
+
+ sanei_genesys_set_lamp_power(dev, sensor, *reg,
+ !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP));
+
+ /* BW threshold */
+ r = sanei_genesys_get_address (reg, 0x2e);
+ r->value = dev->settings.threshold;
+ r = sanei_genesys_get_address (reg, 0x2f);
+ r->value = dev->settings.threshold;
+
+ /* monochrome / color scan */
+ r = sanei_genesys_get_address(reg, REG_0x04);
+ switch (session.params.depth) {
+ case 8:
+ r->value &= ~(REG_0x04_LINEART | REG_0x04_BITSET);
+ break;
+ case 16:
+ r->value &= ~REG_0x04_LINEART;
+ r->value |= REG_0x04_BITSET;
+ break;
+ }
+
+ r->value &= ~(REG_0x04_FILTER | REG_0x04_AFEMOD);
+ if (session.params.channels == 1)
+ {
+ switch (session.params.color_filter)
+ {
+ case ColorFilter::RED:
+ r->value |= 0x24;
+ break;
+ case ColorFilter::BLUE:
+ r->value |= 0x2c;
+ break;
+ case ColorFilter::GREEN:
+ r->value |= 0x28;
+ break;
+ default:
+ break; // should not happen
+ }
+ } else {
+ r->value |= 0x20; // mono
+ }
+
+ sanei_genesys_set_dpihw(*reg, sensor, dpihw);
+
+ if (should_enable_gamma(session, sensor)) {
+ reg->find_reg(REG_0x05).value |= REG_0x05_GMMENB;
+ } else {
+ reg->find_reg(REG_0x05).value &= ~REG_0x05_GMMENB;
+ }
+
+ /* CIS scanners can do true gray by setting LEDADD */
+ /* we set up LEDADD only when asked */
+ if (dev->model->is_cis) {
+ r = sanei_genesys_get_address (reg, 0x87);
+ r->value &= ~REG_0x87_LEDADD;
+ if (session.enable_ledadd) {
+ r->value |= REG_0x87_LEDADD;
+ }
+ /* RGB weighting
+ r = sanei_genesys_get_address (reg, 0x01);
+ r->value &= ~REG_0x01_TRUEGRAY;
+ if (session.enable_ledadd))
+ {
+ r->value |= REG_0x01_TRUEGRAY;
+ }*/
+ }
+
+ unsigned dpiset = session.params.xres * ccd_pixels_per_system_pixel;
+ reg->set16(REG_DPISET, dpiset);
+ DBG(DBG_io2, "%s: dpiset used=%d\n", __func__, dpiset);
+
+ reg->set16(REG_STRPIXEL, session.pixel_startx);
+ reg->set16(REG_ENDPIXEL, session.pixel_endx);
+
+ build_image_pipeline(dev, session);
+
+ /* MAXWD is expressed in 4 words unit */
+ // BUG: we shouldn't multiply by channels here
+ reg->set24(REG_MAXWD, (session.output_line_bytes_raw * session.params.channels >> 2));
+
+ reg->set16(REG_LPERIOD, exposure_time);
+ DBG (DBG_io2, "%s: exposure_time used=%d\n", __func__, exposure_time);
+
+ r = sanei_genesys_get_address (reg, 0x34);
+ r->value = sensor.dummy_pixel;
+}
+
+void CommandSetGl846::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const
+{
+ DBG_HELPER(dbg);
+ session.assert_computed();
+
+ int move;
+ int exposure_time;
+
+ int slope_dpi = 0;
+ int dummy = 0;
+
+ dummy = 3-session.params.channels;
+
+/* slope_dpi */
+/* cis color scan is effectively a gray scan with 3 gray lines per color
+ line and a FILTER of 0 */
+ if (dev->model->is_cis) {
+ slope_dpi = session.params.yres * session.params.channels;
+ } else {
+ slope_dpi = session.params.yres;
+ }
+
+ slope_dpi = slope_dpi * (1 + dummy);
+
+ exposure_time = sensor.exposure_lperiod;
+ const auto& motor_profile = sanei_genesys_get_motor_profile(*gl846_motor_profiles,
+ dev->model->motor_id,
+ exposure_time);
+
+ DBG(DBG_info, "%s : exposure_time=%d pixels\n", __func__, exposure_time);
+ DBG(DBG_info, "%s : scan_step_type=%d\n", __func__,
+ static_cast<unsigned>(motor_profile.step_type));
+
+ /* we enable true gray for cis scanners only, and just when doing
+ * scan since color calibration is OK for this mode
+ */
+ gl846_init_optical_regs_scan(dev, sensor, reg, exposure_time, session);
+
+/*** motor parameters ***/
+
+ /* add tl_y to base movement */
+ move = session.params.starty;
+ DBG(DBG_info, "%s: move=%d steps\n", __func__, move);
+
+ MotorFlag mflags = MotorFlag::NONE;
+ if (has_flag(session.params.flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE)) {
+ mflags |= MotorFlag::DISABLE_BUFFER_FULL_MOVE;
+ }
+ if (has_flag(session.params.flags, ScanFlag::FEEDING)) {
+ mflags |= MotorFlag::FEED;
+ }
+ if (has_flag(session.params.flags, ScanFlag::REVERSE)) {
+ mflags |= MotorFlag::REVERSE;
+ }
+
+ gl846_init_motor_regs_scan(dev, sensor, reg, motor_profile, exposure_time, slope_dpi,
+ dev->model->is_cis ? session.output_line_count * session.params.channels
+ : session.output_line_count,
+ dummy, move, mflags);
+
+ /*** prepares data reordering ***/
+
+ dev->read_buffer.clear();
+ dev->read_buffer.alloc(session.buffer_size_read);
+
+ dev->read_active = true;
+
+ dev->session = session;
+
+ dev->total_bytes_read = 0;
+ dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines;
+
+ DBG(DBG_info, "%s: total bytes to send = %zu\n", __func__, dev->total_bytes_to_read);
+}
+
+ScanSession CommandSetGl846::calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const
+{
+ int start;
+
+ DBG(DBG_info, "%s ", __func__);
+ debug_dump(DBG_info, settings);
+
+ /* start */
+ start = static_cast<int>(dev->model->x_offset);
+ start += static_cast<int>(settings.tl_x);
+ start = static_cast<int>((start * sensor.optical_res) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = settings.xres;
+ session.params.yres = settings.yres;
+ session.params.startx = start; // not used
+ session.params.starty = 0; // not used
+ session.params.pixels = settings.pixels;
+ session.params.requested_pixels = settings.requested_pixels;
+ session.params.lines = settings.lines;
+ session.params.depth = settings.depth;
+ session.params.channels = settings.get_channels();
+ session.params.scan_method = settings.scan_method;
+ session.params.scan_mode = settings.scan_mode;
+ session.params.color_filter = settings.color_filter;
+ session.params.flags = ScanFlag::NONE;
+
+ compute_session(dev, session, sensor);
+
+ return session;
+}
+
+// for fast power saving methods only, like disabling certain amplifiers
+void CommandSetGl846::save_power(Genesys_Device* dev, bool enable) const
+{
+ (void) dev;
+ DBG_HELPER_ARGS(dbg, "enable = %d", enable);
+}
+
+void CommandSetGl846::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const
+{
+ (void) dev;
+ DBG_HELPER_ARGS(dbg, "delay = %d", delay);
+}
+
+// Send the low-level scan command
+void CommandSetGl846::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, bool start_motor) const
+{
+ DBG_HELPER(dbg);
+ (void) sensor;
+ uint8_t val;
+ GenesysRegister *r;
+
+ /* XXX STEF XXX SCAN GPIO */
+ /*
+ val = dev->interface->read_register(REG_0x6C);
+ dev->interface->write_register(REG_0x6C, val);
+ */
+
+ val = REG_0x0D_CLRLNCNT;
+ dev->interface->write_register(REG_0x0D, val);
+ val = REG_0x0D_CLRMCNT;
+ dev->interface->write_register(REG_0x0D, val);
+
+ val = dev->interface->read_register(REG_0x01);
+ val |= REG_0x01_SCAN;
+ dev->interface->write_register(REG_0x01, val);
+ r = sanei_genesys_get_address (reg, REG_0x01);
+ r->value = val;
+
+ scanner_start_action(*dev, start_motor);
+
+ dev->advance_head_pos_by_session(ScanHeadId::PRIMARY);
+}
+
+
+// Send the stop scan command
+void CommandSetGl846::end_scan(Genesys_Device* dev, Genesys_Register_Set* reg,
+ bool check_stop) const
+{
+ (void) reg;
+ DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop);
+
+ if (!dev->model->is_sheetfed) {
+ scanner_stop_action(*dev);
+ }
+}
+
+// Moves the slider to the home (top) postion slowly
+void CommandSetGl846::move_back_home(Genesys_Device* dev, bool wait_until_home) const
+{
+ scanner_move_back_home(*dev, wait_until_home);
+}
+
+// Automatically set top-left edge of the scan area by scanning a 200x200 pixels area at 600 dpi
+// from very top of scanner
+void CommandSetGl846::search_start_position(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ int size;
+ Genesys_Register_Set local_reg;
+
+ int pixels = 600;
+ int dpi = 300;
+
+ local_reg = dev->reg;
+
+ /* sets for a 200 lines * 600 pixels */
+ /* normal scan with no shading */
+
+ // FIXME: the current approach of doing search only for one resolution does not work on scanners
+ // whith employ different sensors with potentially different settings.
+ const auto& sensor = sanei_genesys_find_sensor(dev, dpi, 1, dev->model->default_method);
+
+ ScanSession session;
+ session.params.xres = dpi;
+ session.params.yres = dpi;
+ session.params.startx = 0;
+ session.params.starty = 0; /*we should give a small offset here~60 steps */
+ session.params.pixels = 600;
+ session.params.lines = dev->model->search_lines;
+ session.params.depth = 8;
+ session.params.channels = 1;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::GRAY;
+ session.params.color_filter = ColorFilter::GREEN;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &local_reg, session);
+
+ // send to scanner
+ dev->interface->write_registers(local_reg);
+
+ size = pixels * dev->model->search_lines;
+
+ std::vector<uint8_t> data(size);
+
+ begin_scan(dev, sensor, &local_reg, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("search_start_position");
+ end_scan(dev, &local_reg, true);
+ dev->reg = local_reg;
+ return;
+ }
+
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ sanei_genesys_read_data_from_scanner(dev, data.data(), size);
+
+ if (DBG_LEVEL >= DBG_data) {
+ sanei_genesys_write_pnm_file("gl846_search_position.pnm", data.data(), 8, 1, pixels,
+ dev->model->search_lines);
+ }
+
+ end_scan(dev, &local_reg, true);
+
+ /* update regs to copy ASIC internal state */
+ dev->reg = local_reg;
+
+ // TODO: find out where sanei_genesys_search_reference_point stores information,
+ // and use that correctly
+ for (auto& sensor_update :
+ sanei_genesys_find_sensors_all_for_write(dev, dev->model->default_method))
+ {
+ sanei_genesys_search_reference_point(dev, sensor_update, data.data(), 0, dpi, pixels,
+ dev->model->search_lines);
+ }
+}
+
+// sets up register for coarse gain calibration
+// todo: check it for scanners using it
+void CommandSetGl846::init_regs_for_coarse_calibration(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+
+ ScanSession session;
+ session.params.xres = dev->settings.xres;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = sensor.optical_res / sensor.ccd_pixels_per_system_pixel();
+ session.params.lines = 20;
+ session.params.depth = 16;
+ session.params.channels = dev->settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = dev->settings.scan_mode;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &regs, session);
+
+ DBG(DBG_info, "%s: optical sensor res: %d dpi, actual res: %d\n", __func__,
+ sensor.optical_res / sensor.ccd_pixels_per_system_pixel(), dev->settings.xres);
+
+ dev->interface->write_registers(regs);
+}
+
+// init registers for shading calibration
+void CommandSetGl846::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ float move;
+
+ dev->calib_channels = 3;
+
+ /* initial calibration reg values */
+ regs = dev->reg;
+
+ dev->calib_resolution = sensor.get_register_hwdpi(dev->settings.xres);
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, dev->calib_resolution,
+ dev->calib_channels,
+ dev->settings.scan_method);
+ dev->calib_total_bytes_to_read = 0;
+ dev->calib_lines = dev->model->shading_lines;
+ if (dev->calib_resolution==4800) {
+ dev->calib_lines *= 2;
+ }
+ dev->calib_pixels = (calib_sensor.sensor_pixels * dev->calib_resolution) /
+ calib_sensor.optical_res;
+
+ DBG(DBG_io, "%s: calib_lines = %zu\n", __func__, dev->calib_lines);
+ DBG(DBG_io, "%s: calib_pixels = %zu\n", __func__, dev->calib_pixels);
+
+ /* this is aworkaround insufficent distance for slope
+ * motor acceleration TODO special motor slope for shading */
+ move=1;
+ if(dev->calib_resolution<1200)
+ {
+ move=40;
+ }
+
+ ScanSession session;
+ session.params.xres = dev->calib_resolution;
+ session.params.yres = dev->calib_resolution;
+ session.params.startx = 0;
+ session.params.starty = static_cast<unsigned>(move);
+ session.params.pixels = dev->calib_pixels;
+ session.params.lines = dev->calib_lines;
+ session.params.depth = 16;
+ session.params.channels = dev->calib_channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::DISABLE_BUFFER_FULL_MOVE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ dev->interface->write_registers(regs);
+
+ /* we use GENESYS_FLAG_SHADING_REPARK */
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+}
+
+/** @brief set up registers for the actual scan
+ */
+void CommandSetGl846::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ DBG_HELPER(dbg);
+ float move;
+ int move_dpi;
+ float start;
+
+ debug_dump(DBG_info, dev->settings);
+
+ /* steps to move to reach scanning area:
+ - first we move to physical start of scanning
+ either by a fixed steps amount from the black strip
+ or by a fixed amount from parking position,
+ minus the steps done during shading calibration
+ - then we move by the needed offset whitin physical
+ scanning area
+
+ assumption: steps are expressed at maximum motor resolution
+
+ we need:
+ float y_offset;
+ float y_size;
+ float y_offset_calib;
+ mm_to_steps()=motor dpi / 2.54 / 10=motor dpi / MM_PER_INCH */
+
+ /* if scanner uses GENESYS_FLAG_SEARCH_START y_offset is
+ relative from origin, else, it is from parking position */
+
+ move_dpi = dev->motor.base_ydpi;
+
+ move = static_cast<float>(dev->model->y_offset);
+ move = static_cast<float>(move + dev->settings.tl_y);
+ move = static_cast<float>((move * move_dpi) / MM_PER_INCH);
+ move -= dev->head_pos(ScanHeadId::PRIMARY);
+ DBG(DBG_info, "%s: move=%f steps\n", __func__, move);
+
+ /* fast move to scan area */
+ /* we don't move fast the whole distance since it would involve
+ * computing acceleration/deceleration distance for scan
+ * resolution. So leave a remainder for it so scan makes the final
+ * move tuning */
+ if (dev->settings.get_channels() * dev->settings.yres >= 600 && move > 700) {
+ scanner_move(*dev, dev->model->default_method, static_cast<unsigned>(move - 500),
+ Direction::FORWARD);
+ move=500;
+ }
+
+ DBG(DBG_info, "%s: move=%f steps\n", __func__, move);
+ DBG(DBG_info, "%s: move=%f steps\n", __func__, move);
+
+ /* start */
+ start = static_cast<float>(dev->model->x_offset);
+ start = static_cast<float>(start + dev->settings.tl_x);
+ start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = dev->settings.xres;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = static_cast<unsigned>(start);
+ session.params.starty = static_cast<unsigned>(move);
+ session.params.pixels = dev->settings.pixels;
+ session.params.requested_pixels = dev->settings.requested_pixels;
+ session.params.lines = dev->settings.lines;
+ session.params.depth = dev->settings.depth;
+ session.params.channels = dev->settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = dev->settings.scan_mode;
+ session.params.color_filter = dev->settings.color_filter;
+ // backtracking isn't handled well, so don't enable it
+ session.params.flags = ScanFlag::DISABLE_BUFFER_FULL_MOVE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &dev->reg, session);
+}
+
+
+/**
+ * Send shading calibration data. The buffer is considered to always hold values
+ * for all the channels.
+ */
+void CommandSetGl846::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ uint8_t* data, int size) const
+{
+ DBG_HELPER_ARGS(dbg, "writing %d bytes of shading data", size);
+ uint32_t addr, length, i, x, factor, pixels;
+ uint32_t dpiset, dpihw;
+ uint8_t val,*ptr,*src;
+
+ /* shading data is plit in 3 (up to 5 with IR) areas
+ write(0x10014000,0x00000dd8)
+ URB 23429 bulk_out len 3544 wrote 0x33 0x10 0x....
+ write(0x1003e000,0x00000dd8)
+ write(0x10068000,0x00000dd8)
+ */
+ length = static_cast<uint32_t>(size / 3);
+ unsigned strpixel = dev->session.pixel_startx;
+ unsigned endpixel = dev->session.pixel_endx;
+
+ /* compute deletion factor */
+ dpiset = dev->reg.get16(REG_DPISET);
+ dpihw = sensor.get_register_hwdpi(dpiset);
+ factor=dpihw/dpiset;
+ DBG(DBG_io2, "%s: factor=%d\n", __func__, factor);
+
+ pixels=endpixel-strpixel;
+
+ /* since we're using SHDAREA, substract startx coordinate from shading */
+ strpixel -= (sensor.ccd_start_xoffset * 600) / sensor.optical_res;
+
+ /* turn pixel value into bytes 2x16 bits words */
+ strpixel*=2*2;
+ pixels*=2*2;
+
+ dev->interface->record_key_value("shading_offset", std::to_string(strpixel));
+ dev->interface->record_key_value("shading_pixels", std::to_string(pixels));
+ dev->interface->record_key_value("shading_length", std::to_string(length));
+ dev->interface->record_key_value("shading_factor", std::to_string(factor));
+
+ std::vector<uint8_t> buffer(pixels, 0);
+
+ DBG(DBG_io2, "%s: using chunks of %d (0x%04x) bytes\n", __func__, pixels, pixels);
+
+ /* base addr of data has been written in reg D0-D4 in 4K word, so AHB address
+ * is 8192*reg value */
+
+ /* write actual color channel data */
+ for(i=0;i<3;i++)
+ {
+ /* build up actual shading data by copying the part from the full width one
+ * to the one corresponding to SHDAREA */
+ ptr = buffer.data();
+
+ /* iterate on both sensor segment */
+ for(x=0;x<pixels;x+=4*factor)
+ {
+ /* coefficient source */
+ src=(data+strpixel+i*length)+x;
+
+ /* coefficient copy */
+ ptr[0]=src[0];
+ ptr[1]=src[1];
+ ptr[2]=src[2];
+ ptr[3]=src[3];
+
+ /* next shading coefficient */
+ ptr+=4;
+ }
+
+ val = dev->interface->read_register(0xd0+i);
+ addr = val * 8192 + 0x10000000;
+ dev->interface->write_ahb(addr, pixels, buffer.data());
+ }
+}
+
+/** @brief calibrates led exposure
+ * Calibrate exposure by scanning a white area until the used exposure gives
+ * data white enough.
+ * @param dev device to calibrate
+ */
+SensorExposure CommandSetGl846::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ int num_pixels;
+ int total_size;
+ int used_res;
+ int i, j;
+ int val;
+ int channels;
+ int avg[3], top[3], bottom[3];
+ int turn;
+ uint16_t exp[3];
+
+ float move = static_cast<float>(dev->model->y_offset_calib_white);
+ move = static_cast<float>((move * (dev->motor.base_ydpi / 4)) / MM_PER_INCH);
+ if(move>20)
+ {
+ scanner_move(*dev, dev->model->default_method, static_cast<unsigned>(move),
+ Direction::FORWARD);
+ }
+ DBG(DBG_io, "%s: move=%f steps\n", __func__, move);
+
+ /* offset calibration is always done in color mode */
+ channels = 3;
+ used_res = sensor.get_register_hwdpi(dev->settings.xres);
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, used_res, channels,
+ dev->settings.scan_method);
+ num_pixels = (calib_sensor.sensor_pixels * used_res) / calib_sensor.optical_res;
+
+ /* initial calibration reg values */
+ regs = dev->reg;
+
+ ScanSession session;
+ session.params.xres = used_res;
+ session.params.yres = used_res;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = num_pixels;
+ session.params.lines = 1;
+ session.params.depth = 16;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ total_size = num_pixels * channels * (session.params.depth / 8) * 1;
+ std::vector<uint8_t> line(total_size);
+
+ /* initial loop values and boundaries */
+ exp[0] = calib_sensor.exposure.red;
+ exp[1] = calib_sensor.exposure.green;
+ exp[2] = calib_sensor.exposure.blue;
+
+ bottom[0]=29000;
+ bottom[1]=29000;
+ bottom[2]=29000;
+
+ top[0]=41000;
+ top[1]=51000;
+ top[2]=51000;
+
+ turn = 0;
+
+ /* no move during led calibration */
+ sanei_genesys_set_motor_power(regs, false);
+ bool acceptable = false;
+ do
+ {
+ // set up exposure
+ regs.set16(REG_EXPR, exp[0]);
+ regs.set16(REG_EXPG, exp[1]);
+ regs.set16(REG_EXPB, exp[2]);
+
+ // write registers and scan data
+ dev->interface->write_registers(regs);
+
+ DBG(DBG_info, "%s: starting line reading\n", __func__);
+ begin_scan(dev, calib_sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("led_calibration");
+ scanner_stop_action(*dev);
+ move_back_home(dev, true);
+ return calib_sensor.exposure;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, line.data(), total_size);
+
+ // stop scanning
+ scanner_stop_action(*dev);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[30];
+ std::snprintf(fn, 30, "gl846_led_%02d.pnm", turn);
+ sanei_genesys_write_pnm_file(fn, line.data(), session.params.depth,
+ channels, num_pixels, 1);
+ }
+
+ /* compute average */
+ for (j = 0; j < channels; j++)
+ {
+ avg[j] = 0;
+ for (i = 0; i < num_pixels; i++)
+ {
+ if (dev->model->is_cis)
+ val =
+ line[i * 2 + j * 2 * num_pixels + 1] * 256 +
+ line[i * 2 + j * 2 * num_pixels];
+ else
+ val =
+ line[i * 2 * channels + 2 * j + 1] * 256 +
+ line[i * 2 * channels + 2 * j];
+ avg[j] += val;
+ }
+
+ avg[j] /= num_pixels;
+ }
+
+ DBG(DBG_info, "%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]);
+
+ /* check if exposure gives average within the boundaries */
+ acceptable = true;
+ for(i=0;i<3;i++)
+ {
+ if(avg[i]<bottom[i])
+ {
+ exp[i]=(exp[i]*bottom[i])/avg[i];
+ acceptable = false;
+ }
+ if(avg[i]>top[i])
+ {
+ exp[i]=(exp[i]*top[i])/avg[i];
+ acceptable = false;
+ }
+ }
+
+ turn++;
+ }
+ while (!acceptable && turn < 100);
+
+ DBG(DBG_info, "%s: acceptable exposure: %d,%d,%d\n", __func__, exp[0], exp[1], exp[2]);
+
+ // set these values as final ones for scan
+ dev->reg.set16(REG_EXPR, exp[0]);
+ dev->reg.set16(REG_EXPG, exp[1]);
+ dev->reg.set16(REG_EXPB, exp[2]);
+
+ /* go back home */
+ if(move>20)
+ {
+ move_back_home(dev, true);
+ }
+
+ return { exp[0], exp[1], exp[2] };
+}
+
+/**
+ * set up GPIO/GPOE for idle state
+ */
+static void gl846_init_gpio(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ int idx=0;
+
+ /* search GPIO profile */
+ while (gpios[idx].gpio_id != GpioId::UNKNOWN && dev->model->gpio_id != gpios[idx].gpio_id) {
+ idx++;
+ }
+ if (gpios[idx].gpio_id == GpioId::UNKNOWN)
+ {
+ throw SaneException("failed to find GPIO profile for sensor_id=%d",
+ static_cast<unsigned>(dev->model->sensor_id));
+ }
+
+ dev->interface->write_register(REG_0xA7, gpios[idx].ra7);
+ dev->interface->write_register(REG_0xA6, gpios[idx].ra6);
+
+ dev->interface->write_register(REG_0x6B, gpios[idx].r6b);
+ dev->interface->write_register(REG_0x6C, gpios[idx].r6c);
+ dev->interface->write_register(REG_0x6D, gpios[idx].r6d);
+ dev->interface->write_register(REG_0x6E, gpios[idx].r6e);
+ dev->interface->write_register(REG_0x6F, gpios[idx].r6f);
+
+ dev->interface->write_register(REG_0xA8, gpios[idx].ra8);
+ dev->interface->write_register(REG_0xA9, gpios[idx].ra9);
+}
+
+/**
+ * set memory layout by filling values in dedicated registers
+ */
+static void gl846_init_memory_layout(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ int idx = 0, i;
+ uint8_t val;
+
+ /* point to per model memory layout */
+ idx = 0;
+ while (layouts[idx].model != nullptr && strcmp(dev->model->name,layouts[idx].model)!=0) {
+ if(strcmp(dev->model->name,layouts[idx].model)!=0)
+ idx++;
+ }
+ if (layouts[idx].model == nullptr) {
+ throw SaneException("failed to find memory layout for model %s", dev->model->name);
+ }
+
+ /* CLKSET and DRAMSEL */
+ val = layouts[idx].dramsel;
+ dev->interface->write_register(REG_0x0B, val);
+ dev->reg.find_reg(0x0b).value = val;
+
+ /* prevent further writings by bulk write register */
+ dev->reg.remove_reg(0x0b);
+
+ /* setup base address for shading and scanned data. */
+ for(i=0;i<10;i++)
+ {
+ dev->interface->write_register(0xe0+i, layouts[idx].rx[i]);
+ }
+}
+
+/* *
+ * initialize ASIC from power on condition
+ */
+void CommandSetGl846::asic_boot(Genesys_Device* dev, bool cold) const
+{
+ DBG_HELPER(dbg);
+ uint8_t val;
+
+ // reset ASIC if cold boot
+ if (cold) {
+ dev->interface->write_register(0x0e, 0x01);
+ dev->interface->write_register(0x0e, 0x00);
+ }
+
+ if(dev->usb_mode == 1)
+ {
+ val = 0x14;
+ }
+ else
+ {
+ val = 0x11;
+ }
+ dev->interface->write_0x8c(0x0f, val);
+
+ // test CHKVER
+ val = dev->interface->read_register(REG_0x40);
+ if (val & REG_0x40_CHKVER) {
+ val = dev->interface->read_register(0x00);
+ DBG(DBG_info, "%s: reported version for genesys chip is 0x%02x\n", __func__, val);
+ }
+
+ /* Set default values for registers */
+ gl846_init_registers (dev);
+
+ // Write initial registers
+ dev->interface->write_registers(dev->reg);
+
+ /* Enable DRAM by setting a rising edge on bit 3 of reg 0x0b */
+ val = dev->reg.find_reg(0x0b).value & REG_0x0B_DRAMSEL;
+ val = (val | REG_0x0B_ENBDRAM);
+ dev->interface->write_register(REG_0x0B, val);
+ dev->reg.find_reg(0x0b).value = val;
+
+ /* CIS_LINE */
+ if (dev->model->is_cis)
+ {
+ dev->reg.init_reg(0x08, REG_0x08_CIS_LINE);
+ dev->interface->write_register(0x08, dev->reg.find_reg(0x08).value);
+ }
+
+ // set up clocks
+ dev->interface->write_0x8c(0x10, 0x0e);
+ dev->interface->write_0x8c(0x13, 0x0e);
+
+ // setup gpio
+ gl846_init_gpio(dev);
+
+ // setup internal memory layout
+ gl846_init_memory_layout(dev);
+
+ dev->reg.init_reg(0xf8, 0x05);
+ dev->interface->write_register(0xf8, dev->reg.find_reg(0xf8).value);
+}
+
+/**
+ * initialize backend and ASIC : registers, motor tables, and gamma tables
+ * then ensure scanner's head is at home
+ */
+void CommandSetGl846::init(Genesys_Device* dev) const
+{
+ DBG_INIT ();
+ DBG_HELPER(dbg);
+
+ sanei_genesys_asic_init(dev, 0);
+}
+
+void CommandSetGl846::update_hardware_sensors(Genesys_Scanner* s) const
+{
+ DBG_HELPER(dbg);
+ /* do what is needed to get a new set of events, but try to not lose
+ any of them.
+ */
+ uint8_t val;
+ uint8_t scan, file, email, copy;
+ switch(s->dev->model->gpio_id)
+ {
+ default:
+ scan=0x01;
+ file=0x02;
+ email=0x04;
+ copy=0x08;
+ }
+ val = s->dev->interface->read_register(REG_0x6D);
+
+ s->buttons[BUTTON_SCAN_SW].write((val & scan) == 0);
+ s->buttons[BUTTON_FILE_SW].write((val & file) == 0);
+ s->buttons[BUTTON_EMAIL_SW].write((val & email) == 0);
+ s->buttons[BUTTON_COPY_SW].write((val & copy) == 0);
+}
+
+
+void CommandSetGl846::update_home_sensor_gpio(Genesys_Device& dev) const
+{
+ DBG_HELPER(dbg);
+
+ std::uint8_t val = dev.interface->read_register(REG_0x6C);
+ val |= 0x41;
+ dev.interface->write_register(REG_0x6C, val);
+}
+
+/** @brief search for a full width black or white strip.
+ * This function searches for a black or white stripe across the scanning area.
+ * When searching backward, the searched area must completely be of the desired
+ * color since this area will be used for calibration which scans forward.
+ * @param dev scanner device
+ * @param forward true if searching forward, false if searching backward
+ * @param black true if searching for a black strip, false for a white strip
+ */
+void CommandSetGl846::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, bool forward,
+ bool black) const
+{
+ DBG_HELPER_ARGS(dbg, "%s %s", black ? "black" : "white", forward ? "forward" : "reverse");
+ unsigned int pixels, lines, channels;
+ Genesys_Register_Set local_reg;
+ size_t size;
+ unsigned int pass, count, found, x, y;
+ char title[80];
+
+ set_fe(dev, sensor, AFE_SET);
+
+ scanner_stop_action(*dev);
+
+ // set up for a gray scan at lowest dpi
+ const auto& resolution_settings = dev->model->get_resolution_settings(dev->settings.scan_method);
+ unsigned dpi = resolution_settings.get_min_resolution_x();
+ channels = 1;
+ /* 10 MM */
+ /* lines = (10 * dpi) / MM_PER_INCH; */
+ /* shading calibation is done with dev->motor.base_ydpi */
+ lines = (dev->model->shading_lines * dpi) / dev->motor.base_ydpi;
+ pixels = (sensor.sensor_pixels * dpi) / sensor.optical_res;
+
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+
+ local_reg = dev->reg;
+
+ ScanSession session;
+ session.params.xres = dpi;
+ session.params.yres = dpi;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_mode = ScanColorMode::GRAY;
+ session.params.color_filter = ColorFilter::RED;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA;
+ if (!forward) {
+ session.params.flags |= ScanFlag::REVERSE;
+ }
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &local_reg, session);
+
+ size = pixels * channels * lines * (session.params.depth / 8);
+ std::vector<uint8_t> data(size);
+
+ dev->interface->write_registers(local_reg);
+
+ begin_scan(dev, sensor, &local_reg, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("search_strip");
+ scanner_stop_action(*dev);
+ return;
+ }
+
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ sanei_genesys_read_data_from_scanner(dev, data.data(), size);
+
+ scanner_stop_action(*dev);
+
+ pass = 0;
+ if (DBG_LEVEL >= DBG_data)
+ {
+ std::sprintf(title, "gl846_search_strip_%s_%s%02d.pnm",
+ black ? "black" : "white", forward ? "fwd" : "bwd", pass);
+ sanei_genesys_write_pnm_file(title, data.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ /* loop until strip is found or maximum pass number done */
+ found = 0;
+ while (pass < 20 && !found)
+ {
+ dev->interface->write_registers(local_reg);
+
+ // now start scan
+ begin_scan(dev, sensor, &local_reg, true);
+
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ sanei_genesys_read_data_from_scanner(dev, data.data(), size);
+
+ scanner_stop_action(*dev);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ std::sprintf(title, "gl846_search_strip_%s_%s%02d.pnm",
+ black ? "black" : "white", forward ? "fwd" : "bwd", pass);
+ sanei_genesys_write_pnm_file(title, data.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ /* search data to find black strip */
+ /* when searching forward, we only need one line of the searched color since we
+ * will scan forward. But when doing backward search, we need all the area of the
+ * same color */
+ if (forward)
+ {
+ for (y = 0; y < lines && !found; y++)
+ {
+ count = 0;
+ /* count of white/black pixels depending on the color searched */
+ for (x = 0; x < pixels; x++)
+ {
+ /* when searching for black, detect white pixels */
+ if (black && data[y * pixels + x] > 90)
+ {
+ count++;
+ }
+ /* when searching for white, detect black pixels */
+ if (!black && data[y * pixels + x] < 60)
+ {
+ count++;
+ }
+ }
+
+ /* at end of line, if count >= 3%, line is not fully of the desired color
+ * so we must go to next line of the buffer */
+ /* count*100/pixels < 3 */
+ if ((count * 100) / pixels < 3)
+ {
+ found = 1;
+ DBG(DBG_data, "%s: strip found forward during pass %d at line %d\n", __func__,
+ pass, y);
+ }
+ else
+ {
+ DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count,
+ (100 * count) / pixels);
+ }
+ }
+ }
+ else /* since calibration scans are done forward, we need the whole area
+ to be of the required color when searching backward */
+ {
+ count = 0;
+ for (y = 0; y < lines; y++)
+ {
+ /* count of white/black pixels depending on the color searched */
+ for (x = 0; x < pixels; x++)
+ {
+ /* when searching for black, detect white pixels */
+ if (black && data[y * pixels + x] > 90)
+ {
+ count++;
+ }
+ /* when searching for white, detect black pixels */
+ if (!black && data[y * pixels + x] < 60)
+ {
+ count++;
+ }
+ }
+ }
+
+ /* at end of area, if count >= 3%, area is not fully of the desired color
+ * so we must go to next buffer */
+ if ((count * 100) / (pixels * lines) < 3)
+ {
+ found = 1;
+ DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass);
+ }
+ else
+ {
+ DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count,
+ (100 * count) / pixels);
+ }
+ }
+ pass++;
+ }
+
+ if (found)
+ {
+ DBG(DBG_info, "%s: %s strip found\n", __func__, black ? "black" : "white");
+ }
+ else
+ {
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", black ? "black" : "white");
+ }
+}
+
+/**
+ * average dark pixels of a 8 bits scan
+ */
+static int
+dark_average (uint8_t * data, unsigned int pixels, unsigned int lines,
+ unsigned int channels, unsigned int black)
+{
+ unsigned int i, j, k, average, count;
+ unsigned int avg[3];
+ uint8_t val;
+
+ /* computes average value on black margin */
+ for (k = 0; k < channels; k++)
+ {
+ avg[k] = 0;
+ count = 0;
+ for (i = 0; i < lines; i++)
+ {
+ for (j = 0; j < black; j++)
+ {
+ val = data[i * channels * pixels + j + k];
+ avg[k] += val;
+ count++;
+ }
+ }
+ if (count)
+ avg[k] /= count;
+ DBG(DBG_info, "%s: avg[%d] = %d\n", __func__, k, avg[k]);
+ }
+ average = 0;
+ for (i = 0; i < channels; i++)
+ average += avg[i];
+ average /= channels;
+ DBG(DBG_info, "%s: average = %d\n", __func__, average);
+ return average;
+}
+
+void CommandSetGl846::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ unsigned channels;
+ int pass = 0, avg, total_size;
+ int topavg, bottomavg, lines;
+ int top, bottom, black_pixels, pixels;
+
+ // no gain nor offset for AKM AFE
+ uint8_t reg04 = dev->interface->read_register(REG_0x04);
+ if ((reg04 & REG_0x04_FESET) == 0x02) {
+ return;
+ }
+
+ /* offset calibration is always done in color mode */
+ channels = 3;
+ dev->calib_pixels = sensor.sensor_pixels;
+ lines=1;
+ pixels = (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res;
+ black_pixels = (sensor.black_pixels * sensor.optical_res) / sensor.optical_res;
+ DBG(DBG_io2, "%s: black_pixels=%d\n", __func__, black_pixels);
+
+ ScanSession session;
+ session.params.xres = sensor.optical_res;
+ session.params.yres = sensor.optical_res;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &regs, session);
+
+ sanei_genesys_set_motor_power(regs, false);
+
+ total_size = pixels * channels * lines * (session.params.depth / 8);
+
+ std::vector<uint8_t> first_line(total_size);
+ std::vector<uint8_t> second_line(total_size);
+
+ /* init gain */
+ dev->frontend.set_gain(0, 0);
+ dev->frontend.set_gain(1, 0);
+ dev->frontend.set_gain(2, 0);
+
+ /* scan with no move */
+ bottom = 10;
+ dev->frontend.set_offset(0, bottom);
+ dev->frontend.set_offset(1, bottom);
+ dev->frontend.set_offset(2, bottom);
+
+ set_fe(dev, sensor, AFE_SET);
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting first line reading\n", __func__);
+ begin_scan(dev, sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("offset_calibration");
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size);
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[30];
+ std::snprintf(fn, 30, "gl846_offset%03d.pnm", bottom);
+ sanei_genesys_write_pnm_file(fn, first_line.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ bottomavg = dark_average(first_line.data(), pixels, lines, channels, black_pixels);
+ DBG(DBG_io2, "%s: bottom avg=%d\n", __func__, bottomavg);
+
+ /* now top value */
+ top = 255;
+ dev->frontend.set_offset(0, top);
+ dev->frontend.set_offset(1, top);
+ dev->frontend.set_offset(2, top);
+ set_fe(dev, sensor, AFE_SET);
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting second line reading\n", __func__);
+ begin_scan(dev, sensor, &regs, true);
+ sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size);
+
+ topavg = dark_average(second_line.data(), pixels, lines, channels, black_pixels);
+ DBG(DBG_io2, "%s: top avg=%d\n", __func__, topavg);
+
+ /* loop until acceptable level */
+ while ((pass < 32) && (top - bottom > 1))
+ {
+ pass++;
+
+ /* settings for new scan */
+ dev->frontend.set_offset(0, (top + bottom) / 2);
+ dev->frontend.set_offset(1, (top + bottom) / 2);
+ dev->frontend.set_offset(2, (top + bottom) / 2);
+
+ // scan with no move
+ set_fe(dev, sensor, AFE_SET);
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting second line reading\n", __func__);
+ begin_scan(dev, sensor, &regs, true);
+ sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[30];
+ std::snprintf(fn, 30, "gl846_offset%03d.pnm", dev->frontend.get_offset(1));
+ sanei_genesys_write_pnm_file(fn, second_line.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ avg = dark_average(second_line.data(), pixels, lines, channels, black_pixels);
+ DBG(DBG_info, "%s: avg=%d offset=%d\n", __func__, avg, dev->frontend.get_offset(1));
+
+ /* compute new boundaries */
+ if (topavg == avg)
+ {
+ topavg = avg;
+ top = dev->frontend.get_offset(1);
+ }
+ else
+ {
+ bottomavg = avg;
+ bottom = dev->frontend.get_offset(1);
+ }
+ }
+ DBG(DBG_info, "%s: offset=(%d,%d,%d)\n", __func__,
+ dev->frontend.get_offset(0),
+ dev->frontend.get_offset(1),
+ dev->frontend.get_offset(2));
+}
+
+void CommandSetGl846::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const
+{
+ DBG_HELPER(dbg);
+ int pixels;
+ int total_size;
+ int i, j, channels;
+ int max[3];
+ float gain[3],coeff;
+ int val, code, lines;
+
+ DBG(DBG_proc, "%s: dpi = %d\n", __func__, dpi);
+
+ // no gain nor offset for AKM AFE
+ uint8_t reg04 = dev->interface->read_register(REG_0x04);
+ if ((reg04 & REG_0x04_FESET) == 0x02) {
+ return;
+ }
+
+ /* coarse gain calibration is always done in color mode */
+ channels = 3;
+
+ /* follow CKSEL */
+ if(dev->settings.xres<sensor.optical_res)
+ {
+ coeff = 0.9f;
+ }
+ else
+ {
+ coeff=1.0;
+ }
+ lines=10;
+ pixels = (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res;
+
+ ScanSession session;
+ session.params.xres = sensor.optical_res;
+ session.params.yres = sensor.optical_res;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ try {
+ init_regs_for_scan_session(dev, sensor, &regs, session);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); });
+ throw;
+ }
+
+ sanei_genesys_set_motor_power(regs, false);
+
+ dev->interface->write_registers(regs);
+
+ total_size = pixels * channels * (16 / session.params.depth) * lines;
+
+ std::vector<uint8_t> line(total_size);
+
+ set_fe(dev, sensor, AFE_SET);
+ begin_scan(dev, sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("coarse_gain_calibration");
+ scanner_stop_action(*dev);
+ move_back_home(dev, true);
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, line.data(), total_size);
+
+ if (DBG_LEVEL >= DBG_data) {
+ sanei_genesys_write_pnm_file("gl846_gain.pnm", line.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ /* average value on each channel */
+ for (j = 0; j < channels; j++)
+ {
+ max[j] = 0;
+ for (i = pixels/4; i < (pixels*3/4); i++)
+ {
+ if (dev->model->is_cis)
+ val = line[i + j * pixels];
+ else
+ val = line[i * channels + j];
+
+ max[j] += val;
+ }
+ max[j] = max[j] / (pixels/2);
+
+ gain[j] = (static_cast<float>(sensor.gain_white_ref) * coeff) / max[j];
+
+ /* turn logical gain value into gain code, checking for overflow */
+ code = static_cast<int>(283 - 208 / gain[j]);
+ if (code > 255)
+ code = 255;
+ else if (code < 0)
+ code = 0;
+ dev->frontend.set_gain(j, code);
+
+ DBG(DBG_proc, "%s: channel %d, max=%d, gain = %f, setting:%d\n", __func__, j, max[j], gain[j],
+ dev->frontend.get_gain(j));
+ }
+
+ if (dev->model->is_cis) {
+ uint8_t gain0 = dev->frontend.get_gain(0);
+ if (gain0 > dev->frontend.get_gain(1)) {
+ gain0 = dev->frontend.get_gain(1);
+ }
+ if (gain0 > dev->frontend.get_gain(2)) {
+ gain0 = dev->frontend.get_gain(2);
+ }
+ dev->frontend.set_gain(0, gain0);
+ dev->frontend.set_gain(1, gain0);
+ dev->frontend.set_gain(2, gain0);
+ }
+
+ scanner_stop_action(*dev);
+
+ move_back_home(dev, true);
+}
+
+bool CommandSetGl846::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const
+{
+ (void) dev;
+ return false;
+}
+
+void CommandSetGl846::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, int* channels,
+ int* total_size) const
+{
+ (void) dev;
+ (void) sensor;
+ (void) regs;
+ (void) channels;
+ (void) total_size;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl846::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ sanei_genesys_send_gamma_table(dev, sensor);
+}
+
+void CommandSetGl846::wait_for_motor_stop(Genesys_Device* dev) const
+{
+ (void) dev;
+}
+
+void CommandSetGl846::load_document(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl846::detect_document_end(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl846::eject_document(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl846::move_to_ta(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+std::unique_ptr<CommandSet> create_gl846_cmd_set()
+{
+ return std::unique_ptr<CommandSet>(new CommandSetGl846{});
+}
+
+} // namespace gl846
+} // namespace genesys
diff --git a/backend/genesys/gl846.h b/backend/genesys/gl846.h
new file mode 100644
index 0000000..258015a
--- /dev/null
+++ b/backend/genesys/gl846.h
@@ -0,0 +1,218 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2012-2013 Stéphane Voltz <stef.dev@free.fr>
+
+ 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.
+*/
+
+#include "genesys.h"
+#include "command_set.h"
+
+#ifndef BACKEND_GENESYS_GL846_H
+#define BACKEND_GENESYS_GL846_H
+
+namespace genesys {
+namespace gl846 {
+
+typedef struct
+{
+ GpioId gpio_id;
+ uint8_t r6b;
+ uint8_t r6c;
+ uint8_t r6d;
+ uint8_t r6e;
+ uint8_t r6f;
+ uint8_t ra6;
+ uint8_t ra7;
+ uint8_t ra8;
+ uint8_t ra9;
+} Gpio_Profile;
+
+static Gpio_Profile gpios[]={
+ { GpioId::IMG101, 0x72, 0x1f, 0xa4, 0x13, 0xa7, 0x11, 0xff, 0x19, 0x05},
+ { GpioId::PLUSTEK_OPTICBOOK_3800, 0x30, 0x01, 0x80, 0x2d, 0x80, 0x0c, 0x8f, 0x08, 0x04},
+ { GpioId::UNKNOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+};
+
+typedef struct
+{
+ const char *model;
+ uint8_t dramsel;
+ /* shading data address */
+ uint8_t rd0;
+ uint8_t rd1;
+ uint8_t rd2;
+ /* scanned data address */
+ uint8_t rx[24];
+} Memory_layout;
+
+static Memory_layout layouts[]={
+ /* Image formula 101 */
+ {
+ "canon-image-formula-101",
+ 0x8b,
+ 0x0a, 0x1b, 0x00,
+ { /* RED ODD START / RED ODD END */
+ 0x00, 0xb0, 0x05, 0xe7, /* [0x00b0, 0x05e7] 1336*4000w */
+ /* RED EVEN START / RED EVEN END */
+ 0x05, 0xe8, 0x0b, 0x1f, /* [0x05e8, 0x0b1f] */
+ /* GREEN ODD START / GREEN ODD END */
+ 0x0b, 0x20, 0x10, 0x57, /* [0x0b20, 0x1057] */
+ /* GREEN EVEN START / GREEN EVEN END */
+ 0x10, 0x58, 0x15, 0x8f, /* [0x1058, 0x158f] */
+ /* BLUE ODD START / BLUE ODD END */
+ 0x15, 0x90, 0x1a, 0xc7, /* [0x1590,0x1ac7] */
+ /* BLUE EVEN START / BLUE EVEN END */
+ 0x1a, 0xc8, 0x1f, 0xff /* [0x1ac8,0x1fff] */
+ }
+ },
+ /* OpticBook 3800 */
+ {
+ "plustek-opticbook-3800",
+ 0x2a,
+ 0x0a, 0x0a, 0x0a,
+ { /* RED ODD START / RED ODD END */
+ 0x00, 0x68, 0x03, 0x00,
+ /* RED EVEN START / RED EVEN END */
+ 0x03, 0x01, 0x05, 0x99,
+ /* GREEN ODD START / GREEN ODD END */
+ 0x05, 0x9a, 0x08, 0x32,
+ /* GREEN EVEN START / GREEN EVEN END */
+ 0x08, 0x33, 0x0a, 0xcb,
+ /* BLUE ODD START / BLUE ODD END */
+ 0x0a, 0xcc, 0x0d, 0x64,
+ /* BLUE EVEN START / BLUE EVEN END */
+ 0x0d, 0x65, 0x0f, 0xfd
+ }
+ },
+ /* list terminating entry */
+ { nullptr, 0, 0, 0, 0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} }
+};
+
+class CommandSetGl846 : public CommandSet
+{
+public:
+ ~CommandSetGl846() override = default;
+
+ bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override;
+
+ void init(Genesys_Device* dev) const override;
+
+ void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, int* channels,
+ int* total_size) const override;
+
+ void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const override;
+
+ void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override;
+ void set_powersaving(Genesys_Device* dev, int delay) const override;
+ void save_power(Genesys_Device* dev, bool enable) const override;
+
+ void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, bool start_motor) const override;
+
+ void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override;
+
+ void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void search_start_position(Genesys_Device* dev) const override;
+
+ void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const override;
+
+ SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void wait_for_motor_stop(Genesys_Device* dev) const override;
+
+ void move_back_home(Genesys_Device* dev, bool wait_until_home) const override;
+
+ void update_hardware_sensors(struct Genesys_Scanner* s) const override;
+
+ bool needs_update_home_sensor_gpio() const override { return true; }
+
+ void update_home_sensor_gpio(Genesys_Device& dev) const override;
+
+ void load_document(Genesys_Device* dev) const override;
+
+ void detect_document_end(Genesys_Device* dev) const override;
+
+ void eject_document(Genesys_Device* dev) const override;
+
+ void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ bool forward, bool black) const override;
+
+ void move_to_ta(Genesys_Device* dev) const override;
+
+ void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data,
+ int size) const override;
+
+ ScanSession calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const override;
+
+ void asic_boot(Genesys_Device* dev, bool cold) const override;
+};
+
+enum SlopeTable
+{
+ SCAN_TABLE = 0, // table 1 at 0x4000
+ BACKTRACK_TABLE = 1, // table 2 at 0x4800
+ STOP_TABLE = 2, // table 3 at 0x5000
+ FAST_TABLE = 3, // table 4 at 0x5800
+ HOME_TABLE = 4, // table 5 at 0x6000
+};
+
+} // namespace gl846
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL846_H
diff --git a/backend/genesys/gl846_registers.h b/backend/genesys/gl846_registers.h
new file mode 100644
index 0000000..39b3029
--- /dev/null
+++ b/backend/genesys/gl846_registers.h
@@ -0,0 +1,351 @@
+/* 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_GL846_REGISTERS_H
+#define BACKEND_GENESYS_GL846_REGISTERS_H
+
+#include <cstdint>
+
+namespace genesys {
+namespace gl846 {
+
+using RegAddr = std::uint16_t;
+using RegMask = std::uint8_t;
+using RegShift = unsigned;
+
+static constexpr RegAddr REG_0x01 = 0x01;
+static constexpr RegMask REG_0x01_CISSET = 0x80;
+static constexpr RegMask REG_0x01_DOGENB = 0x40;
+static constexpr RegMask REG_0x01_DVDSET = 0x20;
+static constexpr RegMask REG_0x01_STAGGER = 0x10;
+static constexpr RegMask REG_0x01_COMPENB = 0x08;
+static constexpr RegMask REG_0x01_TRUEGRAY = 0x04;
+static constexpr RegMask REG_0x01_SHDAREA = 0x02;
+static constexpr RegMask REG_0x01_SCAN = 0x01;
+
+static constexpr RegAddr REG_0x02 = 0x02;
+static constexpr RegMask REG_0x02_NOTHOME = 0x80;
+static constexpr RegMask REG_0x02_ACDCDIS = 0x40;
+static constexpr RegMask REG_0x02_AGOHOME = 0x20;
+static constexpr RegMask REG_0x02_MTRPWR = 0x10;
+static constexpr RegMask REG_0x02_FASTFED = 0x08;
+static constexpr RegMask REG_0x02_MTRREV = 0x04;
+static constexpr RegMask REG_0x02_HOMENEG = 0x02;
+static constexpr RegMask REG_0x02_LONGCURV = 0x01;
+
+static constexpr RegAddr REG_0x03 = 0x03;
+static constexpr RegMask REG_0x03_LAMPDOG = 0x80;
+static constexpr RegMask REG_0x03_AVEENB = 0x40;
+static constexpr RegMask REG_0x03_XPASEL = 0x20;
+static constexpr RegMask REG_0x03_LAMPPWR = 0x10;
+static constexpr RegMask REG_0x03_LAMPTIM = 0x0f;
+
+static constexpr RegAddr REG_0x04 = 0x04;
+static constexpr RegMask REG_0x04_LINEART = 0x80;
+static constexpr RegMask REG_0x04_BITSET = 0x40;
+static constexpr RegMask REG_0x04_AFEMOD = 0x30;
+static constexpr RegMask REG_0x04_FILTER = 0x0c;
+static constexpr RegMask REG_0x04_FESET = 0x03;
+
+static constexpr RegShift REG_0x04S_AFEMOD = 4;
+
+static constexpr RegAddr REG_0x05 = 0x05;
+static constexpr RegMask REG_0x05_DPIHW = 0xc0;
+static constexpr RegMask REG_0x05_DPIHW_600 = 0x00;
+static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40;
+static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80;
+static constexpr RegMask REG_0x05_DPIHW_4800 = 0xc0;
+static constexpr RegMask REG_0x05_MTLLAMP = 0x30;
+static constexpr RegMask REG_0x05_GMMENB = 0x08;
+static constexpr RegMask REG_0x05_MTLBASE = 0x03;
+
+static constexpr RegAddr REG_0x06 = 0x06;
+static constexpr RegMask REG_0x06_SCANMOD = 0xe0;
+static constexpr RegShift REG_0x06S_SCANMOD = 5;
+static constexpr RegMask REG_0x06_PWRBIT = 0x10;
+static constexpr RegMask REG_0x06_GAIN4 = 0x08;
+static constexpr RegMask REG_0x06_OPTEST = 0x07;
+
+static constexpr RegMask REG_0x07_LAMPSIM = 0x80;
+
+static constexpr RegMask REG_0x08_DRAM2X = 0x80;
+static constexpr RegMask REG_0x08_MPENB = 0x20;
+static constexpr RegMask REG_0x08_CIS_LINE = 0x10;
+static constexpr RegMask REG_0x08_IR1ENB = 0x08;
+static constexpr RegMask REG_0x08_IR2ENB = 0x04;
+static constexpr RegMask REG_0x08_ENB24M = 0x01;
+
+static constexpr RegMask REG_0x09_MCNTSET = 0xc0;
+static constexpr RegMask REG_0x09_EVEN1ST = 0x20;
+static constexpr RegMask REG_0x09_BLINE1ST = 0x10;
+static constexpr RegMask REG_0x09_BACKSCAN = 0x08;
+static constexpr RegMask REG_0x09_ENHANCE = 0x04;
+static constexpr RegMask REG_0x09_SHORTTG = 0x02;
+static constexpr RegMask REG_0x09_NWAIT = 0x01;
+
+static constexpr RegShift REG_0x09S_MCNTSET = 6;
+static constexpr RegShift REG_0x09S_CLKSET = 4;
+
+
+static constexpr RegAddr REG_0x0A_LPWMEN = 0x10;
+
+static constexpr RegAddr REG_0x0B = 0x0b;
+static constexpr RegMask REG_0x0B_DRAMSEL = 0x07;
+static constexpr RegMask REG_0x0B_ENBDRAM = 0x08;
+static constexpr RegMask REG_0x0B_RFHDIS = 0x10;
+static constexpr RegMask REG_0x0B_CLKSET = 0xe0;
+static constexpr RegMask REG_0x0B_24MHZ = 0x00;
+static constexpr RegMask REG_0x0B_30MHZ = 0x20;
+static constexpr RegMask REG_0x0B_40MHZ = 0x40;
+static constexpr RegMask REG_0x0B_48MHZ = 0x60;
+static constexpr RegMask REG_0x0B_60MHZ = 0x80;
+
+static constexpr RegAddr REG_0x0C = 0x0c;
+static constexpr RegMask REG_0x0C_CCDLMT = 0x0f;
+
+static constexpr RegAddr REG_0x0D = 0x0d;
+static constexpr RegMask REG_0x0D_SCSYNC = 0x40;
+static constexpr RegMask REG_0x0D_CLRERR = 0x20;
+static constexpr RegMask REG_0x0D_FULLSTP = 0x10;
+static constexpr RegMask REG_0x0D_SEND = 0x80;
+static constexpr RegMask REG_0x0D_CLRMCNT = 0x04;
+static constexpr RegMask REG_0x0D_CLRDOCJM = 0x02;
+static constexpr RegMask REG_0x0D_CLRLNCNT = 0x01;
+
+static constexpr RegAddr REG_0x0F = 0x0f;
+
+static constexpr RegMask REG_0x16_CTRLHI = 0x80;
+static constexpr RegMask REG_0x16_TOSHIBA = 0x40;
+static constexpr RegMask REG_0x16_TGINV = 0x20;
+static constexpr RegMask REG_0x16_CK1INV = 0x10;
+static constexpr RegMask REG_0x16_CK2INV = 0x08;
+static constexpr RegMask REG_0x16_CTRLINV = 0x04;
+static constexpr RegMask REG_0x16_CKDIS = 0x02;
+static constexpr RegMask REG_0x16_CTRLDIS = 0x01;
+
+static constexpr RegMask REG_0x17_TGMODE = 0xc0;
+static constexpr RegMask REG_0x17_TGMODE_NO_DUMMY = 0x00;
+static constexpr RegMask REG_0x17_TGMODE_REF = 0x40;
+static constexpr RegMask REG_0x17_TGMODE_XPA = 0x80;
+static constexpr RegMask REG_0x17_TGW = 0x3f;
+static constexpr RegAddr REG_0x17S_TGW = 0;
+
+static constexpr RegAddr REG_0x18 = 0x18;
+static constexpr RegMask REG_0x18_CNSET = 0x80;
+static constexpr RegMask REG_0x18_DCKSEL = 0x60;
+static constexpr RegMask REG_0x18_CKTOGGLE = 0x10;
+static constexpr RegMask REG_0x18_CKDELAY = 0x0c;
+static constexpr RegMask REG_0x18_CKSEL = 0x03;
+
+static constexpr RegMask REG_0x1A_SW2SET = 0x80;
+static constexpr RegMask REG_0x1A_SW1SET = 0x40;
+static constexpr RegMask REG_0x1A_MANUAL3 = 0x02;
+static constexpr RegMask REG_0x1A_MANUAL1 = 0x01;
+static constexpr RegMask REG_0x1A_CK4INV = 0x08;
+static constexpr RegMask REG_0x1A_CK3INV = 0x04;
+static constexpr RegMask REG_0x1A_LINECLP = 0x02;
+
+static constexpr RegAddr REG_0x1C = 0x1c;
+static constexpr RegMask REG_0x1C_TGTIME = 0x07;
+
+static constexpr RegMask REG_0x1D_CK4LOW = 0x80;
+static constexpr RegMask REG_0x1D_CK3LOW = 0x40;
+static constexpr RegMask REG_0x1D_CK1LOW = 0x20;
+static constexpr RegMask REG_0x1D_TGSHLD = 0x1f;
+static constexpr RegShift REG_0x1DS_TGSHLD = 0;
+
+
+static constexpr RegMask REG_0x1E_WDTIME = 0xf0;
+static constexpr RegShift REG_0x1ES_WDTIME = 4;
+static constexpr RegMask REG_0x1E_LINESEL = 0x0f;
+static constexpr RegShift REG_0x1ES_LINESEL = 0;
+
+static constexpr RegAddr REG_FEDCNT = 0x1f;
+
+static constexpr RegAddr REG_0x24 = 0x1c;
+static constexpr RegAddr REG_0x40 = 0x40;
+static constexpr RegMask REG_0x40_DOCSNR = 0x80;
+static constexpr RegMask REG_0x40_ADFSNR = 0x40;
+static constexpr RegMask REG_0x40_COVERSNR = 0x20;
+static constexpr RegMask REG_0x40_CHKVER = 0x10;
+static constexpr RegMask REG_0x40_DOCJAM = 0x08;
+static constexpr RegMask REG_0x40_HISPDFLG = 0x04;
+static constexpr RegMask REG_0x40_MOTMFLG = 0x02;
+static constexpr RegMask REG_0x40_DATAENB = 0x01;
+
+static constexpr RegMask REG_0x41_PWRBIT = 0x80;
+static constexpr RegMask REG_0x41_BUFEMPTY = 0x40;
+static constexpr RegMask REG_0x41_FEEDFSH = 0x20;
+static constexpr RegMask REG_0x41_SCANFSH = 0x10;
+static constexpr RegMask REG_0x41_HOMESNR = 0x08;
+static constexpr RegMask REG_0x41_LAMPSTS = 0x04;
+static constexpr RegMask REG_0x41_FEBUSY = 0x02;
+static constexpr RegMask REG_0x41_MOTORENB = 0x01;
+
+static constexpr RegMask REG_0x58_VSMP = 0xf8;
+static constexpr RegShift REG_0x58S_VSMP = 3;
+static constexpr RegMask REG_0x58_VSMPW = 0x07;
+static constexpr RegAddr REG_0x58S_VSMPW = 0;
+
+static constexpr RegMask REG_0x59_BSMP = 0xf8;
+static constexpr RegAddr REG_0x59S_BSMP = 3;
+static constexpr RegMask REG_0x59_BSMPW = 0x07;
+static constexpr RegShift REG_0x59S_BSMPW = 0;
+
+static constexpr RegMask REG_0x5A_ADCLKINV = 0x80;
+static constexpr RegMask REG_0x5A_RLCSEL = 0x40;
+static constexpr RegMask REG_0x5A_CDSREF = 0x30;
+static constexpr RegShift REG_0x5AS_CDSREF = 4;
+static constexpr RegMask REG_0x5A_RLC = 0x0f;
+static constexpr RegShift REG_0x5AS_RLC = 0;
+
+static constexpr RegMask REG_0x5E_DECSEL = 0xe0;
+static constexpr RegShift REG_0x5ES_DECSEL = 5;
+static constexpr RegMask REG_0x5E_STOPTIM = 0x1f;
+static constexpr RegShift REG_0x5ES_STOPTIM = 0;
+
+static constexpr RegAddr REG_0x60 = 0x60;
+static constexpr RegMask REG_0x60_Z1MOD = 0x1f;
+static constexpr RegAddr REG_0x61 = 0x61;
+static constexpr RegMask REG_0x61_Z1MOD = 0xff;
+static constexpr RegAddr REG_0x62 = 0x62;
+static constexpr RegMask REG_0x62_Z1MOD = 0xff;
+
+static constexpr RegAddr REG_0x63 = 0x63;
+static constexpr RegMask REG_0x63_Z2MOD = 0x1f;
+static constexpr RegAddr REG_0x64 = 0x64;
+static constexpr RegMask REG_0x64_Z2MOD = 0xff;
+static constexpr RegAddr REG_0x65 = 0x65;
+static constexpr RegMask REG_0x65_Z2MOD = 0xff;
+
+static constexpr RegShift REG_0x60S_STEPSEL = 5;
+static constexpr RegMask REG_0x60_STEPSEL = 0xe0;
+static constexpr RegMask REG_0x60_FULLSTEP = 0x00;
+static constexpr RegMask REG_0x60_HALFSTEP = 0x20;
+static constexpr RegMask REG_0x60_EIGHTHSTEP = 0x60;
+static constexpr RegMask REG_0x60_16THSTEP = 0x80;
+
+static constexpr RegShift REG_0x63S_FSTPSEL = 5;
+static constexpr RegMask REG_0x63_FSTPSEL = 0xe0;
+static constexpr RegMask REG_0x63_FULLSTEP = 0x00;
+static constexpr RegMask REG_0x63_HALFSTEP = 0x20;
+static constexpr RegMask REG_0x63_EIGHTHSTEP = 0x60;
+static constexpr RegMask REG_0x63_16THSTEP = 0x80;
+
+static constexpr RegAddr REG_0x67 = 0x67;
+static constexpr RegMask REG_0x67_MTRPWM = 0x80;
+
+static constexpr RegAddr REG_0x68 = 0x68;
+static constexpr RegMask REG_0x68_FASTPWM = 0x80;
+
+static constexpr RegAddr REG_0x6B = 0x6b;
+static constexpr RegMask REG_0x6B_MULTFILM = 0x80;
+static constexpr RegMask REG_0x6B_GPOM13 = 0x40;
+static constexpr RegMask REG_0x6B_GPOM12 = 0x20;
+static constexpr RegMask REG_0x6B_GPOM11 = 0x10;
+static constexpr RegMask REG_0x6B_GPO18 = 0x02;
+static constexpr RegMask REG_0x6B_GPO17 = 0x01;
+
+static constexpr RegAddr REG_0x6C = 0x6c;
+static constexpr RegMask REG_0x6C_GPIO16 = 0x80;
+static constexpr RegMask REG_0x6C_GPIO15 = 0x40;
+static constexpr RegMask REG_0x6C_GPIO14 = 0x20;
+static constexpr RegMask REG_0x6C_GPIO13 = 0x10;
+static constexpr RegMask REG_0x6C_GPIO12 = 0x08;
+static constexpr RegMask REG_0x6C_GPIO11 = 0x04;
+static constexpr RegMask REG_0x6C_GPIO10 = 0x02;
+static constexpr RegMask REG_0x6C_GPIO9 = 0x01;
+static constexpr RegMask REG_0x6C_GPIOH = 0xff;
+static constexpr RegMask REG_0x6C_GPIOL = 0xff;
+
+static constexpr RegAddr REG_0x6D = 0x6d;
+static constexpr RegAddr REG_0x6E = 0x6e;
+static constexpr RegAddr REG_0x6F = 0x6f;
+static constexpr RegAddr REG_0x7E = 0x7e;
+
+static constexpr RegMask REG_0x87_ACYCNRLC = 0x10;
+static constexpr RegMask REG_0x87_ENOFFSET = 0x08;
+static constexpr RegMask REG_0x87_LEDADD = 0x04;
+static constexpr RegMask REG_0x87_CK4ADC = 0x02;
+static constexpr RegMask REG_0x87_AUTOCONF = 0x01;
+
+static constexpr RegAddr REG_0x9E = 0x9e;
+static constexpr RegAddr REG_0x9F = 0x9f;
+
+static constexpr RegAddr REG_0xA6 = 0xa6;
+static constexpr RegAddr REG_0xA7 = 0xa7;
+static constexpr RegAddr REG_0xA8 = 0xa8;
+static constexpr RegAddr REG_0xA9 = 0xa9;
+static constexpr RegAddr REG_0xAB = 0xab;
+
+static constexpr RegAddr REG_EXPR = 0x10;
+static constexpr RegAddr REG_EXPG = 0x12;
+static constexpr RegAddr REG_EXPB = 0x14;
+static constexpr RegAddr REG_EXPDMY = 0x19;
+static constexpr RegAddr REG_STEPNO = 0x21;
+static constexpr RegAddr REG_FWDSTEP = 0x22;
+static constexpr RegAddr REG_BWDSTEP = 0x23;
+static constexpr RegAddr REG_FASTNO = 0x24;
+static constexpr RegAddr REG_DPISET = 0x2c;
+static constexpr RegAddr REG_STRPIXEL = 0x30;
+static constexpr RegAddr REG_ENDPIXEL = 0x32;
+static constexpr RegAddr REG_LINCNT = 0x25;
+static constexpr RegAddr REG_MAXWD = 0x35;
+static constexpr RegAddr REG_LPERIOD = 0x38;
+static constexpr RegAddr REG_FEEDL = 0x3d;
+static constexpr RegAddr REG_FMOVDEC = 0x5f;
+static constexpr RegAddr REG_FSHDEC = 0x69;
+static constexpr RegAddr REG_FMOVNO = 0x6a;
+static constexpr RegAddr REG_CK1MAP = 0x74;
+static constexpr RegAddr REG_CK3MAP = 0x77;
+static constexpr RegAddr REG_CK4MAP = 0x7a;
+
+static constexpr RegAddr REG_0xF8 = 0xf8;
+static constexpr RegMask REG_0xF8_MAXSEL = 0xf0;
+static constexpr RegShift REG_0xF8_SMAXSEL = 4;
+static constexpr RegMask REG_0xF8_MINSEL = 0x0f;
+
+} // namespace gl846
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL846_REGISTERS_H
diff --git a/backend/genesys/gl847.cpp b/backend/genesys/gl847.cpp
new file mode 100644
index 0000000..cb0b527
--- /dev/null
+++ b/backend/genesys/gl847.cpp
@@ -0,0 +1,2140 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr>
+
+
+ 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 "gl847.h"
+#include "gl847_registers.h"
+#include "test_settings.h"
+
+#include <vector>
+
+namespace genesys {
+namespace gl847 {
+
+/**
+ * compute the step multiplier used
+ */
+static int
+gl847_get_step_multiplier (Genesys_Register_Set * regs)
+{
+ GenesysRegister *r = sanei_genesys_get_address(regs, 0x9d);
+ int value = 1;
+ if (r != nullptr)
+ {
+ value = (r->value & 0x0f)>>1;
+ value = 1 << value;
+ }
+ DBG (DBG_io, "%s: step multiplier is %d\n", __func__, value);
+ return value;
+}
+
+/** @brief sensor specific settings
+*/
+static void gl847_setup_sensor(Genesys_Device * dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs)
+{
+ DBG_HELPER(dbg);
+
+ for (const auto& reg : sensor.custom_regs) {
+ regs->set8(reg.address, reg.value);
+ }
+
+ regs->set16(REG_EXPR, sensor.exposure.red);
+ regs->set16(REG_EXPG, sensor.exposure.green);
+ regs->set16(REG_EXPB, sensor.exposure.blue);
+
+ dev->segment_order = sensor.segment_order;
+}
+
+
+/** @brief set all registers to default values .
+ * This function is called only once at the beginning and
+ * fills register startup values for registers reused across scans.
+ * Those that are rarely modified or not modified are written
+ * individually.
+ * @param dev device structure holding register set to initialize
+ */
+static void
+gl847_init_registers (Genesys_Device * dev)
+{
+ DBG_HELPER(dbg);
+ int lide700=0;
+ uint8_t val;
+
+ /* 700F class needs some different initial settings */
+ if (dev->model->model_id == ModelId::CANON_LIDE_700F) {
+ lide700 = 1;
+ }
+
+ dev->reg.clear();
+
+ dev->reg.init_reg(0x01, 0x82);
+ dev->reg.init_reg(0x02, 0x18);
+ dev->reg.init_reg(0x03, 0x50);
+ dev->reg.init_reg(0x04, 0x12);
+ dev->reg.init_reg(0x05, 0x80);
+ dev->reg.init_reg(0x06, 0x50); // FASTMODE + POWERBIT
+ dev->reg.init_reg(0x08, 0x10);
+ dev->reg.init_reg(0x09, 0x01);
+ dev->reg.init_reg(0x0a, 0x00);
+ dev->reg.init_reg(0x0b, 0x01);
+ dev->reg.init_reg(0x0c, 0x02);
+
+ // LED exposures
+ dev->reg.init_reg(0x10, 0x00);
+ dev->reg.init_reg(0x11, 0x00);
+ dev->reg.init_reg(0x12, 0x00);
+ dev->reg.init_reg(0x13, 0x00);
+ dev->reg.init_reg(0x14, 0x00);
+ dev->reg.init_reg(0x15, 0x00);
+
+ dev->reg.init_reg(0x16, 0x10); // SENSOR_DEF
+ dev->reg.init_reg(0x17, 0x08); // SENSOR_DEF
+ dev->reg.init_reg(0x18, 0x00); // SENSOR_DEF
+
+ // EXPDMY
+ dev->reg.init_reg(0x19, 0x50); // SENSOR_DEF
+
+ dev->reg.init_reg(0x1a, 0x34); // SENSOR_DEF
+ dev->reg.init_reg(0x1b, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x1c, 0x02); // SENSOR_DEF
+ dev->reg.init_reg(0x1d, 0x04); // SENSOR_DEF
+ dev->reg.init_reg(0x1e, 0x10);
+ dev->reg.init_reg(0x1f, 0x04);
+ dev->reg.init_reg(0x20, 0x02);
+ dev->reg.init_reg(0x21, 0x10);
+ dev->reg.init_reg(0x22, 0x7f);
+ dev->reg.init_reg(0x23, 0x7f);
+ dev->reg.init_reg(0x24, 0x10);
+ dev->reg.init_reg(0x25, 0x00);
+ dev->reg.init_reg(0x26, 0x00);
+ dev->reg.init_reg(0x27, 0x00);
+ dev->reg.init_reg(0x2c, 0x09);
+ dev->reg.init_reg(0x2d, 0x60);
+ dev->reg.init_reg(0x2e, 0x80);
+ dev->reg.init_reg(0x2f, 0x80);
+ dev->reg.init_reg(0x30, 0x00);
+ dev->reg.init_reg(0x31, 0x10);
+ dev->reg.init_reg(0x32, 0x15);
+ dev->reg.init_reg(0x33, 0x0e);
+ dev->reg.init_reg(0x34, 0x40);
+ dev->reg.init_reg(0x35, 0x00);
+ dev->reg.init_reg(0x36, 0x2a);
+ dev->reg.init_reg(0x37, 0x30);
+ dev->reg.init_reg(0x38, 0x2a);
+ dev->reg.init_reg(0x39, 0xf8);
+ dev->reg.init_reg(0x3d, 0x00);
+ dev->reg.init_reg(0x3e, 0x00);
+ dev->reg.init_reg(0x3f, 0x00);
+ dev->reg.init_reg(0x52, 0x03); // SENSOR_DEF
+ dev->reg.init_reg(0x53, 0x07); // SENSOR_DEF
+ dev->reg.init_reg(0x54, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x55, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x56, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x57, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x58, 0x2a); // SENSOR_DEF
+ dev->reg.init_reg(0x59, 0xe1); // SENSOR_DEF
+ dev->reg.init_reg(0x5a, 0x55); // SENSOR_DEF
+ dev->reg.init_reg(0x5e, 0x41);
+ dev->reg.init_reg(0x5f, 0x40);
+ dev->reg.init_reg(0x60, 0x00);
+ dev->reg.init_reg(0x61, 0x21);
+ dev->reg.init_reg(0x62, 0x40);
+ dev->reg.init_reg(0x63, 0x00);
+ dev->reg.init_reg(0x64, 0x21);
+ dev->reg.init_reg(0x65, 0x40);
+ dev->reg.init_reg(0x67, 0x80);
+ dev->reg.init_reg(0x68, 0x80);
+ dev->reg.init_reg(0x69, 0x20);
+ dev->reg.init_reg(0x6a, 0x20);
+
+ // CK1MAP
+ dev->reg.init_reg(0x74, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x75, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x76, 0x3c); // SENSOR_DEF
+
+ // CK3MAP
+ dev->reg.init_reg(0x77, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x78, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x79, 0x9f); // SENSOR_DEF
+
+ // CK4MAP
+ dev->reg.init_reg(0x7a, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x7b, 0x00); // SENSOR_DEF
+ dev->reg.init_reg(0x7c, 0x55); // SENSOR_DEF
+
+ dev->reg.init_reg(0x7d, 0x00);
+
+ // NOTE: autoconf is a non working option
+ dev->reg.init_reg(0x87, 0x02);
+ dev->reg.init_reg(0x9d, 0x06);
+ dev->reg.init_reg(0xa2, 0x0f);
+ dev->reg.init_reg(0xbd, 0x18);
+ dev->reg.init_reg(0xfe, 0x08);
+
+ // gamma[0] and gamma[256] values
+ dev->reg.init_reg(0xbe, 0x00);
+ dev->reg.init_reg(0xc5, 0x00);
+ dev->reg.init_reg(0xc6, 0x00);
+ dev->reg.init_reg(0xc7, 0x00);
+ dev->reg.init_reg(0xc8, 0x00);
+ dev->reg.init_reg(0xc9, 0x00);
+ dev->reg.init_reg(0xca, 0x00);
+
+ /* LiDE 700 fixups */
+ if (lide700) {
+ dev->reg.init_reg(0x5f, 0x04);
+ dev->reg.init_reg(0x7d, 0x80);
+
+ /* we write to these registers only once */
+ val=0;
+ dev->interface->write_register(REG_0x7E, val);
+ dev->interface->write_register(REG_0x9E, val);
+ dev->interface->write_register(REG_0x9F, val);
+ dev->interface->write_register(REG_0xAB, val);
+ }
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+ sanei_genesys_set_dpihw(dev->reg, sensor, sensor.optical_res);
+
+ /* initalize calibration reg */
+ dev->calib_reg = dev->reg;
+}
+
+/**@brief send slope table for motor movement
+ * Send slope_table in machine byte order
+ * @param dev device to send slope table
+ * @param table_nr index of the slope table in ASIC memory
+ * Must be in the [0-4] range.
+ * @param slope_table pointer to 16 bit values array of the slope table
+ * @param steps number of elements in the slope table
+ */
+static void gl847_send_slope_table(Genesys_Device* dev, int table_nr,
+ const std::vector<uint16_t>& slope_table,
+ int steps)
+{
+ DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %d", table_nr, steps);
+ int i;
+ char msg[10000];
+
+ /* sanity check */
+ if(table_nr<0 || table_nr>4)
+ {
+ throw SaneException("invalid table number %d", table_nr);
+ }
+
+ std::vector<uint8_t> table(steps * 2);
+ for (i = 0; i < steps; i++)
+ {
+ table[i * 2] = slope_table[i] & 0xff;
+ table[i * 2 + 1] = slope_table[i] >> 8;
+ }
+
+ if (DBG_LEVEL >= DBG_io)
+ {
+ std::sprintf(msg, "write slope %d (%d)=", table_nr, steps);
+ for (i = 0; i < steps; i++)
+ {
+ std::sprintf(msg + std::strlen(msg), "%d", slope_table[i]);
+ }
+ DBG (DBG_io, "%s: %s\n", __func__, msg);
+ }
+
+ if (dev->interface->is_mock()) {
+ dev->interface->record_slope_table(table_nr, slope_table);
+ }
+ // slope table addresses are fixed
+ dev->interface->write_ahb(0x10000000 + 0x4000 * table_nr, steps * 2, table.data());
+}
+
+/**
+ * Set register values of Analog Device type frontend
+ * */
+static void gl847_set_ad_fe(Genesys_Device* dev, uint8_t set)
+{
+ DBG_HELPER(dbg);
+ int i;
+
+ // wait for FE to be ready
+ auto status = scanner_read_status(*dev);
+ while (status.is_front_end_busy) {
+ dev->interface->sleep_ms(10);
+ status = scanner_read_status(*dev);
+ };
+
+ if (set == AFE_INIT)
+ {
+ DBG(DBG_proc, "%s(): setting DAC %u\n", __func__,
+ static_cast<unsigned>(dev->model->adc_id));
+
+ dev->frontend = dev->frontend_initial;
+ }
+
+ // reset DAC
+ dev->interface->write_fe_register(0x00, 0x80);
+
+ // write them to analog frontend
+ dev->interface->write_fe_register(0x00, dev->frontend.regs.get_value(0x00));
+
+ dev->interface->write_fe_register(0x01, dev->frontend.regs.get_value(0x01));
+
+ for (i = 0; i < 3; i++) {
+ dev->interface->write_fe_register(0x02 + i, dev->frontend.get_gain(i));
+ }
+ for (i = 0; i < 3; i++) {
+ dev->interface->write_fe_register(0x05 + i, dev->frontend.get_offset(i));
+ }
+}
+
+// Set values of analog frontend
+void CommandSetGl847::set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const
+{
+ DBG_HELPER_ARGS(dbg, "%s", set == AFE_INIT ? "init" :
+ set == AFE_SET ? "set" :
+ set == AFE_POWER_SAVE ? "powersave" : "huh?");
+
+ (void) sensor;
+
+ uint8_t val = dev->interface->read_register(REG_0x04);
+ uint8_t frontend_type = val & REG_0x04_FESET;
+
+ // route to AD devices
+ if (frontend_type == 0x02) {
+ gl847_set_ad_fe(dev, set);
+ return;
+ }
+
+ throw SaneException("unsupported frontend type %d", frontend_type);
+}
+
+
+// @brief set up motor related register for scan
+static void gl847_init_motor_regs_scan(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const Motor_Profile& motor_profile,
+ unsigned int scan_exposure_time,
+ unsigned scan_yres,
+ unsigned int scan_lines,
+ unsigned int scan_dummy,
+ unsigned int feed_steps,
+ MotorFlag flags)
+{
+ DBG_HELPER_ARGS(dbg, "scan_exposure_time=%d, can_yres=%d, step_type=%d, scan_lines=%d, "
+ "scan_dummy=%d, feed_steps=%d, flags=%x",
+ scan_exposure_time, scan_yres, static_cast<unsigned>(motor_profile.step_type),
+ scan_lines, scan_dummy, feed_steps, static_cast<unsigned>(flags));
+ int use_fast_fed;
+ unsigned int fast_dpi;
+ unsigned int feedl, dist;
+ GenesysRegister *r;
+ uint32_t z1, z2;
+ unsigned int min_restep = 0x20;
+ uint8_t val;
+ unsigned int ccdlmt,tgtime;
+
+ unsigned step_multiplier = gl847_get_step_multiplier (reg);
+
+ use_fast_fed=0;
+ /* no fast fed since feed works well */
+ if (dev->settings.yres==4444 && feed_steps > 100 && (!has_flag(flags, MotorFlag::FEED)))
+ {
+ use_fast_fed=1;
+ }
+ DBG(DBG_io, "%s: use_fast_fed=%d\n", __func__, use_fast_fed);
+
+ reg->set24(REG_LINCNT, scan_lines);
+ DBG(DBG_io, "%s: lincnt=%d\n", __func__, scan_lines);
+
+ /* compute register 02 value */
+ r = sanei_genesys_get_address(reg, REG_0x02);
+ r->value = 0x00;
+ sanei_genesys_set_motor_power(*reg, true);
+
+ if (use_fast_fed) {
+ r->value |= REG_0x02_FASTFED;
+ } else {
+ r->value &= ~REG_0x02_FASTFED;
+ }
+
+ if (has_flag(flags, MotorFlag::AUTO_GO_HOME)) {
+ r->value |= REG_0x02_AGOHOME | REG_0x02_NOTHOME;
+ }
+
+ if (has_flag(flags, MotorFlag::DISABLE_BUFFER_FULL_MOVE)
+ ||(scan_yres>=sensor.optical_res))
+ {
+ r->value |= REG_0x02_ACDCDIS;
+ }
+
+ if (has_flag(flags, MotorFlag::REVERSE)) {
+ r->value |= REG_0x02_MTRREV;
+ } else {
+ r->value &= ~REG_0x02_MTRREV;
+ }
+
+ /* scan and backtracking slope table */
+ auto scan_table = sanei_genesys_slope_table(dev->model->asic_type, scan_yres,
+ scan_exposure_time, dev->motor.base_ydpi,
+ step_multiplier, motor_profile);
+ gl847_send_slope_table(dev, SCAN_TABLE, scan_table.table, scan_table.steps_count);
+ gl847_send_slope_table(dev, BACKTRACK_TABLE, scan_table.table, scan_table.steps_count);
+
+ /* fast table */
+ fast_dpi=sanei_genesys_get_lowest_ydpi(dev);
+ StepType fast_step_type = motor_profile.step_type;
+ if (static_cast<unsigned>(motor_profile.step_type) >= static_cast<unsigned>(StepType::QUARTER)) {
+ fast_step_type = StepType::QUARTER;
+ }
+
+ Motor_Profile fast_motor_profile = motor_profile;
+ fast_motor_profile.step_type = fast_step_type;
+
+ auto fast_table = sanei_genesys_slope_table(dev->model->asic_type, fast_dpi,
+ scan_exposure_time, dev->motor.base_ydpi,
+ step_multiplier, fast_motor_profile);
+
+ gl847_send_slope_table(dev, STOP_TABLE, fast_table.table, fast_table.steps_count);
+ gl847_send_slope_table(dev, FAST_TABLE, fast_table.table, fast_table.steps_count);
+ gl847_send_slope_table(dev, HOME_TABLE, fast_table.table, fast_table.steps_count);
+
+ /* correct move distance by acceleration and deceleration amounts */
+ feedl=feed_steps;
+ if (use_fast_fed)
+ {
+ feedl <<= static_cast<unsigned>(fast_step_type);
+ dist = (scan_table.steps_count + 2 * fast_table.steps_count);
+ /* TODO read and decode REG_0xAB */
+ r = sanei_genesys_get_address (reg, 0x5e);
+ dist += (r->value & 31);
+ /* FEDCNT */
+ r = sanei_genesys_get_address (reg, REG_FEDCNT);
+ dist += r->value;
+ }
+ else
+ {
+ feedl <<= static_cast<unsigned>(motor_profile.step_type);
+ dist = scan_table.steps_count;
+ if (has_flag(flags, MotorFlag::FEED)) {
+ dist *= 2;
+ }
+ }
+ DBG(DBG_io2, "%s: acceleration distance=%d\n", __func__, dist);
+
+ /* check for overflow */
+ if (dist < feedl) {
+ feedl -= dist;
+ } else {
+ feedl = 0;
+ }
+
+ reg->set24(REG_FEEDL, feedl);
+ DBG(DBG_io ,"%s: feedl=%d\n", __func__, feedl);
+
+ r = sanei_genesys_get_address(reg, REG_0x0C);
+ ccdlmt = (r->value & REG_0x0C_CCDLMT) + 1;
+
+ r = sanei_genesys_get_address(reg, REG_0x1C);
+ tgtime = 1<<(r->value & REG_0x1C_TGTIME);
+
+ // hi res motor speed GPIO
+ uint8_t effective = dev->interface->read_register(REG_0x6C);
+
+ // if quarter step, bipolar Vref2
+
+ if (motor_profile.step_type == StepType::QUARTER) {
+ val = effective & ~REG_0x6C_GPIO13;
+ } else if (static_cast<unsigned>(motor_profile.step_type) > static_cast<unsigned>(StepType::QUARTER)) {
+ val = effective | REG_0x6C_GPIO13;
+ } else {
+ val = effective;
+ }
+ dev->interface->write_register(REG_0x6C, val);
+
+ // effective scan
+ effective = dev->interface->read_register(REG_0x6C);
+ val = effective | REG_0x6C_GPIO10;
+ dev->interface->write_register(REG_0x6C, val);
+
+ min_restep = scan_table.steps_count / (2 * step_multiplier) - 1;
+ if (min_restep < 1) {
+ min_restep = 1;
+ }
+ r = sanei_genesys_get_address(reg, REG_FWDSTEP);
+ r->value = min_restep;
+ r = sanei_genesys_get_address(reg, REG_BWDSTEP);
+ r->value = min_restep;
+
+ sanei_genesys_calculate_zmod(use_fast_fed,
+ scan_exposure_time*ccdlmt*tgtime,
+ scan_table.table,
+ scan_table.steps_count,
+ feedl,
+ min_restep * step_multiplier,
+ &z1,
+ &z2);
+
+ DBG(DBG_info, "%s: z1 = %d\n", __func__, z1);
+ reg->set24(REG_0x60, z1 | (static_cast<unsigned>(motor_profile.step_type) << (16+REG_0x60S_STEPSEL)));
+
+ DBG(DBG_info, "%s: z2 = %d\n", __func__, z2);
+ reg->set24(REG_0x63, z2 | (static_cast<unsigned>(motor_profile.step_type) << (16+REG_0x63S_FSTPSEL)));
+
+ r = sanei_genesys_get_address (reg, 0x1e);
+ r->value &= 0xf0; /* 0 dummy lines */
+ r->value |= scan_dummy; /* dummy lines */
+
+ r = sanei_genesys_get_address(reg, REG_0x67);
+ r->value = REG_0x67_MTRPWM;
+
+ r = sanei_genesys_get_address(reg, REG_0x68);
+ r->value = REG_0x68_FASTPWM;
+
+ reg->set8(REG_STEPNO, scan_table.steps_count / step_multiplier);
+ reg->set8(REG_FASTNO, scan_table.steps_count / step_multiplier);
+ reg->set8(REG_FSHDEC, scan_table.steps_count / step_multiplier);
+ reg->set8(REG_FMOVNO, fast_table.steps_count / step_multiplier);
+ reg->set8(REG_FMOVDEC, fast_table.steps_count / step_multiplier);
+}
+
+
+/** @brief set up registers related to sensor
+ * Set up the following registers
+ 0x01
+ 0x03
+ 0x10-0x015 R/G/B exposures
+ 0x19 EXPDMY
+ 0x2e BWHI
+ 0x2f BWLO
+ 0x04
+ 0x87
+ 0x05
+ 0x2c,0x2d DPISET
+ 0x30,0x31 STRPIXEL
+ 0x32,0x33 ENDPIXEL
+ 0x35,0x36,0x37 MAXWD [25:2] (>>2)
+ 0x38,0x39 LPERIOD
+ 0x34 DUMMY
+ */
+static void gl847_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, unsigned int exposure_time,
+ const ScanSession& session)
+{
+ DBG_HELPER_ARGS(dbg, "exposure_time=%d", exposure_time);
+ unsigned dpihw;
+ GenesysRegister *r;
+
+ // resolution is divided according to ccd_pixels_per_system_pixel()
+ unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel();
+ DBG(DBG_io2, "%s: ccd_pixels_per_system_pixel=%d\n", __func__, ccd_pixels_per_system_pixel);
+
+ // to manage high resolution device while keeping good low resolution scanning speed, we make
+ // hardware dpi vary
+ dpihw = sensor.get_register_hwdpi(session.params.xres * ccd_pixels_per_system_pixel);
+ DBG(DBG_io2, "%s: dpihw=%d\n", __func__, dpihw);
+
+ gl847_setup_sensor(dev, sensor, reg);
+
+ dev->cmd_set->set_fe(dev, sensor, AFE_SET);
+
+ /* enable shading */
+ regs_set_optical_off(dev->model->asic_type, *reg);
+ r = sanei_genesys_get_address(reg, REG_0x01);
+ r->value |= REG_0x01_SHDAREA;
+
+ if (has_flag(session.params.flags, ScanFlag::DISABLE_SHADING) ||
+ (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION))
+ {
+ r->value &= ~REG_0x01_DVDSET;
+ }
+ else
+ {
+ r->value |= REG_0x01_DVDSET;
+ }
+
+ r = sanei_genesys_get_address (reg, REG_0x03);
+ r->value &= ~REG_0x03_AVEENB;
+
+ sanei_genesys_set_lamp_power(dev, sensor, *reg,
+ !has_flag(session.params.flags, ScanFlag::DISABLE_LAMP));
+
+ /* BW threshold */
+ r = sanei_genesys_get_address (reg, 0x2e);
+ r->value = dev->settings.threshold;
+ r = sanei_genesys_get_address (reg, 0x2f);
+ r->value = dev->settings.threshold;
+
+ /* monochrome / color scan */
+ r = sanei_genesys_get_address (reg, REG_0x04);
+ switch (session.params.depth) {
+ case 8:
+ r->value &= ~(REG_0x04_LINEART | REG_0x04_BITSET);
+ break;
+ case 16:
+ r->value &= ~REG_0x04_LINEART;
+ r->value |= REG_0x04_BITSET;
+ break;
+ }
+
+ r->value &= ~(REG_0x04_FILTER | REG_0x04_AFEMOD);
+ if (session.params.channels == 1)
+ {
+ switch (session.params.color_filter)
+ {
+
+ case ColorFilter::RED:
+ r->value |= 0x14;
+ break;
+ case ColorFilter::BLUE:
+ r->value |= 0x1c;
+ break;
+ case ColorFilter::GREEN:
+ r->value |= 0x18;
+ break;
+ default:
+ break; // should not happen
+ }
+ } else {
+ r->value |= 0x10; // mono
+ }
+
+ sanei_genesys_set_dpihw(*reg, sensor, dpihw);
+
+ if (should_enable_gamma(session, sensor)) {
+ reg->find_reg(REG_0x05).value |= REG_0x05_GMMENB;
+ } else {
+ reg->find_reg(REG_0x05).value &= ~REG_0x05_GMMENB;
+ }
+
+ /* CIS scanners can do true gray by setting LEDADD */
+ /* we set up LEDADD only when asked */
+ if (dev->model->is_cis) {
+ r = sanei_genesys_get_address (reg, 0x87);
+ r->value &= ~REG_0x87_LEDADD;
+ if (session.enable_ledadd) {
+ r->value |= REG_0x87_LEDADD;
+ }
+ /* RGB weighting
+ r = sanei_genesys_get_address (reg, 0x01);
+ r->value &= ~REG_0x01_TRUEGRAY;
+ if (session.enable_ledadd) {
+ r->value |= REG_0x01_TRUEGRAY;
+ }
+ */
+ }
+
+ unsigned dpiset = session.params.xres * ccd_pixels_per_system_pixel;
+ reg->set16(REG_DPISET, dpiset);
+ DBG (DBG_io2, "%s: dpiset used=%d\n", __func__, dpiset);
+
+ reg->set16(REG_STRPIXEL, session.pixel_startx);
+ reg->set16(REG_ENDPIXEL, session.pixel_endx);
+
+ build_image_pipeline(dev, session);
+
+ /* MAXWD is expressed in 4 words unit */
+ // BUG: we shouldn't multiply by channels here
+ reg->set24(REG_MAXWD, (session.output_line_bytes_raw * session.params.channels >> 2));
+
+ reg->set16(REG_LPERIOD, exposure_time);
+ DBG(DBG_io2, "%s: exposure_time used=%d\n", __func__, exposure_time);
+
+ r = sanei_genesys_get_address (reg, 0x34);
+ r->value = sensor.dummy_pixel;
+}
+
+void CommandSetGl847::init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const
+{
+ DBG_HELPER(dbg);
+ session.assert_computed();
+
+ int move;
+ int exposure_time;
+
+ int slope_dpi = 0;
+ int dummy = 0;
+
+ dummy = 3 - session.params.channels;
+
+/* slope_dpi */
+/* cis color scan is effectively a gray scan with 3 gray lines per color
+ line and a FILTER of 0 */
+ if (dev->model->is_cis) {
+ slope_dpi = session.params.yres * session.params.channels;
+ } else {
+ slope_dpi = session.params.yres;
+ }
+
+ slope_dpi = slope_dpi * (1 + dummy);
+
+ exposure_time = sensor.exposure_lperiod;
+ const auto& motor_profile = sanei_genesys_get_motor_profile(*gl847_motor_profiles,
+ dev->model->motor_id,
+ exposure_time);
+
+ DBG(DBG_info, "%s : exposure_time=%d pixels\n", __func__, exposure_time);
+ DBG(DBG_info, "%s : scan_step_type=%d\n", __func__,
+ static_cast<unsigned>(motor_profile.step_type));
+
+ /* we enable true gray for cis scanners only, and just when doing
+ * scan since color calibration is OK for this mode
+ */
+ gl847_init_optical_regs_scan(dev, sensor, reg, exposure_time, session);
+
+ move = session.params.starty;
+ DBG(DBG_info, "%s: move=%d steps\n", __func__, move);
+
+ MotorFlag mflags = MotorFlag::NONE;
+ if (has_flag(session.params.flags, ScanFlag::DISABLE_BUFFER_FULL_MOVE)) {
+ mflags |= MotorFlag::DISABLE_BUFFER_FULL_MOVE;
+ }
+ if (has_flag(session.params.flags, ScanFlag::FEEDING)) {
+ mflags |= MotorFlag::FEED;
+ }
+ if (has_flag(session.params.flags, ScanFlag::REVERSE)) {
+ mflags |= MotorFlag::REVERSE;
+ }
+
+ gl847_init_motor_regs_scan(dev, sensor, reg, motor_profile, exposure_time, slope_dpi,
+ dev->model->is_cis ? session.output_line_count * session.params.channels
+ : session.output_line_count,
+ dummy, move, mflags);
+
+ dev->read_buffer.clear();
+ dev->read_buffer.alloc(session.buffer_size_read);
+
+ dev->read_active = true;
+
+ dev->session = session;
+
+ dev->total_bytes_read = 0;
+ dev->total_bytes_to_read = session.output_line_bytes_requested * session.params.lines;
+
+ DBG(DBG_info, "%s: total bytes to send = %zu\n", __func__, dev->total_bytes_to_read);
+}
+
+ScanSession CommandSetGl847::calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const
+{
+ int start;
+
+ DBG(DBG_info, "%s ", __func__);
+ debug_dump(DBG_info, settings);
+
+ /* start */
+ start = static_cast<int>(dev->model->x_offset);
+ start = static_cast<int>(start + settings.tl_x);
+ start = static_cast<int>((start * sensor.optical_res) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = settings.xres;
+ session.params.yres = settings.yres;
+ session.params.startx = start; // not used
+ session.params.starty = 0; // not used
+ session.params.pixels = settings.pixels;
+ session.params.requested_pixels = settings.requested_pixels;
+ session.params.lines = settings.lines;
+ session.params.depth = settings.depth;
+ session.params.channels = settings.get_channels();
+ session.params.scan_method = settings.scan_method;
+ session.params.scan_mode = settings.scan_mode;
+ session.params.color_filter = settings.color_filter;
+ session.params.flags = ScanFlag::NONE;
+
+ compute_session(dev, session, sensor);
+
+ return session;
+}
+
+// for fast power saving methods only, like disabling certain amplifiers
+void CommandSetGl847::save_power(Genesys_Device* dev, bool enable) const
+{
+ DBG_HELPER_ARGS(dbg, "enable = %d", enable);
+ (void) dev;
+}
+
+void CommandSetGl847::set_powersaving(Genesys_Device* dev, int delay /* in minutes */) const
+{
+ (void) dev;
+ DBG_HELPER_ARGS(dbg, "delay = %d", delay);
+}
+
+// Send the low-level scan command
+void CommandSetGl847::begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg, bool start_motor) const
+{
+ DBG_HELPER(dbg);
+ (void) sensor;
+ uint8_t val;
+ GenesysRegister *r;
+
+ // clear GPIO 10
+ if (dev->model->gpio_id != GpioId::CANON_LIDE_700F) {
+ val = dev->interface->read_register(REG_0x6C);
+ val &= ~REG_0x6C_GPIO10;
+ dev->interface->write_register(REG_0x6C, val);
+ }
+
+ val = REG_0x0D_CLRLNCNT;
+ dev->interface->write_register(REG_0x0D, val);
+ val = REG_0x0D_CLRMCNT;
+ dev->interface->write_register(REG_0x0D, val);
+
+ val = dev->interface->read_register(REG_0x01);
+ val |= REG_0x01_SCAN;
+ dev->interface->write_register(REG_0x01, val);
+ r = sanei_genesys_get_address (reg, REG_0x01);
+ r->value = val;
+
+ scanner_start_action(*dev, start_motor);
+
+ dev->advance_head_pos_by_session(ScanHeadId::PRIMARY);
+}
+
+
+// Send the stop scan command
+void CommandSetGl847::end_scan(Genesys_Device* dev, Genesys_Register_Set* reg,
+ bool check_stop) const
+{
+ (void) reg;
+ DBG_HELPER_ARGS(dbg, "check_stop = %d", check_stop);
+
+ if (!dev->model->is_sheetfed) {
+ scanner_stop_action(*dev);
+ }
+}
+
+/** Park head
+ * Moves the slider to the home (top) position slowly
+ * @param dev device to park
+ * @param wait_until_home true to make the function waiting for head
+ * to be home before returning, if fals returne immediately
+*/
+void CommandSetGl847::move_back_home(Genesys_Device* dev, bool wait_until_home) const
+{
+ scanner_move_back_home(*dev, wait_until_home);
+}
+
+// Automatically set top-left edge of the scan area by scanning a 200x200 pixels area at 600 dpi
+// from very top of scanner
+void CommandSetGl847::search_start_position(Genesys_Device* dev) const
+{
+ DBG_HELPER(dbg);
+ int size;
+ Genesys_Register_Set local_reg;
+
+ int pixels = 600;
+ int dpi = 300;
+
+ local_reg = dev->reg;
+
+ /* sets for a 200 lines * 600 pixels */
+ /* normal scan with no shading */
+
+ // FIXME: the current approach of doing search only for one resolution does not work on scanners
+ // whith employ different sensors with potentially different settings.
+ const auto& sensor = sanei_genesys_find_sensor(dev, dpi, 1, dev->model->default_method);
+
+ ScanSession session;
+ session.params.xres = dpi;
+ session.params.yres = dpi;
+ session.params.startx = 0;
+ session.params.starty = 0; /*we should give a small offset here~60 steps */
+ session.params.pixels = 600;
+ session.params.lines = dev->model->search_lines;
+ session.params.depth = 8;
+ session.params.channels = 1;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::GRAY;
+ session.params.color_filter = ColorFilter::GREEN;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &local_reg, session);
+
+ // send to scanner
+ dev->interface->write_registers(local_reg);
+
+ size = pixels * dev->model->search_lines;
+
+ std::vector<uint8_t> data(size);
+
+ begin_scan(dev, sensor, &local_reg, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("search_start_position");
+ end_scan(dev, &local_reg, true);
+ dev->reg = local_reg;
+ return;
+ }
+
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ sanei_genesys_read_data_from_scanner(dev, data.data(), size);
+
+ if (DBG_LEVEL >= DBG_data) {
+ sanei_genesys_write_pnm_file("gl847_search_position.pnm", data.data(), 8, 1, pixels,
+ dev->model->search_lines);
+ }
+
+ end_scan(dev, &local_reg, true);
+
+ /* update regs to copy ASIC internal state */
+ dev->reg = local_reg;
+
+ // TODO: find out where sanei_genesys_search_reference_point stores information,
+ // and use that correctly
+ for (auto& sensor_update :
+ sanei_genesys_find_sensors_all_for_write(dev, dev->model->default_method))
+ {
+ sanei_genesys_search_reference_point(dev, sensor_update, data.data(), 0, dpi, pixels,
+ dev->model->search_lines);
+ }
+}
+
+// sets up register for coarse gain calibration
+// todo: check it for scanners using it
+void CommandSetGl847::init_regs_for_coarse_calibration(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+
+ ScanSession session;
+ session.params.xres = dev->settings.xres;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = sensor.optical_res / sensor.ccd_pixels_per_system_pixel();
+ session.params.lines = 20;
+ session.params.depth = 16;
+ session.params.channels = dev->settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = dev->settings.scan_mode;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &regs, session);
+
+ DBG(DBG_info, "%s: optical sensor res: %d dpi, actual res: %d\n", __func__,
+ sensor.optical_res / sensor.ccd_pixels_per_system_pixel(), dev->settings.xres);
+
+ dev->interface->write_registers(regs);
+}
+
+// init registers for shading calibration
+void CommandSetGl847::init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+
+ dev->calib_channels = 3;
+
+ /* initial calibration reg values */
+ regs = dev->reg;
+
+ dev->calib_resolution = sensor.get_register_hwdpi(dev->settings.xres);
+
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, dev->calib_resolution,
+ dev->calib_channels,
+ dev->settings.scan_method);
+
+ dev->calib_total_bytes_to_read = 0;
+ dev->calib_lines = dev->model->shading_lines;
+ if (dev->calib_resolution == 4800) {
+ dev->calib_lines *= 2;
+ }
+ dev->calib_pixels = (calib_sensor.sensor_pixels * dev->calib_resolution) /
+ calib_sensor.optical_res;
+
+ DBG(DBG_io, "%s: calib_lines = %zu\n", __func__, dev->calib_lines);
+ DBG(DBG_io, "%s: calib_pixels = %zu\n", __func__, dev->calib_pixels);
+
+ ScanSession session;
+ session.params.xres = dev->calib_resolution;
+ session.params.yres = dev->motor.base_ydpi;
+ session.params.startx = 0;
+ session.params.starty = 20;
+ session.params.pixels = dev->calib_pixels;
+ session.params.lines = dev->calib_lines;
+ session.params.depth = 16;
+ session.params.channels = dev->calib_channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::DISABLE_BUFFER_FULL_MOVE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ dev->interface->write_registers(regs);
+
+ /* we use GENESYS_FLAG_SHADING_REPARK */
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+}
+
+/** @brief set up registers for the actual scan
+ */
+void CommandSetGl847::init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ DBG_HELPER(dbg);
+ float move;
+ int move_dpi;
+ float start;
+
+ debug_dump(DBG_info, dev->settings);
+
+ /* steps to move to reach scanning area:
+ - first we move to physical start of scanning
+ either by a fixed steps amount from the black strip
+ or by a fixed amount from parking position,
+ minus the steps done during shading calibration
+ - then we move by the needed offset whitin physical
+ scanning area
+
+ assumption: steps are expressed at maximum motor resolution
+
+ we need:
+ float y_offset;
+ float y_size;
+ float y_offset_calib;
+ mm_to_steps()=motor dpi / 2.54 / 10=motor dpi / MM_PER_INCH */
+
+ /* if scanner uses GENESYS_FLAG_SEARCH_START y_offset is
+ relative from origin, else, it is from parking position */
+
+ move_dpi = dev->motor.base_ydpi;
+
+ move = static_cast<float>(dev->model->y_offset);
+ move = static_cast<float>(move + dev->settings.tl_y);
+ move = static_cast<float>((move * move_dpi) / MM_PER_INCH);
+ move -= dev->head_pos(ScanHeadId::PRIMARY);
+ DBG(DBG_info, "%s: move=%f steps\n", __func__, move);
+
+ /* fast move to scan area */
+ /* we don't move fast the whole distance since it would involve
+ * computing acceleration/deceleration distance for scan
+ * resolution. So leave a remainder for it so scan makes the final
+ * move tuning */
+ if (dev->settings.get_channels() * dev->settings.yres >= 600 && move > 700) {
+ scanner_move(*dev, dev->model->default_method, static_cast<unsigned>(move - 500),
+ Direction::FORWARD);
+ move=500;
+ }
+
+ DBG(DBG_info, "%s: move=%f steps\n", __func__, move);
+ DBG(DBG_info, "%s: move=%f steps\n", __func__, move);
+
+ /* start */
+ start = static_cast<float>(dev->model->x_offset);
+ start = static_cast<float>(start + dev->settings.tl_x);
+ start = static_cast<float>((start * sensor.optical_res) / MM_PER_INCH);
+
+ ScanSession session;
+ session.params.xres = dev->settings.xres;
+ session.params.yres = dev->settings.yres;
+ session.params.startx = static_cast<unsigned>(start);
+ session.params.starty = static_cast<unsigned>(move);
+ session.params.pixels = dev->settings.pixels;
+ session.params.requested_pixels = dev->settings.requested_pixels;
+ session.params.lines = dev->settings.lines;
+ session.params.depth = dev->settings.depth;
+ session.params.channels = dev->settings.get_channels();
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = dev->settings.scan_mode;
+ session.params.color_filter = dev->settings.color_filter;
+ // backtracking isn't handled well, so don't enable it
+ session.params.flags = ScanFlag::DISABLE_BUFFER_FULL_MOVE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &dev->reg, session);
+}
+
+
+/**
+ * Send shading calibration data. The buffer is considered to always hold values
+ * for all the channels.
+ */
+void CommandSetGl847::send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ uint8_t* data, int size) const
+{
+ DBG_HELPER_ARGS(dbg, "writing %d bytes of shading data", size);
+ uint32_t addr, length, i, x, factor, pixels;
+ uint32_t dpiset, dpihw;
+ uint8_t val,*ptr,*src;
+
+ /* shading data is plit in 3 (up to 5 with IR) areas
+ write(0x10014000,0x00000dd8)
+ URB 23429 bulk_out len 3544 wrote 0x33 0x10 0x....
+ write(0x1003e000,0x00000dd8)
+ write(0x10068000,0x00000dd8)
+ */
+ length = static_cast<std::uint32_t>(size / 3);
+ std::uint32_t strpixel = dev->session.pixel_startx;
+ std::uint32_t endpixel = dev->session.pixel_endx;
+
+ /* compute deletion factor */
+ dpiset = dev->reg.get16(REG_DPISET);
+ dpihw = sensor.get_register_hwdpi(dpiset);
+ factor=dpihw/dpiset;
+ DBG(DBG_io2, "%s: factor=%d\n", __func__, factor);
+
+ pixels=endpixel-strpixel;
+
+ /* since we're using SHDAREA, substract startx coordinate from shading */
+ strpixel -= (sensor.ccd_start_xoffset * 600) / sensor.optical_res;
+
+ /* turn pixel value into bytes 2x16 bits words */
+ strpixel*=2*2;
+ pixels*=2*2;
+
+ dev->interface->record_key_value("shading_offset", std::to_string(strpixel));
+ dev->interface->record_key_value("shading_pixels", std::to_string(pixels));
+ dev->interface->record_key_value("shading_length", std::to_string(length));
+ dev->interface->record_key_value("shading_factor", std::to_string(factor));
+
+ std::vector<uint8_t> buffer(pixels, 0);
+
+ DBG(DBG_io2, "%s: using chunks of %d (0x%04x) bytes\n", __func__, pixels, pixels);
+
+ /* base addr of data has been written in reg D0-D4 in 4K word, so AHB address
+ * is 8192*reg value */
+
+ /* write actual color channel data */
+ for(i=0;i<3;i++)
+ {
+ /* build up actual shading data by copying the part from the full width one
+ * to the one corresponding to SHDAREA */
+ ptr = buffer.data();
+
+ /* iterate on both sensor segment */
+ for(x=0;x<pixels;x+=4*factor)
+ {
+ /* coefficient source */
+ src=(data+strpixel+i*length)+x;
+
+ /* coefficient copy */
+ ptr[0]=src[0];
+ ptr[1]=src[1];
+ ptr[2]=src[2];
+ ptr[3]=src[3];
+
+ /* next shading coefficient */
+ ptr+=4;
+ }
+
+ val = dev->interface->read_register(0xd0+i);
+ addr = val * 8192 + 0x10000000;
+ dev->interface->write_ahb(addr, pixels, buffer.data());
+ }
+}
+
+/** @brief calibrates led exposure
+ * Calibrate exposure by scanning a white area until the used exposure gives
+ * data white enough.
+ * @param dev device to calibrate
+ */
+SensorExposure CommandSetGl847::led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ int num_pixels;
+ int total_size;
+ int used_res;
+ int i, j;
+ int val;
+ int channels;
+ int avg[3], top[3], bottom[3];
+ int turn;
+ uint16_t exp[3];
+ float move;
+
+ move = static_cast<float>(dev->model->y_offset_calib_white);
+ move = static_cast<float>((move * (dev->motor.base_ydpi / 4)) / MM_PER_INCH);
+ if (move > 20) {
+ scanner_move(*dev, dev->model->default_method, static_cast<unsigned>(move),
+ Direction::FORWARD);
+ }
+ DBG(DBG_io, "%s: move=%f steps\n", __func__, move);
+
+ /* offset calibration is always done in color mode */
+ channels = 3;
+ used_res = sensor.get_register_hwdpi(dev->settings.xres);
+ const auto& calib_sensor = sanei_genesys_find_sensor(dev, used_res, channels,
+ dev->settings.scan_method);
+ num_pixels = (calib_sensor.sensor_pixels * used_res) / calib_sensor.optical_res;
+
+ /* initial calibration reg values */
+ regs = dev->reg;
+
+ ScanSession session;
+ session.params.xres = used_res;
+ session.params.yres = used_res;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = num_pixels;
+ session.params.lines = 1;
+ session.params.depth = 16;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, calib_sensor);
+
+ init_regs_for_scan_session(dev, calib_sensor, &regs, session);
+
+ total_size = num_pixels * channels * (session.params.depth/8) * 1;
+ std::vector<uint8_t> line(total_size);
+
+ // initial loop values and boundaries
+ exp[0] = calib_sensor.exposure.red;
+ exp[1] = calib_sensor.exposure.green;
+ exp[2] = calib_sensor.exposure.blue;
+
+ bottom[0] = 28000;
+ bottom[1] = 28000;
+ bottom[2] = 28000;
+
+ top[0] = 32000;
+ top[1] = 32000;
+ top[2] = 32000;
+
+ turn = 0;
+
+ /* no move during led calibration */
+ bool acceptable = false;
+ sanei_genesys_set_motor_power(regs, false);
+ do
+ {
+ // set up exposure
+ regs.set16(REG_EXPR,exp[0]);
+ regs.set16(REG_EXPG,exp[1]);
+ regs.set16(REG_EXPB,exp[2]);
+
+ // write registers and scan data
+ dev->interface->write_registers(regs);
+
+ DBG(DBG_info, "%s: starting line reading\n", __func__);
+ begin_scan(dev, calib_sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("led_calibration");
+ scanner_stop_action(*dev);
+ move_back_home(dev, true);
+ return calib_sensor.exposure;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, line.data(), total_size);
+
+ // stop scanning
+ scanner_stop_action(*dev);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[30];
+ std::snprintf(fn, 30, "gl847_led_%02d.pnm", turn);
+ sanei_genesys_write_pnm_file(fn, line.data(), session.params.depth,
+ channels, num_pixels, 1);
+ }
+
+ /* compute average */
+ for (j = 0; j < channels; j++)
+ {
+ avg[j] = 0;
+ for (i = 0; i < num_pixels; i++)
+ {
+ if (dev->model->is_cis)
+ val =
+ line[i * 2 + j * 2 * num_pixels + 1] * 256 +
+ line[i * 2 + j * 2 * num_pixels];
+ else
+ val =
+ line[i * 2 * channels + 2 * j + 1] * 256 +
+ line[i * 2 * channels + 2 * j];
+ avg[j] += val;
+ }
+
+ avg[j] /= num_pixels;
+ }
+
+ DBG(DBG_info, "%s: average: %d,%d,%d\n", __func__, avg[0], avg[1], avg[2]);
+
+ /* check if exposure gives average within the boundaries */
+ acceptable = true;
+ for(i=0;i<3;i++)
+ {
+ if (avg[i] < bottom[i] || avg[i] > top[i]) {
+ auto target = (bottom[i] + top[i]) / 2;
+ exp[i] = (exp[i] * target) / avg[i];
+ acceptable = false;
+ }
+ }
+
+ turn++;
+ }
+ while (!acceptable && turn < 100);
+
+ DBG(DBG_info, "%s: acceptable exposure: %d,%d,%d\n", __func__, exp[0], exp[1], exp[2]);
+
+ // set these values as final ones for scan
+ dev->reg.set16(REG_EXPR, exp[0]);
+ dev->reg.set16(REG_EXPG, exp[1]);
+ dev->reg.set16(REG_EXPB, exp[2]);
+
+ // go back home
+ if (move>20) {
+ move_back_home(dev, true);
+ }
+
+ return { exp[0], exp[1], exp[2] };
+}
+
+/**
+ * set up GPIO/GPOE for idle state
+ */
+static void gl847_init_gpio(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ int idx=0;
+
+ /* search GPIO profile */
+ while(gpios[idx].gpio_id != GpioId::UNKNOWN && dev->model->gpio_id != gpios[idx].gpio_id) {
+ idx++;
+ }
+ if (gpios[idx].gpio_id == GpioId::UNKNOWN) {
+ throw SaneException("failed to find GPIO profile for sensor_id=%d",
+ static_cast<unsigned>(dev->model->sensor_id));
+ }
+
+ dev->interface->write_register(REG_0xA7, gpios[idx].ra7);
+ dev->interface->write_register(REG_0xA6, gpios[idx].ra6);
+
+ dev->interface->write_register(REG_0x6E, gpios[idx].r6e);
+ dev->interface->write_register(REG_0x6C, 0x00);
+
+ dev->interface->write_register(REG_0x6B, gpios[idx].r6b);
+ dev->interface->write_register(REG_0x6C, gpios[idx].r6c);
+ dev->interface->write_register(REG_0x6D, gpios[idx].r6d);
+ dev->interface->write_register(REG_0x6E, gpios[idx].r6e);
+ dev->interface->write_register(REG_0x6F, gpios[idx].r6f);
+
+ dev->interface->write_register(REG_0xA8, gpios[idx].ra8);
+ dev->interface->write_register(REG_0xA9, gpios[idx].ra9);
+}
+
+/**
+ * set memory layout by filling values in dedicated registers
+ */
+static void gl847_init_memory_layout(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+ int idx = 0;
+ uint8_t val;
+
+ /* point to per model memory layout */
+ idx = 0;
+ if (dev->model->model_id == ModelId::CANON_LIDE_100) {
+ idx = 0;
+ }
+ if (dev->model->model_id == ModelId::CANON_LIDE_200) {
+ idx = 1;
+ }
+ if (dev->model->model_id == ModelId::CANON_5600F) {
+ idx = 2;
+ }
+ if (dev->model->model_id == ModelId::CANON_LIDE_700F) {
+ idx = 3;
+ }
+
+ /* CLKSET nd DRAMSEL */
+ val = layouts[idx].dramsel;
+ dev->interface->write_register(REG_0x0B, val);
+ dev->reg.find_reg(0x0b).value = val;
+
+ /* prevent further writings by bulk write register */
+ dev->reg.remove_reg(0x0b);
+
+ /* setup base address for shading data. */
+ /* values must be multiplied by 8192=0x4000 to give address on AHB */
+ /* R-Channel shading bank0 address setting for CIS */
+ dev->interface->write_register(0xd0, layouts[idx].rd0);
+ /* G-Channel shading bank0 address setting for CIS */
+ dev->interface->write_register(0xd1, layouts[idx].rd1);
+ /* B-Channel shading bank0 address setting for CIS */
+ dev->interface->write_register(0xd2, layouts[idx].rd2);
+
+ /* setup base address for scanned data. */
+ /* values must be multiplied by 1024*2=0x0800 to give address on AHB */
+ /* R-Channel ODD image buffer 0x0124->0x92000 */
+ /* size for each buffer is 0x16d*1k word */
+ dev->interface->write_register(0xe0, layouts[idx].re0);
+ dev->interface->write_register(0xe1, layouts[idx].re1);
+ /* R-Channel ODD image buffer end-address 0x0291->0x148800 => size=0xB6800*/
+ dev->interface->write_register(0xe2, layouts[idx].re2);
+ dev->interface->write_register(0xe3, layouts[idx].re3);
+
+ /* R-Channel EVEN image buffer 0x0292 */
+ dev->interface->write_register(0xe4, layouts[idx].re4);
+ dev->interface->write_register(0xe5, layouts[idx].re5);
+ /* R-Channel EVEN image buffer end-address 0x03ff*/
+ dev->interface->write_register(0xe6, layouts[idx].re6);
+ dev->interface->write_register(0xe7, layouts[idx].re7);
+
+ /* same for green, since CIS, same addresses */
+ dev->interface->write_register(0xe8, layouts[idx].re0);
+ dev->interface->write_register(0xe9, layouts[idx].re1);
+ dev->interface->write_register(0xea, layouts[idx].re2);
+ dev->interface->write_register(0xeb, layouts[idx].re3);
+ dev->interface->write_register(0xec, layouts[idx].re4);
+ dev->interface->write_register(0xed, layouts[idx].re5);
+ dev->interface->write_register(0xee, layouts[idx].re6);
+ dev->interface->write_register(0xef, layouts[idx].re7);
+
+/* same for blue, since CIS, same addresses */
+ dev->interface->write_register(0xf0, layouts[idx].re0);
+ dev->interface->write_register(0xf1, layouts[idx].re1);
+ dev->interface->write_register(0xf2, layouts[idx].re2);
+ dev->interface->write_register(0xf3, layouts[idx].re3);
+ dev->interface->write_register(0xf4, layouts[idx].re4);
+ dev->interface->write_register(0xf5, layouts[idx].re5);
+ dev->interface->write_register(0xf6, layouts[idx].re6);
+ dev->interface->write_register(0xf7, layouts[idx].re7);
+}
+
+/* *
+ * initialize ASIC from power on condition
+ */
+void CommandSetGl847::asic_boot(Genesys_Device* dev, bool cold) const
+{
+ DBG_HELPER(dbg);
+
+ // reset ASIC if cold boot
+ if (cold) {
+ dev->interface->write_register(0x0e, 0x01);
+ dev->interface->write_register(0x0e, 0x00);
+ }
+
+ // test CHKVER
+ uint8_t val = dev->interface->read_register(REG_0x40);
+ if (val & REG_0x40_CHKVER) {
+ val = dev->interface->read_register(0x00);
+ DBG(DBG_info, "%s: reported version for genesys chip is 0x%02x\n", __func__, val);
+ }
+
+ /* Set default values for registers */
+ gl847_init_registers (dev);
+
+ // Write initial registers
+ dev->interface->write_registers(dev->reg);
+
+ /* Enable DRAM by setting a rising edge on bit 3 of reg 0x0b */
+ val = dev->reg.find_reg(0x0b).value & REG_0x0B_DRAMSEL;
+ val = (val | REG_0x0B_ENBDRAM);
+ dev->interface->write_register(REG_0x0B, val);
+ dev->reg.find_reg(0x0b).value = val;
+
+ /* CIS_LINE */
+ dev->reg.init_reg(0x08, REG_0x08_CIS_LINE);
+ dev->interface->write_register(0x08, dev->reg.find_reg(0x08).value);
+
+ // set up end access
+ dev->interface->write_0x8c(0x10, 0x0b);
+ dev->interface->write_0x8c(0x13, 0x0e);
+
+ // setup gpio
+ gl847_init_gpio(dev);
+
+ // setup internal memory layout
+ gl847_init_memory_layout (dev);
+
+ dev->reg.init_reg(0xf8, 0x01);
+ dev->interface->write_register(0xf8, dev->reg.find_reg(0xf8).value);
+}
+
+/**
+ * initialize backend and ASIC : registers, motor tables, and gamma tables
+ * then ensure scanner's head is at home
+ */
+void CommandSetGl847::init(Genesys_Device* dev) const
+{
+ DBG_INIT ();
+ DBG_HELPER(dbg);
+
+ sanei_genesys_asic_init(dev, 0);
+}
+
+void CommandSetGl847::update_hardware_sensors(Genesys_Scanner* s) const
+{
+ DBG_HELPER(dbg);
+ /* do what is needed to get a new set of events, but try to not lose
+ any of them.
+ */
+ uint8_t val;
+ uint8_t scan, file, email, copy;
+ switch(s->dev->model->gpio_id) {
+ case GpioId::CANON_LIDE_700F:
+ scan=0x04;
+ file=0x02;
+ email=0x01;
+ copy=0x08;
+ break;
+ default:
+ scan=0x01;
+ file=0x02;
+ email=0x04;
+ copy=0x08;
+ }
+ val = s->dev->interface->read_register(REG_0x6D);
+
+ s->buttons[BUTTON_SCAN_SW].write((val & scan) == 0);
+ s->buttons[BUTTON_FILE_SW].write((val & file) == 0);
+ s->buttons[BUTTON_EMAIL_SW].write((val & email) == 0);
+ s->buttons[BUTTON_COPY_SW].write((val & copy) == 0);
+}
+
+void CommandSetGl847::update_home_sensor_gpio(Genesys_Device& dev) const
+{
+ DBG_HELPER(dbg);
+
+ if (dev.model->gpio_id == GpioId::CANON_LIDE_700F) {
+ std::uint8_t val = dev.interface->read_register(REG_0x6C);
+ val &= ~REG_0x6C_GPIO10;
+ dev.interface->write_register(REG_0x6C, val);
+ } else {
+ std::uint8_t val = dev.interface->read_register(REG_0x6C);
+ val |= REG_0x6C_GPIO10;
+ dev.interface->write_register(REG_0x6C, val);
+ }
+}
+
+/** @brief search for a full width black or white strip.
+ * This function searches for a black or white stripe across the scanning area.
+ * When searching backward, the searched area must completely be of the desired
+ * color since this area will be used for calibration which scans forward.
+ * @param dev scanner device
+ * @param forward true if searching forward, false if searching backward
+ * @param black true if searching for a black strip, false for a white strip
+ */
+void CommandSetGl847::search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor, bool forward,
+ bool black) const
+{
+ DBG_HELPER_ARGS(dbg, "%s %s", black ? "black" : "white", forward ? "forward" : "reverse");
+ unsigned int pixels, lines, channels;
+ Genesys_Register_Set local_reg;
+ size_t size;
+ unsigned int pass, count, found, x, y;
+ char title[80];
+
+ set_fe(dev, sensor, AFE_SET);
+ scanner_stop_action(*dev);
+
+ // set up for a gray scan at lowest dpi
+ const auto& resolution_settings = dev->model->get_resolution_settings(dev->settings.scan_method);
+ unsigned dpi = resolution_settings.get_min_resolution_x();
+ channels = 1;
+ /* 10 MM */
+ /* lines = (10 * dpi) / MM_PER_INCH; */
+ /* shading calibation is done with dev->motor.base_ydpi */
+ lines = (dev->model->shading_lines * dpi) / dev->motor.base_ydpi;
+ pixels = (sensor.sensor_pixels * dpi) / sensor.optical_res;
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+
+ local_reg = dev->reg;
+
+ ScanSession session;
+ session.params.xres = dpi;
+ session.params.yres = dpi;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::GRAY;
+ session.params.color_filter = ColorFilter::RED;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA;
+ if (!forward) {
+ session.params.flags |= ScanFlag::REVERSE;
+ }
+ compute_session(dev, session, sensor);
+
+ size = pixels * channels * lines * (session.params.depth / 8);
+ std::vector<uint8_t> data(size);
+
+ init_regs_for_scan_session(dev, sensor, &local_reg, session);
+
+ dev->interface->write_registers(local_reg);
+
+ begin_scan(dev, sensor, &local_reg, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("search_strip");
+ scanner_stop_action(*dev);
+ return;
+ }
+
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ sanei_genesys_read_data_from_scanner(dev, data.data(), size);
+
+ scanner_stop_action(*dev);
+
+ pass = 0;
+ if (DBG_LEVEL >= DBG_data)
+ {
+ std::sprintf(title, "gl847_search_strip_%s_%s%02d.pnm",
+ black ? "black" : "white", forward ? "fwd" : "bwd", pass);
+ sanei_genesys_write_pnm_file(title, data.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ /* loop until strip is found or maximum pass number done */
+ found = 0;
+ while (pass < 20 && !found)
+ {
+ dev->interface->write_registers(local_reg);
+
+ // now start scan
+ begin_scan(dev, sensor, &local_reg, true);
+
+ wait_until_buffer_non_empty(dev);
+
+ // now we're on target, we can read data
+ sanei_genesys_read_data_from_scanner(dev, data.data(), size);
+
+ scanner_stop_action(*dev);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ std::sprintf(title, "gl847_search_strip_%s_%s%02d.pnm",
+ black ? "black" : "white",
+ forward ? "fwd" : "bwd", static_cast<int>(pass));
+ sanei_genesys_write_pnm_file(title, data.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ /* search data to find black strip */
+ /* when searching forward, we only need one line of the searched color since we
+ * will scan forward. But when doing backward search, we need all the area of the
+ * same color */
+ if (forward)
+ {
+ for (y = 0; y < lines && !found; y++)
+ {
+ count = 0;
+ /* count of white/black pixels depending on the color searched */
+ for (x = 0; x < pixels; x++)
+ {
+ /* when searching for black, detect white pixels */
+ if (black && data[y * pixels + x] > 90)
+ {
+ count++;
+ }
+ /* when searching for white, detect black pixels */
+ if (!black && data[y * pixels + x] < 60)
+ {
+ count++;
+ }
+ }
+
+ /* at end of line, if count >= 3%, line is not fully of the desired color
+ * so we must go to next line of the buffer */
+ /* count*100/pixels < 3 */
+ if ((count * 100) / pixels < 3)
+ {
+ found = 1;
+ DBG(DBG_data, "%s: strip found forward during pass %d at line %d\n", __func__,
+ pass, y);
+ }
+ else
+ {
+ DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count,
+ (100 * count) / pixels);
+ }
+ }
+ }
+ else /* since calibration scans are done forward, we need the whole area
+ to be of the required color when searching backward */
+ {
+ count = 0;
+ for (y = 0; y < lines; y++)
+ {
+ /* count of white/black pixels depending on the color searched */
+ for (x = 0; x < pixels; x++)
+ {
+ /* when searching for black, detect white pixels */
+ if (black && data[y * pixels + x] > 90)
+ {
+ count++;
+ }
+ /* when searching for white, detect black pixels */
+ if (!black && data[y * pixels + x] < 60)
+ {
+ count++;
+ }
+ }
+ }
+
+ /* at end of area, if count >= 3%, area is not fully of the desired color
+ * so we must go to next buffer */
+ if ((count * 100) / (pixels * lines) < 3)
+ {
+ found = 1;
+ DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass);
+ }
+ else
+ {
+ DBG(DBG_data, "%s: pixels=%d, count=%d (%d%%)\n", __func__, pixels, count,
+ (100 * count) / pixels);
+ }
+ }
+ pass++;
+ }
+
+ if (found)
+ {
+ DBG(DBG_info, "%s: %s strip found\n", __func__, black ? "black" : "white");
+ }
+ else
+ {
+ throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", black ? "black" : "white");
+ }
+}
+
+/**
+ * average dark pixels of a 8 bits scan
+ */
+static int
+dark_average (uint8_t * data, unsigned int pixels, unsigned int lines,
+ unsigned int channels, unsigned int black)
+{
+ unsigned int i, j, k, average, count;
+ unsigned int avg[3];
+ uint8_t val;
+
+ /* computes average value on black margin */
+ for (k = 0; k < channels; k++)
+ {
+ avg[k] = 0;
+ count = 0;
+ for (i = 0; i < lines; i++)
+ {
+ for (j = 0; j < black; j++)
+ {
+ val = data[i * channels * pixels + j + k];
+ avg[k] += val;
+ count++;
+ }
+ }
+ if (count)
+ avg[k] /= count;
+ DBG(DBG_info, "%s: avg[%d] = %d\n", __func__, k, avg[k]);
+ }
+ average = 0;
+ for (i = 0; i < channels; i++)
+ average += avg[i];
+ average /= channels;
+ DBG(DBG_info, "%s: average = %d\n", __func__, average);
+ return average;
+}
+
+void CommandSetGl847::offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const
+{
+ DBG_HELPER(dbg);
+ unsigned channels;
+ int pass = 0, avg, total_size;
+ int topavg, bottomavg, lines;
+ int top, bottom, black_pixels, pixels;
+
+ // no gain nor offset for AKM AFE
+ uint8_t reg04 = dev->interface->read_register(REG_0x04);
+ if ((reg04 & REG_0x04_FESET) == 0x02) {
+ return;
+ }
+
+ /* offset calibration is always done in color mode */
+ channels = 3;
+ dev->calib_pixels = sensor.sensor_pixels;
+ lines=1;
+ pixels= (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res;
+ black_pixels = (sensor.black_pixels * sensor.optical_res) / sensor.optical_res;
+ DBG(DBG_io2, "%s: black_pixels=%d\n", __func__, black_pixels);
+
+ ScanSession session;
+ session.params.xres = sensor.optical_res;
+ session.params.yres = sensor.optical_res;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ init_regs_for_scan_session(dev, sensor, &regs, session);
+
+ sanei_genesys_set_motor_power(regs, false);
+
+ /* allocate memory for scans */
+ total_size = pixels * channels * lines * (session.params.depth / 8); /* colors * bytes_per_color * scan lines */
+
+ std::vector<uint8_t> first_line(total_size);
+ std::vector<uint8_t> second_line(total_size);
+
+ /* init gain */
+ dev->frontend.set_gain(0, 0);
+ dev->frontend.set_gain(1, 0);
+ dev->frontend.set_gain(2, 0);
+
+ /* scan with no move */
+ bottom = 10;
+ dev->frontend.set_offset(0, bottom);
+ dev->frontend.set_offset(1, bottom);
+ dev->frontend.set_offset(2, bottom);
+
+ set_fe(dev, sensor, AFE_SET);
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting first line reading\n", __func__);
+ begin_scan(dev, sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("offset_calibration");
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size);
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[30];
+ std::snprintf(fn, 30, "gl847_offset%03d.pnm", bottom);
+ sanei_genesys_write_pnm_file(fn, first_line.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ bottomavg = dark_average (first_line.data(), pixels, lines, channels, black_pixels);
+ DBG(DBG_io2, "%s: bottom avg=%d\n", __func__, bottomavg);
+
+ /* now top value */
+ top = 255;
+ dev->frontend.set_offset(0, top);
+ dev->frontend.set_offset(1, top);
+ dev->frontend.set_offset(2, top);
+ set_fe(dev, sensor, AFE_SET);
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting second line reading\n", __func__);
+ begin_scan(dev, sensor, &regs, true);
+ sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size);
+
+ topavg = dark_average(second_line.data(), pixels, lines, channels, black_pixels);
+ DBG(DBG_io2, "%s: top avg=%d\n", __func__, topavg);
+
+ /* loop until acceptable level */
+ while ((pass < 32) && (top - bottom > 1))
+ {
+ pass++;
+
+ /* settings for new scan */
+ dev->frontend.set_offset(0, (top + bottom) / 2);
+ dev->frontend.set_offset(1, (top + bottom) / 2);
+ dev->frontend.set_offset(2, (top + bottom) / 2);
+
+ // scan with no move
+ set_fe(dev, sensor, AFE_SET);
+ dev->interface->write_registers(regs);
+ DBG(DBG_info, "%s: starting second line reading\n", __func__);
+ begin_scan(dev, sensor, &regs, true);
+ sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size);
+
+ if (DBG_LEVEL >= DBG_data)
+ {
+ char fn[30];
+ std::snprintf(fn, 30, "gl847_offset%03d.pnm", dev->frontend.get_offset(1));
+ sanei_genesys_write_pnm_file(fn, second_line.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ avg = dark_average(second_line.data(), pixels, lines, channels, black_pixels);
+ DBG(DBG_info, "%s: avg=%d offset=%d\n", __func__, avg, dev->frontend.get_offset(1));
+
+ /* compute new boundaries */
+ if (topavg == avg)
+ {
+ topavg = avg;
+ top = dev->frontend.get_offset(1);
+ }
+ else
+ {
+ bottomavg = avg;
+ bottom = dev->frontend.get_offset(1);
+ }
+ }
+ DBG(DBG_info, "%s: offset=(%d,%d,%d)\n", __func__,
+ dev->frontend.get_offset(0),
+ dev->frontend.get_offset(1),
+ dev->frontend.get_offset(2));
+}
+
+void CommandSetGl847::coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const
+{
+ DBG_HELPER_ARGS(dbg, "dpi = %d", dpi);
+ int pixels;
+ int total_size;
+ int i, j, channels;
+ int max[3];
+ float gain[3],coeff;
+ int val, code, lines;
+
+ // no gain nor offset for AKM AFE
+ uint8_t reg04 = dev->interface->read_register(REG_0x04);
+ if ((reg04 & REG_0x04_FESET) == 0x02) {
+ return;
+ }
+
+ /* coarse gain calibration is always done in color mode */
+ channels = 3;
+
+ /* follow CKSEL */
+ if(dev->settings.xres<sensor.optical_res)
+ {
+ coeff = 0.9f;
+ }
+ else
+ {
+ coeff=1.0;
+ }
+ lines=10;
+ pixels = (sensor.sensor_pixels * sensor.optical_res) / sensor.optical_res;
+
+ ScanSession session;
+ session.params.xres = sensor.optical_res;
+ session.params.yres = sensor.optical_res;
+ session.params.startx = 0;
+ session.params.starty = 0;
+ session.params.pixels = pixels;
+ session.params.lines = lines;
+ session.params.depth = 8;
+ session.params.channels = channels;
+ session.params.scan_method = dev->settings.scan_method;
+ session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
+ session.params.color_filter = dev->settings.color_filter;
+ session.params.flags = ScanFlag::DISABLE_SHADING |
+ ScanFlag::DISABLE_GAMMA |
+ ScanFlag::SINGLE_LINE |
+ ScanFlag::IGNORE_LINE_DISTANCE;
+ compute_session(dev, session, sensor);
+
+ try {
+ init_regs_for_scan_session(dev, sensor, &regs, session);
+ } catch (...) {
+ catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); });
+ throw;
+ }
+
+ sanei_genesys_set_motor_power(regs, false);
+
+ dev->interface->write_registers(regs);
+
+ total_size = pixels * channels * (16 / session.params.depth) * lines;
+
+ std::vector<uint8_t> line(total_size);
+
+ set_fe(dev, sensor, AFE_SET);
+ begin_scan(dev, sensor, &regs, true);
+
+ if (is_testing_mode()) {
+ dev->interface->test_checkpoint("coarse_gain_calibration");
+ scanner_stop_action(*dev);
+ move_back_home(dev, true);
+ return;
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, line.data(), total_size);
+
+ if (DBG_LEVEL >= DBG_data) {
+ sanei_genesys_write_pnm_file("gl847_gain.pnm", line.data(), session.params.depth,
+ channels, pixels, lines);
+ }
+
+ /* average value on each channel */
+ for (j = 0; j < channels; j++)
+ {
+ max[j] = 0;
+ for (i = pixels/4; i < (pixels*3/4); i++)
+ {
+ if (dev->model->is_cis) {
+ val = line[i + j * pixels];
+ } else {
+ val = line[i * channels + j];
+ }
+
+ max[j] += val;
+ }
+ max[j] = max[j] / (pixels/2);
+
+ gain[j] = (static_cast<float>(sensor.gain_white_ref) * coeff) / max[j];
+
+ /* turn logical gain value into gain code, checking for overflow */
+ code = static_cast<int>(283 - 208 / gain[j]);
+ if (code > 255)
+ code = 255;
+ else if (code < 0)
+ code = 0;
+ dev->frontend.set_gain(j, code);
+
+ DBG(DBG_proc, "%s: channel %d, max=%d, gain = %f, setting:%d\n", __func__, j, max[j], gain[j],
+ dev->frontend.get_gain(j));
+ }
+
+ if (dev->model->is_cis) {
+ uint8_t gain0 = dev->frontend.get_gain(0);
+ if (gain0 > dev->frontend.get_gain(1)) {
+ gain0 = dev->frontend.get_gain(1);
+ }
+ if (gain0 > dev->frontend.get_gain(2)) {
+ gain0 = dev->frontend.get_gain(2);
+ }
+ dev->frontend.set_gain(0, gain0);
+ dev->frontend.set_gain(1, gain0);
+ dev->frontend.set_gain(2, gain0);
+ }
+
+ if (channels == 1) {
+ dev->frontend.set_gain(0, dev->frontend.get_gain(1));
+ dev->frontend.set_gain(2, dev->frontend.get_gain(1));
+ }
+
+ scanner_stop_action(*dev);
+
+ move_back_home(dev, true);
+}
+
+bool CommandSetGl847::needs_home_before_init_regs_for_scan(Genesys_Device* dev) const
+{
+ (void) dev;
+ return false;
+}
+
+void CommandSetGl847::init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, int* channels,
+ int* total_size) const
+{
+ (void) dev;
+ (void) sensor;
+ (void) regs;
+ (void) channels;
+ (void) total_size;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl847::send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const
+{
+ sanei_genesys_send_gamma_table(dev, sensor);
+}
+
+void CommandSetGl847::wait_for_motor_stop(Genesys_Device* dev) const
+{
+ (void) dev;
+}
+
+void CommandSetGl847::load_document(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl847::detect_document_end(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl847::eject_document(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+void CommandSetGl847::move_to_ta(Genesys_Device* dev) const
+{
+ (void) dev;
+ throw SaneException("not implemented");
+}
+
+std::unique_ptr<CommandSet> create_gl847_cmd_set()
+{
+ return std::unique_ptr<CommandSet>(new CommandSetGl847{});
+}
+
+} // namespace gl847
+} // namespace genesys
diff --git a/backend/genesys/gl847.h b/backend/genesys/gl847.h
new file mode 100644
index 0000000..a51c293
--- /dev/null
+++ b/backend/genesys/gl847.h
@@ -0,0 +1,206 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr>
+
+ 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_GL847_H
+#define BACKEND_GENESYS_GL847_H
+
+#include "genesys.h"
+#include "command_set.h"
+
+namespace genesys {
+namespace gl847 {
+
+typedef struct
+{
+ GpioId gpio_id;
+ uint8_t r6b;
+ uint8_t r6c;
+ uint8_t r6d;
+ uint8_t r6e;
+ uint8_t r6f;
+ uint8_t ra6;
+ uint8_t ra7;
+ uint8_t ra8;
+ uint8_t ra9;
+} Gpio_Profile;
+
+static Gpio_Profile gpios[]={
+ { GpioId::CANON_LIDE_200, 0x02, 0xf9, 0x20, 0xff, 0x00, 0x04, 0x04, 0x00, 0x00},
+ { GpioId::CANON_LIDE_700F, 0x06, 0xdb, 0xff, 0xff, 0x80, 0x15, 0x07, 0x20, 0x10},
+ { GpioId::UNKNOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+};
+
+typedef struct
+{
+ uint8_t dramsel;
+ uint8_t rd0;
+ uint8_t rd1;
+ uint8_t rd2;
+ uint8_t re0;
+ uint8_t re1;
+ uint8_t re2;
+ uint8_t re3;
+ uint8_t re4;
+ uint8_t re5;
+ uint8_t re6;
+ uint8_t re7;
+} Memory_layout;
+
+static Memory_layout layouts[]={
+ /* LIDE 100 */
+ {
+ 0x29,
+ 0x0a, 0x15, 0x20,
+ 0x00, 0xac, 0x02, 0x55, 0x02, 0x56, 0x03, 0xff
+ },
+ /* LIDE 200 */
+ {
+ 0x29,
+ 0x0a, 0x1f, 0x34,
+ 0x01, 0x24, 0x02, 0x91, 0x02, 0x92, 0x03, 0xff
+ },
+ /* 5600F */
+ {
+ 0x29,
+ 0x0a, 0x1f, 0x34,
+ 0x01, 0x24, 0x02, 0x91, 0x02, 0x92, 0x03, 0xff
+ },
+ /* LIDE 700F */
+ {
+ 0x2a,
+ 0x0a, 0x33, 0x5c,
+ 0x02, 0x14, 0x09, 0x09, 0x09, 0x0a, 0x0f, 0xff
+ }
+};
+
+class CommandSetGl847 : public CommandSet
+{
+public:
+ ~CommandSetGl847() override = default;
+
+ bool needs_home_before_init_regs_for_scan(Genesys_Device* dev) const override;
+
+ void init(Genesys_Device* dev) const override;
+
+ void init_regs_for_warmup(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, int* channels,
+ int* total_size) const override;
+
+ void init_regs_for_coarse_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void init_regs_for_scan(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void init_regs_for_scan_session(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* reg,
+ const ScanSession& session) const override;
+
+ void set_fe(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t set) const override;
+ void set_powersaving(Genesys_Device* dev, int delay) const override;
+ void save_power(Genesys_Device* dev, bool enable) const override;
+
+ void begin_scan(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set* regs, bool start_motor) const override;
+
+ void end_scan(Genesys_Device* dev, Genesys_Register_Set* regs, bool check_stop) const override;
+
+ void send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor) const override;
+
+ void search_start_position(Genesys_Device* dev) const override;
+
+ void offset_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void coarse_gain_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, int dpi) const override;
+
+ SensorExposure led_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs) const override;
+
+ void wait_for_motor_stop(Genesys_Device* dev) const override;
+
+ void move_back_home(Genesys_Device* dev, bool wait_until_home) const override;
+
+ void update_hardware_sensors(struct Genesys_Scanner* s) const override;
+
+ bool needs_update_home_sensor_gpio() const override { return true; }
+
+ void update_home_sensor_gpio(Genesys_Device& dev) const override;
+
+ void load_document(Genesys_Device* dev) const override;
+
+ void detect_document_end(Genesys_Device* dev) const override;
+
+ void eject_document(Genesys_Device* dev) const override;
+
+ void search_strip(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ bool forward, bool black) const override;
+
+ void move_to_ta(Genesys_Device* dev) const override;
+
+ void send_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, uint8_t* data,
+ int size) const override;
+
+ ScanSession calculate_scan_session(const Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ const Genesys_Settings& settings) const override;
+
+ void asic_boot(Genesys_Device* dev, bool cold) const override;
+};
+
+enum SlopeTable
+{
+ SCAN_TABLE = 0, // table 1 at 0x4000
+ BACKTRACK_TABLE = 1, // table 2 at 0x4800
+ STOP_TABLE = 2, // table 3 at 0x5000
+ FAST_TABLE = 3, // table 4 at 0x5800
+ HOME_TABLE = 4, // table 5 at 0x6000
+};
+
+} // namespace gl847
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL847_H
diff --git a/backend/genesys/gl847_registers.h b/backend/genesys/gl847_registers.h
new file mode 100644
index 0000000..0603a6a
--- /dev/null
+++ b/backend/genesys/gl847_registers.h
@@ -0,0 +1,333 @@
+/* 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_GL847_REGISTERS_H
+#define BACKEND_GENESYS_GL847_REGISTERS_H
+
+#include <cstdint>
+
+namespace genesys {
+namespace gl847 {
+
+using RegAddr = std::uint16_t;
+using RegMask = std::uint8_t;
+using RegShift = unsigned;
+
+static constexpr RegAddr REG_0x01 = 0x01;
+static constexpr RegMask REG_0x01_CISSET = 0x80;
+static constexpr RegMask REG_0x01_DOGENB = 0x40;
+static constexpr RegMask REG_0x01_DVDSET = 0x20;
+static constexpr RegMask REG_0x01_STAGGER = 0x10;
+static constexpr RegMask REG_0x01_COMPENB = 0x08;
+static constexpr RegMask REG_0x01_TRUEGRAY = 0x04;
+static constexpr RegMask REG_0x01_SHDAREA = 0x02;
+static constexpr RegMask REG_0x01_SCAN = 0x01;
+
+static constexpr RegAddr REG_0x02 = 0x02;
+static constexpr RegMask REG_0x02_NOTHOME = 0x80;
+static constexpr RegMask REG_0x02_ACDCDIS = 0x40;
+static constexpr RegMask REG_0x02_AGOHOME = 0x20;
+static constexpr RegMask REG_0x02_MTRPWR = 0x10;
+static constexpr RegMask REG_0x02_FASTFED = 0x08;
+static constexpr RegMask REG_0x02_MTRREV = 0x04;
+static constexpr RegMask REG_0x02_HOMENEG = 0x02;
+static constexpr RegMask REG_0x02_LONGCURV = 0x01;
+
+static constexpr RegAddr REG_0x03 = 0x03;
+static constexpr RegMask REG_0x03_LAMPDOG = 0x80;
+static constexpr RegMask REG_0x03_AVEENB = 0x40;
+static constexpr RegMask REG_0x03_XPASEL = 0x20;
+static constexpr RegMask REG_0x03_LAMPPWR = 0x10;
+static constexpr RegMask REG_0x03_LAMPTIM = 0x0f;
+
+static constexpr RegAddr REG_0x04 = 0x04;
+static constexpr RegMask REG_0x04_LINEART = 0x80;
+static constexpr RegMask REG_0x04_BITSET = 0x40;
+static constexpr RegMask REG_0x04_AFEMOD = 0x30;
+static constexpr RegMask REG_0x04_FILTER = 0x0c;
+static constexpr RegMask REG_0x04_FESET = 0x03;
+static constexpr RegShift REG_0x04S_AFEMOD = 4;
+
+static constexpr RegAddr REG_0x05 = 0x05;
+static constexpr RegMask REG_0x05_DPIHW = 0xc0;
+static constexpr RegMask REG_0x05_DPIHW_600 = 0x00;
+static constexpr RegMask REG_0x05_DPIHW_1200 = 0x40;
+static constexpr RegMask REG_0x05_DPIHW_2400 = 0x80;
+static constexpr RegMask REG_0x05_DPIHW_4800 = 0xc0;
+static constexpr RegMask REG_0x05_MTLLAMP = 0x30;
+static constexpr RegMask REG_0x05_GMMENB = 0x08;
+static constexpr RegMask REG_0x05_MTLBASE = 0x03;
+
+static constexpr RegAddr REG_0x06 = 0x06;
+static constexpr RegMask REG_0x06_SCANMOD = 0xe0;
+static constexpr RegMask REG_0x06S_SCANMOD = 5;
+static constexpr RegMask REG_0x06_PWRBIT = 0x10;
+static constexpr RegMask REG_0x06_GAIN4 = 0x08;
+static constexpr RegMask REG_0x06_OPTEST = 0x07;
+
+static constexpr RegMask REG_0x07_LAMPSIM = 0x80;
+
+static constexpr RegMask REG_0x08_DRAM2X = 0x80;
+static constexpr RegMask REG_0x08_MPENB = 0x20;
+static constexpr RegMask REG_0x08_CIS_LINE = 0x10;
+static constexpr RegMask REG_0x08_IR1ENB = 0x08;
+static constexpr RegMask REG_0x08_IR2ENB = 0x04;
+static constexpr RegMask REG_0x08_ENB24M = 0x01;
+
+static constexpr RegMask REG_0x09_MCNTSET = 0xc0;
+static constexpr RegMask REG_0x09_EVEN1ST = 0x20;
+static constexpr RegMask REG_0x09_BLINE1ST = 0x10;
+static constexpr RegMask REG_0x09_BACKSCAN = 0x08;
+static constexpr RegMask REG_0x09_ENHANCE = 0x04;
+static constexpr RegMask REG_0x09_SHORTTG = 0x02;
+static constexpr RegMask REG_0x09_NWAIT = 0x01;
+
+static constexpr RegShift REG_0x09S_MCNTSET = 6;
+static constexpr RegShift REG_0x09S_CLKSET = 4;
+
+static constexpr RegMask REG_0x0A_LPWMEN = 0x10;
+
+static constexpr RegAddr REG_0x0B = 0x0b;
+static constexpr RegMask REG_0x0B_DRAMSEL = 0x07;
+static constexpr RegMask REG_0x0B_ENBDRAM = 0x08;
+static constexpr RegMask REG_0x0B_RFHDIS = 0x10;
+static constexpr RegMask REG_0x0B_CLKSET = 0xe0;
+static constexpr RegMask REG_0x0B_24MHZ = 0x00;
+static constexpr RegMask REG_0x0B_30MHZ = 0x20;
+static constexpr RegMask REG_0x0B_40MHZ = 0x40;
+static constexpr RegMask REG_0x0B_48MHZ = 0x60;
+static constexpr RegMask REG_0x0B_60MHZ = 0x80;
+
+static constexpr RegAddr REG_0x0C = 0x0c;
+static constexpr RegMask REG_0x0C_CCDLMT = 0x0f;
+
+static constexpr RegAddr REG_0x0D = 0x0d;
+static constexpr RegMask REG_0x0D_FULLSTP = 0x10;
+static constexpr RegMask REG_0x0D_SEND = 0x80;
+static constexpr RegMask REG_0x0D_CLRMCNT = 0x04;
+static constexpr RegMask REG_0x0D_CLRDOCJM = 0x02;
+static constexpr RegMask REG_0x0D_CLRLNCNT = 0x01;
+
+static constexpr RegAddr REG_0x0F = 0x0f;
+
+static constexpr RegMask REG_0x16_CTRLHI = 0x80;
+static constexpr RegMask REG_0x16_TOSHIBA = 0x40;
+static constexpr RegMask REG_0x16_TGINV = 0x20;
+static constexpr RegMask REG_0x16_CK1INV = 0x10;
+static constexpr RegMask REG_0x16_CK2INV = 0x08;
+static constexpr RegMask REG_0x16_CTRLINV = 0x04;
+static constexpr RegMask REG_0x16_CKDIS = 0x02;
+static constexpr RegMask REG_0x16_CTRLDIS = 0x01;
+
+static constexpr RegMask REG_0x17_TGMODE = 0xc0;
+static constexpr RegMask REG_0x17_TGMODE_NO_DUMMY = 0x00;
+static constexpr RegMask REG_0x17_TGMODE_REF = 0x40;
+static constexpr RegMask REG_0x17_TGMODE_XPA = 0x80;
+static constexpr RegMask REG_0x17_TGW = 0x3f;
+static constexpr RegMask REG_0x17S_TGW = 0;
+
+static constexpr RegAddr REG_0x18 = 0x18;
+static constexpr RegMask REG_0x18_CNSET = 0x80;
+static constexpr RegMask REG_0x18_DCKSEL = 0x60;
+static constexpr RegMask REG_0x18_CKTOGGLE = 0x10;
+static constexpr RegMask REG_0x18_CKDELAY = 0x0c;
+static constexpr RegMask REG_0x18_CKSEL = 0x03;
+
+static constexpr RegMask REG_0x1A_SW2SET = 0x80;
+static constexpr RegMask REG_0x1A_SW1SET = 0x40;
+static constexpr RegMask REG_0x1A_MANUAL3 = 0x02;
+static constexpr RegMask REG_0x1A_MANUAL1 = 0x01;
+static constexpr RegMask REG_0x1A_CK4INV = 0x08;
+static constexpr RegMask REG_0x1A_CK3INV = 0x04;
+static constexpr RegMask REG_0x1A_LINECLP = 0x02;
+
+static constexpr RegAddr REG_0x1C = 0x1c;
+static constexpr RegMask REG_0x1C_TGTIME = 0x07;
+
+static constexpr RegMask REG_0x1D_CK4LOW = 0x80;
+static constexpr RegMask REG_0x1D_CK3LOW = 0x40;
+static constexpr RegMask REG_0x1D_CK1LOW = 0x20;
+static constexpr RegMask REG_0x1D_TGSHLD = 0x1f;
+static constexpr RegMask REG_0x1DS_TGSHLD = 0;
+
+static constexpr RegMask REG_0x1E_WDTIME = 0xf0;
+static constexpr RegMask REG_0x1ES_WDTIME = 4;
+static constexpr RegMask REG_0x1E_LINESEL = 0x0f;
+static constexpr RegMask REG_0x1ES_LINESEL = 0;
+
+static constexpr RegAddr REG_FEDCNT = 0x1f;
+
+static constexpr RegAddr REG_0x24 = 0x1c;
+static constexpr RegAddr REG_0x40 = 0x40;
+static constexpr RegMask REG_0x40_CHKVER = 0x10;
+static constexpr RegMask REG_0x40_HISPDFLG = 0x04;
+static constexpr RegMask REG_0x40_MOTMFLG = 0x02;
+static constexpr RegMask REG_0x40_DATAENB = 0x01;
+
+static constexpr RegMask REG_0x41_PWRBIT = 0x80;
+static constexpr RegMask REG_0x41_BUFEMPTY = 0x40;
+static constexpr RegMask REG_0x41_FEEDFSH = 0x20;
+static constexpr RegMask REG_0x41_SCANFSH = 0x10;
+static constexpr RegMask REG_0x41_HOMESNR = 0x08;
+static constexpr RegMask REG_0x41_LAMPSTS = 0x04;
+static constexpr RegMask REG_0x41_FEBUSY = 0x02;
+static constexpr RegMask REG_0x41_MOTORENB = 0x01;
+
+static constexpr RegMask REG_0x58_VSMP = 0xf8;
+static constexpr RegShift REG_0x58S_VSMP = 3;
+static constexpr RegMask REG_0x58_VSMPW = 0x07;
+static constexpr RegShift REG_0x58S_VSMPW = 0;
+
+static constexpr RegMask REG_0x59_BSMP = 0xf8;
+static constexpr RegShift REG_0x59S_BSMP = 3;
+static constexpr RegMask REG_0x59_BSMPW = 0x07;
+static constexpr RegShift REG_0x59S_BSMPW = 0;
+
+static constexpr RegMask REG_0x5A_ADCLKINV = 0x80;
+static constexpr RegMask REG_0x5A_RLCSEL = 0x40;
+static constexpr RegMask REG_0x5A_CDSREF = 0x30;
+static constexpr RegShift REG_0x5AS_CDSREF = 4;
+static constexpr RegMask REG_0x5A_RLC = 0x0f;
+static constexpr RegShift REG_0x5AS_RLC = 0;
+
+static constexpr RegMask REG_0x5E_DECSEL = 0xe0;
+static constexpr RegShift REG_0x5ES_DECSEL = 5;
+static constexpr RegMask REG_0x5E_STOPTIM = 0x1f;
+static constexpr RegShift REG_0x5ES_STOPTIM = 0;
+
+static constexpr RegAddr REG_0x60 = 0x60;
+static constexpr RegMask REG_0x60_Z1MOD = 0x1f;
+static constexpr RegAddr REG_0x61 = 0x61;
+static constexpr RegMask REG_0x61_Z1MOD = 0xff;
+static constexpr RegAddr REG_0x62 = 0x62;
+static constexpr RegMask REG_0x62_Z1MOD = 0xff;
+
+static constexpr RegAddr REG_0x63 = 0x63;
+static constexpr RegMask REG_0x63_Z2MOD = 0x1f;
+static constexpr RegAddr REG_0x64 = 0x64;
+static constexpr RegMask REG_0x64_Z2MOD = 0xff;
+static constexpr RegAddr REG_0x65 = 0x65;
+static constexpr RegMask REG_0x65_Z2MOD = 0xff;
+
+static constexpr RegShift REG_0x60S_STEPSEL = 5;
+static constexpr RegMask REG_0x60_STEPSEL = 0xe0;
+static constexpr RegMask REG_0x60_FULLSTEP = 0x00;
+static constexpr RegMask REG_0x60_HALFSTEP = 0x20;
+static constexpr RegMask REG_0x60_EIGHTHSTEP = 0x60;
+static constexpr RegMask REG_0x60_16THSTEP = 0x80;
+
+static constexpr RegShift REG_0x63S_FSTPSEL = 5;
+static constexpr RegMask REG_0x63_FSTPSEL = 0xe0;
+static constexpr RegMask REG_0x63_FULLSTEP = 0x00;
+static constexpr RegMask REG_0x63_HALFSTEP = 0x20;
+static constexpr RegMask REG_0x63_EIGHTHSTEP = 0x60;
+static constexpr RegMask REG_0x63_16THSTEP = 0x80;
+
+static constexpr RegAddr REG_0x67 = 0x67;
+static constexpr RegMask REG_0x67_MTRPWM = 0x80;
+
+static constexpr RegAddr REG_0x68 = 0x68;
+static constexpr RegMask REG_0x68_FASTPWM = 0x80;
+
+static constexpr RegAddr REG_0x6B = 0x6b;
+static constexpr RegMask REG_0x6B_MULTFILM = 0x80;
+static constexpr RegMask REG_0x6B_GPOM13 = 0x40;
+static constexpr RegMask REG_0x6B_GPOM12 = 0x20;
+static constexpr RegMask REG_0x6B_GPOM11 = 0x10;
+static constexpr RegMask REG_0x6B_GPO18 = 0x02;
+static constexpr RegMask REG_0x6B_GPO17 = 0x01;
+
+static constexpr RegShift REG_0x6C = 0x6c;
+static constexpr RegMask REG_0x6C_GPIO16 = 0x80;
+static constexpr RegMask REG_0x6C_GPIO15 = 0x40;
+static constexpr RegMask REG_0x6C_GPIO14 = 0x20;
+static constexpr RegMask REG_0x6C_GPIO13 = 0x10;
+static constexpr RegMask REG_0x6C_GPIO12 = 0x08;
+static constexpr RegMask REG_0x6C_GPIO11 = 0x04;
+static constexpr RegMask REG_0x6C_GPIO10 = 0x02;
+static constexpr RegMask REG_0x6C_GPIO9 = 0x01;
+static constexpr RegMask REG_0x6C_GPIOH = 0xff;
+static constexpr RegMask REG_0x6C_GPIOL = 0xff;
+
+static constexpr RegAddr REG_0x6D = 0x6d;
+static constexpr RegAddr REG_0x6E = 0x6e;
+static constexpr RegAddr REG_0x6F = 0x6f;
+static constexpr RegAddr REG_0x7E = 0x7e;
+
+static constexpr RegMask REG_0x87_LEDADD = 0x04;
+
+static constexpr RegAddr REG_0x9E = 0x9e;
+static constexpr RegAddr REG_0x9F = 0x9f;
+
+static constexpr RegAddr REG_0xA6 = 0xa6;
+static constexpr RegAddr REG_0xA7 = 0xa7;
+static constexpr RegAddr REG_0xA8 = 0xa8;
+static constexpr RegAddr REG_0xA9 = 0xa9;
+static constexpr RegAddr REG_0xAB = 0xab;
+
+static constexpr RegAddr REG_EXPR = 0x10;
+static constexpr RegAddr REG_EXPG = 0x12;
+static constexpr RegAddr REG_EXPB = 0x14;
+static constexpr RegAddr REG_EXPDMY = 0x19;
+static constexpr RegAddr REG_STEPNO = 0x21;
+static constexpr RegAddr REG_FWDSTEP = 0x22;
+static constexpr RegAddr REG_BWDSTEP = 0x23;
+static constexpr RegAddr REG_FASTNO = 0x24;
+static constexpr RegAddr REG_DPISET = 0x2c;
+static constexpr RegAddr REG_STRPIXEL = 0x30;
+static constexpr RegAddr REG_ENDPIXEL = 0x32;
+static constexpr RegAddr REG_LINCNT = 0x25;
+static constexpr RegAddr REG_MAXWD = 0x35;
+static constexpr RegAddr REG_LPERIOD = 0x38;
+static constexpr RegAddr REG_FEEDL = 0x3d;
+static constexpr RegAddr REG_FMOVDEC = 0x5f;
+static constexpr RegAddr REG_FSHDEC = 0x69;
+static constexpr RegAddr REG_FMOVNO = 0x6a;
+static constexpr RegAddr REG_CK1MAP = 0x74;
+static constexpr RegAddr REG_CK3MAP = 0x77;
+static constexpr RegAddr REG_CK4MAP = 0x7a;
+
+} // namespace gl847
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_GL847_REGISTERS_H
diff --git a/backend/genesys/image.cpp b/backend/genesys/image.cpp
new file mode 100644
index 0000000..7d386c6
--- /dev/null
+++ b/backend/genesys/image.cpp
@@ -0,0 +1,204 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+#define DEBUG_DECLARE_ONLY
+
+#include "image.h"
+
+#include <array>
+
+namespace genesys {
+
+Image::Image() = default;
+
+Image::Image(std::size_t width, std::size_t height, PixelFormat format) :
+ width_{width},
+ height_{height},
+ format_{format},
+ row_bytes_{get_pixel_row_bytes(format_, width_)}
+{
+ data_.resize(get_row_bytes() * height);
+}
+
+std::uint8_t* Image::get_row_ptr(std::size_t y)
+{
+ return data_.data() + row_bytes_ * y;
+}
+
+const std::uint8_t* Image::get_row_ptr(std::size_t y) const
+{
+ return data_.data() + row_bytes_ * y;
+}
+
+Pixel Image::get_pixel(std::size_t x, std::size_t y) const
+{
+ return get_pixel_from_row(get_row_ptr(y), x, format_);
+}
+
+void Image::set_pixel(std::size_t x, std::size_t y, const Pixel& pixel)
+{
+ set_pixel_to_row(get_row_ptr(y), x, pixel, format_);
+}
+
+RawPixel Image::get_raw_pixel(std::size_t x, std::size_t y) const
+{
+ return get_raw_pixel_from_row(get_row_ptr(y), x, format_);
+}
+
+std::uint16_t Image::get_raw_channel(std::size_t x, std::size_t y, unsigned channel) const
+{
+ return get_raw_channel_from_row(get_row_ptr(y), x, channel, format_);
+}
+
+void Image::set_raw_pixel(std::size_t x, std::size_t y, const RawPixel& pixel)
+{
+ set_raw_pixel_to_row(get_row_ptr(y), x, pixel, format_);
+}
+
+void Image::resize(std::size_t width, std::size_t height, PixelFormat format)
+{
+ width_ = width;
+ height_ = height;
+ format_ = format;
+ row_bytes_ = get_pixel_row_bytes(format_, width_);
+ data_.resize(get_row_bytes() * height);
+}
+
+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));
+ }
+}
+
+} // namespace genesys
diff --git a/backend/genesys/image.h b/backend/genesys/image.h
new file mode 100644
index 0000000..c96b1bb
--- /dev/null
+++ b/backend/genesys/image.h
@@ -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.
+
+ 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 "image_pixel.h"
+#include <vector>
+
+namespace genesys {
+
+class Image
+{
+public:
+ Image();
+ Image(std::size_t width, std::size_t height, PixelFormat format);
+
+ std::size_t get_width() const { return width_; }
+ std::size_t get_height() const { return height_; }
+ PixelFormat get_format() const { return format_; }
+ std::size_t get_row_bytes() const { return row_bytes_; }
+
+ std::uint8_t* get_row_ptr(std::size_t y);
+ const std::uint8_t* get_row_ptr(std::size_t y) const;
+
+ Pixel get_pixel(std::size_t x, std::size_t y) const;
+ void set_pixel(std::size_t x, std::size_t y, const Pixel& pixel);
+
+ RawPixel get_raw_pixel(std::size_t x, std::size_t y) const;
+ std::uint16_t get_raw_channel(std::size_t x, std::size_t y, unsigned channel) const;
+ void set_raw_pixel(std::size_t x, std::size_t y, const RawPixel& pixel);
+
+ void resize(std::size_t width, std::size_t height, PixelFormat format);
+private:
+ std::size_t width_ = 0;
+ std::size_t height_ = 0;
+ PixelFormat format_ = PixelFormat::UNKNOWN;
+ std::size_t row_bytes_ = 0;
+ std::vector<std::uint8_t> data_;
+};
+
+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);
+
+} // namespace genesys
+
+#endif // ifndef BACKEND_GENESYS_IMAGE_H
diff --git a/backend/genesys/image_buffer.cpp b/backend/genesys/image_buffer.cpp
new file mode 100644
index 0000000..07c6987
--- /dev/null
+++ b/backend/genesys/image_buffer.cpp
@@ -0,0 +1,203 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+#define DEBUG_DECLARE_ONLY
+
+#include "image_buffer.h"
+#include "image.h"
+
+namespace genesys {
+
+ImageBuffer::ImageBuffer(std::size_t size, ProducerCallback producer) :
+ producer_{producer},
+ size_{size},
+ buffer_offset_{size}
+{
+ buffer_.resize(size_);
+}
+
+bool 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 true;
+ }
+
+ // now the buffer is empty and there's more data to be read
+ bool got_data = true;
+ do {
+ buffer_offset_ = 0;
+ got_data &= producer_(size_, buffer_.data());
+
+ copy_buffer();
+ } while(out_data < out_data_end && got_data);
+
+ return got_data;
+}
+
+void FakeBufferModel::push_step(std::size_t buffer_size, std::size_t row_bytes)
+{
+ sizes_.push_back(buffer_size);
+ available_sizes_.push_back(0);
+ row_bytes_.push_back(row_bytes);
+}
+
+std::size_t FakeBufferModel::available_space() const
+{
+ if (sizes_.empty())
+ throw SaneException("Model has not been setup");
+ return sizes_.front() - available_sizes_.front();
+}
+
+void FakeBufferModel::simulate_read(std::size_t size)
+{
+ if (sizes_.empty()) {
+ throw SaneException("Model has not been setup");
+ }
+ if (available_space() < size) {
+ throw SaneException("Attempted to simulate read of too much memory");
+ }
+
+ available_sizes_.front() += size;
+
+ for (unsigned i = 1; i < sizes_.size(); ++i) {
+ auto avail_src = available_sizes_[i - 1];
+ auto avail_dst = sizes_[i] - available_sizes_[i];
+
+ auto avail = (std::min(avail_src, avail_dst) / row_bytes_[i]) * row_bytes_[i];
+ available_sizes_[i - 1] -= avail;
+ available_sizes_[i] += avail;
+ }
+ available_sizes_.back() = 0;
+}
+
+ImageBufferGenesysUsb::ImageBufferGenesysUsb(std::size_t total_size,
+ const FakeBufferModel& buffer_model,
+ ProducerCallback producer) :
+ remaining_size_{total_size},
+ buffer_model_{buffer_model},
+ producer_{producer}
+{}
+
+bool ImageBufferGenesysUsb::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 true;
+ }
+
+ // now the buffer is empty and there's more data to be read
+ do {
+ if (remaining_size_ == 0)
+ return false;
+
+ auto bytes_to_read = get_read_size();
+ buffer_offset_ = 0;
+ buffer_end_ = bytes_to_read;
+ buffer_.resize(bytes_to_read);
+
+ producer_(bytes_to_read, buffer_.data());
+
+ if (remaining_size_ < bytes_to_read) {
+ remaining_size_ = 0;
+ } else {
+ remaining_size_ -= bytes_to_read;
+ }
+
+ copy_buffer();
+ } while(out_data < out_data_end);
+ return true;
+}
+
+std::size_t ImageBufferGenesysUsb::get_read_size()
+{
+ std::size_t size = buffer_model_.available_space();
+
+ // never read an odd number. exception: last read
+ // the chip internal counter does not count half words.
+ size &= ~1;
+
+ // Some setups need the reads to be multiples of 256 bytes
+ size &= ~0xff;
+
+ if (remaining_size_ < size) {
+ size = remaining_size_;
+ /*round up to a multiple of 256 bytes */
+ size += (size & 0xff) ? 0x100 : 0x00;
+ size &= ~0xff;
+ }
+
+ buffer_model_.simulate_read(size);
+
+ return size;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/image_buffer.h b/backend/genesys/image_buffer.h
new file mode 100644
index 0000000..43c3eb7
--- /dev/null
+++ b/backend/genesys/image_buffer.h
@@ -0,0 +1,129 @@
+/* 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 "enums.h"
+#include "row_buffer.h"
+#include <algorithm>
+#include <functional>
+
+namespace genesys {
+
+// This class allows reading from row-based source in smaller or larger chunks of data
+class ImageBuffer
+{
+public:
+ using ProducerCallback = std::function<bool(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_; }
+
+ bool 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_;
+};
+
+class FakeBufferModel
+{
+public:
+ FakeBufferModel() {}
+
+ void push_step(std::size_t buffer_size, std::size_t row_bytes);
+
+ std::size_t available_space() const;
+
+ void simulate_read(std::size_t size);
+
+private:
+ std::vector<std::size_t> sizes_;
+ std::vector<std::size_t> available_sizes_;
+ std::vector<std::size_t> row_bytes_;
+};
+
+// This class is similar to ImageBuffer, but preserves historical peculiarities of buffer handling
+// in the backend to preserve exact behavior
+class ImageBufferGenesysUsb
+{
+public:
+ using ProducerCallback = std::function<void(std::size_t size, std::uint8_t* out_data)>;
+
+ ImageBufferGenesysUsb() {}
+ ImageBufferGenesysUsb(std::size_t total_size, const FakeBufferModel& buffer_model,
+ ProducerCallback producer);
+
+ std::size_t remaining_size() const { return remaining_size_; }
+
+ void set_remaining_size(std::size_t bytes) { remaining_size_ = bytes; }
+
+ std::size_t available() const { return buffer_end_ - buffer_offset_; }
+
+ bool get_data(std::size_t size, std::uint8_t* out_data);
+
+private:
+
+ std::size_t get_read_size();
+
+ std::size_t remaining_size_ = 0;
+
+ std::size_t buffer_offset_ = 0;
+ std::size_t buffer_end_ = 0;
+ std::vector<std::uint8_t> buffer_;
+
+ FakeBufferModel buffer_model_;
+
+ ProducerCallback producer_;
+};
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_IMAGE_BUFFER_H
diff --git a/backend/genesys/image_pipeline.cpp b/backend/genesys/image_pipeline.cpp
new file mode 100644
index 0000000..c01b7f4
--- /dev/null
+++ b/backend/genesys/image_pipeline.cpp
@@ -0,0 +1,839 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+#define DEBUG_DECLARE_ONLY
+
+#include "image_pipeline.h"
+#include "image.h"
+#include "low.h"
+#include <cmath>
+#include <numeric>
+
+namespace genesys {
+
+ImagePipelineNode::~ImagePipelineNode() {}
+
+std::size_t ImagePipelineNodeBytesSource::consume_remaining_bytes(std::size_t bytes)
+{
+ if (bytes > remaining_bytes_) {
+ bytes = remaining_bytes_;
+ }
+ remaining_bytes_ -= bytes;
+ return bytes;
+}
+
+bool ImagePipelineNodeCallableSource::get_next_row_data(std::uint8_t* out_data)
+{
+ bool got_data = producer_(get_row_bytes(), out_data);
+ if (!got_data)
+ eof_ = true;
+ return got_data;
+}
+
+ImagePipelineNodeBufferedCallableSource::ImagePipelineNodeBufferedCallableSource(
+ std::size_t width, std::size_t height, PixelFormat format, std::size_t input_batch_size,
+ ProducerCallback producer) :
+ width_{width},
+ height_{height},
+ format_{format},
+ buffer_{input_batch_size, producer}
+{
+ set_remaining_bytes(height_ * get_row_bytes());
+}
+
+bool ImagePipelineNodeBufferedCallableSource::get_next_row_data(std::uint8_t* out_data)
+{
+ if (curr_row_ >= get_height()) {
+ DBG(DBG_warn, "%s: reading out of bounds. Row %zu, height: %zu\n", __func__,
+ curr_row_, get_height());
+ eof_ = true;
+ return false;
+ }
+
+ bool got_data = true;
+
+ auto row_bytes = get_row_bytes();
+ auto bytes_to_ask = consume_remaining_bytes(row_bytes);
+ if (bytes_to_ask < row_bytes) {
+ got_data = false;
+ }
+
+ got_data &= buffer_.get_data(bytes_to_ask, out_data);
+ curr_row_++;
+ if (!got_data) {
+ eof_ = true;
+ }
+ return got_data;
+}
+
+
+ImagePipelineNodeBufferedGenesysUsb::ImagePipelineNodeBufferedGenesysUsb(
+ std::size_t width, std::size_t height, PixelFormat format, std::size_t total_size,
+ const FakeBufferModel& buffer_model, ProducerCallback producer) :
+ width_{width},
+ height_{height},
+ format_{format},
+ buffer_{total_size, buffer_model, producer}
+{
+ set_remaining_bytes(total_size);
+}
+
+bool ImagePipelineNodeBufferedGenesysUsb::get_next_row_data(std::uint8_t* out_data)
+{
+ if (remaining_bytes() != buffer_.remaining_size() + buffer_.available()) {
+ buffer_.set_remaining_size(remaining_bytes() - buffer_.available());
+ }
+ bool got_data = true;
+
+ std::size_t row_bytes = get_row_bytes();
+ std::size_t ask_bytes = consume_remaining_bytes(row_bytes);
+ if (ask_bytes < row_bytes) {
+ got_data = false;
+ }
+ got_data &= buffer_.get_data(ask_bytes, out_data);
+ if (!got_data) {
+ eof_ = true;
+ }
+ return got_data;
+}
+
+ImagePipelineNodeArraySource::ImagePipelineNodeArraySource(std::size_t width, std::size_t height,
+ PixelFormat format,
+ std::vector<std::uint8_t> data) :
+ width_{width},
+ height_{height},
+ format_{format},
+ data_{std::move(data)},
+ next_row_{0}
+{
+ auto size = get_row_bytes() * height_;
+ if (data_.size() < size) {
+ throw SaneException("The given array is too small (%zu bytes). Need at least %zu",
+ data_.size(), size);
+ }
+ set_remaining_bytes(size);
+}
+
+bool ImagePipelineNodeArraySource::get_next_row_data(std::uint8_t* out_data)
+{
+ if (next_row_ >= height_) {
+ eof_ = true;
+ return false;
+ }
+
+ bool got_data = true;
+
+ auto row_bytes = get_row_bytes();
+ auto bytes_to_ask = consume_remaining_bytes(row_bytes);
+ if (bytes_to_ask < row_bytes) {
+ got_data = false;
+ }
+
+ std::memcpy(out_data, data_.data() + get_row_bytes() * next_row_, bytes_to_ask);
+ next_row_++;
+
+ if (!got_data) {
+ eof_ = true;
+ }
+ return got_data;
+}
+
+
+ImagePipelineNodeImageSource::ImagePipelineNodeImageSource(const Image& source) :
+ source_{source}
+{}
+
+bool ImagePipelineNodeImageSource::get_next_row_data(std::uint8_t* out_data)
+{
+ if (next_row_ >= get_height()) {
+ return false;
+ }
+ std::memcpy(out_data, source_.get_row_ptr(next_row_), get_row_bytes());
+ next_row_++;
+ return true;
+}
+
+bool ImagePipelineNodeFormatConvert::get_next_row_data(std::uint8_t* out_data)
+{
+ auto src_format = source_.get_format();
+ if (src_format == dst_format_) {
+ return source_.get_next_row_data(out_data);
+ }
+
+ buffer_.clear();
+ buffer_.resize(source_.get_row_bytes());
+ bool got_data = source_.get_next_row_data(buffer_.data());
+
+ convert_pixel_row_format(buffer_.data(), src_format, out_data, dst_format_, get_width());
+ return got_data;
+}
+
+ImagePipelineNodeDesegment::ImagePipelineNodeDesegment(ImagePipelineNode& source,
+ std::size_t output_width,
+ const std::vector<unsigned>& segment_order,
+ std::size_t segment_pixels,
+ std::size_t interleaved_lines,
+ std::size_t pixels_per_chunk) :
+ source_(source),
+ output_width_{output_width},
+ segment_order_{segment_order},
+ segment_pixels_{segment_pixels},
+ interleaved_lines_{interleaved_lines},
+ pixels_per_chunk_{pixels_per_chunk},
+ buffer_{source_.get_row_bytes()}
+{
+ DBG_HELPER_ARGS(dbg, "segment_count=%zu, segment_size=%zu, interleaved_lines=%zu, "
+ "pixels_per_shunk=%zu", segment_order.size(), segment_pixels,
+ interleaved_lines, pixels_per_chunk);
+
+ if (source_.get_height() % interleaved_lines_ > 0) {
+ throw SaneException("Height is not a multiple of the number of lines to interelave %zu/%zu",
+ source_.get_height(), interleaved_lines_);
+ }
+}
+
+ImagePipelineNodeDesegment::ImagePipelineNodeDesegment(ImagePipelineNode& source,
+ std::size_t output_width,
+ std::size_t segment_count,
+ std::size_t segment_pixels,
+ std::size_t interleaved_lines,
+ std::size_t pixels_per_chunk) :
+ source_(source),
+ output_width_{output_width},
+ segment_pixels_{segment_pixels},
+ interleaved_lines_{interleaved_lines},
+ pixels_per_chunk_{pixels_per_chunk},
+ buffer_{source_.get_row_bytes()}
+{
+ DBG_HELPER_ARGS(dbg, "segment_count=%zu, segment_size=%zu, interleaved_lines=%zu, "
+ "pixels_per_shunk=%zu", segment_count, segment_pixels, interleaved_lines,
+ pixels_per_chunk);
+
+ segment_order_.resize(segment_count);
+ std::iota(segment_order_.begin(), segment_order_.end(), 0);
+}
+
+bool ImagePipelineNodeDesegment::get_next_row_data(uint8_t* out_data)
+{
+ bool got_data = true;
+
+ buffer_.clear();
+ for (std::size_t i = 0; i < interleaved_lines_; ++i) {
+ buffer_.push_back();
+ got_data &= source_.get_next_row_data(buffer_.get_row_ptr(i));
+ }
+ if (!buffer_.is_linear()) {
+ throw SaneException("Buffer is not linear");
+ }
+
+ auto format = get_format();
+ auto segment_count = segment_order_.size();
+
+ const std::uint8_t* in_data = buffer_.get_row_ptr(0);
+
+ std::size_t groups_count = output_width_ / (segment_order_.size() * pixels_per_chunk_);
+
+ for (std::size_t igroup = 0; igroup < groups_count; ++igroup) {
+ for (std::size_t isegment = 0; isegment < segment_count; ++isegment) {
+ auto input_offset = igroup * pixels_per_chunk_;
+ input_offset += segment_pixels_ * segment_order_[isegment];
+ auto output_offset = (igroup * segment_count + isegment) * pixels_per_chunk_;
+
+ for (std::size_t ipixel = 0; ipixel < pixels_per_chunk_; ++ipixel) {
+ auto pixel = get_raw_pixel_from_row(in_data, input_offset + ipixel, format);
+ set_raw_pixel_to_row(out_data, output_offset + ipixel, pixel, format);
+ }
+ }
+ }
+ return got_data;
+}
+
+ImagePipelineNodeDeinterleaveLines::ImagePipelineNodeDeinterleaveLines(
+ ImagePipelineNode& source, std::size_t interleaved_lines, std::size_t pixels_per_chunk) :
+ ImagePipelineNodeDesegment(source, source.get_width() * interleaved_lines,
+ interleaved_lines, source.get_width(),
+ interleaved_lines, pixels_per_chunk)
+{}
+
+ImagePipelineNodeSwap16BitEndian::ImagePipelineNodeSwap16BitEndian(ImagePipelineNode& source) :
+ source_(source),
+ needs_swapping_{false}
+{
+ if (get_pixel_format_depth(source_.get_format()) == 16) {
+ needs_swapping_ = true;
+ } else {
+ DBG(DBG_info, "%s: this pipeline node does nothing for non 16-bit formats", __func__);
+ }
+}
+
+bool ImagePipelineNodeSwap16BitEndian::get_next_row_data(std::uint8_t* out_data)
+{
+ bool got_data = source_.get_next_row_data(out_data);
+ if (needs_swapping_) {
+ std::size_t pixels = get_row_bytes() / 2;
+ for (std::size_t i = 0; i < pixels; ++i) {
+ std::swap(*out_data, *(out_data + 1));
+ out_data += 2;
+ }
+ }
+ return got_data;
+}
+
+ImagePipelineNodeMergeMonoLines::ImagePipelineNodeMergeMonoLines(ImagePipelineNode& source,
+ ColorOrder color_order) :
+ source_(source),
+ buffer_(source_.get_row_bytes())
+{
+ DBG_HELPER_ARGS(dbg, "color_order %d", static_cast<unsigned>(color_order));
+
+ output_format_ = get_output_format(source_.get_format(), color_order);
+}
+
+bool ImagePipelineNodeMergeMonoLines::get_next_row_data(std::uint8_t* out_data)
+{
+ bool got_data = true;
+
+ buffer_.clear();
+ for (unsigned i = 0; i < 3; ++i) {
+ buffer_.push_back();
+ got_data &= source_.get_next_row_data(buffer_.get_row_ptr(i));
+ }
+
+ const auto* row0 = buffer_.get_row_ptr(0);
+ const auto* row1 = buffer_.get_row_ptr(1);
+ const auto* row2 = buffer_.get_row_ptr(2);
+
+ auto format = source_.get_format();
+
+ for (std::size_t x = 0, width = get_width(); x < width; ++x) {
+ std::uint16_t ch0 = get_raw_channel_from_row(row0, x, 0, format);
+ std::uint16_t ch1 = get_raw_channel_from_row(row1, x, 0, format);
+ std::uint16_t ch2 = get_raw_channel_from_row(row2, x, 0, format);
+ set_raw_channel_to_row(out_data, x, 0, ch0, output_format_);
+ set_raw_channel_to_row(out_data, x, 1, ch1, output_format_);
+ set_raw_channel_to_row(out_data, x, 2, ch2, output_format_);
+ }
+ return got_data;
+}
+
+PixelFormat ImagePipelineNodeMergeMonoLines::get_output_format(PixelFormat input_format,
+ ColorOrder order)
+{
+ switch (input_format) {
+ case PixelFormat::I1: {
+ if (order == ColorOrder::RGB) {
+ return PixelFormat::RGB111;
+ }
+ break;
+ }
+ case PixelFormat::I8: {
+ if (order == ColorOrder::RGB) {
+ return PixelFormat::RGB888;
+ }
+ if (order == ColorOrder::BGR) {
+ return PixelFormat::BGR888;
+ }
+ break;
+ }
+ case PixelFormat::I16: {
+ if (order == ColorOrder::RGB) {
+ return PixelFormat::RGB161616;
+ }
+ if (order == ColorOrder::BGR) {
+ return PixelFormat::BGR161616;
+ }
+ break;
+ }
+ default: break;
+ }
+ throw SaneException("Unsupported format combidation %d %d",
+ static_cast<unsigned>(input_format),
+ static_cast<unsigned>(order));
+}
+
+ImagePipelineNodeSplitMonoLines::ImagePipelineNodeSplitMonoLines(ImagePipelineNode& source) :
+ source_(source),
+ next_channel_{0}
+{
+ output_format_ = get_output_format(source_.get_format());
+}
+
+bool ImagePipelineNodeSplitMonoLines::get_next_row_data(std::uint8_t* out_data)
+{
+ bool got_data = true;
+
+ if (next_channel_ == 0) {
+ buffer_.resize(source_.get_row_bytes());
+ got_data &= source_.get_next_row_data(buffer_.data());
+ }
+
+ const auto* row = buffer_.data();
+ auto format = source_.get_format();
+
+ for (std::size_t x = 0, width = get_width(); x < width; ++x) {
+ std::uint16_t ch = get_raw_channel_from_row(row, x, next_channel_, format);
+ set_raw_channel_to_row(out_data, x, 0, ch, output_format_);
+ }
+ next_channel_ = (next_channel_ + 1) % 3;
+
+ return got_data;
+}
+
+PixelFormat ImagePipelineNodeSplitMonoLines::get_output_format(PixelFormat input_format)
+{
+ switch (input_format) {
+ case PixelFormat::RGB111: return PixelFormat::I1;
+ case PixelFormat::RGB888:
+ case PixelFormat::BGR888: return PixelFormat::I8;
+ case PixelFormat::RGB161616:
+ case PixelFormat::BGR161616: return PixelFormat::I16;
+ default: break;
+ }
+ throw SaneException("Unsupported input format %d", static_cast<unsigned>(input_format));
+}
+
+ImagePipelineNodeComponentShiftLines::ImagePipelineNodeComponentShiftLines(
+ ImagePipelineNode& source, unsigned shift_r, unsigned shift_g, unsigned shift_b) :
+ source_(source),
+ buffer_{source.get_row_bytes()}
+{
+ DBG_HELPER_ARGS(dbg, "shifts={%d, %d, %d}", shift_r, shift_g, shift_b);
+
+ switch (source.get_format()) {
+ case PixelFormat::RGB111:
+ case PixelFormat::RGB888:
+ case PixelFormat::RGB161616: {
+ channel_shifts_ = { shift_r, shift_g, shift_b };
+ break;
+ }
+ case PixelFormat::BGR888:
+ case PixelFormat::BGR161616: {
+ channel_shifts_ = { shift_b, shift_g, shift_r };
+ break;
+ }
+ default:
+ throw SaneException("Unsupported input format %d",
+ static_cast<unsigned>(source.get_format()));
+ }
+ extra_height_ = *std::max_element(channel_shifts_.begin(), channel_shifts_.end());
+}
+
+bool ImagePipelineNodeComponentShiftLines::get_next_row_data(std::uint8_t* out_data)
+{
+ bool got_data = true;
+
+ if (!buffer_.empty()) {
+ buffer_.pop_front();
+ }
+ while (buffer_.height() < extra_height_ + 1) {
+ buffer_.push_back();
+ got_data &= source_.get_next_row_data(buffer_.get_back_row_ptr());
+ }
+
+ auto format = get_format();
+ const auto* row0 = buffer_.get_row_ptr(channel_shifts_[0]);
+ const auto* row1 = buffer_.get_row_ptr(channel_shifts_[1]);
+ const auto* row2 = buffer_.get_row_ptr(channel_shifts_[2]);
+
+ for (std::size_t x = 0, width = get_width(); x < width; ++x) {
+ std::uint16_t ch0 = get_raw_channel_from_row(row0, x, 0, format);
+ std::uint16_t ch1 = get_raw_channel_from_row(row1, x, 1, format);
+ std::uint16_t ch2 = get_raw_channel_from_row(row2, x, 2, format);
+ set_raw_channel_to_row(out_data, x, 0, ch0, format);
+ set_raw_channel_to_row(out_data, x, 1, ch1, format);
+ set_raw_channel_to_row(out_data, x, 2, ch2, format);
+ }
+ return got_data;
+}
+
+ImagePipelineNodePixelShiftLines::ImagePipelineNodePixelShiftLines(
+ ImagePipelineNode& source, const std::vector<std::size_t>& shifts) :
+ source_(source),
+ pixel_shifts_{shifts},
+ buffer_{get_row_bytes()}
+{
+ DBG_HELPER(dbg);
+ DBG(DBG_proc, "%s: shifts={", __func__);
+ for (auto el : pixel_shifts_) {
+ DBG(DBG_proc, " %zu", el);
+ }
+ DBG(DBG_proc, " }\n");
+
+ if (pixel_shifts_.size() > MAX_SHIFTS) {
+ throw SaneException("Unsupported number of shift configurations %zu", pixel_shifts_.size());
+ }
+
+ extra_height_ = *std::max_element(pixel_shifts_.begin(), pixel_shifts_.end());
+}
+
+bool ImagePipelineNodePixelShiftLines::get_next_row_data(std::uint8_t* out_data)
+{
+ bool got_data = true;
+
+ if (!buffer_.empty()) {
+ buffer_.pop_front();
+ }
+ while (buffer_.height() < extra_height_ + 1) {
+ buffer_.push_back();
+ got_data &= source_.get_next_row_data(buffer_.get_back_row_ptr());
+ }
+
+ auto format = get_format();
+ auto shift_count = pixel_shifts_.size();
+
+ std::array<std::uint8_t*, MAX_SHIFTS> rows;
+
+ for (std::size_t irow = 0; irow < shift_count; ++irow) {
+ rows[irow] = buffer_.get_row_ptr(pixel_shifts_[irow]);
+ }
+
+ for (std::size_t x = 0, width = get_width(); x < width;) {
+ for (std::size_t irow = 0; irow < shift_count && x < width; irow++, x++) {
+ RawPixel pixel = get_raw_pixel_from_row(rows[irow], x, format);
+ set_raw_pixel_to_row(out_data, x, pixel, format);
+ }
+ }
+ return got_data;
+}
+
+ImagePipelineNodeExtract::ImagePipelineNodeExtract(ImagePipelineNode& source,
+ std::size_t offset_x, std::size_t offset_y,
+ std::size_t width, std::size_t height) :
+ source_(source),
+ offset_x_{offset_x},
+ offset_y_{offset_y},
+ width_{width},
+ height_{height}
+{
+ cached_line_.resize(source_.get_row_bytes());
+}
+
+ImagePipelineNodeExtract::~ImagePipelineNodeExtract() {}
+
+ImagePipelineNodeScaleRows::ImagePipelineNodeScaleRows(ImagePipelineNode& source,
+ std::size_t width) :
+ source_(source),
+ width_{width}
+{
+ cached_line_.resize(source_.get_row_bytes());
+}
+
+bool ImagePipelineNodeScaleRows::get_next_row_data(std::uint8_t* out_data)
+{
+ auto src_width = source_.get_width();
+ auto dst_width = width_;
+
+ bool got_data = source_.get_next_row_data(cached_line_.data());
+
+ const auto* src_data = cached_line_.data();
+ auto format = get_format();
+ auto channels = get_pixel_channels(format);
+
+ if (src_width > dst_width) {
+ // average
+ std::uint32_t counter = src_width / 2;
+ unsigned src_x = 0;
+ for (unsigned dst_x = 0; dst_x < dst_width; dst_x++) {
+ unsigned avg[3] = {0, 0, 0};
+ unsigned count = 0;
+ while (counter < src_width && src_x < src_width) {
+ counter += dst_width;
+
+ for (unsigned c = 0; c < channels; c++) {
+ avg[c] += get_raw_channel_from_row(src_data, src_x, c, format);
+ }
+
+ src_x++;
+ count++;
+ }
+ counter -= src_width;
+
+ for (unsigned c = 0; c < channels; c++) {
+ set_raw_channel_to_row(out_data, dst_x, c, avg[c] / count, format);
+ }
+ }
+ } else {
+ // interpolate and copy pixels
+ std::uint32_t counter = dst_width / 2;
+ unsigned dst_x = 0;
+
+ for (unsigned src_x = 0; src_x < src_width; src_x++) {
+ unsigned avg[3] = {0, 0, 0};
+ for (unsigned c = 0; c < channels; c++) {
+ avg[c] += get_raw_channel_from_row(src_data, src_x, c, format);
+ }
+ while ((counter < dst_width || src_x + 1 == src_width) && dst_x < dst_width) {
+ counter += src_width;
+
+ for (unsigned c = 0; c < channels; c++) {
+ set_raw_channel_to_row(out_data, dst_x, c, avg[c], format);
+ }
+ dst_x++;
+ }
+ counter -= dst_width;
+ }
+ }
+ return got_data;
+}
+
+bool ImagePipelineNodeExtract::get_next_row_data(std::uint8_t* out_data)
+{
+ bool got_data = true;
+
+ while (current_line_ < offset_y_) {
+ got_data &= source_.get_next_row_data(cached_line_.data());
+ current_line_++;
+ }
+ if (current_line_ >= offset_y_ + source_.get_height()) {
+ std::fill(out_data, out_data + get_row_bytes(), 0);
+ current_line_++;
+ return got_data;
+ }
+ // now we're sure that the following holds:
+ // offset_y_ <= current_line_ < offset_y_ + source_.get_height())
+ got_data &= source_.get_next_row_data(cached_line_.data());
+
+ auto format = get_format();
+ auto x_src_width = source_.get_width() > offset_x_ ? source_.get_width() - offset_x_ : 0;
+ x_src_width = std::min(x_src_width, width_);
+ auto x_pad_after = width_ > x_src_width ? width_ - x_src_width : 0;
+
+ if (get_pixel_format_depth(format) < 8) {
+ // we need to copy pixels one-by-one as there's no per-bit addressing
+ for (std::size_t i = 0; i < x_src_width; ++i) {
+ auto pixel = get_raw_pixel_from_row(cached_line_.data(), i + offset_x_, format);
+ set_raw_pixel_to_row(out_data, i, pixel, format);
+ }
+ for (std::size_t i = 0; i < x_pad_after; ++i) {
+ set_raw_pixel_to_row(out_data, i + x_src_width, RawPixel{}, format);
+ }
+ } else {
+ std::size_t bpp = get_pixel_format_depth(format) / 8;
+ if (x_src_width > 0) {
+ std::memcpy(out_data, cached_line_.data() + offset_x_ * bpp,
+ x_src_width * bpp);
+ }
+ if (x_pad_after > 0) {
+ std::fill(out_data + x_src_width * bpp,
+ out_data + (x_src_width + x_pad_after) * bpp, 0);
+ }
+ }
+
+ current_line_++;
+
+ return got_data;
+}
+
+ImagePipelineNodeCalibrate::ImagePipelineNodeCalibrate(ImagePipelineNode& source,
+ const std::vector<std::uint16_t>& bottom,
+ const std::vector<std::uint16_t>& top) :
+ source_{source}
+{
+ auto size = std::min(bottom.size(), top.size());
+ offset_.reserve(size);
+ multiplier_.reserve(size);
+
+ for (std::size_t i = 0; i < size; ++i) {
+ offset_.push_back(bottom[i] / 65535.0f);
+ multiplier_.push_back(65535.0f / (top[i] - bottom[i]));
+ }
+}
+
+bool ImagePipelineNodeCalibrate::get_next_row_data(std::uint8_t* out_data)
+{
+ bool ret = source_.get_next_row_data(out_data);
+
+ auto format = get_format();
+ auto depth = get_pixel_format_depth(format);
+ std::size_t max_value = 1;
+ switch (depth) {
+ case 8: max_value = 255; break;
+ case 16: max_value = 65535; break;
+ default:
+ throw SaneException("Unsupported depth for calibration %d", depth);
+ }
+ unsigned channels = get_pixel_channels(format);
+
+ std::size_t max_calib_i = offset_.size();
+ std::size_t curr_calib_i = 0;
+
+ for (std::size_t x = 0, width = get_width(); x < width && curr_calib_i < max_calib_i; ++x) {
+ for (unsigned ch = 0; ch < channels && curr_calib_i < max_calib_i; ++ch) {
+ std::int32_t value = get_raw_channel_from_row(out_data, x, ch, format);
+
+ float value_f = static_cast<float>(value) / max_value;
+ value_f = (value_f - offset_[curr_calib_i]) * multiplier_[curr_calib_i];
+ value_f = std::round(value_f * max_value);
+ value = clamp<std::int32_t>(static_cast<std::int32_t>(value_f), 0, max_value);
+ set_raw_channel_to_row(out_data, x, ch, value, format);
+
+ curr_calib_i++;
+ }
+ }
+ return ret;
+}
+
+ImagePipelineNodeDebug::ImagePipelineNodeDebug(ImagePipelineNode& source,
+ const std::string& path) :
+ source_(source),
+ path_{path},
+ buffer_{source_.get_row_bytes()}
+{}
+
+ImagePipelineNodeDebug::~ImagePipelineNodeDebug()
+{
+ catch_all_exceptions(__func__, [&]()
+ {
+ if (buffer_.empty())
+ return;
+
+ auto format = get_format();
+ buffer_.linearize();
+ sanei_genesys_write_pnm_file(path_.c_str(), buffer_.get_front_row_ptr(),
+ get_pixel_format_depth(format),
+ get_pixel_channels(format),
+ get_width(), buffer_.height());
+ });
+}
+
+bool ImagePipelineNodeDebug::get_next_row_data(std::uint8_t* out_data)
+{
+ buffer_.push_back();
+ bool got_data = source_.get_next_row_data(out_data);
+ std::memcpy(buffer_.get_back_row_ptr(), out_data, get_row_bytes());
+ return got_data;
+}
+
+std::size_t ImagePipelineStack::get_input_width() const
+{
+ ensure_node_exists();
+ return nodes_.front()->get_width();
+}
+
+std::size_t ImagePipelineStack::get_input_height() const
+{
+ ensure_node_exists();
+ return nodes_.front()->get_height();
+}
+
+PixelFormat ImagePipelineStack::get_input_format() const
+{
+ ensure_node_exists();
+ return nodes_.front()->get_format();
+}
+
+std::size_t ImagePipelineStack::get_input_row_bytes() const
+{
+ ensure_node_exists();
+ return nodes_.front()->get_row_bytes();
+}
+
+std::size_t ImagePipelineStack::get_output_width() const
+{
+ ensure_node_exists();
+ return nodes_.back()->get_width();
+}
+
+std::size_t ImagePipelineStack::get_output_height() const
+{
+ ensure_node_exists();
+ return nodes_.back()->get_height();
+}
+
+PixelFormat ImagePipelineStack::get_output_format() const
+{
+ ensure_node_exists();
+ return nodes_.back()->get_format();
+}
+
+std::size_t ImagePipelineStack::get_output_row_bytes() const
+{
+ ensure_node_exists();
+ return nodes_.back()->get_row_bytes();
+}
+
+void ImagePipelineStack::ensure_node_exists() const
+{
+ if (nodes_.empty()) {
+ throw SaneException("The pipeline does not contain any nodes");
+ }
+}
+
+void ImagePipelineStack::clear()
+{
+ // we need to destroy the nodes back to front, so that the destructors still have valid
+ // references to sources
+ for (auto it = nodes_.rbegin(); it != nodes_.rend(); ++it) {
+ it->reset();
+ }
+ nodes_.clear();
+}
+
+std::vector<std::uint8_t> ImagePipelineStack::get_all_data()
+{
+ auto row_bytes = get_output_row_bytes();
+ auto height = get_output_height();
+
+ std::vector<std::uint8_t> ret;
+ ret.resize(row_bytes * height);
+
+ for (std::size_t i = 0; i < height; ++i) {
+ get_next_row_data(ret.data() + row_bytes * i);
+ }
+ return ret;
+}
+
+Image ImagePipelineStack::get_image()
+{
+ auto height = get_output_height();
+
+ Image ret;
+ ret.resize(get_output_width(), height, get_output_format());
+
+ for (std::size_t i = 0; i < height; ++i) {
+ get_next_row_data(ret.get_row_ptr(i));
+ }
+ return ret;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/image_pipeline.h b/backend/genesys/image_pipeline.h
new file mode 100644
index 0000000..2986837
--- /dev/null
+++ b/backend/genesys/image_pipeline.h
@@ -0,0 +1,572 @@
+/* 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 "image.h"
+#include "image_pixel.h"
+#include "image_buffer.h"
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+
+namespace genesys {
+
+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 bool eof() const = 0;
+
+ // returns true if the row was filled successfully, false otherwise (e.g. if not enough data
+ // was available.
+ virtual bool get_next_row_data(std::uint8_t* out_data) = 0;
+};
+
+class ImagePipelineNodeBytesSource : public ImagePipelineNode
+{
+public:
+ std::size_t remaining_bytes() const { return remaining_bytes_; }
+ void set_remaining_bytes(std::size_t bytes) { remaining_bytes_ = bytes; }
+
+ std::size_t consume_remaining_bytes(std::size_t bytes);
+
+private:
+ std::size_t remaining_bytes_ = 0;
+};
+
+// A pipeline node that produces data from a callable
+class ImagePipelineNodeCallableSource : public ImagePipelineNode
+{
+public:
+ using ProducerCallback = std::function<bool(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_; }
+
+ bool eof() const override { return eof_; }
+
+ bool get_next_row_data(std::uint8_t* out_data) override;
+
+private:
+ ProducerCallback producer_;
+ std::size_t width_ = 0;
+ std::size_t height_ = 0;
+ PixelFormat format_ = PixelFormat::UNKNOWN;
+ bool eof_ = false;
+};
+
+// A pipeline node that produces data from a callable requesting fixed-size chunks.
+class ImagePipelineNodeBufferedCallableSource : public ImagePipelineNodeBytesSource
+{
+public:
+ using ProducerCallback = std::function<bool(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_; }
+
+ bool eof() const override { return eof_; }
+
+ bool 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;
+
+ bool eof_ = false;
+ std::size_t curr_row_ = 0;
+
+ ImageBuffer buffer_;
+};
+
+class ImagePipelineNodeBufferedGenesysUsb : public ImagePipelineNodeBytesSource
+{
+public:
+ using ProducerCallback = std::function<void(std::size_t size, std::uint8_t* out_data)>;
+
+ ImagePipelineNodeBufferedGenesysUsb(std::size_t width, std::size_t height,
+ PixelFormat format, std::size_t total_size,
+ const FakeBufferModel& buffer_model,
+ 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_; }
+
+ bool eof() const override { return eof_; }
+
+ bool get_next_row_data(std::uint8_t* out_data) override;
+
+ 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;
+
+ bool eof_ = false;
+
+ ImageBufferGenesysUsb buffer_;
+};
+
+// A pipeline node that produces data from the given array.
+class ImagePipelineNodeArraySource : public ImagePipelineNodeBytesSource
+{
+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_; }
+
+ bool eof() const override { return eof_; }
+
+ bool 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;
+
+ bool eof_ = false;
+
+ std::vector<std::uint8_t> data_;
+ std::size_t next_row_ = 0;
+};
+
+
+/// A pipeline node that produces data from the given image
+class ImagePipelineNodeImageSource : public ImagePipelineNode
+{
+public:
+ ImagePipelineNodeImageSource(const Image& source);
+
+ 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 source_.get_format(); }
+
+ bool eof() const override { return next_row_ >= get_height(); }
+
+ bool get_next_row_data(std::uint8_t* out_data) override;
+
+private:
+ const Image& source_;
+ 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_; }
+
+ bool eof() const override { return source_.eof(); }
+
+ bool 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:
+ ImagePipelineNodeDesegment(ImagePipelineNode& source,
+ std::size_t output_width,
+ const std::vector<unsigned>& segment_order,
+ std::size_t segment_pixels,
+ std::size_t interleaved_lines,
+ std::size_t pixels_per_chunk);
+
+ ImagePipelineNodeDesegment(ImagePipelineNode& source,
+ std::size_t output_width,
+ std::size_t segment_count,
+ std::size_t segment_pixels,
+ std::size_t interleaved_lines,
+ std::size_t pixels_per_chunk);
+
+ ~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(); }
+
+ bool eof() const override { return source_.eof(); }
+
+ bool 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_pixels_ = 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 that swaps bytes in 16-bit components on big-endian systems
+class ImagePipelineNodeSwap16BitEndian : public ImagePipelineNode
+{
+public:
+ ImagePipelineNodeSwap16BitEndian(ImagePipelineNode& source);
+
+ 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 source_.get_format(); }
+
+ bool eof() const override { return source_.eof(); }
+
+ bool get_next_row_data(std::uint8_t* out_data) override;
+
+private:
+ ImagePipelineNode& source_;
+ bool needs_swapping_ = false;
+};
+
+// 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_; }
+
+ bool eof() const override { return source_.eof(); }
+
+ bool 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_; }
+
+ bool eof() const override { return source_.eof(); }
+
+ bool 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(); }
+
+ bool eof() const override { return source_.eof(); }
+
+ bool 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(); }
+
+ bool eof() const override { return source_.eof(); }
+
+ bool 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(); }
+
+ bool eof() const override { return source_.eof(); }
+
+ bool 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<std::uint8_t> cached_line_;
+};
+
+// A pipeline node that scales rows to the specified width by using a point filter
+class ImagePipelineNodeScaleRows : public ImagePipelineNode
+{
+public:
+ ImagePipelineNodeScaleRows(ImagePipelineNode& source, std::size_t width);
+
+ std::size_t get_width() const override { return width_; }
+ std::size_t get_height() const override { return source_.get_height(); }
+ PixelFormat get_format() const override { return source_.get_format(); }
+
+ bool eof() const override { return source_.eof(); }
+
+ bool get_next_row_data(std::uint8_t* out_data) override;
+
+private:
+ ImagePipelineNode& source_;
+ std::size_t width_ = 0;
+
+ std::vector<std::uint8_t> cached_line_;
+};
+
+// A pipeline node that mimics the calibration behavior on Genesys chips
+class ImagePipelineNodeCalibrate : public ImagePipelineNode
+{
+public:
+
+ ImagePipelineNodeCalibrate(ImagePipelineNode& source, const std::vector<std::uint16_t>& bottom,
+ const std::vector<std::uint16_t>& top);
+
+ 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 source_.get_format(); }
+
+ bool eof() const override { return source_.eof(); }
+
+ bool get_next_row_data(std::uint8_t* out_data) override;
+
+private:
+ ImagePipelineNode& source_;
+
+ std::vector<float> offset_;
+ std::vector<float> multiplier_;
+};
+
+class ImagePipelineNodeDebug : public ImagePipelineNode
+{
+public:
+ ImagePipelineNodeDebug(ImagePipelineNode& source, const std::string& path);
+ ~ImagePipelineNodeDebug() override;
+
+ 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 source_.get_format(); }
+
+ bool eof() const override { return source_.eof(); }
+
+ bool get_next_row_data(std::uint8_t* out_data) override;
+
+private:
+ ImagePipelineNode& source_;
+ std::string path_;
+ RowBuffer buffer_;
+};
+
+class ImagePipelineStack
+{
+public:
+ ImagePipelineStack() {}
+ ~ImagePipelineStack() { clear(); }
+
+ 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;
+
+ ImagePipelineNode& front() { return *(nodes_.front().get()); }
+
+ bool eof() const { return nodes_.back()->eof(); }
+
+ void 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)...)));
+ }
+
+ bool get_next_row_data(std::uint8_t* out_data)
+ {
+ return nodes_.back()->get_next_row_data(out_data);
+ }
+
+ std::vector<std::uint8_t> get_all_data();
+
+ Image get_image();
+
+private:
+ void ensure_node_exists() const;
+
+ std::vector<std::unique_ptr<ImagePipelineNode>> nodes_;
+};
+
+} // namespace genesys
+
+#endif // ifndef BACKEND_GENESYS_IMAGE_PIPELINE_H
diff --git a/backend/genesys/image_pixel.cpp b/backend/genesys/image_pixel.cpp
new file mode 100644
index 0000000..1b83e12
--- /dev/null
+++ b/backend/genesys/image_pixel.cpp
@@ -0,0 +1,509 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+#define DEBUG_DECLARE_ONLY
+
+#include "image.h"
+
+#include <array>
+
+namespace genesys {
+
+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.59f;
+ val += (pixel.b >> 8) * 0.11f;
+ data[x] = static_cast<std::uint16_t>(val);
+ return;
+ }
+ case PixelFormat::I16: {
+ x *= 2;
+ float val = pixel.r * 0.3f;
+ val += pixel.g * 0.59f;
+ val += pixel.b * 0.11f;
+ auto val16 = static_cast<std::uint16_t>(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);
+
+} // namespace genesys
diff --git a/backend/genesys/image_pixel.h b/backend/genesys/image_pixel.h
new file mode 100644
index 0000000..2dda271
--- /dev/null
+++ b/backend/genesys/image_pixel.h
@@ -0,0 +1,144 @@
+/* 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_PIXEL_H
+#define BACKEND_GENESYS_IMAGE_PIXEL_H
+
+#include "enums.h"
+#include <algorithm>
+#include <cstdint>
+#include <cstddef>
+
+namespace genesys {
+
+enum class PixelFormat
+{
+ UNKNOWN,
+ I1,
+ RGB111,
+ I8,
+ RGB888,
+ BGR888,
+ I16,
+ RGB161616,
+ BGR161616,
+};
+
+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));
+ }
+};
+
+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);
+
+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);
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_IMAGE_PIXEL_H
diff --git a/backend/genesys/low.cpp b/backend/genesys/low.cpp
new file mode 100644
index 0000000..7937fcc
--- /dev/null
+++ b/backend/genesys/low.cpp
@@ -0,0 +1,1994 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2010-2013 Stéphane Voltz <stef.dev@free.fr>
+
+
+ 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 "low.h"
+#include "assert.h"
+#include "test_settings.h"
+
+#include "gl124_registers.h"
+#include "gl646_registers.h"
+#include "gl841_registers.h"
+#include "gl843_registers.h"
+#include "gl846_registers.h"
+#include "gl847_registers.h"
+#include "gl646_registers.h"
+
+#include <cstdio>
+#include <cmath>
+#include <vector>
+
+/* ------------------------------------------------------------------------ */
+/* functions calling ASIC specific functions */
+/* ------------------------------------------------------------------------ */
+
+namespace genesys {
+
+/**
+ * setup the hardware dependent functions
+ */
+
+namespace gl124 { std::unique_ptr<CommandSet> create_gl124_cmd_set(); }
+namespace gl646 { std::unique_ptr<CommandSet> create_gl646_cmd_set(); }
+namespace gl841 { std::unique_ptr<CommandSet> create_gl841_cmd_set(); }
+namespace gl843 { std::unique_ptr<CommandSet> create_gl843_cmd_set(); }
+namespace gl846 { std::unique_ptr<CommandSet> create_gl846_cmd_set(); }
+namespace gl847 { std::unique_ptr<CommandSet> create_gl847_cmd_set(); }
+
+void sanei_genesys_init_cmd_set(Genesys_Device* dev)
+{
+ DBG_INIT ();
+ DBG_HELPER(dbg);
+ switch (dev->model->asic_type) {
+ case AsicType::GL646: dev->cmd_set = gl646::create_gl646_cmd_set(); break;
+ case AsicType::GL841: dev->cmd_set = gl841::create_gl841_cmd_set(); break;
+ case AsicType::GL843: dev->cmd_set = gl843::create_gl843_cmd_set(); break;
+ case AsicType::GL845: // since only a few reg bits differs we handle both together
+ case AsicType::GL846: dev->cmd_set = gl846::create_gl846_cmd_set(); break;
+ case AsicType::GL847: dev->cmd_set = gl847::create_gl847_cmd_set(); break;
+ case AsicType::GL124: dev->cmd_set = gl124::create_gl124_cmd_set(); break;
+ default: throw SaneException(SANE_STATUS_INVAL, "unknown ASIC type");
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/* General IO and debugging functions */
+/* ------------------------------------------------------------------------ */
+
+void sanei_genesys_write_file(const char* filename, const std::uint8_t* data, std::size_t length)
+{
+ DBG_HELPER(dbg);
+ std::FILE* out = std::fopen(filename, "w");
+ if (!out) {
+ throw SaneException("could not open %s for writing: %s", filename, strerror(errno));
+ }
+ std::fwrite(data, 1, length, out);
+ std::fclose(out);
+}
+
+// Write data to a pnm file (e.g. calibration). For debugging only
+// data is RGB or grey, with little endian byte order
+void sanei_genesys_write_pnm_file(const char* filename, const std::uint8_t* data, int depth,
+ int channels, int pixels_per_line, int lines)
+{
+ DBG_HELPER_ARGS(dbg, "depth=%d, channels=%d, ppl=%d, lines=%d", depth, channels,
+ pixels_per_line, lines);
+ int count;
+
+ std::FILE* out = std::fopen(filename, "w");
+ if (!out)
+ {
+ throw SaneException("could not open %s for writing: %s\n", filename, strerror(errno));
+ }
+ if(depth==1)
+ {
+ fprintf (out, "P4\n%d\n%d\n", pixels_per_line, lines);
+ }
+ else
+ {
+ std::fprintf(out, "P%c\n%d\n%d\n%d\n", channels == 1 ? '5' : '6', pixels_per_line, lines,
+ static_cast<int>(std::pow(static_cast<double>(2),
+ static_cast<double>(depth - 1))));
+ }
+ if (channels == 3)
+ {
+ for (count = 0; count < (pixels_per_line * lines * 3); count++)
+ {
+ if (depth == 16)
+ fputc (*(data + 1), out);
+ fputc (*(data++), out);
+ if (depth == 16)
+ data++;
+ }
+ }
+ else
+ {
+ if (depth==1)
+ {
+ pixels_per_line/=8;
+ }
+ for (count = 0; count < (pixels_per_line * lines); count++)
+ {
+ switch (depth)
+ {
+ case 8:
+ fputc (*(data + count), out);
+ break;
+ case 16:
+ fputc (*(data + 1), out);
+ fputc (*(data), out);
+ data += 2;
+ break;
+ default:
+ fputc(data[count], out);
+ break;
+ }
+ }
+ }
+ std::fclose(out);
+}
+
+void sanei_genesys_write_pnm_file16(const char* filename, const uint16_t* data, unsigned channels,
+ unsigned pixels_per_line, unsigned lines)
+{
+ DBG_HELPER_ARGS(dbg, "channels=%d, ppl=%d, lines=%d", channels,
+ pixels_per_line, lines);
+
+ std::FILE* out = std::fopen(filename, "w");
+ if (!out) {
+ throw SaneException("could not open %s for writing: %s\n", filename, strerror(errno));
+ }
+ std::fprintf(out, "P%c\n%d\n%d\n%d\n", channels == 1 ? '5' : '6',
+ pixels_per_line, lines, 256 * 256 - 1);
+
+ for (unsigned count = 0; count < (pixels_per_line * lines * channels); count++) {
+ fputc(*data >> 8, out);
+ fputc(*data & 0xff, out);
+ data++;
+ }
+ std::fclose(out);
+}
+
+bool is_supported_write_pnm_file_image_format(PixelFormat format)
+{
+ switch (format) {
+ case PixelFormat::I1:
+ case PixelFormat::RGB111:
+ case PixelFormat::I8:
+ case PixelFormat::RGB888:
+ case PixelFormat::I16:
+ case PixelFormat::RGB161616:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void sanei_genesys_write_pnm_file(const char* filename, const Image& image)
+{
+ if (!is_supported_write_pnm_file_image_format(image.get_format())) {
+ throw SaneException("Unsupported format %d", static_cast<unsigned>(image.get_format()));
+ }
+
+ sanei_genesys_write_pnm_file(filename, image.get_row_ptr(0),
+ get_pixel_format_depth(image.get_format()),
+ get_pixel_channels(image.get_format()),
+ image.get_width(), image.get_height());
+}
+
+/* ------------------------------------------------------------------------ */
+/* Read and write RAM, registers and AFE */
+/* ------------------------------------------------------------------------ */
+
+unsigned sanei_genesys_get_bulk_max_size(AsicType asic_type)
+{
+ /* Genesys supports 0xFE00 maximum size in general, wheraus GL646 supports
+ 0xFFC0. We use 0xF000 because that's the packet limit in the Linux usbmon
+ USB capture stack. By default it limits packet size to b_size / 5 where
+ b_size is the size of the ring buffer. By default it's 300*1024, so the
+ packet is limited 61440 without any visibility to acquiring software.
+ */
+ if (asic_type == AsicType::GL124 ||
+ asic_type == AsicType::GL846 ||
+ asic_type == AsicType::GL847)
+ {
+ return 0xeff0;
+ }
+ return 0xf000;
+}
+
+// Set address for writing data
+void sanei_genesys_set_buffer_address(Genesys_Device* dev, uint32_t addr)
+{
+ DBG_HELPER(dbg);
+
+ if (dev->model->asic_type==AsicType::GL847 ||
+ dev->model->asic_type==AsicType::GL845 ||
+ dev->model->asic_type==AsicType::GL846 ||
+ dev->model->asic_type==AsicType::GL124)
+ {
+ DBG(DBG_warn, "%s: shouldn't be used for GL846+ ASICs\n", __func__);
+ return;
+ }
+
+ DBG(DBG_io, "%s: setting address to 0x%05x\n", __func__, addr & 0xfffffff0);
+
+ addr = addr >> 4;
+
+ dev->interface->write_register(0x2b, (addr & 0xff));
+
+ addr = addr >> 8;
+ dev->interface->write_register(0x2a, (addr & 0xff));
+}
+
+/* ------------------------------------------------------------------------ */
+/* Medium level functions */
+/* ------------------------------------------------------------------------ */
+
+Status scanner_read_status(Genesys_Device& dev)
+{
+ DBG_HELPER(dbg);
+ std::uint16_t address = 0;
+
+ switch (dev.model->asic_type) {
+ case AsicType::GL124: address = 0x101; break;
+ case AsicType::GL646:
+ case AsicType::GL841:
+ case AsicType::GL843:
+ case AsicType::GL845:
+ case AsicType::GL846:
+ case AsicType::GL847: address = 0x41; break;
+ default: throw SaneException("Unsupported asic type");
+ }
+
+ // same for all chips
+ constexpr std::uint8_t PWRBIT = 0x80;
+ constexpr std::uint8_t BUFEMPTY = 0x40;
+ constexpr std::uint8_t FEEDFSH = 0x20;
+ constexpr std::uint8_t SCANFSH = 0x10;
+ constexpr std::uint8_t HOMESNR = 0x08;
+ constexpr std::uint8_t LAMPSTS = 0x04;
+ constexpr std::uint8_t FEBUSY = 0x02;
+ constexpr std::uint8_t MOTORENB = 0x01;
+
+ auto value = dev.interface->read_register(address);
+ Status status;
+ status.is_replugged = !(value & PWRBIT);
+ status.is_buffer_empty = value & BUFEMPTY;
+ status.is_feeding_finished = value & FEEDFSH;
+ status.is_scanning_finished = value & SCANFSH;
+ status.is_at_home = value & HOMESNR;
+ status.is_lamp_on = value & LAMPSTS;
+ status.is_front_end_busy = value & FEBUSY;
+ status.is_motor_enabled = value & MOTORENB;
+
+ if (DBG_LEVEL >= DBG_io) {
+ debug_print_status(dbg, status);
+ }
+
+ return status;
+}
+
+Status scanner_read_reliable_status(Genesys_Device& dev)
+{
+ DBG_HELPER(dbg);
+
+ scanner_read_status(dev);
+ dev.interface->sleep_ms(100);
+ return scanner_read_status(dev);
+}
+
+void scanner_read_print_status(Genesys_Device& dev)
+{
+ scanner_read_status(dev);
+}
+
+/**
+ * decodes and prints content of status register
+ * @param val value read from status register
+ */
+void debug_print_status(DebugMessageHelper& dbg, Status val)
+{
+ std::stringstream str;
+ str << val;
+ dbg.vlog(DBG_info, "status=%s\n", str.str().c_str());
+}
+
+#if 0
+/* returns pixels per line from register set */
+/*candidate for moving into chip specific files?*/
+static int
+genesys_pixels_per_line (Genesys_Register_Set * reg)
+{
+ int pixels_per_line;
+
+ pixels_per_line = reg->get8(0x32) * 256 + reg->get8(0x33);
+ pixels_per_line -= (reg->get8(0x30) * 256 + reg->get8(0x31));
+
+ return pixels_per_line;
+}
+
+/* returns dpiset from register set */
+/*candidate for moving into chip specific files?*/
+static int
+genesys_dpiset (Genesys_Register_Set * reg)
+{
+ return reg->get8(0x2c) * 256 + reg->get8(0x2d);
+}
+#endif
+
+/** read the number of valid words in scanner's RAM
+ * ie registers 42-43-44
+ */
+// candidate for moving into chip specific files?
+void sanei_genesys_read_valid_words(Genesys_Device* dev, unsigned int* words)
+{
+ DBG_HELPER(dbg);
+
+ switch (dev->model->asic_type)
+ {
+ case AsicType::GL124:
+ *words = dev->interface->read_register(0x102) & 0x03;
+ *words = *words * 256 + dev->interface->read_register(0x103);
+ *words = *words * 256 + dev->interface->read_register(0x104);
+ *words = *words * 256 + dev->interface->read_register(0x105);
+ break;
+
+ case AsicType::GL845:
+ case AsicType::GL846:
+ *words = dev->interface->read_register(0x42) & 0x02;
+ *words = *words * 256 + dev->interface->read_register(0x43);
+ *words = *words * 256 + dev->interface->read_register(0x44);
+ *words = *words * 256 + dev->interface->read_register(0x45);
+ break;
+
+ case AsicType::GL847:
+ *words = dev->interface->read_register(0x42) & 0x03;
+ *words = *words * 256 + dev->interface->read_register(0x43);
+ *words = *words * 256 + dev->interface->read_register(0x44);
+ *words = *words * 256 + dev->interface->read_register(0x45);
+ break;
+
+ default:
+ *words = dev->interface->read_register(0x44);
+ *words += dev->interface->read_register(0x43) * 256;
+ if (dev->model->asic_type == AsicType::GL646) {
+ *words += ((dev->interface->read_register(0x42) & 0x03) * 256 * 256);
+ } else {
+ *words += ((dev->interface->read_register(0x42) & 0x0f) * 256 * 256);
+ }
+ }
+
+ DBG(DBG_proc, "%s: %d words\n", __func__, *words);
+}
+
+/** read the number of lines scanned
+ * ie registers 4b-4c-4d
+ */
+void sanei_genesys_read_scancnt(Genesys_Device* dev, unsigned int* words)
+{
+ DBG_HELPER(dbg);
+
+ if (dev->model->asic_type == AsicType::GL124) {
+ *words = (dev->interface->read_register(0x10b) & 0x0f) << 16;
+ *words += (dev->interface->read_register(0x10c) << 8);
+ *words += dev->interface->read_register(0x10d);
+ }
+ else
+ {
+ *words = dev->interface->read_register(0x4d);
+ *words += dev->interface->read_register(0x4c) * 256;
+ if (dev->model->asic_type == AsicType::GL646) {
+ *words += ((dev->interface->read_register(0x4b) & 0x03) * 256 * 256);
+ } else {
+ *words += ((dev->interface->read_register(0x4b) & 0x0f) * 256 * 256);
+ }
+ }
+
+ DBG(DBG_proc, "%s: %d lines\n", __func__, *words);
+}
+
+/** @brief Check if the scanner's internal data buffer is empty
+ * @param *dev device to test for data
+ * @param *empty return value
+ * @return empty will be set to true if there is no scanned data.
+ **/
+bool sanei_genesys_is_buffer_empty(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+
+ dev->interface->sleep_ms(1);
+
+ auto status = scanner_read_status(*dev);
+
+ if (status.is_buffer_empty) {
+ /* fix timing issue on USB3 (or just may be too fast) hardware
+ * spotted by John S. Weber <jweber53@gmail.com>
+ */
+ dev->interface->sleep_ms(1);
+ DBG(DBG_io2, "%s: buffer is empty\n", __func__);
+ return true;
+ }
+
+
+ DBG(DBG_io, "%s: buffer is filled\n", __func__);
+ return false;
+}
+
+void wait_until_buffer_non_empty(Genesys_Device* dev, bool check_status_twice)
+{
+ // FIXME: reduce MAX_RETRIES once tests are updated
+ const unsigned MAX_RETRIES = 100000;
+ for (unsigned i = 0; i < MAX_RETRIES; ++i) {
+
+ if (check_status_twice) {
+ // FIXME: this only to preserve previous behavior, can be removed
+ scanner_read_status(*dev);
+ }
+
+ bool empty = sanei_genesys_is_buffer_empty(dev);
+ dev->interface->sleep_ms(10);
+ if (!empty)
+ return;
+ }
+ throw SaneException(SANE_STATUS_IO_ERROR, "failed to read data");
+}
+
+void wait_until_has_valid_words(Genesys_Device* dev)
+{
+ unsigned words = 0;
+ unsigned sleep_time_ms = 10;
+
+ for (unsigned wait_ms = 0; wait_ms < 50000; wait_ms += sleep_time_ms) {
+ sanei_genesys_read_valid_words(dev, &words);
+ if (words != 0)
+ break;
+ dev->interface->sleep_ms(sleep_time_ms);
+ }
+
+ if (words == 0) {
+ throw SaneException(SANE_STATUS_IO_ERROR, "timeout, buffer does not get filled");
+ }
+}
+
+// Read data (e.g scanned image) from scan buffer
+void sanei_genesys_read_data_from_scanner(Genesys_Device* dev, uint8_t* data, size_t size)
+{
+ DBG_HELPER_ARGS(dbg, "size = %zu bytes", size);
+
+ if (size & 1)
+ DBG(DBG_info, "WARNING %s: odd number of bytes\n", __func__);
+
+ wait_until_has_valid_words(dev);
+
+ dev->interface->bulk_read_data(0x45, data, size);
+}
+
+Image read_unshuffled_image_from_scanner(Genesys_Device* dev, const ScanSession& session,
+ std::size_t total_bytes)
+{
+ DBG_HELPER(dbg);
+
+ auto format = create_pixel_format(session.params.depth,
+ dev->model->is_cis ? 1 : session.params.channels,
+ dev->model->line_mode_color_order);
+
+ auto width = get_pixels_from_row_bytes(format, session.output_line_bytes_raw);
+ auto height = session.output_line_count * (dev->model->is_cis ? session.params.channels : 1);
+
+ Image image(width, height, format);
+
+ auto max_bytes = image.get_row_bytes() * height;
+ if (total_bytes > max_bytes) {
+ throw SaneException("Trying to read too much data %zu (max %zu)", total_bytes, max_bytes);
+ }
+ if (total_bytes != max_bytes) {
+ DBG(DBG_info, "WARNING %s: trying to read not enough data (%zu, full fill %zu\n", __func__,
+ total_bytes, max_bytes);
+ }
+
+ sanei_genesys_read_data_from_scanner(dev, image.get_row_ptr(0), total_bytes);
+
+ ImagePipelineStack pipeline;
+ pipeline.push_first_node<ImagePipelineNodeImageSource>(image);
+
+ if ((dev->model->flags & GENESYS_FLAG_16BIT_DATA_INVERTED) && session.params.depth == 16) {
+ dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>();
+ }
+
+#ifdef WORDS_BIGENDIAN
+ if (depth == 16) {
+ dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>();
+ }
+#endif
+
+ if (dev->model->is_cis && session.params.channels == 3) {
+ dev->pipeline.push_node<ImagePipelineNodeMergeMonoLines>(dev->model->line_mode_color_order);
+ }
+
+ if (dev->pipeline.get_output_format() == PixelFormat::BGR888) {
+ dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB888);
+ }
+
+ if (dev->pipeline.get_output_format() == PixelFormat::BGR161616) {
+ dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB161616);
+ }
+
+ return pipeline.get_image();
+}
+
+void sanei_genesys_read_feed_steps(Genesys_Device* dev, unsigned int* steps)
+{
+ DBG_HELPER(dbg);
+
+ if (dev->model->asic_type == AsicType::GL124) {
+ *steps = (dev->interface->read_register(0x108) & 0x1f) << 16;
+ *steps += (dev->interface->read_register(0x109) << 8);
+ *steps += dev->interface->read_register(0x10a);
+ }
+ else
+ {
+ *steps = dev->interface->read_register(0x4a);
+ *steps += dev->interface->read_register(0x49) * 256;
+ if (dev->model->asic_type == AsicType::GL646) {
+ *steps += ((dev->interface->read_register(0x48) & 0x03) * 256 * 256);
+ } else if (dev->model->asic_type == AsicType::GL841) {
+ *steps += ((dev->interface->read_register(0x48) & 0x0f) * 256 * 256);
+ } else {
+ *steps += ((dev->interface->read_register(0x48) & 0x1f) * 256 * 256);
+ }
+ }
+
+ DBG(DBG_proc, "%s: %d steps\n", __func__, *steps);
+}
+
+void sanei_genesys_set_lamp_power(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, bool set)
+{
+ static const uint8_t REG_0x03_LAMPPWR = 0x10;
+
+ if (set) {
+ regs.find_reg(0x03).value |= REG_0x03_LAMPPWR;
+
+ if (dev->model->asic_type == AsicType::GL841) {
+ regs_set_exposure(dev->model->asic_type, regs,
+ sanei_genesys_fixup_exposure(sensor.exposure));
+ regs.set8(0x19, 0x50);
+ }
+
+ if (dev->model->asic_type == AsicType::GL843) {
+ regs_set_exposure(dev->model->asic_type, regs, sensor.exposure);
+
+ // we don't actually turn on lamp on infrared scan
+ if ((dev->model->model_id == ModelId::CANON_8400F ||
+ dev->model->model_id == ModelId::CANON_8600F ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) &&
+ dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
+ {
+ regs.find_reg(0x03).value &= ~REG_0x03_LAMPPWR;
+ }
+ }
+ } else {
+ regs.find_reg(0x03).value &= ~REG_0x03_LAMPPWR;
+
+ if (dev->model->asic_type == AsicType::GL841) {
+ regs_set_exposure(dev->model->asic_type, regs, {0x0101, 0x0101, 0x0101});
+ regs.set8(0x19, 0xff);
+ }
+
+ if (dev->model->asic_type == AsicType::GL843) {
+ if (dev->model->model_id == ModelId::PANASONIC_KV_SS080 ||
+ dev->model->model_id == ModelId::HP_SCANJET_4850C ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4010 ||
+ dev->model->model_id == ModelId::HP_SCANJET_G4050)
+ {
+ // BUG: datasheet says we shouldn't set exposure to zero
+ regs_set_exposure(dev->model->asic_type, regs, {0, 0, 0});
+ }
+ }
+ }
+ regs.state.is_lamp_on = set;
+}
+
+void sanei_genesys_set_motor_power(Genesys_Register_Set& regs, bool set)
+{
+ static const uint8_t REG_0x02_MTRPWR = 0x10;
+
+ if (set) {
+ regs.find_reg(0x02).value |= REG_0x02_MTRPWR;
+ } else {
+ regs.find_reg(0x02).value &= ~REG_0x02_MTRPWR;
+ }
+ regs.state.is_motor_on = set;
+}
+
+bool should_enable_gamma(const ScanSession& session, const Genesys_Sensor& sensor)
+{
+ if ((session.params.flags & ScanFlag::DISABLE_GAMMA) != ScanFlag::NONE) {
+ return false;
+ }
+ if (sensor.gamma[0] == 1.0f || sensor.gamma[1] == 1.0f || sensor.gamma[2] == 1.0f) {
+ return false;
+ }
+ if (session.params.depth == 16)
+ return false;
+
+ return true;
+}
+
+std::vector<uint16_t> get_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ int color)
+{
+ if (!dev->gamma_override_tables[color].empty()) {
+ return dev->gamma_override_tables[color];
+ } else {
+ std::vector<uint16_t> ret;
+ sanei_genesys_create_default_gamma_table(dev, ret, sensor.gamma[color]);
+ return ret;
+ }
+}
+
+/** @brief generates gamma buffer to transfer
+ * Generates gamma table buffer to send to ASIC. Applies
+ * contrast and brightness if set.
+ * @param dev device to set up
+ * @param bits number of bits used by gamma
+ * @param max value for gamma
+ * @param size of the gamma table
+ * @param gamma allocated gamma buffer to fill
+ */
+void sanei_genesys_generate_gamma_buffer(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ int bits,
+ int max,
+ int size,
+ uint8_t* gamma)
+{
+ DBG_HELPER(dbg);
+ std::vector<uint16_t> rgamma = get_gamma_table(dev, sensor, GENESYS_RED);
+ std::vector<uint16_t> ggamma = get_gamma_table(dev, sensor, GENESYS_GREEN);
+ std::vector<uint16_t> bgamma = get_gamma_table(dev, sensor, GENESYS_BLUE);
+
+ if(dev->settings.contrast!=0 || dev->settings.brightness!=0)
+ {
+ std::vector<uint16_t> lut(65536);
+ sanei_genesys_load_lut(reinterpret_cast<unsigned char *>(lut.data()),
+ bits,
+ bits,
+ 0,
+ max,
+ dev->settings.contrast,
+ dev->settings.brightness);
+ for (int i = 0; i < size; i++)
+ {
+ uint16_t value=rgamma[i];
+ value=lut[value];
+ gamma[i * 2 + size * 0 + 0] = value & 0xff;
+ gamma[i * 2 + size * 0 + 1] = (value >> 8) & 0xff;
+
+ value=ggamma[i];
+ value=lut[value];
+ gamma[i * 2 + size * 2 + 0] = value & 0xff;
+ gamma[i * 2 + size * 2 + 1] = (value >> 8) & 0xff;
+
+ value=bgamma[i];
+ value=lut[value];
+ gamma[i * 2 + size * 4 + 0] = value & 0xff;
+ gamma[i * 2 + size * 4 + 1] = (value >> 8) & 0xff;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < size; i++)
+ {
+ uint16_t value=rgamma[i];
+ gamma[i * 2 + size * 0 + 0] = value & 0xff;
+ gamma[i * 2 + size * 0 + 1] = (value >> 8) & 0xff;
+
+ value=ggamma[i];
+ gamma[i * 2 + size * 2 + 0] = value & 0xff;
+ gamma[i * 2 + size * 2 + 1] = (value >> 8) & 0xff;
+
+ value=bgamma[i];
+ gamma[i * 2 + size * 4 + 0] = value & 0xff;
+ gamma[i * 2 + size * 4 + 1] = (value >> 8) & 0xff;
+ }
+ }
+}
+
+
+/** @brief send gamma table to scanner
+ * This function sends generic gamma table (ie ones built with
+ * provided gamma) or the user defined one if provided by
+ * fontend. Used by gl846+ ASICs
+ * @param dev device to write to
+ */
+void sanei_genesys_send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+ int size;
+ int i;
+
+ size = 256 + 1;
+
+ /* allocate temporary gamma tables: 16 bits words, 3 channels */
+ std::vector<uint8_t> gamma(size * 2 * 3, 255);
+
+ sanei_genesys_generate_gamma_buffer(dev, sensor, 16, 65535, size, gamma.data());
+
+ // loop sending gamma tables NOTE: 0x01000000 not 0x10000000
+ for (i = 0; i < 3; i++) {
+ // clear corresponding GMM_N bit
+ uint8_t val = dev->interface->read_register(0xbd);
+ val &= ~(0x01 << i);
+ dev->interface->write_register(0xbd, val);
+
+ // clear corresponding GMM_F bit
+ val = dev->interface->read_register(0xbe);
+ val &= ~(0x01 << i);
+ dev->interface->write_register(0xbe, val);
+
+ // FIXME: currently the last word of each gamma table is not initialied, so to work around
+ // unstable data, just set it to 0 which is the most likely value of uninitialized memory
+ // (proper value is probably 0xff)
+ gamma[size * 2 * i + size * 2 - 2] = 0;
+ gamma[size * 2 * i + size * 2 - 1] = 0;
+
+ /* set GMM_Z */
+ dev->interface->write_register(0xc5+2*i, gamma[size*2*i+1]);
+ dev->interface->write_register(0xc6+2*i, gamma[size*2*i]);
+
+ dev->interface->write_ahb(0x01000000 + 0x200 * i, (size-1) * 2,
+ gamma.data() + i * size * 2+2);
+ }
+}
+
+static unsigned align_int_up(unsigned num, unsigned alignment)
+{
+ unsigned mask = alignment - 1;
+ if (num & mask)
+ num = (num & ~mask) + alignment;
+ return num;
+}
+
+void compute_session_buffer_sizes(AsicType asic, ScanSession& s)
+{
+ size_t line_bytes = s.output_line_bytes;
+ size_t line_bytes_stagger = s.output_line_bytes;
+
+ if (asic != AsicType::GL646) {
+ // BUG: this is historical artifact and should be removed. Note that buffer sizes affect
+ // how often we request the scanner for data and thus change the USB traffic.
+ line_bytes_stagger =
+ multiply_by_depth_ceil(s.optical_pixels, s.params.depth) * s.params.channels;
+ }
+
+ struct BufferConfig {
+ size_t* result_size = nullptr;
+ size_t lines = 0;
+ size_t lines_mult = 0;
+ size_t max_size = 0; // does not apply if 0
+ size_t stagger_lines = 0;
+
+ BufferConfig() = default;
+ BufferConfig(std::size_t* rs, std::size_t l, std::size_t lm, std::size_t ms,
+ std::size_t sl) :
+ result_size{rs},
+ lines{l},
+ lines_mult{lm},
+ max_size{ms},
+ stagger_lines{sl}
+ {}
+ };
+
+ std::array<BufferConfig, 4> configs;
+ if (asic == AsicType::GL124 || asic == AsicType::GL843) {
+ configs = { {
+ { &s.buffer_size_read, 32, 1, 0, s.max_color_shift_lines + s.num_staggered_lines },
+ { &s.buffer_size_lines, 32, 1, 0, s.max_color_shift_lines + s.num_staggered_lines },
+ { &s.buffer_size_shrink, 16, 1, 0, 0 },
+ { &s.buffer_size_out, 8, 1, 0, 0 },
+ } };
+ } else if (asic == AsicType::GL841) {
+ size_t max_buf = sanei_genesys_get_bulk_max_size(asic);
+ configs = { {
+ { &s.buffer_size_read, 8, 2, max_buf, s.max_color_shift_lines + s.num_staggered_lines },
+ { &s.buffer_size_lines, 8, 2, max_buf, s.max_color_shift_lines + s.num_staggered_lines },
+ { &s.buffer_size_shrink, 8, 1, max_buf, 0 },
+ { &s.buffer_size_out, 8, 1, 0, 0 },
+ } };
+ } else {
+ configs = { {
+ { &s.buffer_size_read, 16, 1, 0, s.max_color_shift_lines + s.num_staggered_lines },
+ { &s.buffer_size_lines, 16, 1, 0, s.max_color_shift_lines + s.num_staggered_lines },
+ { &s.buffer_size_shrink, 8, 1, 0, 0 },
+ { &s.buffer_size_out, 8, 1, 0, 0 },
+ } };
+ }
+
+ for (BufferConfig& config : configs) {
+ size_t buf_size = line_bytes * config.lines;
+ if (config.max_size > 0 && buf_size > config.max_size) {
+ buf_size = (config.max_size / line_bytes) * line_bytes;
+ }
+ buf_size *= config.lines_mult;
+ buf_size += line_bytes_stagger * config.stagger_lines;
+ *config.result_size = buf_size;
+ }
+}
+
+void compute_session_pipeline(const Genesys_Device* dev, ScanSession& s)
+{
+ auto channels = s.params.channels;
+ auto depth = s.params.depth;
+
+ s.pipeline_needs_reorder = true;
+ if (channels != 3 && depth != 16) {
+ s.pipeline_needs_reorder = false;
+ }
+#ifndef WORDS_BIGENDIAN
+ if (channels != 3 && depth == 16) {
+ s.pipeline_needs_reorder = false;
+ }
+ if (channels == 3 && depth == 16 && !dev->model->is_cis &&
+ dev->model->line_mode_color_order == ColorOrder::RGB)
+ {
+ s.pipeline_needs_reorder = false;
+ }
+#endif
+ if (channels == 3 && depth == 8 && !dev->model->is_cis &&
+ dev->model->line_mode_color_order == ColorOrder::RGB)
+ {
+ s.pipeline_needs_reorder = false;
+ }
+ s.pipeline_needs_ccd = s.max_color_shift_lines + s.num_staggered_lines > 0;
+ s.pipeline_needs_shrink = dev->settings.requested_pixels != s.output_pixels;
+}
+
+void compute_session_pixel_offsets(const Genesys_Device* dev, ScanSession& s,
+ const Genesys_Sensor& sensor)
+{
+ unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel();
+
+ if (dev->model->asic_type == AsicType::GL646) {
+
+ // startx cannot be below dummy pixel value
+ s.pixel_startx = sensor.dummy_pixel;
+ if (has_flag(s.params.flags, ScanFlag::USE_XCORRECTION) && sensor.ccd_start_xoffset > 0) {
+ s.pixel_startx = sensor.ccd_start_xoffset;
+ }
+ s.pixel_startx += s.params.startx;
+
+ if (sensor.stagger_config.stagger_at_resolution(s.params.xres, s.params.yres) > 0) {
+ s.pixel_startx |= 1;
+ }
+
+ s.pixel_endx = s.pixel_startx + s.optical_pixels;
+
+ s.pixel_startx /= sensor.ccd_pixels_per_system_pixel() * s.ccd_size_divisor;
+ s.pixel_endx /= sensor.ccd_pixels_per_system_pixel() * s.ccd_size_divisor;
+
+ } else if (dev->model->asic_type == AsicType::GL841) {
+ s.pixel_startx = ((sensor.ccd_start_xoffset + s.params.startx) * s.optical_resolution)
+ / sensor.optical_res;
+
+ s.pixel_startx += sensor.dummy_pixel + 1;
+
+ if (s.num_staggered_lines > 0 && (s.pixel_startx & 1) == 0) {
+ s.pixel_startx++;
+ }
+
+ /* In case of SHDAREA, we need to align start on pixel average factor, startx is
+ different than 0 only when calling for function to setup for scan, where shading data
+ needs to be align.
+
+ NOTE: we can check the value of the register here, because we don't set this bit
+ anywhere except in initialization.
+ */
+ const uint8_t REG_0x01_SHDAREA = 0x02;
+ if ((dev->reg.find_reg(0x01).value & REG_0x01_SHDAREA) != 0) {
+ unsigned average_factor = s.optical_resolution / s.params.xres;
+ s.pixel_startx = align_multiple_floor(s.pixel_startx, average_factor);
+ }
+
+ s.pixel_endx = s.pixel_startx + s.optical_pixels;
+
+ } else if (dev->model->asic_type == AsicType::GL843) {
+
+ s.pixel_startx = (s.params.startx + sensor.dummy_pixel) / ccd_pixels_per_system_pixel;
+ s.pixel_endx = s.pixel_startx + s.optical_pixels / ccd_pixels_per_system_pixel;
+
+ s.pixel_startx /= s.hwdpi_divisor;
+ s.pixel_endx /= s.hwdpi_divisor;
+
+ // in case of stagger we have to start at an odd coordinate
+ bool stagger_starts_even = dev->model->model_id == ModelId::CANON_8400F;
+ if (s.num_staggered_lines > 0) {
+ if (!stagger_starts_even && (s.pixel_startx & 1) == 0) {
+ s.pixel_startx++;
+ s.pixel_endx++;
+ } else if (stagger_starts_even && (s.pixel_startx & 1) != 0) {
+ s.pixel_startx++;
+ s.pixel_endx++;
+ }
+ }
+
+ } else if (dev->model->asic_type == AsicType::GL845 ||
+ dev->model->asic_type == AsicType::GL846 ||
+ dev->model->asic_type == AsicType::GL847)
+ {
+ s.pixel_startx = s.params.startx;
+
+ if (s.num_staggered_lines > 0) {
+ s.pixel_startx |= 1;
+ }
+
+ s.pixel_startx += sensor.ccd_start_xoffset * ccd_pixels_per_system_pixel;
+ s.pixel_endx = s.pixel_startx + s.optical_pixels_raw;
+
+ s.pixel_startx /= s.hwdpi_divisor * s.segment_count * ccd_pixels_per_system_pixel;
+ s.pixel_endx /= s.hwdpi_divisor * s.segment_count * ccd_pixels_per_system_pixel;
+
+ } else if (dev->model->asic_type == AsicType::GL124) {
+ s.pixel_startx = s.params.startx;
+
+ if (s.num_staggered_lines > 0) {
+ s.pixel_startx |= 1;
+ }
+
+ s.pixel_startx /= ccd_pixels_per_system_pixel;
+ // FIXME: should we add sensor.dummy_pxel to pixel_startx at this point?
+ s.pixel_endx = s.pixel_startx + s.optical_pixels / ccd_pixels_per_system_pixel;
+
+ s.pixel_startx /= s.hwdpi_divisor * s.segment_count;
+ s.pixel_endx /= s.hwdpi_divisor * s.segment_count;
+
+ std::uint32_t segcnt = (sensor.custom_regs.get_value(gl124::REG_SEGCNT) << 16) +
+ (sensor.custom_regs.get_value(gl124::REG_SEGCNT + 1) << 8) +
+ sensor.custom_regs.get_value(gl124::REG_SEGCNT + 2);
+ if (s.pixel_endx == segcnt) {
+ s.pixel_endx = 0;
+ }
+ }
+
+ s.pixel_count_multiplier = sensor.pixel_count_multiplier;
+
+ s.pixel_startx *= sensor.pixel_count_multiplier;
+ s.pixel_endx *= sensor.pixel_count_multiplier;
+}
+
+void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Sensor& sensor)
+{
+ DBG_HELPER(dbg);
+
+ (void) dev;
+ s.params.assert_valid();
+
+ if (s.params.depth != 8 && s.params.depth != 16) {
+ throw SaneException("Unsupported depth setting %d", s.params.depth);
+ }
+
+ unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel();
+
+ // compute optical and output resolutions
+
+ if (dev->model->asic_type == AsicType::GL843) {
+ // FIXME: this may be incorrect, but need more scanners to test
+ s.hwdpi_divisor = sensor.get_hwdpi_divisor_for_dpi(s.params.xres);
+ } else {
+ s.hwdpi_divisor = sensor.get_hwdpi_divisor_for_dpi(s.params.xres * ccd_pixels_per_system_pixel);
+ }
+
+ s.ccd_size_divisor = sensor.get_ccd_size_divisor_for_dpi(s.params.xres);
+
+ if (dev->model->asic_type == AsicType::GL646) {
+ s.optical_resolution = sensor.optical_res;
+ } else {
+ s.optical_resolution = sensor.optical_res / s.ccd_size_divisor;
+ }
+ s.output_resolution = s.params.xres;
+
+ if (s.output_resolution > s.optical_resolution) {
+ throw std::runtime_error("output resolution higher than optical resolution");
+ }
+
+ // compute the number of optical pixels that will be acquired by the chip
+ s.optical_pixels = (s.params.pixels * s.optical_resolution) / s.output_resolution;
+ if (s.optical_pixels * s.output_resolution < s.params.pixels * s.optical_resolution) {
+ s.optical_pixels++;
+ }
+
+ if (dev->model->asic_type == AsicType::GL841) {
+ if (s.optical_pixels & 1)
+ s.optical_pixels++;
+ }
+
+ if (dev->model->asic_type == AsicType::GL646 && s.params.xres == 400) {
+ s.optical_pixels = (s.optical_pixels / 6) * 6;
+ }
+
+ if (dev->model->asic_type == AsicType::GL843) {
+ // ensure the number of optical pixels is divisible by 2.
+ // In quarter-CCD mode optical_pixels is 4x larger than the actual physical number
+ s.optical_pixels = align_int_up(s.optical_pixels, 2 * s.ccd_size_divisor);
+
+ if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 ||
+ dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I)
+ {
+ s.optical_pixels = align_int_up(s.optical_pixels, 16);
+ }
+ }
+
+ // after all adjustments on the optical pixels have been made, compute the number of pixels
+ // to retrieve from the chip
+ s.output_pixels = (s.optical_pixels * s.output_resolution) / s.optical_resolution;
+
+ // Note: staggering is not applied for calibration. Staggering starts at 2400 dpi
+ s.num_staggered_lines = 0;
+ if (!has_flag(s.params.flags, ScanFlag::IGNORE_LINE_DISTANCE))
+ {
+ s.num_staggered_lines = sensor.stagger_config.stagger_at_resolution(s.params.xres,
+ s.params.yres);
+ }
+
+ s.color_shift_lines_r = dev->model->ld_shift_r;
+ s.color_shift_lines_g = dev->model->ld_shift_g;
+ s.color_shift_lines_b = dev->model->ld_shift_b;
+
+ if (dev->model->motor_id == MotorId::G4050 && s.params.yres > 600) {
+ // it seems base_dpi of the G4050 motor is changed above 600 dpi
+ s.color_shift_lines_r = (s.color_shift_lines_r * 3800) / dev->motor.base_ydpi;
+ s.color_shift_lines_g = (s.color_shift_lines_g * 3800) / dev->motor.base_ydpi;
+ s.color_shift_lines_b = (s.color_shift_lines_b * 3800) / dev->motor.base_ydpi;
+ }
+
+ s.color_shift_lines_r = (s.color_shift_lines_r * s.params.yres) / dev->motor.base_ydpi;
+ s.color_shift_lines_g = (s.color_shift_lines_g * s.params.yres) / dev->motor.base_ydpi;
+ s.color_shift_lines_b = (s.color_shift_lines_b * s.params.yres) / dev->motor.base_ydpi;
+
+ s.max_color_shift_lines = 0;
+ if (s.params.channels > 1 && !has_flag(s.params.flags, ScanFlag::IGNORE_LINE_DISTANCE)) {
+ s.max_color_shift_lines = std::max(s.color_shift_lines_r, std::max(s.color_shift_lines_g,
+ s.color_shift_lines_b));
+ }
+
+ s.output_line_count = s.params.lines + s.max_color_shift_lines + s.num_staggered_lines;
+
+ s.output_channel_bytes = multiply_by_depth_ceil(s.output_pixels, s.params.depth);
+ s.output_line_bytes = s.output_channel_bytes * s.params.channels;
+
+ s.segment_count = sensor.get_segment_count();
+
+ s.optical_pixels_raw = s.optical_pixels;
+ s.output_line_bytes_raw = s.output_line_bytes;
+ s.conseq_pixel_dist = 0;
+
+ if (dev->model->asic_type == AsicType::GL845 ||
+ dev->model->asic_type == AsicType::GL846 ||
+ dev->model->asic_type == AsicType::GL847)
+ {
+ if (s.segment_count > 1) {
+ s.conseq_pixel_dist = sensor.segment_size;
+
+ // in case of multi-segments sensor, we have to add the width of the sensor crossed by
+ // the scan area
+ unsigned extra_segment_scan_area = align_multiple_ceil(s.conseq_pixel_dist, 2);
+ extra_segment_scan_area *= s.segment_count - 1;
+ extra_segment_scan_area *= s.hwdpi_divisor * s.segment_count;
+ extra_segment_scan_area *= ccd_pixels_per_system_pixel;
+
+ s.optical_pixels_raw += extra_segment_scan_area;
+ }
+
+ s.output_line_bytes_raw = multiply_by_depth_ceil(
+ (s.optical_pixels_raw * s.output_resolution) / sensor.optical_res / s.segment_count,
+ s.params.depth);
+ }
+
+ if (dev->model->asic_type == AsicType::GL841) {
+ if (dev->model->is_cis) {
+ s.output_line_bytes_raw = s.output_channel_bytes;
+ }
+ }
+
+ if (dev->model->asic_type == AsicType::GL124) {
+ if (dev->model->is_cis) {
+ s.output_line_bytes_raw = s.output_channel_bytes;
+ }
+ s.conseq_pixel_dist = s.output_pixels / s.ccd_size_divisor / s.segment_count;
+ }
+
+ if (dev->model->asic_type == AsicType::GL843) {
+ s.conseq_pixel_dist = s.output_pixels / s.segment_count;
+ }
+
+ s.output_segment_pixel_group_count = 0;
+ if (dev->model->asic_type == AsicType::GL124 ||
+ dev->model->asic_type == AsicType::GL843)
+ {
+ s.output_segment_pixel_group_count = multiply_by_depth_ceil(
+ s.output_pixels / s.ccd_size_divisor / s.segment_count, s.params.depth);
+ }
+ if (dev->model->asic_type == AsicType::GL845 ||
+ dev->model->asic_type == AsicType::GL846 ||
+ dev->model->asic_type == AsicType::GL847)
+ {
+ s.output_segment_pixel_group_count = multiply_by_depth_ceil(
+ s.optical_pixels / (s.hwdpi_divisor * s.segment_count * ccd_pixels_per_system_pixel),
+ s.params.depth);
+ }
+
+ s.output_line_bytes_requested = multiply_by_depth_ceil(
+ s.params.get_requested_pixels() * s.params.channels, s.params.depth);
+
+ s.output_total_bytes_raw = s.output_line_bytes_raw * s.output_line_count;
+ s.output_total_bytes = s.output_line_bytes * s.output_line_count;
+
+ compute_session_buffer_sizes(dev->model->asic_type, s);
+ compute_session_pipeline(dev, s);
+ compute_session_pixel_offsets(dev, s, sensor);
+
+ if (dev->model->asic_type == AsicType::GL124 ||
+ dev->model->asic_type == AsicType::GL845 ||
+ dev->model->asic_type == AsicType::GL846)
+ {
+ s.enable_ledadd = (s.params.channels == 1 && dev->model->is_cis && dev->settings.true_gray);
+ }
+
+ if (dev->model->asic_type == AsicType::GL841 ||
+ dev->model->asic_type == AsicType::GL843)
+ {
+ // no 16 bit gamma for this ASIC
+ if (s.params.depth == 16) {
+ s.params.flags |= ScanFlag::DISABLE_GAMMA;
+ }
+ }
+
+ s.computed = true;
+
+ DBG(DBG_info, "%s ", __func__);
+ debug_dump(DBG_info, s);
+}
+
+static std::size_t get_usb_buffer_read_size(AsicType asic, const ScanSession& session)
+{
+ switch (asic) {
+ case AsicType::GL646:
+ // buffer not used on this chip set
+ return 1;
+
+ case AsicType::GL124:
+ // BUG: we shouldn't multiply by channels here nor divide by ccd_size_divisor
+ return session.output_line_bytes_raw / session.ccd_size_divisor * session.params.channels;
+
+ case AsicType::GL845:
+ case AsicType::GL846:
+ case AsicType::GL847:
+ // BUG: we shouldn't multiply by channels here
+ return session.output_line_bytes_raw * session.params.channels;
+
+ case AsicType::GL843:
+ return session.output_line_bytes_raw * 2;
+
+ default:
+ throw SaneException("Unknown asic type");
+ }
+}
+
+static FakeBufferModel get_fake_usb_buffer_model(const ScanSession& session)
+{
+ FakeBufferModel model;
+ model.push_step(session.buffer_size_read, 1);
+
+ if (session.pipeline_needs_reorder) {
+ model.push_step(session.buffer_size_lines, session.output_line_bytes);
+ }
+ if (session.pipeline_needs_ccd) {
+ model.push_step(session.buffer_size_shrink, session.output_line_bytes);
+ }
+ if (session.pipeline_needs_shrink) {
+ model.push_step(session.buffer_size_out, session.output_line_bytes);
+ }
+
+ return model;
+}
+
+void build_image_pipeline(Genesys_Device* dev, const ScanSession& session)
+{
+ static unsigned s_pipeline_index = 0;
+
+ s_pipeline_index++;
+
+ auto format = create_pixel_format(session.params.depth,
+ dev->model->is_cis ? 1 : session.params.channels,
+ dev->model->line_mode_color_order);
+ auto depth = get_pixel_format_depth(format);
+ auto width = get_pixels_from_row_bytes(format, session.output_line_bytes_raw);
+
+ auto read_data_from_usb = [dev](std::size_t size, std::uint8_t* data)
+ {
+ dev->interface->bulk_read_data(0x45, data, size);
+ return true;
+ };
+
+ auto lines = session.output_line_count * (dev->model->is_cis ? session.params.channels : 1);
+
+ dev->pipeline.clear();
+
+ // FIXME: here we are complicating things for the time being to preserve the existing behaviour
+ // This allows to be sure that the changes to the image pipeline have not introduced
+ // regressions.
+
+ if (session.segment_count > 1) {
+ // BUG: we're reading one line too much
+ dev->pipeline.push_first_node<ImagePipelineNodeBufferedCallableSource>(
+ width, lines + 1, format,
+ get_usb_buffer_read_size(dev->model->asic_type, session), read_data_from_usb);
+
+ auto output_width = session.output_segment_pixel_group_count * session.segment_count;
+ dev->pipeline.push_node<ImagePipelineNodeDesegment>(output_width, dev->segment_order,
+ session.conseq_pixel_dist,
+ 1, 1);
+ } else {
+ auto read_bytes_left_after_deseg = session.output_line_bytes * session.output_line_count;
+ if (dev->model->asic_type == AsicType::GL646) {
+ read_bytes_left_after_deseg *= dev->model->is_cis ? session.params.channels : 1;
+ }
+
+ dev->pipeline.push_first_node<ImagePipelineNodeBufferedGenesysUsb>(
+ width, lines, format, read_bytes_left_after_deseg,
+ get_fake_usb_buffer_model(session), read_data_from_usb);
+ }
+
+ if (DBG_LEVEL >= DBG_io2) {
+ dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" +
+ std::to_string(s_pipeline_index) +
+ "_0_before_swap.pnm");
+ }
+
+ if ((dev->model->flags & GENESYS_FLAG_16BIT_DATA_INVERTED) && depth == 16) {
+ dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>();
+ }
+
+#ifdef WORDS_BIGENDIAN
+ if (depth == 16) {
+ dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>();
+ }
+#endif
+
+ if (DBG_LEVEL >= DBG_io2) {
+ dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" +
+ std::to_string(s_pipeline_index) +
+ "_1_after_swap.pnm");
+ }
+
+ if (dev->model->is_cis && session.params.channels == 3) {
+ dev->pipeline.push_node<ImagePipelineNodeMergeMonoLines>(dev->model->line_mode_color_order);
+ }
+
+ if (dev->pipeline.get_output_format() == PixelFormat::BGR888) {
+ dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB888);
+ }
+
+ if (dev->pipeline.get_output_format() == PixelFormat::BGR161616) {
+ dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB161616);
+ }
+
+ if (session.max_color_shift_lines > 0 && session.params.channels == 3) {
+ dev->pipeline.push_node<ImagePipelineNodeComponentShiftLines>(
+ session.color_shift_lines_r,
+ session.color_shift_lines_g,
+ session.color_shift_lines_b);
+ }
+
+ if (DBG_LEVEL >= DBG_io2) {
+ dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" +
+ std::to_string(s_pipeline_index) +
+ "_2_after_shift.pnm");
+ }
+
+ if (session.num_staggered_lines > 0) {
+ std::vector<std::size_t> shifts{0, session.num_staggered_lines};
+ dev->pipeline.push_node<ImagePipelineNodePixelShiftLines>(shifts);
+ }
+
+ if (DBG_LEVEL >= DBG_io2) {
+ dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" +
+ std::to_string(s_pipeline_index) +
+ "_3_after_stagger.pnm");
+ }
+
+ if ((dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) &&
+ !(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION))
+ {
+ dev->pipeline.push_node<ImagePipelineNodeCalibrate>(dev->dark_average_data,
+ dev->white_average_data);
+
+ if (DBG_LEVEL >= DBG_io2) {
+ dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" +
+ std::to_string(s_pipeline_index) +
+ "_4_after_calibrate.pnm");
+ }
+ }
+
+ if (session.output_pixels != session.params.get_requested_pixels()) {
+ dev->pipeline.push_node<ImagePipelineNodeScaleRows>(session.params.get_requested_pixels());
+ }
+
+ auto read_from_pipeline = [dev](std::size_t size, std::uint8_t* out_data)
+ {
+ (void) size; // will be always equal to dev->pipeline.get_output_row_bytes()
+ return dev->pipeline.get_next_row_data(out_data);
+ };
+ dev->pipeline_buffer = ImageBuffer{dev->pipeline.get_output_row_bytes(),
+ read_from_pipeline};
+}
+
+std::uint8_t compute_frontend_gain_wolfson(float value, float target_value)
+{
+ /* the flow of data through the frontend ADC is as follows (see e.g. WM8192 datasheet)
+ input
+ -> apply offset (o = i + 260mV * (DAC[7:0]-127.5)/127.5) ->
+ -> apply gain (o = i * 208/(283-PGA[7:0])
+ -> ADC
+
+ Here we have some input data that was acquired with zero gain (PGA==0).
+ We want to compute gain such that the output would approach full ADC range (controlled by
+ target_value).
+
+ We want to solve the following for {PGA}:
+
+ {value} = {input} * 208 / (283 - 0)
+ {target_value} = {input} * 208 / (283 - {PGA})
+
+ The solution is the following equation:
+
+ {PGA} = 283 * (1 - {value} / {target_value})
+ */
+ float gain = value / target_value;
+ int code = static_cast<int>(283 * (1 - gain));
+ return clamp(code, 0, 255);
+}
+
+std::uint8_t compute_frontend_gain_analog_devices(float value, float target_value)
+{
+ /* The flow of data through the frontend ADC is as follows (see e.g. AD9826 datasheet)
+ input
+ -> apply offset (o = i + 300mV * (OFFSET[8] ? 1 : -1) * (OFFSET[7:0] / 127)
+ -> apply gain (o = i * 6 / (1 + 5 * ( 63 - PGA[5:0] ) / 63 ) )
+ -> ADC
+
+ We want to solve the following for {PGA}:
+
+ {value} = {input} * 6 / (1 + 5 * ( 63 - 0) / 63 ) )
+ {target_value} = {input} * 6 / (1 + 5 * ( 63 - {PGA}) / 63 ) )
+
+ The solution is the following equation:
+
+ {PGA} = (378 / 5) * ({target_value} - {value} / {target_value})
+ */
+ int code = static_cast<int>((378.0f / 5.0f) * ((target_value - value) / target_value));
+ return clamp(code, 0, 63);
+}
+
+std::uint8_t compute_frontend_gain(float value, float target_value,
+ FrontendType frontend_type)
+{
+ if (frontend_type == FrontendType::WOLFSON) {
+ return compute_frontend_gain_wolfson(value, target_value);
+ }
+ if (frontend_type == FrontendType::ANALOG_DEVICES) {
+ return compute_frontend_gain_analog_devices(value, target_value);
+ }
+ throw SaneException("Unknown frontend to compute gain for");
+}
+
+/** @brief initialize device
+ * Initialize backend and ASIC : registers, motor tables, and gamma tables
+ * then ensure scanner's head is at home. Designed for gl846+ ASICs.
+ * Detects cold boot (ie first boot since device plugged) in this case
+ * an extensice setup up is done at hardware level.
+ *
+ * @param dev device to initialize
+ * @param max_regs umber of maximum used registers
+ */
+void sanei_genesys_asic_init(Genesys_Device* dev, bool /*max_regs*/)
+{
+ DBG_HELPER(dbg);
+
+ uint8_t val;
+ bool cold = true;
+
+ // URB 16 control 0xc0 0x0c 0x8e 0x0b len 1 read 0x00 */
+ dev->interface->get_usb_device().control_msg(REQUEST_TYPE_IN, REQUEST_REGISTER,
+ VALUE_GET_REGISTER, 0x00, 1, &val);
+
+ DBG (DBG_io2, "%s: value=0x%02x\n", __func__, val);
+ DBG (DBG_info, "%s: device is %s\n", __func__, (val & 0x08) ? "USB 1.0" : "USB2.0");
+ if (val & 0x08)
+ {
+ dev->usb_mode = 1;
+ }
+ else
+ {
+ dev->usb_mode = 2;
+ }
+
+ /* Check if the device has already been initialized and powered up. We read register 0x06 and
+ check PWRBIT, if reset scanner has been freshly powered up. This bit will be set to later
+ so that following reads can detect power down/up cycle
+ */
+ if (!is_testing_mode()) {
+ if (dev->interface->read_register(0x06) & 0x10) {
+ cold = false;
+ }
+ }
+ DBG (DBG_info, "%s: device is %s\n", __func__, cold ? "cold" : "warm");
+
+ /* don't do anything if backend is initialized and hardware hasn't been
+ * replug */
+ if (dev->already_initialized && !cold)
+ {
+ DBG (DBG_info, "%s: already initialized, nothing to do\n", __func__);
+ return;
+ }
+
+ // set up hardware and registers
+ dev->cmd_set->asic_boot(dev, cold);
+
+ /* now hardware part is OK, set up device struct */
+ dev->white_average_data.clear();
+ dev->dark_average_data.clear();
+
+ dev->settings.color_filter = ColorFilter::RED;
+
+ /* duplicate initial values into calibration registers */
+ dev->calib_reg = dev->reg;
+
+ const auto& sensor = sanei_genesys_find_sensor_any(dev);
+
+ // Set analog frontend
+ dev->cmd_set->set_fe(dev, sensor, AFE_INIT);
+
+ dev->already_initialized = true;
+
+ // Move to home if needed
+ dev->cmd_set->move_back_home(dev, true);
+ dev->set_head_pos_zero(ScanHeadId::PRIMARY);
+
+ // Set powersaving (default = 15 minutes)
+ dev->cmd_set->set_powersaving(dev, 15);
+}
+
+void scanner_start_action(Genesys_Device& dev, bool start_motor)
+{
+ DBG_HELPER(dbg);
+ switch (dev.model->asic_type) {
+ case AsicType::GL646:
+ case AsicType::GL841:
+ case AsicType::GL843:
+ case AsicType::GL845:
+ case AsicType::GL846:
+ case AsicType::GL847:
+ case AsicType::GL124:
+ break;
+ default:
+ throw SaneException("Unsupported chip");
+ }
+
+ if (start_motor) {
+ dev.interface->write_register(0x0f, 0x01);
+ } else {
+ dev.interface->write_register(0x0f, 0);
+ }
+}
+
+void sanei_genesys_set_dpihw(Genesys_Register_Set& regs, const Genesys_Sensor& sensor,
+ unsigned dpihw)
+{
+ // same across GL646, GL841, GL843, GL846, GL847, GL124
+ const uint8_t REG_0x05_DPIHW_MASK = 0xc0;
+ const uint8_t REG_0x05_DPIHW_600 = 0x00;
+ const uint8_t REG_0x05_DPIHW_1200 = 0x40;
+ const uint8_t REG_0x05_DPIHW_2400 = 0x80;
+ const uint8_t REG_0x05_DPIHW_4800 = 0xc0;
+
+ if (sensor.register_dpihw_override != 0) {
+ dpihw = sensor.register_dpihw_override;
+ }
+
+ uint8_t dpihw_setting;
+ switch (dpihw) {
+ case 600:
+ dpihw_setting = REG_0x05_DPIHW_600;
+ break;
+ case 1200:
+ dpihw_setting = REG_0x05_DPIHW_1200;
+ break;
+ case 2400:
+ dpihw_setting = REG_0x05_DPIHW_2400;
+ break;
+ case 4800:
+ dpihw_setting = REG_0x05_DPIHW_4800;
+ break;
+ default:
+ throw SaneException("Unknown dpihw value: %d", dpihw);
+ }
+ regs.set8_mask(0x05, dpihw_setting, REG_0x05_DPIHW_MASK);
+}
+
+void regs_set_exposure(AsicType asic_type, Genesys_Register_Set& regs,
+ const SensorExposure& exposure)
+{
+ switch (asic_type) {
+ case AsicType::GL124: {
+ regs.set24(gl124::REG_EXPR, exposure.red);
+ regs.set24(gl124::REG_EXPG, exposure.green);
+ regs.set24(gl124::REG_EXPB, exposure.blue);
+ break;
+ }
+ case AsicType::GL646: {
+ regs.set16(gl646::REG_EXPR, exposure.red);
+ regs.set16(gl646::REG_EXPG, exposure.green);
+ regs.set16(gl646::REG_EXPB, exposure.blue);
+ break;
+ }
+ case AsicType::GL841: {
+ regs.set16(gl841::REG_EXPR, exposure.red);
+ regs.set16(gl841::REG_EXPG, exposure.green);
+ regs.set16(gl841::REG_EXPB, exposure.blue);
+ break;
+ }
+ case AsicType::GL843: {
+ regs.set16(gl843::REG_EXPR, exposure.red);
+ regs.set16(gl843::REG_EXPG, exposure.green);
+ regs.set16(gl843::REG_EXPB, exposure.blue);
+ break;
+ }
+ case AsicType::GL845:
+ case AsicType::GL846: {
+ regs.set16(gl846::REG_EXPR, exposure.red);
+ regs.set16(gl846::REG_EXPG, exposure.green);
+ regs.set16(gl846::REG_EXPB, exposure.blue);
+ break;
+ }
+ case AsicType::GL847: {
+ regs.set16(gl847::REG_EXPR, exposure.red);
+ regs.set16(gl847::REG_EXPG, exposure.green);
+ regs.set16(gl847::REG_EXPB, exposure.blue);
+ break;
+ }
+ default:
+ throw SaneException("Unsupported asic");
+ }
+}
+
+void regs_set_optical_off(AsicType asic_type, Genesys_Register_Set& regs)
+{
+ DBG_HELPER(dbg);
+ switch (asic_type) {
+ case AsicType::GL646: {
+ regs.find_reg(gl646::REG_0x01).value &= ~gl646::REG_0x01_SCAN;
+ break;
+ }
+ case AsicType::GL841: {
+ regs.find_reg(gl841::REG_0x01).value &= ~gl841::REG_0x01_SCAN;
+ break;
+ }
+ case AsicType::GL843: {
+ regs.find_reg(gl843::REG_0x01).value &= ~gl843::REG_0x01_SCAN;
+ break;
+ }
+ case AsicType::GL845:
+ case AsicType::GL846: {
+ regs.find_reg(gl846::REG_0x01).value &= ~gl846::REG_0x01_SCAN;
+ break;
+ }
+ case AsicType::GL847: {
+ regs.find_reg(gl847::REG_0x01).value &= ~gl847::REG_0x01_SCAN;
+ break;
+ }
+ case AsicType::GL124: {
+ regs.find_reg(gl124::REG_0x01).value &= ~gl124::REG_0x01_SCAN;
+ break;
+ }
+ default:
+ throw SaneException("Unsupported asic");
+ }
+}
+
+bool get_registers_gain4_bit(AsicType asic_type, const Genesys_Register_Set& regs)
+{
+ switch (asic_type) {
+ case AsicType::GL646:
+ return static_cast<bool>(regs.get8(gl646::REG_0x06) & gl646::REG_0x06_GAIN4);
+ case AsicType::GL841:
+ return static_cast<bool>(regs.get8(gl841::REG_0x06) & gl841::REG_0x06_GAIN4);
+ case AsicType::GL843:
+ return static_cast<bool>(regs.get8(gl843::REG_0x06) & gl843::REG_0x06_GAIN4);
+ case AsicType::GL845:
+ case AsicType::GL846:
+ return static_cast<bool>(regs.get8(gl846::REG_0x06) & gl846::REG_0x06_GAIN4);
+ case AsicType::GL847:
+ return static_cast<bool>(regs.get8(gl847::REG_0x06) & gl847::REG_0x06_GAIN4);
+ case AsicType::GL124:
+ return static_cast<bool>(regs.get8(gl124::REG_0x06) & gl124::REG_0x06_GAIN4);
+ default:
+ throw SaneException("Unsupported chipset");
+ }
+}
+
+/**
+ * Wait for the scanning head to park
+ */
+void sanei_genesys_wait_for_home(Genesys_Device* dev)
+{
+ DBG_HELPER(dbg);
+
+ /* clear the parking status whatever the outcome of the function */
+ dev->parking = false;
+
+ if (is_testing_mode()) {
+ return;
+ }
+
+ // read initial status, if head isn't at home and motor is on we are parking, so we wait.
+ // gl847/gl124 need 2 reads for reliable results
+ auto status = scanner_read_status(*dev);
+ dev->interface->sleep_ms(10);
+ status = scanner_read_status(*dev);
+
+ if (status.is_at_home) {
+ DBG (DBG_info,
+ "%s: already at home\n", __func__);
+ return;
+ }
+
+ unsigned timeout_ms = 200000;
+ unsigned elapsed_ms = 0;
+ do
+ {
+ dev->interface->sleep_ms(100);
+ elapsed_ms += 100;
+
+ status = scanner_read_status(*dev);
+ } while (elapsed_ms < timeout_ms && !status.is_at_home);
+
+ /* if after the timeout, head is still not parked, error out */
+ if (elapsed_ms >= timeout_ms && !status.is_at_home) {
+ DBG (DBG_error, "%s: failed to reach park position in %dseconds\n", __func__,
+ timeout_ms / 1000);
+ throw SaneException(SANE_STATUS_IO_ERROR, "failed to reach park position");
+ }
+}
+
+/** @brief motor profile
+ * search for the database of motor profiles and get the best one. Each
+ * profile is at full step and at a reference exposure. Use first entry
+ * by default.
+ * @param motors motor profile database
+ * @param motor_type motor id
+ * @param exposure exposure time
+ * @return a pointer to a Motor_Profile struct
+ */
+const Motor_Profile& sanei_genesys_get_motor_profile(const std::vector<Motor_Profile>& motors,
+ MotorId motor_id, int exposure)
+{
+ int idx;
+
+ idx=-1;
+ for (std::size_t i = 0; i < motors.size(); ++i) {
+ // exact match
+ if (motors[i].motor_id == motor_id && motors[i].exposure==exposure) {
+ return motors[i];
+ }
+
+ // closest match
+ if (motors[i].motor_id == motor_id) {
+ /* if profile exposure is higher than the required one,
+ * the entry is a candidate for the closest match */
+ if (motors[i].exposure == 0 || motors[i].exposure >= exposure)
+ {
+ if(idx<0)
+ {
+ /* no match found yet */
+ idx=i;
+ }
+ else
+ {
+ /* test for better match */
+ if(motors[i].exposure<motors[idx].exposure)
+ {
+ idx=i;
+ }
+ }
+ }
+ }
+ }
+
+ /* default fallback */
+ if(idx<0)
+ {
+ DBG (DBG_warn,"%s: using default motor profile\n",__func__);
+ idx=0;
+ }
+
+ return motors[idx];
+}
+
+MotorSlopeTable sanei_genesys_slope_table(AsicType asic_type, int dpi, int exposure, int base_dpi,
+ unsigned step_multiplier,
+ const Motor_Profile& motor_profile)
+{
+ unsigned target_speed_w = ((exposure * dpi) / base_dpi);
+
+ auto table = create_slope_table(motor_profile.slope, target_speed_w, motor_profile.step_type,
+ step_multiplier, 2 * step_multiplier,
+ get_slope_table_max_size(asic_type));
+ return table;
+}
+
+MotorSlopeTable create_slope_table_fastest(AsicType asic_type, unsigned step_multiplier,
+ const Motor_Profile& motor_profile)
+{
+ return create_slope_table(motor_profile.slope, motor_profile.slope.max_speed_w,
+ motor_profile.step_type,
+ step_multiplier, 2 * step_multiplier,
+ get_slope_table_max_size(asic_type));
+}
+
+/** @brief returns the lowest possible ydpi for the device
+ * Parses device entry to find lowest motor dpi.
+ * @param dev device description
+ * @return lowest motor resolution
+ */
+int sanei_genesys_get_lowest_ydpi(Genesys_Device *dev)
+{
+ const auto& resolution_settings = dev->model->get_resolution_settings(dev->settings.scan_method);
+ return resolution_settings.get_min_resolution_y();
+}
+
+/** @brief returns the lowest possible dpi for the device
+ * Parses device entry to find lowest motor or sensor dpi.
+ * @param dev device description
+ * @return lowest motor resolution
+ */
+int sanei_genesys_get_lowest_dpi(Genesys_Device *dev)
+{
+ const auto& resolution_settings = dev->model->get_resolution_settings(dev->settings.scan_method);
+ return std::min(resolution_settings.get_min_resolution_x(),
+ resolution_settings.get_min_resolution_y());
+}
+
+/** @brief check is a cache entry may be used
+ * Compares current settings with the cache entry and return
+ * true if they are compatible.
+ * A calibration cache is compatible if color mode and x dpi match the user
+ * requested scan. In the case of CIS scanners, dpi isn't a criteria.
+ * flatbed cache entries are considred too old and then expires if they
+ * are older than the expiration time option, forcing calibration at least once
+ * then given time. */
+bool sanei_genesys_is_compatible_calibration(Genesys_Device* dev,
+ const ScanSession& session,
+ const Genesys_Calibration_Cache* cache,
+ bool for_overwrite)
+{
+ DBG_HELPER(dbg);
+#ifdef HAVE_SYS_TIME_H
+ struct timeval time;
+#endif
+
+ bool compatible = true;
+
+ const auto& dev_params = session.params;
+
+ if (dev_params.scan_method != cache->params.scan_method) {
+ dbg.vlog(DBG_io, "incompatible: scan_method %d vs. %d\n",
+ static_cast<unsigned>(dev_params.scan_method),
+ static_cast<unsigned>(cache->params.scan_method));
+ compatible = false;
+ }
+
+ if (dev_params.xres != cache->params.xres) {
+ dbg.vlog(DBG_io, "incompatible: params.xres %d vs. %d\n",
+ dev_params.xres, cache->params.xres);
+ compatible = false;
+ }
+
+ if (dev_params.yres != cache->params.yres) {
+ // exposure depends on selected sensor and we select the sensor according to yres
+ dbg.vlog(DBG_io, "incompatible: params.yres %d vs. %d\n",
+ dev_params.yres, cache->params.yres);
+ compatible = false;
+ }
+
+ if (dev_params.channels != cache->params.channels) {
+ // exposure depends on total number of pixels at least on gl841
+ dbg.vlog(DBG_io, "incompatible: params.channels %d vs. %d\n",
+ dev_params.channels, cache->params.channels);
+ compatible = false;
+ }
+
+ if (dev_params.startx != cache->params.startx) {
+ // exposure depends on total number of pixels at least on gl841
+ dbg.vlog(DBG_io, "incompatible: params.startx %d vs. %d\n",
+ dev_params.startx, cache->params.startx);
+ compatible = false;
+ }
+
+ if (dev_params.pixels != cache->params.pixels) {
+ // exposure depends on total number of pixels at least on gl841
+ dbg.vlog(DBG_io, "incompatible: params.pixels %d vs. %d\n",
+ dev_params.pixels, cache->params.pixels);
+ compatible = false;
+ }
+
+ if (!compatible)
+ {
+ DBG (DBG_proc, "%s: completed, non compatible cache\n", __func__);
+ return false;
+ }
+
+ /* a cache entry expires after afetr expiration time for non sheetfed scanners */
+ /* this is not taken into account when overwriting cache entries */
+#ifdef HAVE_SYS_TIME_H
+ if (!for_overwrite && dev->settings.expiration_time >=0)
+ {
+ gettimeofday(&time, nullptr);
+ if ((time.tv_sec - cache->last_calibration > dev->settings.expiration_time*60)
+ && !dev->model->is_sheetfed
+ && (dev->settings.scan_method == ScanMethod::FLATBED))
+ {
+ DBG (DBG_proc, "%s: expired entry, non compatible cache\n", __func__);
+ return false;
+ }
+ }
+#endif
+
+ return true;
+}
+
+/** @brief build lookup table for digital enhancements
+ * Function to build a lookup table (LUT), often
+ used by scanners to implement brightness/contrast/gamma
+ or by backends to speed binarization/thresholding
+
+ offset and slope inputs are -127 to +127
+
+ slope rotates line around central input/output val,
+ 0 makes horizontal line
+
+ pos zero neg
+ . x . . x
+ . x . . x
+ out . x .xxxxxxxxxxx . x
+ . x . . x
+ ....x....... ............ .......x....
+ in in in
+
+ offset moves line vertically, and clamps to output range
+ 0 keeps the line crossing the center of the table
+
+ high low
+ . xxxxxxxx .
+ . x .
+ out x . x
+ . . x
+ ............ xxxxxxxx....
+ in in
+
+ out_min/max provide bounds on output values,
+ useful when building thresholding lut.
+ 0 and 255 are good defaults otherwise.
+ * @param lut pointer where to store the generated lut
+ * @param in_bits number of bits for in values
+ * @param out_bits number of bits of out values
+ * @param out_min minimal out value
+ * @param out_max maximal out value
+ * @param slope slope of the generated data
+ * @param offset offset of the generated data
+ */
+void sanei_genesys_load_lut(unsigned char* lut,
+ int in_bits, int out_bits,
+ int out_min, int out_max,
+ int slope, int offset)
+{
+ DBG_HELPER(dbg);
+ int i, j;
+ double shift, rise;
+ int max_in_val = (1 << in_bits) - 1;
+ int max_out_val = (1 << out_bits) - 1;
+ uint8_t *lut_p8 = lut;
+ uint16_t* lut_p16 = reinterpret_cast<std::uint16_t*>(lut);
+
+ /* slope is converted to rise per unit run:
+ * first [-127,127] to [-.999,.999]
+ * then to [-PI/4,PI/4] then [0,PI/2]
+ * then take the tangent (T.O.A)
+ * then multiply by the normal linear slope
+ * because the table may not be square, i.e. 1024x256*/
+ auto pi_4 = M_PI / 4.0;
+ rise = std::tan(static_cast<double>(slope) / 128 * pi_4 + pi_4) * max_out_val / max_in_val;
+
+ /* line must stay vertically centered, so figure
+ * out vertical offset at central input value */
+ shift = static_cast<double>(max_out_val) / 2 - (rise * max_in_val / 2);
+
+ /* convert the user offset setting to scale of output
+ * first [-127,127] to [-1,1]
+ * then to [-max_out_val/2,max_out_val/2]*/
+ shift += static_cast<double>(offset) / 127 * max_out_val / 2;
+
+ for (i = 0; i <= max_in_val; i++)
+ {
+ j = static_cast<int>(rise * i + shift);
+
+ /* cap data to required range */
+ if (j < out_min)
+ {
+ j = out_min;
+ }
+ else if (j > out_max)
+ {
+ j = out_max;
+ }
+
+ /* copy result according to bit depth */
+ if (out_bits <= 8)
+ {
+ *lut_p8 = j;
+ lut_p8++;
+ }
+ else
+ {
+ *lut_p16 = j;
+ lut_p16++;
+ }
+ }
+}
+
+} // namespace genesys
diff --git a/backend/genesys/low.h b/backend/genesys/low.h
new file mode 100644
index 0000000..d7f5dd2
--- /dev/null
+++ b/backend/genesys/low.h
@@ -0,0 +1,525 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2003 Oliver Rauch
+ Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
+ Copyright (C) 2004, 2005 Gerhard Jaeger <gerhard@gjaeger.de>
+ Copyright (C) 2004-2013 Stéphane Voltz <stef.dev@free.fr>
+ Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
+ Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com>
+ Parts of the structs have been taken from the gt68xx backend by
+ Sergey Vlasov <vsu@altlinux.ru> et al.
+
+ 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 GENESYS_LOW_H
+#define GENESYS_LOW_H
+
+
+#include "../include/sane/config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+#include <stddef.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_MKDIR
+#include <sys/stat.h>
+#include <sys/types.h>
+#endif
+
+#include "../include/sane/sane.h"
+#include "../include/sane/sanei.h"
+#include "../include/sane/saneopts.h"
+
+#include "../include/sane/sanei_backend.h"
+#include "../include/sane/sanei_usb.h"
+
+#include "../include/_stdint.h"
+
+#include "device.h"
+#include "enums.h"
+#include "error.h"
+#include "fwd.h"
+#include "usb_device.h"
+#include "sensor.h"
+#include "serialize.h"
+#include "settings.h"
+#include "static_init.h"
+#include "status.h"
+#include "register.h"
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <functional>
+#include <iostream>
+#include <sstream>
+#include <limits>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#define GENESYS_RED 0
+#define GENESYS_GREEN 1
+#define GENESYS_BLUE 2
+
+/* Flags */
+#define GENESYS_FLAG_UNTESTED (1 << 0) /**< Print a warning for these scanners */
+#define GENESYS_FLAG_14BIT_GAMMA (1 << 1) /**< use 14bit Gamma table instead of 12 */
+#define GENESYS_FLAG_XPA (1 << 3)
+#define GENESYS_FLAG_SKIP_WARMUP (1 << 4) /**< skip genesys_warmup() */
+/** @brief offset calibration flag
+ * signals that the scanner does offset calibration. In this case off_calibration() and
+ * coarse_gain_calibration() functions must be implemented
+ */
+#define GENESYS_FLAG_OFFSET_CALIBRATION (1 << 5)
+#define GENESYS_FLAG_SEARCH_START (1 << 6) /**< do start search before scanning */
+#define GENESYS_FLAG_REPARK (1 << 7) /**< repark head (and check for lock) by
+ moving without scanning */
+#define GENESYS_FLAG_DARK_CALIBRATION (1 << 8) /**< do dark calibration */
+
+#define GENESYS_FLAG_MUST_WAIT (1 << 10) /**< tells wether the scanner must wait for the head when parking */
+
+
+#define GENESYS_FLAG_HAS_UTA (1 << 11) /**< scanner has a transparency adapter */
+
+#define GENESYS_FLAG_DARK_WHITE_CALIBRATION (1 << 12) /**< yet another calibration method. does white and dark shading in one run, depending on a black and a white strip*/
+#define GENESYS_FLAG_CUSTOM_GAMMA (1 << 13) /**< allow custom gamma tables */
+#define GENESYS_FLAG_NO_CALIBRATION (1 << 14) /**< allow scanners to use skip the calibration, needed for sheetfed scanners */
+#define GENESYS_FLAG_SIS_SENSOR (1 << 16) /**< handling of multi-segments sensors in software */
+#define GENESYS_FLAG_SHADING_NO_MOVE (1 << 17) /**< scanner doesn't move sensor during shading calibration */
+#define GENESYS_FLAG_SHADING_REPARK (1 << 18) /**< repark head between shading scans */
+#define GENESYS_FLAG_FULL_HWDPI_MODE (1 << 19) /**< scanner always use maximum hw dpi to setup the sensor */
+// scanner has infrared transparency scanning capability
+#define GENESYS_FLAG_HAS_UTA_INFRARED (1 << 20)
+// scanner calibration is handled on the host side
+#define GENESYS_FLAG_CALIBRATION_HOST_SIDE (1 << 21)
+#define GENESYS_FLAG_16BIT_DATA_INVERTED (1 << 22)
+
+#define GENESYS_HAS_NO_BUTTONS 0 /**< scanner has no supported button */
+#define GENESYS_HAS_SCAN_SW (1 << 0) /**< scanner has SCAN button */
+#define GENESYS_HAS_FILE_SW (1 << 1) /**< scanner has FILE button */
+#define GENESYS_HAS_COPY_SW (1 << 2) /**< scanner has COPY button */
+#define GENESYS_HAS_EMAIL_SW (1 << 3) /**< scanner has EMAIL button */
+#define GENESYS_HAS_PAGE_LOADED_SW (1 << 4) /**< scanner has paper in detection */
+#define GENESYS_HAS_OCR_SW (1 << 5) /**< scanner has OCR button */
+#define GENESYS_HAS_POWER_SW (1 << 6) /**< scanner has power button */
+#define GENESYS_HAS_CALIBRATE (1 << 7) /**< scanner has 'calibrate' software button to start calibration */
+#define GENESYS_HAS_EXTRA_SW (1 << 8) /**< scanner has extra function button */
+
+/* USB control message values */
+#define REQUEST_TYPE_IN (USB_TYPE_VENDOR | USB_DIR_IN)
+#define REQUEST_TYPE_OUT (USB_TYPE_VENDOR | USB_DIR_OUT)
+#define REQUEST_REGISTER 0x0c
+#define REQUEST_BUFFER 0x04
+#define VALUE_BUFFER 0x82
+#define VALUE_SET_REGISTER 0x83
+#define VALUE_READ_REGISTER 0x84
+#define VALUE_WRITE_REGISTER 0x85
+#define VALUE_INIT 0x87
+#define GPIO_OUTPUT_ENABLE 0x89
+#define GPIO_READ 0x8a
+#define GPIO_WRITE 0x8b
+#define VALUE_BUF_ENDACCESS 0x8c
+#define VALUE_GET_REGISTER 0x8e
+#define INDEX 0x00
+
+/* todo: used?
+#define VALUE_READ_STATUS 0x86
+*/
+
+/* Read/write bulk data/registers */
+#define BULK_OUT 0x01
+#define BULK_IN 0x00
+#define BULK_RAM 0x00
+#define BULK_REGISTER 0x11
+
+#define BULKOUT_MAXSIZE 0xF000
+
+/* AFE values */
+#define AFE_INIT 1
+#define AFE_SET 2
+#define AFE_POWER_SAVE 4
+
+#define LOWORD(x) ((uint16_t)((x) & 0xffff))
+#define HIWORD(x) ((uint16_t)((x) >> 16))
+#define LOBYTE(x) ((uint8_t)((x) & 0xFF))
+#define HIBYTE(x) ((uint8_t)((x) >> 8))
+
+/* Global constants */
+/* TODO: emove this leftover of early backend days */
+#define MOTOR_SPEED_MAX 350
+#define DARK_VALUE 0
+
+#define MAX_RESOLUTIONS 13
+#define MAX_DPI 4
+
+namespace genesys {
+
+struct Genesys_USB_Device_Entry {
+
+ Genesys_USB_Device_Entry(unsigned v, unsigned p, const Genesys_Model& m) :
+ vendor(v), product(p), model(m)
+ {}
+
+ // USB vendor identifier
+ std::uint16_t vendor;
+ // USB product identifier
+ std::uint16_t product;
+ // Scanner model information
+ Genesys_Model model;
+};
+
+/**
+ * structure for motor database
+ */
+struct Motor_Profile
+{
+ MotorId motor_id;
+ int exposure; // used only to select the wanted motor
+ StepType step_type; // default step type for given exposure
+ MotorSlope slope;
+};
+
+extern StaticInit<std::vector<Motor_Profile>> gl843_motor_profiles;
+extern StaticInit<std::vector<Motor_Profile>> gl846_motor_profiles;
+extern StaticInit<std::vector<Motor_Profile>> gl847_motor_profiles;
+extern StaticInit<std::vector<Motor_Profile>> gl124_motor_profiles;
+
+/*--------------------------------------------------------------------------*/
+/* common functions needed by low level specific functions */
+/*--------------------------------------------------------------------------*/
+
+inline GenesysRegister* sanei_genesys_get_address(Genesys_Register_Set* regs, uint16_t addr)
+{
+ auto* ret = regs->find_reg_address(addr);
+ if (ret == nullptr) {
+ DBG(DBG_error, "%s: failed to find address for register 0x%02x, crash expected !\n",
+ __func__, addr);
+ }
+ return ret;
+}
+
+extern void sanei_genesys_init_cmd_set(Genesys_Device* dev);
+
+// reads the status of the scanner
+Status scanner_read_status(Genesys_Device& dev);
+
+// reads the status of the scanner reliably. This is done by reading the status twice. The first
+// read sometimes returns the home sensor as engaged when this is not true.
+Status scanner_read_reliable_status(Genesys_Device& dev);
+
+// reads and prints the scanner status
+void scanner_read_print_status(Genesys_Device& dev);
+
+void debug_print_status(DebugMessageHelper& dbg, Status status);
+
+extern void sanei_genesys_write_ahb(Genesys_Device* dev, uint32_t addr, uint32_t size,
+ uint8_t* data);
+
+extern void sanei_genesys_init_structs (Genesys_Device * dev);
+
+const Genesys_Sensor& sanei_genesys_find_sensor_any(Genesys_Device* dev);
+const Genesys_Sensor& sanei_genesys_find_sensor(Genesys_Device* dev, unsigned dpi,
+ unsigned channels, ScanMethod scan_method);
+bool sanei_genesys_has_sensor(Genesys_Device* dev, unsigned dpi, unsigned channels,
+ ScanMethod scan_method);
+Genesys_Sensor& sanei_genesys_find_sensor_for_write(Genesys_Device* dev, unsigned dpi,
+ unsigned channels, ScanMethod scan_method);
+
+std::vector<std::reference_wrapper<const Genesys_Sensor>>
+ sanei_genesys_find_sensors_all(Genesys_Device* dev, ScanMethod scan_method);
+std::vector<std::reference_wrapper<Genesys_Sensor>>
+ sanei_genesys_find_sensors_all_for_write(Genesys_Device* dev, ScanMethod scan_method);
+
+extern void sanei_genesys_init_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ int pixels_per_line);
+
+extern void sanei_genesys_read_valid_words(Genesys_Device* dev, unsigned int* steps);
+
+extern void sanei_genesys_read_scancnt(Genesys_Device* dev, unsigned int* steps);
+
+extern void sanei_genesys_read_feed_steps(Genesys_Device* dev, unsigned int* steps);
+
+void sanei_genesys_set_lamp_power(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ Genesys_Register_Set& regs, bool set);
+
+void sanei_genesys_set_motor_power(Genesys_Register_Set& regs, bool set);
+
+bool should_enable_gamma(const ScanSession& session, const Genesys_Sensor& sensor);
+
+/** Calculates the values of the Z{1,2}MOD registers. They are a phase correction to synchronize
+ with the line clock during acceleration and deceleration.
+
+ two_table is true if moving is done by two tables, false otherwise.
+
+ acceleration_steps is the number of steps for acceleration, i.e. the number written to
+ REG_STEPNO.
+
+ move_steps number of steps to move, i.e. the number written to REG_FEEDL.
+
+ buffer_acceleration_steps, the number of steps for acceleration when buffer condition is met,
+ i.e. the number written to REG_FWDSTEP.
+*/
+void sanei_genesys_calculate_zmod(bool two_table,
+ uint32_t exposure_time,
+ const std::vector<uint16_t>& slope_table,
+ unsigned acceleration_steps,
+ unsigned move_steps,
+ unsigned buffer_acceleration_steps,
+ uint32_t* out_z1, uint32_t* out_z2);
+
+extern void sanei_genesys_set_buffer_address(Genesys_Device* dev, uint32_t addr);
+
+unsigned sanei_genesys_get_bulk_max_size(AsicType asic_type);
+
+SANE_Int sanei_genesys_exposure_time2(Genesys_Device * dev, float ydpi, StepType step_type,
+ int endpixel, int led_exposure);
+
+MotorSlopeTable sanei_genesys_create_slope_table3(AsicType asic_type, const Genesys_Motor& motor,
+ StepType step_type, int exposure_time,
+ unsigned yres);
+
+void sanei_genesys_create_default_gamma_table(Genesys_Device* dev,
+ std::vector<uint16_t>& gamma_table, float gamma);
+
+std::vector<uint16_t> get_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor,
+ int color);
+
+void sanei_genesys_send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& sensor);
+
+extern void sanei_genesys_stop_motor(Genesys_Device* dev);
+
+extern void sanei_genesys_search_reference_point(Genesys_Device* dev, Genesys_Sensor& sensor,
+ const uint8_t* src_data, int start_pixel, int dpi,
+ int width, int height);
+
+// moves the scan head by the specified steps at the motor base dpi
+void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, Direction direction);
+
+void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home);
+void scanner_move_back_home_ta(Genesys_Device& dev);
+
+void scanner_clear_scan_and_feed_counts(Genesys_Device& dev);
+
+extern void sanei_genesys_write_file(const char* filename, const std::uint8_t* data,
+ std::size_t length);
+
+extern void sanei_genesys_write_pnm_file(const char* filename, const std::uint8_t* data, int depth,
+ int channels, int pixels_per_line, int lines);
+
+void sanei_genesys_write_pnm_file(const char* filename, const Image& image);
+
+extern void sanei_genesys_write_pnm_file16(const char* filename, const uint16_t *data, unsigned channels,
+ unsigned pixels_per_line, unsigned lines);
+
+void wait_until_buffer_non_empty(Genesys_Device* dev, bool check_status_twice = false);
+
+extern void sanei_genesys_read_data_from_scanner(Genesys_Device* dev, uint8_t* data, size_t size);
+
+Image read_unshuffled_image_from_scanner(Genesys_Device* dev, const ScanSession& session,
+ std::size_t total_bytes);
+
+void regs_set_exposure(AsicType asic_type, Genesys_Register_Set& regs,
+ const SensorExposure& exposure);
+
+void regs_set_optical_off(AsicType asic_type, Genesys_Register_Set& regs);
+
+void sanei_genesys_set_dpihw(Genesys_Register_Set& regs, const Genesys_Sensor& sensor,
+ unsigned dpihw);
+
+inline uint16_t sanei_genesys_fixup_exposure_value(uint16_t value)
+{
+ if ((value & 0xff00) == 0) {
+ value |= 0x100;
+ }
+ if ((value & 0x00ff) == 0) {
+ value |= 0x1;
+ }
+ return value;
+}
+
+inline SensorExposure sanei_genesys_fixup_exposure(SensorExposure exposure)
+{
+ exposure.red = sanei_genesys_fixup_exposure_value(exposure.red);
+ exposure.green = sanei_genesys_fixup_exposure_value(exposure.green);
+ exposure.blue = sanei_genesys_fixup_exposure_value(exposure.blue);
+ return exposure;
+}
+
+bool get_registers_gain4_bit(AsicType asic_type, const Genesys_Register_Set& regs);
+
+extern void sanei_genesys_wait_for_home(Genesys_Device* dev);
+
+extern void sanei_genesys_asic_init(Genesys_Device* dev, bool cold);
+
+void scanner_start_action(Genesys_Device& dev, bool start_motor);
+void scanner_stop_action(Genesys_Device& dev);
+void scanner_stop_action_no_move(Genesys_Device& dev, Genesys_Register_Set& regs);
+
+bool scanner_is_motor_stopped(Genesys_Device& dev);
+
+const Motor_Profile& sanei_genesys_get_motor_profile(const std::vector<Motor_Profile>& motors,
+ MotorId motor_id, int exposure);
+
+MotorSlopeTable sanei_genesys_slope_table(AsicType asic_type, int dpi, int exposure, int base_dpi,
+ unsigned step_multiplier,
+ const Motor_Profile& motor_profile);
+
+MotorSlopeTable create_slope_table_fastest(AsicType asic_type, unsigned step_multiplier,
+ const Motor_Profile& motor_profile);
+
+/** @brief find lowest motor resolution for the device.
+ * Parses the resolution list for motor and
+ * returns the lowest value.
+ * @param dev for which to find the lowest motor resolution
+ * @return the lowest available motor resolution for the device
+ */
+extern
+int sanei_genesys_get_lowest_ydpi(Genesys_Device *dev);
+
+/** @brief find lowest resolution for the device.
+ * Parses the resolution list for motor and sensor and
+ * returns the lowest value.
+ * @param dev for which to find the lowest resolution
+ * @return the lowest available resolution for the device
+ */
+extern
+int sanei_genesys_get_lowest_dpi(Genesys_Device *dev);
+
+bool sanei_genesys_is_compatible_calibration(Genesys_Device* dev,
+ const ScanSession& session,
+ const Genesys_Calibration_Cache* cache,
+ bool for_overwrite);
+
+extern void sanei_genesys_load_lut(unsigned char* lut,
+ int in_bits, int out_bits,
+ int out_min, int out_max,
+ int slope, int offset);
+
+extern void sanei_genesys_generate_gamma_buffer(Genesys_Device* dev,
+ const Genesys_Sensor& sensor,
+ int bits,
+ int max,
+ int size,
+ uint8_t* gamma);
+
+void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Sensor& sensor);
+
+void build_image_pipeline(Genesys_Device* dev, const ScanSession& session);
+
+std::uint8_t compute_frontend_gain(float value, float target_value,
+ FrontendType frontend_type);
+
+template<class T>
+inline T abs_diff(T a, T b)
+{
+ if (a < b) {
+ return b - a;
+ } else {
+ return a - b;
+ }
+}
+
+inline uint64_t align_multiple_floor(uint64_t x, uint64_t multiple)
+{
+ return (x / multiple) * multiple;
+}
+
+inline uint64_t align_multiple_ceil(uint64_t x, uint64_t multiple)
+{
+ return ((x + multiple - 1) / multiple) * multiple;
+}
+
+inline uint64_t multiply_by_depth_ceil(uint64_t pixels, uint64_t depth)
+{
+ if (depth == 1) {
+ return (pixels / 8) + ((pixels % 8) ? 1 : 0);
+ } else {
+ return pixels * (depth / 8);
+ }
+}
+
+template<class T>
+inline T clamp(const T& value, const T& lo, const T& hi)
+{
+ if (value < lo)
+ return lo;
+ if (value > hi)
+ return hi;
+ return value;
+}
+
+/*---------------------------------------------------------------------------*/
+/* ASIC specific functions declarations */
+/*---------------------------------------------------------------------------*/
+
+extern StaticInit<std::vector<Genesys_Sensor>> s_sensors;
+extern StaticInit<std::vector<Genesys_Frontend>> s_frontends;
+extern StaticInit<std::vector<Genesys_Gpo>> s_gpo;
+extern StaticInit<std::vector<Genesys_Motor>> s_motors;
+extern StaticInit<std::vector<Genesys_USB_Device_Entry>> s_usb_devices;
+
+void genesys_init_sensor_tables();
+void genesys_init_frontend_tables();
+void genesys_init_gpo_tables();
+void genesys_init_motor_tables();
+void genesys_init_motor_profile_tables();
+void genesys_init_usb_device_tables();
+
+template<class T>
+void debug_dump(unsigned level, const T& value)
+{
+ std::stringstream out;
+ out << value;
+ DBG(level, "%s\n", out.str().c_str());
+}
+
+} // namespace genesys
+
+#endif /* not GENESYS_LOW_H */
diff --git a/backend/genesys/motor.cpp b/backend/genesys/motor.cpp
new file mode 100644
index 0000000..910266a
--- /dev/null
+++ b/backend/genesys/motor.cpp
@@ -0,0 +1,180 @@
+/* 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 "motor.h"
+#include "utilities.h"
+#include <cmath>
+
+namespace genesys {
+
+unsigned MotorSlope::get_table_step_shifted(unsigned step, StepType step_type) const
+{
+ // first two steps are always equal to the initial speed
+ if (step < 2) {
+ return initial_speed_w >> static_cast<unsigned>(step_type);
+ }
+ step--;
+
+ float initial_speed_v = 1.0f / initial_speed_w;
+ float speed_v = std::sqrt(initial_speed_v * initial_speed_v + 2 * acceleration * step);
+ return static_cast<unsigned>(1.0f / speed_v) >> static_cast<unsigned>(step_type);
+}
+
+float compute_acceleration_for_steps(unsigned initial_w, unsigned max_w, unsigned steps)
+{
+ float initial_speed_v = 1.0f / static_cast<float>(initial_w);
+ float max_speed_v = 1.0f / static_cast<float>(max_w);
+ return (max_speed_v * max_speed_v - initial_speed_v * initial_speed_v) / (2 * steps);
+}
+
+
+MotorSlope MotorSlope::create_from_steps(unsigned initial_w, unsigned max_w,
+ unsigned steps)
+{
+ MotorSlope slope;
+ slope.initial_speed_w = initial_w;
+ slope.max_speed_w = max_w;
+ slope.acceleration = compute_acceleration_for_steps(initial_w, max_w, steps);
+ return slope;
+}
+
+void MotorSlopeTable::slice_steps(unsigned count)
+{
+ if (count >= table.size() || count > steps_count) {
+ throw SaneException("Excepssive steps count");
+ }
+ steps_count = count;
+}
+
+unsigned get_slope_table_max_size(AsicType asic_type)
+{
+ switch (asic_type) {
+ case AsicType::GL646:
+ case AsicType::GL841: return 255;
+ case AsicType::GL843:
+ case AsicType::GL845:
+ case AsicType::GL846:
+ case AsicType::GL847:
+ case AsicType::GL124: return 1024;
+ default:
+ throw SaneException("Unknown asic type");
+ }
+}
+
+MotorSlopeTable create_slope_table(const MotorSlope& slope, unsigned target_speed_w,
+ StepType step_type, unsigned steps_alignment,
+ unsigned min_size, unsigned max_size)
+{
+ DBG_HELPER_ARGS(dbg, "target_speed_w: %d, step_type: %d, steps_alignment: %d, min_size: %d",
+ target_speed_w, static_cast<unsigned>(step_type), steps_alignment, min_size);
+ MotorSlopeTable table;
+
+ unsigned step_shift = static_cast<unsigned>(step_type);
+
+ unsigned target_speed_shifted_w = target_speed_w >> step_shift;
+ unsigned max_speed_shifted_w = slope.max_speed_w >> step_shift;
+
+ if (target_speed_shifted_w < max_speed_shifted_w) {
+ dbg.log(DBG_warn, "failed to reach target speed");
+ }
+
+ unsigned final_speed = std::max(target_speed_shifted_w, max_speed_shifted_w);
+
+ table.table.reserve(max_size);
+
+ while (table.table.size() < max_size - 1) {
+ unsigned current = slope.get_table_step_shifted(table.table.size(), step_type);
+ if (current <= final_speed) {
+ break;
+ }
+ table.table.push_back(current);
+ table.pixeltime_sum += current;
+ }
+
+ // make sure the target speed (or the max speed if target speed is too high) is present in
+ // the table
+ table.table.push_back(final_speed);
+ table.pixeltime_sum += table.table.back();
+
+ // fill the table up to the specified size
+ while (table.table.size() < max_size - 1 &&
+ (table.table.size() % steps_alignment != 0 || table.table.size() < min_size))
+ {
+ table.table.push_back(table.table.back());
+ table.pixeltime_sum += table.table.back();
+ }
+
+ table.steps_count = table.table.size();
+
+ // fill the rest of the table with the final speed
+ table.table.resize(max_size, final_speed);
+
+ return table;
+}
+
+std::ostream& operator<<(std::ostream& out, const MotorSlope& slope)
+{
+ out << "MotorSlope{\n"
+ << " initial_speed_w: " << slope.initial_speed_w << '\n'
+ << " max_speed_w: " << slope.max_speed_w << '\n'
+ << " a: " << slope.acceleration << '\n'
+ << '}';
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Genesys_Motor& motor)
+{
+ out << "Genesys_Motor{\n"
+ << " id: " << static_cast<unsigned>(motor.id) << '\n'
+ << " base_ydpi: " << motor.base_ydpi << '\n'
+ << " optical_ydpi: " << motor.optical_ydpi << '\n'
+ << " slopes: "
+ << format_indent_braced_list(4, format_vector_indent_braced(4, "MotorSlope",
+ motor.slopes))
+ << '}';
+ return out;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/motor.h b/backend/genesys/motor.h
new file mode 100644
index 0000000..d80da6d
--- /dev/null
+++ b/backend/genesys/motor.h
@@ -0,0 +1,177 @@
+/* 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_MOTOR_H
+#define BACKEND_GENESYS_MOTOR_H
+
+#include <cstdint>
+#include <vector>
+#include "enums.h"
+
+namespace genesys {
+
+/* Describes a motor acceleration curve.
+
+ Definitions:
+ v - speed in steps per pixeltime
+ w - speed in pixel times per step. w = 1 / v
+ a - acceleration in steps per pixeltime squared
+ s - distance travelled in steps
+ t - time in pixeltime
+
+ The physical mode defines the curve in physical quantities. We asssume that the scanner head
+ accelerates from standstill to the target speed uniformly. Then:
+
+ v(t) = v(0) + a * t (2)
+
+ Where `a` is acceleration, `t` is time. Also we can calculate the travelled distance `s`:
+
+ s(t) = v(0) * t + a * t^2 / 2 (3)
+
+ The actual motor slope is defined as the duration of each motor step. That means we need to
+ define speed in terms of travelled distance.
+
+ Solving (3) for `t` gives:
+
+ sqrt( v(0)^2 + 2 * a * s ) - v(0)
+ t(s) = --------------------------------- (4)
+ a
+
+ Combining (4) and (2) will yield:
+
+ v(s) = sqrt( v(0)^2 + 2 * a * s ) (5)
+
+ The data in the slope struct MotorSlope corresponds to the above in the following way:
+
+ maximum_start_speed is `w(0) = 1/v(0)`
+
+ maximum_speed is defines maximum speed which should not be exceeded
+
+ minimum_steps is not used
+
+ g is `a`
+
+ Given the start and target speeds on a known motor curve, `a` can be computed as follows:
+
+ v(t1)^2 - v(t0)^2
+ a = ----------------- (6)
+ 2 * s
+
+ Here `v(t0)` and `v(t1)` are the start and target speeds and `s` is the number of step required
+ to reach the target speeds.
+*/
+struct MotorSlope
+{
+ // initial speed in pixeltime per step
+ unsigned initial_speed_w = 0;
+
+ // max speed in pixeltime per step
+ unsigned max_speed_w = 0;
+
+ // maximum number of steps in the table
+ unsigned max_step_count;
+
+ // acceleration in steps per pixeltime squared.
+ float acceleration = 0;
+
+ unsigned get_table_step_shifted(unsigned step, StepType step_type) const;
+
+ static MotorSlope create_from_steps(unsigned initial_w, unsigned max_w,
+ unsigned steps);
+};
+
+struct MotorSlopeTable
+{
+ std::vector<std::uint16_t> table;
+ unsigned steps_count = 0;
+ unsigned pixeltime_sum = 0;
+
+ void slice_steps(unsigned count);
+};
+
+unsigned get_slope_table_max_size(AsicType asic_type);
+
+MotorSlopeTable create_slope_table(const MotorSlope& slope, unsigned target_speed_w,
+ StepType step_type, unsigned steps_alignment,
+ unsigned min_size, unsigned max_size);
+
+std::ostream& operator<<(std::ostream& out, const MotorSlope& slope);
+
+
+struct Genesys_Motor
+{
+ Genesys_Motor() = default;
+
+ // id of the motor description
+ MotorId id = MotorId::UNKNOWN;
+ // motor base steps. Unit: 1/inch
+ int base_ydpi = 0;
+ // maximum resolution in y-direction. Unit: 1/inch
+ int optical_ydpi = 0;
+ // slopes to derive individual slopes from
+ std::vector<MotorSlope> slopes;
+
+ MotorSlope& get_slope(StepType step_type)
+ {
+ return slopes[static_cast<unsigned>(step_type)];
+ }
+
+ const MotorSlope& get_slope(StepType step_type) const
+ {
+ return slopes[static_cast<unsigned>(step_type)];
+ }
+
+ StepType max_step_type() const
+ {
+ if (slopes.empty()) {
+ throw std::runtime_error("Slopes table is empty");
+ }
+ return static_cast<StepType>(slopes.size() - 1);
+ }
+};
+
+std::ostream& operator<<(std::ostream& out, const Genesys_Motor& motor);
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_MOTOR_H
diff --git a/backend/genesys/register.h b/backend/genesys/register.h
new file mode 100644
index 0000000..bbc7ec8
--- /dev/null
+++ b/backend/genesys/register.h
@@ -0,0 +1,537 @@
+/* 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_REGISTER_H
+#define BACKEND_GENESYS_REGISTER_H
+
+#include "utilities.h"
+
+#include <algorithm>
+#include <climits>
+#include <cstdint>
+#include <iostream>
+#include <iomanip>
+#include <stdexcept>
+#include <vector>
+
+namespace genesys {
+
+template<class Value>
+struct Register
+{
+ std::uint16_t address = 0;
+ Value value = 0;
+};
+
+using GenesysRegister = Register<std::uint8_t>;
+
+template<class Value>
+inline bool operator<(const Register<Value>& lhs, const Register<Value>& rhs)
+{
+ return lhs.address < rhs.address;
+}
+
+struct GenesysRegisterSetState
+{
+ bool is_lamp_on = false;
+ bool is_xpa_on = false;
+ bool is_motor_on = false;
+ bool is_xpa_motor_on = false;
+};
+
+template<class Value>
+class RegisterContainer
+{
+public:
+
+ enum Options {
+ SEQUENTIAL = 1
+ };
+
+ using RegisterType = Register<Value>;
+ using ContainerType = std::vector<RegisterType>;
+ using iterator = typename ContainerType::iterator;
+ using const_iterator = typename ContainerType::const_iterator;
+
+ RegisterContainer() = default;
+
+ RegisterContainer(Options opts) : RegisterContainer()
+ {
+ if ((opts & SEQUENTIAL) == SEQUENTIAL) {
+ sorted_ = false;
+ }
+ }
+
+ void init_reg(std::uint16_t address, Value default_value)
+ {
+ if (find_reg_index(address) >= 0) {
+ set(address, default_value);
+ return;
+ }
+ RegisterType reg;
+ reg.address = address;
+ reg.value = default_value;
+ registers_.push_back(reg);
+ if (sorted_)
+ std::sort(registers_.begin(), registers_.end());
+ }
+
+ bool has_reg(std::uint16_t address) const
+ {
+ return find_reg_index(address) >= 0;
+ }
+
+ void remove_reg(std::uint16_t address)
+ {
+ int i = find_reg_index(address);
+ if (i < 0) {
+ throw std::runtime_error("the register does not exist");
+ }
+ registers_.erase(registers_.begin() + i);
+ }
+
+ RegisterType& find_reg(std::uint16_t address)
+ {
+ int i = find_reg_index(address);
+ if (i < 0) {
+ throw std::runtime_error("the register does not exist");
+ }
+ return registers_[i];
+ }
+
+ const RegisterType& find_reg(std::uint16_t address) const
+ {
+ int i = find_reg_index(address);
+ if (i < 0) {
+ throw std::runtime_error("the register does not exist");
+ }
+ return registers_[i];
+ }
+
+ void set(std::uint16_t address, Value value)
+ {
+ find_reg(address).value = value;
+ }
+
+ Value get(std::uint16_t address) const
+ {
+ return find_reg(address).value;
+ }
+
+ void reserve(std::size_t size) { registers_.reserve(size); }
+ void clear() { registers_.clear(); }
+ std::size_t size() const { return registers_.size(); }
+
+ iterator begin() { return registers_.begin(); }
+ const_iterator begin() const { return registers_.begin(); }
+
+ iterator end() { return registers_.end(); }
+ const_iterator end() const { return registers_.end(); }
+
+private:
+ int find_reg_index(std::uint16_t address) const
+ {
+ if (!sorted_) {
+ for (std::size_t i = 0; i < registers_.size(); i++) {
+ if (registers_[i].address == address) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ RegisterType search;
+ search.address = address;
+ auto it = std::lower_bound(registers_.begin(), registers_.end(), search);
+ if (it == registers_.end())
+ return -1;
+ if (it->address != address)
+ return -1;
+ return std::distance(registers_.begin(), it);
+ }
+
+ // registers are stored in a sorted vector
+ bool sorted_ = true;
+ std::vector<RegisterType> registers_;
+};
+
+template<class Value>
+std::ostream& operator<<(std::ostream& out, const RegisterContainer<Value>& container)
+{
+ StreamStateSaver state_saver{out};
+
+ out << "RegisterContainer{\n";
+ out << std::hex;
+ out.fill('0');
+
+ for (const auto& reg : container) {
+ unsigned address_width = sizeof(reg.address) * 2;
+ unsigned value_width = sizeof(reg.value) * 2;
+
+ out << " 0x" << std::setw(address_width) << static_cast<unsigned>(reg.address)
+ << " = 0x" << std::setw(value_width) << static_cast<unsigned>(reg.value) << '\n';
+ }
+ out << "}";
+ return out;
+}
+
+class Genesys_Register_Set
+{
+public:
+ static constexpr unsigned MAX_REGS = 256;
+
+ using ContainerType = RegisterContainer<std::uint8_t>;
+ using iterator = typename ContainerType::iterator;
+ using const_iterator = typename ContainerType::const_iterator;
+
+ // FIXME: this shouldn't live here, but in a separate struct that contains Genesys_Register_Set
+ GenesysRegisterSetState state;
+
+ enum Options {
+ SEQUENTIAL = 1
+ };
+
+ Genesys_Register_Set()
+ {
+ registers_.reserve(MAX_REGS);
+ }
+
+ // by default the register set is sorted by address. In certain cases it's importand to send
+ // the registers in certain order: use the SEQUENTIAL option for that
+ Genesys_Register_Set(Options opts) : registers_{static_cast<ContainerType::Options>(opts)}
+ {
+ registers_.reserve(MAX_REGS);
+ }
+
+ const ContainerType& registers() const
+ {
+ return registers_;
+ }
+
+ void init_reg(std::uint16_t address, std::uint8_t default_value)
+ {
+ registers_.init_reg(address, default_value);
+ }
+
+ bool has_reg(std::uint16_t address) const { return registers_.has_reg(address); }
+
+ void remove_reg(std::uint16_t address) { registers_.remove_reg(address); }
+
+ GenesysRegister& find_reg(std::uint16_t address)
+ {
+ return registers_.find_reg(address);
+ }
+
+ const GenesysRegister& find_reg(std::uint16_t address) const
+ {
+ return registers_.find_reg(address);
+ }
+
+ GenesysRegister* find_reg_address(std::uint16_t address)
+ {
+ return &find_reg(address);
+ }
+
+ const GenesysRegister* find_reg_address(std::uint16_t address) const
+ {
+ return &find_reg(address);
+ }
+
+ void set8(std::uint16_t address, std::uint8_t value)
+ {
+ find_reg(address).value = value;
+ }
+
+ void set8_mask(std::uint16_t address, std::uint8_t value, std::uint8_t mask)
+ {
+ auto& reg = find_reg(address);
+ reg.value = (reg.value & ~mask) | value;
+ }
+
+ void set16(std::uint16_t address, std::uint16_t value)
+ {
+ find_reg(address).value = (value >> 8) & 0xff;
+ find_reg(address + 1).value = value & 0xff;
+ }
+
+ void set24(std::uint16_t address, std::uint32_t value)
+ {
+ find_reg(address).value = (value >> 16) & 0xff;
+ find_reg(address + 1).value = (value >> 8) & 0xff;
+ find_reg(address + 2).value = value & 0xff;
+ }
+
+ std::uint8_t get8(std::uint16_t address) const
+ {
+ return find_reg(address).value;
+ }
+
+ std::uint16_t get16(std::uint16_t address) const
+ {
+ return (find_reg(address).value << 8) | find_reg(address + 1).value;
+ }
+
+ std::uint32_t get24(std::uint16_t address) const
+ {
+ return (find_reg(address).value << 16) |
+ (find_reg(address + 1).value << 8) |
+ find_reg(address + 2).value;
+ }
+
+ void clear() { registers_.clear(); }
+ std::size_t size() const { return registers_.size(); }
+
+ iterator begin() { return registers_.begin(); }
+ const_iterator begin() const { return registers_.begin(); }
+
+ iterator end() { return registers_.end(); }
+ const_iterator end() const { return registers_.end(); }
+
+private:
+
+ // registers are stored in a sorted vector
+ ContainerType registers_;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const Genesys_Register_Set& regs)
+{
+ out << regs.registers();
+ return out;
+}
+
+template<class Value>
+struct RegisterSetting
+{
+ using ValueType = Value;
+ using AddressType = std::uint16_t;
+
+ RegisterSetting() = default;
+
+ RegisterSetting(AddressType p_address, ValueType p_value) :
+ address(p_address), value(p_value)
+ {}
+
+ RegisterSetting(AddressType p_address, ValueType p_value, ValueType p_mask) :
+ address(p_address), value(p_value), mask(p_mask)
+ {}
+
+ AddressType address = 0;
+ ValueType value = 0;
+ ValueType mask = 0xff;
+
+ bool operator==(const RegisterSetting& other) const
+ {
+ return address == other.address && value == other.value && mask == other.mask;
+ }
+};
+
+using GenesysRegisterSetting = RegisterSetting<std::uint8_t>;
+using GenesysRegisterSetting16 = RegisterSetting<std::uint16_t>;
+
+template<class Stream, class Value>
+void serialize(Stream& str, RegisterSetting<Value>& reg)
+{
+ serialize(str, reg.address);
+ serialize(str, reg.value);
+ serialize(str, reg.mask);
+}
+
+template<class Value>
+class RegisterSettingSet
+{
+public:
+ using ValueType = Value;
+ using SettingType = RegisterSetting<ValueType>;
+ using AddressType = typename SettingType::AddressType;
+
+ using container = std::vector<SettingType>;
+ using iterator = typename container::iterator;
+ using const_iterator = typename container::const_iterator;
+
+ RegisterSettingSet() = default;
+ RegisterSettingSet(std::initializer_list<SettingType> ilist) :
+ registers_(ilist)
+ {}
+
+ iterator begin() { return registers_.begin(); }
+ const_iterator begin() const { return registers_.begin(); }
+ iterator end() { return registers_.end(); }
+ const_iterator end() const { return registers_.end(); }
+
+ SettingType& operator[](std::size_t i) { return registers_[i]; }
+ const SettingType& operator[](std::size_t i) const { return registers_[i]; }
+
+ std::size_t size() const { return registers_.size(); }
+ bool empty() const { return registers_.empty(); }
+ void clear() { registers_.clear(); }
+
+ void push_back(SettingType reg) { registers_.push_back(reg); }
+
+ void merge(const RegisterSettingSet& other)
+ {
+ for (const auto& reg : other) {
+ set_value(reg.address, reg.value);
+ }
+ }
+
+ SettingType& find_reg(AddressType address)
+ {
+ int i = find_reg_index(address);
+ if (i < 0) {
+ throw std::runtime_error("the register does not exist");
+ }
+ return registers_[i];
+ }
+
+ const SettingType& find_reg(AddressType address) const
+ {
+ int i = find_reg_index(address);
+ if (i < 0) {
+ throw std::runtime_error("the register does not exist");
+ }
+ return registers_[i];
+ }
+
+ ValueType get_value(AddressType address) const
+ {
+ int index = find_reg_index(address);
+ if (index >= 0) {
+ return registers_[index].value;
+ }
+ throw std::out_of_range("Unknown register");
+ }
+
+ void set_value(AddressType address, ValueType value)
+ {
+ int index = find_reg_index(address);
+ if (index >= 0) {
+ registers_[index].value = value;
+ return;
+ }
+ push_back(SettingType(address, value));
+ }
+
+ template<class V>
+ friend void serialize(std::istream& str, RegisterSettingSet<V>& reg);
+ template<class V>
+ friend void serialize(std::ostream& str, RegisterSettingSet<V>& reg);
+
+ bool operator==(const RegisterSettingSet& other) const
+ {
+ return registers_ == other.registers_;
+ }
+
+private:
+
+ int find_reg_index(AddressType address) const
+ {
+ for (std::size_t i = 0; i < registers_.size(); i++) {
+ if (registers_[i].address == address) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ std::vector<SettingType> registers_;
+};
+
+using GenesysRegisterSettingSet = RegisterSettingSet<std::uint8_t>;
+using GenesysRegisterSettingSet16 = RegisterSettingSet<std::uint16_t>;
+
+template<class Value>
+std::ostream& operator<<(std::ostream& out, const RegisterSettingSet<Value>& container)
+{
+ StreamStateSaver state_saver{out};
+
+ out << "RegisterSettingSet{\n";
+ out << std::hex;
+ out.fill('0');
+
+ for (const auto& reg : container) {
+ unsigned address_width = sizeof(reg.address) * 2;
+ unsigned value_width = sizeof(reg.value) * 2;
+ unsigned mask_width = sizeof(reg.mask) * 2;
+
+ out << " 0x" << std::setw(address_width) << static_cast<unsigned>(reg.address)
+ << " = 0x" << std::setw(value_width) << static_cast<unsigned>(reg.value)
+ << " & 0x" << std::setw(mask_width) << static_cast<unsigned>(reg.mask) << '\n';
+ }
+ out << "}";
+ return out;
+}
+
+template<class Value>
+inline void serialize(std::istream& str, RegisterSettingSet<Value>& reg)
+{
+ using AddressType = typename RegisterSetting<Value>::AddressType;
+
+ reg.clear();
+ const std::size_t max_register_address = 1 << (sizeof(AddressType) * CHAR_BIT);
+ serialize(str, reg.registers_, max_register_address);
+}
+
+template<class Value>
+inline void serialize(std::ostream& str, RegisterSettingSet<Value>& reg)
+{
+ serialize(str, reg.registers_);
+}
+
+template<class F, class Value>
+void apply_registers_ordered(const RegisterSettingSet<Value>& set,
+ std::initializer_list<std::uint16_t> order, F f)
+{
+ for (std::uint16_t addr : order) {
+ f(set.find_reg(addr));
+ }
+ for (const auto& reg : set) {
+ if (std::find(order.begin(), order.end(), reg.address) != order.end()) {
+ continue;
+ }
+ f(reg);
+ }
+}
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_REGISTER_H
diff --git a/backend/genesys/register_cache.h b/backend/genesys/register_cache.h
new file mode 100644
index 0000000..dce701a
--- /dev/null
+++ b/backend/genesys/register_cache.h
@@ -0,0 +1,92 @@
+/* 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_REGISTER_CACHE_H
+#define BACKEND_GENESYS_REGISTER_CACHE_H
+
+#include "register.h"
+
+namespace genesys {
+
+template<class Value>
+class RegisterCache
+{
+public:
+ void update(std::uint16_t address, Value value)
+ {
+ if (regs_.has_reg(address)) {
+ regs_.set(address, value);
+ } else {
+ regs_.init_reg(address, value);
+ }
+ }
+
+ void update(const Genesys_Register_Set& regs)
+ {
+ for (const auto& reg : regs) {
+ update(reg.address, reg.value);
+ }
+ }
+
+ Value get(std::uint16_t address) const
+ {
+ return regs_.get(address);
+ }
+
+private:
+ RegisterContainer<Value> regs_;
+
+ template<class V>
+ friend std::ostream& operator<<(std::ostream& out, const RegisterCache<V>& cache);
+};
+
+template<class Value>
+std::ostream& operator<<(std::ostream& out, const RegisterCache<Value>& cache)
+{
+ out << cache.regs_;
+ return out;
+}
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_LINE_BUFFER_H
diff --git a/backend/genesys/row_buffer.h b/backend/genesys/row_buffer.h
new file mode 100644
index 0000000..e1a0c82
--- /dev/null
+++ b/backend/genesys/row_buffer.h
@@ -0,0 +1,214 @@
+/* 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 "error.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstddef>
+#include <vector>
+
+namespace genesys {
+
+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_;
+};
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_LINE_BUFFER_H
diff --git a/backend/genesys/scanner_interface.cpp b/backend/genesys/scanner_interface.cpp
new file mode 100644
index 0000000..0b60b66
--- /dev/null
+++ b/backend/genesys/scanner_interface.cpp
@@ -0,0 +1,52 @@
+/* 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 "scanner_interface.h"
+
+namespace genesys {
+
+ScannerInterface::~ScannerInterface() = default;
+
+} // namespace genesys
diff --git a/backend/genesys/scanner_interface.h b/backend/genesys/scanner_interface.h
new file mode 100644
index 0000000..03c7132
--- /dev/null
+++ b/backend/genesys/scanner_interface.h
@@ -0,0 +1,112 @@
+/* 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_SCANNER_INTERFACE_H
+#define BACKEND_GENESYS_SCANNER_INTERFACE_H
+
+#include "fwd.h"
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace genesys {
+
+// Represents an interface through which all low level operations are performed.
+class ScannerInterface
+{
+public:
+ enum Flags {
+ FLAG_NONE = 0,
+ FLAG_SWAP_REGISTERS = 1 << 0,
+ FLAG_SMALL_ADDRESS = 1 << 1
+ };
+
+ virtual ~ScannerInterface();
+
+ virtual bool is_mock() const = 0;
+
+ virtual std::uint8_t read_register(std::uint16_t address) = 0;
+ virtual void write_register(std::uint16_t address, std::uint8_t value) = 0;
+ virtual void write_registers(const Genesys_Register_Set& regs) = 0;
+
+ virtual void write_0x8c(std::uint8_t index, std::uint8_t value) = 0;
+ virtual void bulk_read_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) = 0;
+ virtual void bulk_write_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) = 0;
+
+ // GL646, GL841, GL843 have different ways to write to RAM and to gamma tables
+ // FIXME: remove flags when updating tests
+ virtual void write_buffer(std::uint8_t type, std::uint32_t addr, std::uint8_t* data,
+ std::size_t size, Flags flags = FLAG_NONE) = 0;
+
+ virtual void write_gamma(std::uint8_t type, std::uint32_t addr, std::uint8_t* data,
+ std::size_t size, Flags flags = FLAG_NONE) = 0;
+
+ // GL845, GL846, GL847 and GL124 have a uniform way to write to RAM tables
+ virtual void write_ahb(std::uint32_t addr, std::uint32_t size, std::uint8_t* data) = 0;
+
+ virtual std::uint16_t read_fe_register(std::uint8_t address) = 0;
+ virtual void write_fe_register(std::uint8_t address, std::uint16_t value) = 0;
+
+ virtual IUsbDevice& get_usb_device() = 0;
+
+ // sleeps the specified number of microseconds. Will not sleep if testing mode is enabled.
+ virtual void sleep_us(unsigned microseconds) = 0;
+
+ void sleep_ms(unsigned milliseconds)
+ {
+ sleep_us(milliseconds * 1000);
+ }
+
+ virtual void record_progress_message(const char* msg) = 0;
+
+ virtual void record_slope_table(unsigned table_nr, const std::vector<std::uint16_t>& steps) = 0;
+
+ virtual void record_key_value(const std::string& key, const std::string& value) = 0;
+
+ virtual void test_checkpoint(const std::string& name) = 0;
+};
+
+} // namespace genesys
+
+#endif
diff --git a/backend/genesys/scanner_interface_usb.cpp b/backend/genesys/scanner_interface_usb.cpp
new file mode 100644
index 0000000..d4d83dd
--- /dev/null
+++ b/backend/genesys/scanner_interface_usb.cpp
@@ -0,0 +1,515 @@
+/* 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 "scanner_interface_usb.h"
+#include "low.h"
+#include <thread>
+
+namespace genesys {
+
+ScannerInterfaceUsb::~ScannerInterfaceUsb() = default;
+
+ScannerInterfaceUsb::ScannerInterfaceUsb(Genesys_Device* dev) : dev_{dev} {}
+
+bool ScannerInterfaceUsb::is_mock() const
+{
+ return false;
+}
+
+std::uint8_t ScannerInterfaceUsb::read_register(std::uint16_t address)
+{
+ DBG_HELPER(dbg);
+
+ std::uint8_t value = 0;
+
+ if (dev_->model->asic_type == AsicType::GL847 ||
+ dev_->model->asic_type == AsicType::GL845 ||
+ dev_->model->asic_type == AsicType::GL846 ||
+ dev_->model->asic_type == AsicType::GL124)
+ {
+ std::uint8_t value2x8[2];
+ std::uint16_t address16 = 0x22 + (address << 8);
+
+ std::uint16_t usb_value = VALUE_GET_REGISTER;
+ if (address > 0xff) {
+ usb_value |= 0x100;
+ }
+
+ usb_dev_.control_msg(REQUEST_TYPE_IN, REQUEST_BUFFER, usb_value, address16, 2, value2x8);
+
+ // check usb link status
+ if (value2x8[1] != 0x55) {
+ throw SaneException(SANE_STATUS_IO_ERROR, "invalid read, scanner unplugged?");
+ }
+
+ DBG(DBG_io, "%s (0x%02x, 0x%02x) completed\n", __func__, address, value2x8[0]);
+
+ value = value2x8[0];
+
+ } else {
+
+ if (address > 0xff) {
+ throw SaneException("Invalid register address 0x%04x", address);
+ }
+
+ std::uint8_t address8 = address & 0xff;
+
+ usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, INDEX,
+ 1, &address8);
+ usb_dev_.control_msg(REQUEST_TYPE_IN, REQUEST_REGISTER, VALUE_READ_REGISTER, INDEX,
+ 1, &value);
+ }
+
+ DBG(DBG_proc, "%s (0x%02x, 0x%02x) completed\n", __func__, address, value);
+ return value;
+}
+
+void ScannerInterfaceUsb::write_register(std::uint16_t address, std::uint8_t value)
+{
+ DBG_HELPER_ARGS(dbg, "address: 0x%04x, value: 0x%02x", static_cast<unsigned>(address),
+ static_cast<unsigned>(value));
+
+ if (dev_->model->asic_type == AsicType::GL847 ||
+ dev_->model->asic_type == AsicType::GL845 ||
+ dev_->model->asic_type == AsicType::GL846 ||
+ dev_->model->asic_type == AsicType::GL124)
+ {
+ std::uint8_t buffer[2];
+
+ buffer[0] = address & 0xff;
+ buffer[1] = value;
+
+ std::uint16_t usb_value = VALUE_SET_REGISTER;
+ if (address > 0xff) {
+ usb_value |= 0x100;
+ }
+
+ usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, usb_value, INDEX,
+ 2, buffer);
+
+ } else {
+ if (address > 0xff) {
+ throw SaneException("Invalid register address 0x%04x", address);
+ }
+
+ std::uint8_t address8 = address & 0xff;
+
+ usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, INDEX,
+ 1, &address8);
+
+ usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_WRITE_REGISTER, INDEX,
+ 1, &value);
+
+ }
+ DBG(DBG_io, "%s (0x%02x, 0x%02x) completed\n", __func__, address, value);
+}
+
+void ScannerInterfaceUsb::write_registers(const Genesys_Register_Set& regs)
+{
+ DBG_HELPER(dbg);
+ if (dev_->model->asic_type == AsicType::GL646 ||
+ dev_->model->asic_type == AsicType::GL841)
+ {
+ uint8_t outdata[8];
+ std::vector<uint8_t> buffer;
+ buffer.reserve(regs.size() * 2);
+
+ /* copy registers and values in data buffer */
+ for (const auto& r : regs) {
+ buffer.push_back(r.address);
+ buffer.push_back(r.value);
+ }
+
+ DBG(DBG_io, "%s (elems= %zu, size = %zu)\n", __func__, regs.size(), buffer.size());
+
+ if (dev_->model->asic_type == AsicType::GL646) {
+ outdata[0] = BULK_OUT;
+ outdata[1] = BULK_REGISTER;
+ outdata[2] = 0x00;
+ outdata[3] = 0x00;
+ outdata[4] = (buffer.size() & 0xff);
+ outdata[5] = ((buffer.size() >> 8) & 0xff);
+ outdata[6] = ((buffer.size() >> 16) & 0xff);
+ outdata[7] = ((buffer.size() >> 24) & 0xff);
+
+ usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, INDEX,
+ sizeof(outdata), outdata);
+
+ size_t write_size = buffer.size();
+
+ usb_dev_.bulk_write(buffer.data(), &write_size);
+ } else {
+ for (std::size_t i = 0; i < regs.size();) {
+ std::size_t c = regs.size() - i;
+ if (c > 32) /*32 is max on GL841. checked that.*/
+ c = 32;
+
+ usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_SET_REGISTER,
+ INDEX, c * 2, buffer.data() + i * 2);
+
+ i += c;
+ }
+ }
+ } else {
+ for (const auto& r : regs) {
+ write_register(r.address, r.value);
+ }
+ }
+
+ DBG(DBG_io, "%s: wrote %zu registers\n", __func__, regs.size());
+}
+
+void ScannerInterfaceUsb::write_0x8c(std::uint8_t index, std::uint8_t value)
+{
+ DBG_HELPER_ARGS(dbg, "0x%02x,0x%02x", index, value);
+ usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_BUF_ENDACCESS, index, 1, &value);
+}
+
+static void bulk_read_data_send_header(UsbDevice& usb_dev, AsicType asic_type, size_t size)
+{
+ DBG_HELPER(dbg);
+
+ uint8_t outdata[8];
+ if (asic_type == AsicType::GL124 ||
+ asic_type == AsicType::GL846 ||
+ asic_type == AsicType::GL847)
+ {
+ // hard coded 0x10000000 address
+ outdata[0] = 0;
+ outdata[1] = 0;
+ outdata[2] = 0;
+ outdata[3] = 0x10;
+ } else if (asic_type == AsicType::GL841 ||
+ asic_type == AsicType::GL843) {
+ outdata[0] = BULK_IN;
+ outdata[1] = BULK_RAM;
+ outdata[2] = 0x82; //
+ outdata[3] = 0x00;
+ } else {
+ outdata[0] = BULK_IN;
+ outdata[1] = BULK_RAM;
+ outdata[2] = 0x00;
+ outdata[3] = 0x00;
+ }
+
+ /* data size to transfer */
+ outdata[4] = (size & 0xff);
+ outdata[5] = ((size >> 8) & 0xff);
+ outdata[6] = ((size >> 16) & 0xff);
+ outdata[7] = ((size >> 24) & 0xff);
+
+ usb_dev.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, 0x00,
+ sizeof(outdata), outdata);
+}
+
+void ScannerInterfaceUsb::bulk_read_data(std::uint8_t addr, std::uint8_t* data, std::size_t size)
+{
+ // currently supported: GL646, GL841, GL843, GL846, GL847, GL124
+ DBG_HELPER(dbg);
+
+ unsigned is_addr_used = 1;
+ unsigned has_header_before_each_chunk = 0;
+ if (dev_->model->asic_type == AsicType::GL124 ||
+ dev_->model->asic_type == AsicType::GL846 ||
+ dev_->model->asic_type == AsicType::GL847)
+ {
+ is_addr_used = 0;
+ has_header_before_each_chunk = 1;
+ }
+
+ if (is_addr_used) {
+ DBG(DBG_io, "%s: requesting %zu bytes from 0x%02x addr\n", __func__, size, addr);
+ } else {
+ DBG(DBG_io, "%s: requesting %zu bytes\n", __func__, size);
+ }
+
+ if (size == 0)
+ return;
+
+ if (is_addr_used) {
+ usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, 0x00,
+ 1, &addr);
+ }
+
+ std::size_t target_size = size;
+
+ std::size_t max_in_size = sanei_genesys_get_bulk_max_size(dev_->model->asic_type);
+
+ if (!has_header_before_each_chunk) {
+ bulk_read_data_send_header(usb_dev_, dev_->model->asic_type, size);
+ }
+
+ // loop until computed data size is read
+ while (target_size > 0) {
+ std::size_t block_size = std::min(target_size, max_in_size);
+
+ if (has_header_before_each_chunk) {
+ bulk_read_data_send_header(usb_dev_, dev_->model->asic_type, block_size);
+ }
+
+ DBG(DBG_io2, "%s: trying to read %zu bytes of data\n", __func__, block_size);
+
+ usb_dev_.bulk_read(data, &block_size);
+
+ DBG(DBG_io2, "%s: read %zu bytes, %zu remaining\n", __func__, block_size, target_size - block_size);
+
+ target_size -= block_size;
+ data += block_size;
+ }
+}
+
+void ScannerInterfaceUsb::bulk_write_data(std::uint8_t addr, std::uint8_t* data, std::size_t len)
+{
+ DBG_HELPER_ARGS(dbg, "writing %zu bytes", len);
+
+ // supported: GL646, GL841, GL843
+ std::size_t size;
+ std::uint8_t outdata[8];
+
+ usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_REGISTER, VALUE_SET_REGISTER, INDEX,
+ 1, &addr);
+
+ std::size_t max_out_size = sanei_genesys_get_bulk_max_size(dev_->model->asic_type);
+
+ while (len) {
+ if (len > max_out_size)
+ size = max_out_size;
+ else
+ size = len;
+
+ if (dev_->model->asic_type == AsicType::GL841) {
+ outdata[0] = BULK_OUT;
+ outdata[1] = BULK_RAM;
+ // both 0x82 and 0x00 works on GL841.
+ outdata[2] = 0x82;
+ outdata[3] = 0x00;
+ } else {
+ outdata[0] = BULK_OUT;
+ outdata[1] = BULK_RAM;
+ // 8600F uses 0x82, but 0x00 works too. 8400F uses 0x02 for certain transactions.
+ outdata[2] = 0x00;
+ outdata[3] = 0x00;
+ }
+
+ outdata[4] = (size & 0xff);
+ outdata[5] = ((size >> 8) & 0xff);
+ outdata[6] = ((size >> 16) & 0xff);
+ outdata[7] = ((size >> 24) & 0xff);
+
+ usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, 0x00,
+ sizeof(outdata), outdata);
+
+ usb_dev_.bulk_write(data, &size);
+
+ DBG(DBG_io2, "%s: wrote %zu bytes, %zu remaining\n", __func__, size, len - size);
+
+ len -= size;
+ data += size;
+ }
+}
+
+void ScannerInterfaceUsb::write_buffer(std::uint8_t type, std::uint32_t addr, std::uint8_t* data,
+ std::size_t size, Flags flags)
+{
+ DBG_HELPER_ARGS(dbg, "type: 0x%02x, addr: 0x%08x, size: 0x%08zx", type, addr, size);
+ if (dev_->model->asic_type != AsicType::GL646 &&
+ dev_->model->asic_type != AsicType::GL841 &&
+ dev_->model->asic_type != AsicType::GL843)
+ {
+ throw SaneException("Unsupported transfer mode");
+ }
+
+ if (dev_->model->asic_type == AsicType::GL843) {
+ if (flags & FLAG_SWAP_REGISTERS) {
+ if (!(flags & FLAG_SMALL_ADDRESS)) {
+ write_register(0x29, ((addr >> 20) & 0xff));
+ }
+ write_register(0x2a, ((addr >> 12) & 0xff));
+ write_register(0x2b, ((addr >> 4) & 0xff));
+ } else {
+ write_register(0x2b, ((addr >> 4) & 0xff));
+ write_register(0x2a, ((addr >> 12) & 0xff));
+ if (!(flags & FLAG_SMALL_ADDRESS)) {
+ write_register(0x29, ((addr >> 20) & 0xff));
+ }
+ }
+ } else {
+ write_register(0x2b, ((addr >> 4) & 0xff));
+ write_register(0x2a, ((addr >> 12) & 0xff));
+ }
+ bulk_write_data(type, data, size);
+}
+
+void ScannerInterfaceUsb::write_gamma(std::uint8_t type, std::uint32_t addr, std::uint8_t* data,
+ std::size_t size, Flags flags)
+{
+ DBG_HELPER_ARGS(dbg, "type: 0x%02x, addr: 0x%08x, size: 0x%08zx", type, addr, size);
+ if (dev_->model->asic_type != AsicType::GL646 &&
+ dev_->model->asic_type != AsicType::GL841 &&
+ dev_->model->asic_type != AsicType::GL843)
+ {
+ throw SaneException("Unsupported transfer mode");
+ }
+
+ if (flags & FLAG_SWAP_REGISTERS) {
+ write_register(0x5b, ((addr >> 12) & 0xff));
+ write_register(0x5c, ((addr >> 4) & 0xff));
+ } else {
+ write_register(0x5c, ((addr >> 4) & 0xff));
+ write_register(0x5b, ((addr >> 12) & 0xff));
+ }
+ bulk_write_data(type, data, size);
+}
+
+void ScannerInterfaceUsb::write_ahb(std::uint32_t addr, std::uint32_t size, std::uint8_t* data)
+{
+ DBG_HELPER_ARGS(dbg, "address: 0x%08x, size: %d", static_cast<unsigned>(addr),
+ static_cast<unsigned>(size));
+
+ if (dev_->model->asic_type != AsicType::GL845 &&
+ dev_->model->asic_type != AsicType::GL846 &&
+ dev_->model->asic_type != AsicType::GL847 &&
+ dev_->model->asic_type != AsicType::GL124)
+ {
+ throw SaneException("Unsupported transfer type");
+ }
+ std::uint8_t outdata[8];
+ outdata[0] = addr & 0xff;
+ outdata[1] = ((addr >> 8) & 0xff);
+ outdata[2] = ((addr >> 16) & 0xff);
+ outdata[3] = ((addr >> 24) & 0xff);
+ outdata[4] = (size & 0xff);
+ outdata[5] = ((size >> 8) & 0xff);
+ outdata[6] = ((size >> 16) & 0xff);
+ outdata[7] = ((size >> 24) & 0xff);
+
+ // write addr and size for AHB
+ usb_dev_.control_msg(REQUEST_TYPE_OUT, REQUEST_BUFFER, VALUE_BUFFER, 0x01, 8, outdata);
+
+ std::size_t max_out_size = sanei_genesys_get_bulk_max_size(dev_->model->asic_type);
+
+ // write actual data
+ std::size_t written = 0;
+ do {
+ std::size_t block_size = std::min(size - written, max_out_size);
+
+ usb_dev_.bulk_write(data + written, &block_size);
+
+ written += block_size;
+ } while (written < size);
+}
+
+std::uint16_t ScannerInterfaceUsb::read_fe_register(std::uint8_t address)
+{
+ DBG_HELPER(dbg);
+ Genesys_Register_Set reg;
+
+ reg.init_reg(0x50, address);
+
+ // set up read address
+ write_registers(reg);
+
+ // read data
+ std::uint16_t value = read_register(0x46) << 8;
+ value |= read_register(0x47);
+
+ DBG(DBG_io, "%s (0x%02x, 0x%04x)\n", __func__, address, value);
+ return value;
+}
+
+void ScannerInterfaceUsb::write_fe_register(std::uint8_t address, std::uint16_t value)
+{
+ DBG_HELPER_ARGS(dbg, "0x%02x, 0x%04x", address, value);
+ Genesys_Register_Set reg(Genesys_Register_Set::SEQUENTIAL);
+
+ reg.init_reg(0x51, address);
+ if (dev_->model->asic_type == AsicType::GL124) {
+ reg.init_reg(0x5d, (value / 256) & 0xff);
+ reg.init_reg(0x5e, value & 0xff);
+ } else {
+ reg.init_reg(0x3a, (value / 256) & 0xff);
+ reg.init_reg(0x3b, value & 0xff);
+ }
+
+ write_registers(reg);
+}
+
+IUsbDevice& ScannerInterfaceUsb::get_usb_device()
+{
+ return usb_dev_;
+}
+
+void ScannerInterfaceUsb::sleep_us(unsigned microseconds)
+{
+ if (sanei_usb_is_replay_mode_enabled()) {
+ return;
+ }
+ std::this_thread::sleep_for(std::chrono::microseconds{microseconds});
+}
+
+void ScannerInterfaceUsb::record_progress_message(const char* msg)
+{
+ sanei_usb_testing_record_message(msg);
+}
+
+void ScannerInterfaceUsb::record_slope_table(unsigned table_nr,
+ const std::vector<std::uint16_t>& steps)
+{
+ (void) table_nr;
+ (void) steps;
+}
+
+void ScannerInterfaceUsb::record_key_value(const std::string& key, const std::string& value)
+{
+ (void) key;
+ (void) value;
+}
+
+void ScannerInterfaceUsb::test_checkpoint(const std::string& name)
+{
+ (void) name;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/scanner_interface_usb.h b/backend/genesys/scanner_interface_usb.h
new file mode 100644
index 0000000..06b51ff
--- /dev/null
+++ b/backend/genesys/scanner_interface_usb.h
@@ -0,0 +1,98 @@
+/* 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_SCANNER_INTERFACE_USB_H
+#define BACKEND_GENESYS_SCANNER_INTERFACE_USB_H
+
+#include "scanner_interface.h"
+#include "usb_device.h"
+
+namespace genesys {
+
+class ScannerInterfaceUsb : public ScannerInterface
+{
+public:
+ ScannerInterfaceUsb(Genesys_Device* dev);
+
+ ~ScannerInterfaceUsb() override;
+
+ bool is_mock() const override;
+
+ std::uint8_t read_register(std::uint16_t address) override;
+ void write_register(std::uint16_t address, std::uint8_t value) override;
+ void write_registers(const Genesys_Register_Set& regs) override;
+
+ void write_0x8c(std::uint8_t index, std::uint8_t value) override;
+ void bulk_read_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) override;
+ void bulk_write_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) override;
+
+ void write_buffer(std::uint8_t type, std::uint32_t addr, std::uint8_t* data,
+ std::size_t size, Flags flags) override;
+ void write_gamma(std::uint8_t type, std::uint32_t addr, std::uint8_t* data,
+ std::size_t size, Flags flags) override;
+
+ void write_ahb(std::uint32_t addr, std::uint32_t size, std::uint8_t* data) override;
+
+ std::uint16_t read_fe_register(std::uint8_t address) override;
+ void write_fe_register(std::uint8_t address, std::uint16_t value) override;
+
+ IUsbDevice& get_usb_device() override;
+
+ void sleep_us(unsigned microseconds) override;
+
+ void record_progress_message(const char* msg) override;
+
+ void record_slope_table(unsigned table_nr, const std::vector<std::uint16_t>& steps) override;
+
+ void record_key_value(const std::string& key, const std::string& value) override;
+
+ void test_checkpoint(const std::string& name) override;
+
+private:
+ Genesys_Device* dev_;
+ UsbDevice usb_dev_;
+};
+
+} // namespace genesys
+
+#endif
diff --git a/backend/genesys/sensor.cpp b/backend/genesys/sensor.cpp
new file mode 100644
index 0000000..e54af65
--- /dev/null
+++ b/backend/genesys/sensor.cpp
@@ -0,0 +1,160 @@
+/* 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 "sensor.h"
+#include "utilities.h"
+#include <iomanip>
+
+namespace genesys {
+
+std::ostream& operator<<(std::ostream& out, const StaggerConfig& config)
+{
+ out << "StaggerConfig{\n"
+ << " min_resolution: " << config.min_resolution() << '\n'
+ << " lines_at_min: " << config.lines_at_min() << '\n'
+ << "}";
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const FrontendType& type)
+{
+ switch (type) {
+ case FrontendType::UNKNOWN: out << "UNKNOWN"; break;
+ case FrontendType::WOLFSON: out << "WOLFSON"; break;
+ case FrontendType::ANALOG_DEVICES: out << "ANALOG_DEVICES"; break;
+ default: out << "(unknown value)";
+ }
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const GenesysFrontendLayout& layout)
+{
+ StreamStateSaver state_saver{out};
+
+ out << "GenesysFrontendLayout{\n"
+ << " type: " << layout.type << '\n'
+ << std::hex
+ << " offset_addr[0]: " << layout.offset_addr[0] << '\n'
+ << " offset_addr[1]: " << layout.offset_addr[1] << '\n'
+ << " offset_addr[2]: " << layout.offset_addr[2] << '\n'
+ << " gain_addr[0]: " << layout.gain_addr[0] << '\n'
+ << " gain_addr[1]: " << layout.gain_addr[1] << '\n'
+ << " gain_addr[2]: " << layout.gain_addr[2] << '\n'
+ << '}';
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Genesys_Frontend& frontend)
+{
+ StreamStateSaver state_saver{out};
+
+ out << "Genesys_Frontend{\n"
+ << " id: " << static_cast<unsigned>(frontend.id) << '\n'
+ << " regs: " << format_indent_braced_list(4, frontend.regs) << '\n'
+ << std::hex
+ << " reg2[0]: " << frontend.reg2[0] << '\n'
+ << " reg2[1]: " << frontend.reg2[1] << '\n'
+ << " reg2[2]: " << frontend.reg2[2] << '\n'
+ << " layout: " << format_indent_braced_list(4, frontend.layout) << '\n'
+ << '}';
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const SensorExposure& exposure)
+{
+ out << "SensorExposure{\n"
+ << " red: " << exposure.red << '\n'
+ << " green: " << exposure.green << '\n'
+ << " blue: " << exposure.blue << '\n'
+ << '}';
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const ResolutionFilter& resolutions)
+{
+ if (resolutions.matches_any()) {
+ out << "ANY";
+ return out;
+ }
+ out << format_vector_unsigned(4, resolutions.resolutions());
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Genesys_Sensor& sensor)
+{
+ out << "Genesys_Sensor{\n"
+ << " sensor_id: " << static_cast<unsigned>(sensor.sensor_id) << '\n'
+ << " optical_res: " << sensor.optical_res << '\n'
+ << " resolutions: " << format_indent_braced_list(4, sensor.resolutions) << '\n'
+ << " channels: " << format_vector_unsigned(4, sensor.channels) << '\n'
+ << " method: " << sensor.method << '\n'
+ << " register_dpihw_override: " << sensor.register_dpihw_override << '\n'
+ << " logical_dpihw_override: " << sensor.logical_dpihw_override << '\n'
+ << " dpiset_override: " << sensor.dpiset_override << '\n'
+ << " ccd_size_divisor: " << sensor.ccd_size_divisor << '\n'
+ << " pixel_count_multiplier: " << sensor.pixel_count_multiplier << '\n'
+ << " black_pixels: " << sensor.black_pixels << '\n'
+ << " dummy_pixel: " << sensor.dummy_pixel << '\n'
+ << " ccd_start_xoffset: " << sensor.ccd_start_xoffset << '\n'
+ << " sensor_pixels: " << sensor.sensor_pixels << '\n'
+ << " fau_gain_white_ref: " << sensor.fau_gain_white_ref << '\n'
+ << " gain_white_ref: " << sensor.gain_white_ref << '\n'
+ << " exposure: " << format_indent_braced_list(4, sensor.exposure) << '\n'
+ << " exposure_lperiod: " << sensor.exposure_lperiod << '\n'
+ << " segment_size: " << sensor.segment_size << '\n'
+ << " segment_order: "
+ << format_indent_braced_list(4, format_vector_unsigned(4, sensor.segment_order)) << '\n'
+ << " stagger_config: " << format_indent_braced_list(4, sensor.stagger_config) << '\n'
+ << " custom_base_regs: " << format_indent_braced_list(4, sensor.custom_base_regs) << '\n'
+ << " custom_regs: " << format_indent_braced_list(4, sensor.custom_regs) << '\n'
+ << " custom_fe_regs: " << format_indent_braced_list(4, sensor.custom_fe_regs) << '\n'
+ << " gamma.red: " << sensor.gamma[0] << '\n'
+ << " gamma.green: " << sensor.gamma[1] << '\n'
+ << " gamma.blue: " << sensor.gamma[2] << '\n'
+ << "}";
+ return out;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/sensor.h b/backend/genesys/sensor.h
new file mode 100644
index 0000000..e70728e
--- /dev/null
+++ b/backend/genesys/sensor.h
@@ -0,0 +1,470 @@
+/* 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_SENSOR_H
+#define BACKEND_GENESYS_SENSOR_H
+
+#include "enums.h"
+#include "register.h"
+#include "serialize.h"
+#include <array>
+#include <functional>
+
+namespace genesys {
+
+template<class T, size_t Size>
+struct AssignableArray : public std::array<T, Size> {
+ AssignableArray() = default;
+ AssignableArray(const AssignableArray&) = default;
+ AssignableArray& operator=(const AssignableArray&) = default;
+
+ AssignableArray& operator=(std::initializer_list<T> init)
+ {
+ if (init.size() != std::array<T, Size>::size())
+ throw std::runtime_error("An array of incorrect size assigned");
+ std::copy(init.begin(), init.end(), std::array<T, Size>::begin());
+ return *this;
+ }
+};
+
+
+class StaggerConfig
+{
+public:
+ StaggerConfig() = default;
+ StaggerConfig(unsigned min_resolution, unsigned lines_at_min) :
+ min_resolution_{min_resolution},
+ lines_at_min_{lines_at_min}
+ {
+ }
+
+ unsigned stagger_at_resolution(unsigned xresolution, unsigned yresolution) const
+ {
+ if (min_resolution_ == 0 || xresolution < min_resolution_)
+ return 0;
+ return yresolution / min_resolution_ * lines_at_min_;
+ }
+
+ unsigned min_resolution() const { return min_resolution_; }
+ unsigned lines_at_min() const { return lines_at_min_; }
+
+ bool operator==(const StaggerConfig& other) const
+ {
+ return min_resolution_ == other.min_resolution_ &&
+ lines_at_min_ == other.lines_at_min_;
+ }
+
+private:
+ unsigned min_resolution_ = 0;
+ unsigned lines_at_min_ = 0;
+
+ template<class Stream>
+ friend void serialize(Stream& str, StaggerConfig& x);
+};
+
+template<class Stream>
+void serialize(Stream& str, StaggerConfig& x)
+{
+ serialize(str, x.min_resolution_);
+ serialize(str, x.lines_at_min_);
+}
+
+std::ostream& operator<<(std::ostream& out, const StaggerConfig& config);
+
+
+enum class FrontendType : unsigned
+{
+ UNKNOWN,
+ WOLFSON,
+ ANALOG_DEVICES
+};
+
+inline void serialize(std::istream& str, FrontendType& x)
+{
+ unsigned value;
+ serialize(str, value);
+ x = static_cast<FrontendType>(value);
+}
+
+inline void serialize(std::ostream& str, FrontendType& x)
+{
+ unsigned value = static_cast<unsigned>(x);
+ serialize(str, value);
+}
+
+std::ostream& operator<<(std::ostream& out, const FrontendType& type);
+
+struct GenesysFrontendLayout
+{
+ FrontendType type = FrontendType::UNKNOWN;
+ std::array<std::uint16_t, 3> offset_addr = {};
+ std::array<std::uint16_t, 3> gain_addr = {};
+
+ bool operator==(const GenesysFrontendLayout& other) const
+ {
+ return type == other.type &&
+ offset_addr == other.offset_addr &&
+ gain_addr == other.gain_addr;
+ }
+};
+
+template<class Stream>
+void serialize(Stream& str, GenesysFrontendLayout& x)
+{
+ serialize(str, x.type);
+ serialize_newline(str);
+ serialize(str, x.offset_addr);
+ serialize_newline(str);
+ serialize(str, x.gain_addr);
+}
+
+std::ostream& operator<<(std::ostream& out, const GenesysFrontendLayout& layout);
+
+/** @brief Data structure to set up analog frontend.
+ The analog frontend converts analog value from image sensor to digital value. It has its own
+ control registers which are set up with this structure. The values are written using
+ fe_write_data.
+ */
+struct Genesys_Frontend
+{
+ Genesys_Frontend() = default;
+
+ // id of the frontend description
+ AdcId id = AdcId::UNKNOWN;
+
+ // all registers of the frontend. Note that the registers can hold 9-bit values
+ RegisterSettingSet<std::uint16_t> regs;
+
+ // extra control registers
+ std::array<std::uint16_t, 3> reg2 = {};
+
+ GenesysFrontendLayout layout;
+
+ void set_offset(unsigned which, std::uint16_t value)
+ {
+ regs.set_value(layout.offset_addr[which], value);
+ }
+
+ void set_gain(unsigned which, std::uint16_t value)
+ {
+ regs.set_value(layout.gain_addr[which], value);
+ }
+
+ std::uint16_t get_offset(unsigned which) const
+ {
+ return regs.get_value(layout.offset_addr[which]);
+ }
+
+ std::uint16_t get_gain(unsigned which) const
+ {
+ return regs.get_value(layout.gain_addr[which]);
+ }
+
+ bool operator==(const Genesys_Frontend& other) const
+ {
+ return id == other.id &&
+ regs == other.regs &&
+ reg2 == other.reg2 &&
+ layout == other.layout;
+ }
+};
+
+std::ostream& operator<<(std::ostream& out, const Genesys_Frontend& frontend);
+
+template<class Stream>
+void serialize(Stream& str, Genesys_Frontend& x)
+{
+ serialize(str, x.id);
+ serialize_newline(str);
+ serialize(str, x.regs);
+ serialize_newline(str);
+ serialize(str, x.reg2);
+ serialize_newline(str);
+ serialize(str, x.layout);
+}
+
+struct SensorExposure {
+ std::uint16_t red = 0;
+ std::uint16_t green = 0;
+ std::uint16_t blue = 0;
+
+ SensorExposure() = default;
+ SensorExposure(std::uint16_t r, std::uint16_t g, std::uint16_t b) :
+ red{r}, green{g}, blue{b}
+ {}
+
+ bool operator==(const SensorExposure& other) const
+ {
+ return red == other.red && green == other.green && blue == other.blue;
+ }
+};
+
+std::ostream& operator<<(std::ostream& out, const SensorExposure& exposure);
+
+
+class ResolutionFilter
+{
+public:
+ struct Any {};
+ static constexpr Any ANY{};
+
+ ResolutionFilter() : matches_any_{false} {}
+ ResolutionFilter(Any) : matches_any_{true} {}
+ ResolutionFilter(std::initializer_list<unsigned> resolutions) :
+ matches_any_{false},
+ resolutions_{resolutions}
+ {}
+
+ bool matches(unsigned resolution) const
+ {
+ if (matches_any_)
+ return true;
+ auto it = std::find(resolutions_.begin(), resolutions_.end(), resolution);
+ return it != resolutions_.end();
+ }
+
+ bool operator==(const ResolutionFilter& other) const
+ {
+ return matches_any_ == other.matches_any_ && resolutions_ == other.resolutions_;
+ }
+
+ bool matches_any() const { return matches_any_; }
+ const std::vector<unsigned>& resolutions() const { return resolutions_; }
+
+private:
+ bool matches_any_ = false;
+ std::vector<unsigned> resolutions_;
+
+ template<class Stream>
+ friend void serialize(Stream& str, ResolutionFilter& x);
+};
+
+std::ostream& operator<<(std::ostream& out, const ResolutionFilter& resolutions);
+
+template<class Stream>
+void serialize(Stream& str, ResolutionFilter& x)
+{
+ serialize(str, x.matches_any_);
+ serialize_newline(str);
+ serialize(str, x.resolutions_);
+}
+
+
+struct Genesys_Sensor {
+
+ Genesys_Sensor() = default;
+ ~Genesys_Sensor() = default;
+
+ // id of the sensor description
+ SensorId sensor_id = SensorId::UNKNOWN;
+
+ // sensor resolution in CCD pixels. Note that we may read more than one CCD pixel per logical
+ // pixel, see ccd_pixels_per_system_pixel()
+ unsigned optical_res = 0;
+
+ // the resolution list that the sensor is usable at.
+ ResolutionFilter resolutions = ResolutionFilter::ANY;
+
+ // the channel list that the sensor is usable at
+ std::vector<unsigned> channels = { 1, 3 };
+
+ // the scan method used with the sensor
+ ScanMethod method = ScanMethod::FLATBED;
+
+ // The scanner may be setup to use a custom dpihw that does not correspond to any actual
+ // resolution. The value zero does not set the override.
+ unsigned register_dpihw_override = 0;
+
+ // The scanner may be setup to use a custom logical dpihw that does not correspond to any actual
+ // resolution. The value zero does not set the override.
+ unsigned logical_dpihw_override = 0;
+
+ // The scanner may be setup to use a custom dpiset value that does not correspond to any actual
+ // resolution. The value zero does not set the override.
+ unsigned dpiset_override = 0;
+
+ // CCD may present itself as half or quarter-size CCD on certain resolutions
+ int ccd_size_divisor = 1;
+
+ // Some scanners need an additional multiplier over the scan coordinates
+ int pixel_count_multiplier = 1;
+
+ int black_pixels = 0;
+ // value of the dummy register
+ int dummy_pixel = 0;
+ // last pixel of CCD margin at optical resolution
+ int ccd_start_xoffset = 0;
+ // total pixels used by the sensor
+ int sensor_pixels = 0;
+ // TA CCD target code (reference gain)
+ int fau_gain_white_ref = 0;
+ // CCD target code (reference gain)
+ int gain_white_ref = 0;
+
+ // red, green and blue initial exposure values
+ SensorExposure exposure;
+
+ int exposure_lperiod = -1;
+
+ // the number of pixels in a single segment.
+ // only on gl843
+ unsigned segment_size = 0;
+
+ // the order of the segments, if any, for the sensor. If the sensor is not segmented or uses
+ // only single segment, this array can be empty
+ // only on gl843
+ std::vector<unsigned> segment_order;
+
+ // some CCDs use two arrays of pixels for double resolution. On such CCDs when scanning at
+ // high-enough resolution, every other pixel column is shifted
+ StaggerConfig stagger_config;
+
+ GenesysRegisterSettingSet custom_base_regs; // gl646-specific
+ GenesysRegisterSettingSet custom_regs;
+ GenesysRegisterSettingSet custom_fe_regs;
+
+ // red, green and blue gamma coefficient for default gamma tables
+ AssignableArray<float, 3> gamma;
+
+ std::function<unsigned(const Genesys_Sensor&, unsigned)> get_logical_hwdpi_fun;
+ std::function<unsigned(const Genesys_Sensor&, unsigned)> get_register_hwdpi_fun;
+ std::function<unsigned(const Genesys_Sensor&, unsigned)> get_ccd_size_divisor_fun;
+ std::function<unsigned(const Genesys_Sensor&, unsigned)> get_hwdpi_divisor_fun;
+
+ unsigned get_logical_hwdpi(unsigned xres) const { return get_logical_hwdpi_fun(*this, xres); }
+ unsigned get_register_hwdpi(unsigned xres) const { return get_register_hwdpi_fun(*this, xres); }
+ unsigned get_ccd_size_divisor_for_dpi(unsigned xres) const
+ {
+ return get_ccd_size_divisor_fun(*this, xres);
+ }
+ unsigned get_hwdpi_divisor_for_dpi(unsigned xres) const
+ {
+ return get_hwdpi_divisor_fun(*this, xres);
+ }
+
+ // how many CCD pixels are processed per system pixel time. This corresponds to CKSEL + 1
+ unsigned ccd_pixels_per_system_pixel() const
+ {
+ // same on GL646, GL841, GL843, GL846, GL847, GL124
+ constexpr unsigned REG_CKSEL = 0x03;
+ return (custom_regs.get_value(0x18) & REG_CKSEL) + 1;
+ }
+
+ bool matches_channel_count(unsigned count) const
+ {
+ return std::find(channels.begin(), channels.end(), count) != channels.end();
+ }
+
+ unsigned get_segment_count() const
+ {
+ if (segment_order.size() < 2)
+ return 1;
+ return segment_order.size();
+ }
+
+ bool operator==(const Genesys_Sensor& other) const
+ {
+ return sensor_id == other.sensor_id &&
+ optical_res == other.optical_res &&
+ resolutions == other.resolutions &&
+ method == other.method &&
+ ccd_size_divisor == other.ccd_size_divisor &&
+ black_pixels == other.black_pixels &&
+ dummy_pixel == other.dummy_pixel &&
+ ccd_start_xoffset == other.ccd_start_xoffset &&
+ sensor_pixels == other.sensor_pixels &&
+ fau_gain_white_ref == other.fau_gain_white_ref &&
+ gain_white_ref == other.gain_white_ref &&
+ exposure == other.exposure &&
+ exposure_lperiod == other.exposure_lperiod &&
+ segment_size == other.segment_size &&
+ segment_order == other.segment_order &&
+ stagger_config == other.stagger_config &&
+ custom_base_regs == other.custom_base_regs &&
+ custom_regs == other.custom_regs &&
+ custom_fe_regs == other.custom_fe_regs &&
+ gamma == other.gamma;
+ }
+};
+
+template<class Stream>
+void serialize(Stream& str, Genesys_Sensor& x)
+{
+ serialize(str, x.sensor_id);
+ serialize(str, x.optical_res);
+ serialize(str, x.resolutions);
+ serialize(str, x.method);
+ serialize(str, x.ccd_size_divisor);
+ serialize(str, x.black_pixels);
+ serialize(str, x.dummy_pixel);
+ serialize(str, x.ccd_start_xoffset);
+ serialize(str, x.sensor_pixels);
+ serialize(str, x.fau_gain_white_ref);
+ serialize(str, x.gain_white_ref);
+ serialize_newline(str);
+ serialize(str, x.exposure.blue);
+ serialize(str, x.exposure.green);
+ serialize(str, x.exposure.red);
+ serialize(str, x.exposure_lperiod);
+ serialize_newline(str);
+ serialize(str, x.segment_size);
+ serialize_newline(str);
+ serialize(str, x.segment_order);
+ serialize_newline(str);
+ serialize(str, x.stagger_config);
+ serialize_newline(str);
+ serialize(str, x.custom_base_regs);
+ serialize_newline(str);
+ serialize(str, x.custom_regs);
+ serialize_newline(str);
+ serialize(str, x.custom_fe_regs);
+ serialize_newline(str);
+ serialize(str, x.gamma);
+ serialize_newline(str);
+}
+
+std::ostream& operator<<(std::ostream& out, const Genesys_Sensor& sensor);
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_SENSOR_H
diff --git a/backend/genesys/serialize.cpp b/backend/genesys/serialize.cpp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/backend/genesys/serialize.cpp
diff --git a/backend/genesys/serialize.h b/backend/genesys/serialize.h
new file mode 100644
index 0000000..ed40a4e
--- /dev/null
+++ b/backend/genesys/serialize.h
@@ -0,0 +1,150 @@
+/* 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_SERIALIZE_H
+#define BACKEND_GENESYS_SERIALIZE_H
+
+#include "error.h"
+#include <array>
+#include <iostream>
+#include <limits>
+#include <string>
+#include <vector>
+
+namespace genesys {
+
+// it would be best to use something like boost.serialization
+
+inline void serialize_newline(std::ostream& str) { str << '\n'; }
+inline void serialize_newline(std::istream& str) { (void) str; }
+
+inline void serialize(std::ostream& str, bool x) { str << static_cast<unsigned>(x) << " "; }
+inline void serialize(std::istream& str, bool& x) { unsigned v; str >> v; x = v; }
+inline void serialize(std::ostream& str, char x) { str << static_cast<int>(x) << " "; }
+inline void serialize(std::istream& str, char& x) { int v; str >> v; x = v; }
+inline void serialize(std::ostream& str, unsigned char x) { str << static_cast<unsigned>(x) << " "; }
+inline void serialize(std::istream& str, unsigned char& x) { unsigned v; str >> v; x = v; }
+inline void serialize(std::ostream& str, signed char x) { str << static_cast<int>(x) << " "; }
+inline void serialize(std::istream& str, signed char& x) { int v; str >> v; x = v; }
+inline void serialize(std::ostream& str, short x) { str << x << " "; }
+inline void serialize(std::istream& str, short& x) { str >> x; }
+inline void serialize(std::ostream& str, unsigned short x) { str << x << " "; }
+inline void serialize(std::istream& str, unsigned short& x) { str >> x; }
+inline void serialize(std::ostream& str, int x) { str << x << " "; }
+inline void serialize(std::istream& str, int& x) { str >> x; }
+inline void serialize(std::ostream& str, unsigned int x) { str << x << " "; }
+inline void serialize(std::istream& str, unsigned int& x) { str >> x; }
+inline void serialize(std::ostream& str, long x) { str << x << " "; }
+inline void serialize(std::istream& str, long& x) { str >> x; }
+inline void serialize(std::ostream& str, unsigned long x) { str << x << " "; }
+inline void serialize(std::istream& str, unsigned long& x) { str >> x; }
+inline void serialize(std::ostream& str, long long x) { str << x << " "; }
+inline void serialize(std::istream& str, long long& x) { str >> x; }
+inline void serialize(std::ostream& str, unsigned long long x) { str << x << " "; }
+inline void serialize(std::istream& str, unsigned long long& x) { str >> x; }
+inline void serialize(std::ostream& str, float x) { str << x << " "; }
+inline void serialize(std::istream& str, float& x) { str >> x; }
+inline void serialize(std::ostream& str, double x) { str << x << " "; }
+inline void serialize(std::istream& str, double& x) { str >> x; }
+inline void serialize(std::ostream& str, const std::string& x) { str << x << " "; }
+inline void serialize(std::istream& str, std::string& x) { str >> x; }
+
+template<class T>
+void serialize(std::ostream& str, std::vector<T>& x)
+{
+ serialize(str, x.size());
+ serialize_newline(str);
+
+ for (auto& item : x) {
+ serialize(str, item);
+ serialize_newline(str);
+ }
+}
+
+template<class T>
+void serialize(std::istream& str, std::vector<T>& x,
+ size_t max_size = std::numeric_limits<size_t>::max())
+{
+ size_t new_size;
+ serialize(str, new_size);
+
+ if (new_size > max_size) {
+ throw SaneException("Too large std::vector to deserialize");
+ }
+ x.reserve(new_size);
+ for (size_t i = 0; i < new_size; ++i) {
+ T item;
+ serialize(str, item);
+ x.push_back(item);
+ }
+}
+
+template<class T, size_t Size>
+void serialize(std::ostream& str, std::array<T, Size>& x)
+{
+ serialize(str, x.size());
+ serialize_newline(str);
+
+ for (auto& item : x) {
+ serialize(str, item);
+ serialize_newline(str);
+ }
+}
+
+template<class T, size_t Size>
+void serialize(std::istream& str, std::array<T, Size>& x)
+{
+ size_t new_size;
+ serialize(str, new_size);
+
+ if (new_size > Size) {
+ throw SaneException("Incorrect std::array size to deserialize");
+ }
+ for (auto& item : x) {
+ serialize(str, item);
+ }
+}
+
+} // namespace genesys
+
+#endif
diff --git a/backend/genesys/settings.cpp b/backend/genesys/settings.cpp
new file mode 100644
index 0000000..41c66de
--- /dev/null
+++ b/backend/genesys/settings.cpp
@@ -0,0 +1,142 @@
+/* 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 "settings.h"
+#include "utilities.h"
+#include <iomanip>
+
+namespace genesys {
+
+std::ostream& operator<<(std::ostream& out, const Genesys_Settings& settings)
+{
+ StreamStateSaver state_saver{out};
+
+ out << "Genesys_Settings{\n"
+ << " xres: " << settings.xres << " yres: " << settings.yres << '\n'
+ << " lines: " << settings.lines << '\n'
+ << " pixels per line (actual): " << settings.pixels << '\n'
+ << " pixels per line (requested): " << settings.requested_pixels << '\n'
+ << " depth: " << settings.depth << '\n';
+ auto prec = out.precision();
+ out.precision(3);
+ out << " tl_x: " << settings.tl_x << " tl_y: " << settings.tl_y << '\n';
+ out.precision(prec);
+ out << " scan_mode: " << settings.scan_mode << '\n'
+ << '}';
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const SetupParams& params)
+{
+ StreamStateSaver state_saver{out};
+
+ out << "SetupParams{\n"
+ << " xres: " << params.xres << " yres: " << params.yres << '\n'
+ << " lines: " << params.lines << '\n'
+ << " pixels per line (actual): " << params.pixels << '\n'
+ << " pixels per line (requested): " << params.requested_pixels << '\n'
+ << " depth: " << params.depth << '\n'
+ << " channels: " << params.channels << '\n'
+ << " startx: " << params.startx << " starty: " << params.starty << '\n'
+ << " scan_mode: " << params.scan_mode << '\n'
+ << " color_filter: " << params.color_filter << '\n'
+ << " flags: " << params.flags << '\n'
+ << "}";
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const ScanSession& session)
+{
+ out << "ScanSession{\n"
+ << " computed: " << session.computed << '\n'
+ << " hwdpi_divisor: " << session.hwdpi_divisor << '\n'
+ << " ccd_size_divisor: " << session.ccd_size_divisor << '\n'
+ << " optical_resolution: " << session.optical_resolution << '\n'
+ << " optical_pixels: " << session.optical_pixels << '\n'
+ << " optical_pixels_raw: " << session.optical_pixels_raw << '\n'
+ << " output_resolution: " << session.output_resolution << '\n'
+ << " output_pixels: " << session.output_pixels << '\n'
+ << " output_line_bytes: " << session.output_line_bytes << '\n'
+ << " output_line_bytes_raw: " << session.output_line_bytes_raw << '\n'
+ << " output_line_count: " << session.output_line_count << '\n'
+ << " num_staggered_lines: " << session.num_staggered_lines << '\n'
+ << " color_shift_lines_r: " << session.color_shift_lines_r << '\n'
+ << " color_shift_lines_g: " << session.color_shift_lines_g << '\n'
+ << " color_shift_lines_b: " << session.color_shift_lines_b << '\n'
+ << " max_color_shift_lines: " << session.max_color_shift_lines << '\n'
+ << " enable_ledadd: " << session.enable_ledadd << '\n'
+ << " segment_count: " << session.segment_count << '\n'
+ << " pixel_startx: " << session.pixel_startx << '\n'
+ << " pixel_endx: " << session.pixel_endx << '\n'
+ << " conseq_pixel_dist: " << session.conseq_pixel_dist << '\n'
+ << " output_segment_pixel_group_count: "
+ << session.output_segment_pixel_group_count << '\n'
+ << " buffer_size_read: " << session.buffer_size_read << '\n'
+ << " buffer_size_read: " << session.buffer_size_lines << '\n'
+ << " buffer_size_shrink: " << session.buffer_size_shrink << '\n'
+ << " buffer_size_out: " << session.buffer_size_out << '\n'
+ << " filters: "
+ << (session.pipeline_needs_reorder ? " reorder": "")
+ << (session.pipeline_needs_ccd ? " ccd": "")
+ << (session.pipeline_needs_shrink ? " shrink": "") << '\n'
+ << " params: " << format_indent_braced_list(4, session.params) << '\n'
+ << "}";
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const SANE_Parameters& params)
+{
+ out << "SANE_Parameters{\n"
+ << " format: " << static_cast<unsigned>(params.format) << '\n'
+ << " last_frame: " << params.last_frame << '\n'
+ << " bytes_per_line: " << params.bytes_per_line << '\n'
+ << " pixels_per_line: " << params.pixels_per_line << '\n'
+ << " lines: " << params.lines << '\n'
+ << " depth: " << params.depth << '\n'
+ << '}';
+ return out;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/settings.h b/backend/genesys/settings.h
new file mode 100644
index 0000000..a697e60
--- /dev/null
+++ b/backend/genesys/settings.h
@@ -0,0 +1,328 @@
+/* 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_SETTINGS_H
+#define BACKEND_GENESYS_SETTINGS_H
+
+#include "enums.h"
+#include "serialize.h"
+
+namespace genesys {
+
+struct Genesys_Settings
+{
+ ScanMethod scan_method = ScanMethod::FLATBED;
+ ScanColorMode scan_mode = ScanColorMode::LINEART;
+
+ // horizontal dpi
+ unsigned xres = 0;
+ // vertical dpi
+ unsigned yres = 0;
+
+ //x start on scan table in mm
+ double tl_x = 0;
+ // y start on scan table in mm
+ double tl_y = 0;
+
+ // number of lines at scan resolution
+ unsigned int lines = 0;
+ // number of pixels expected from the scanner
+ unsigned int pixels = 0;
+ // number of pixels expected by the frontend
+ unsigned requested_pixels = 0;
+
+ // bit depth of the scan
+ unsigned int depth = 0;
+
+ ColorFilter color_filter = ColorFilter::NONE;
+
+ // true if scan is true gray, false if monochrome scan
+ int true_gray = 0;
+
+ // lineart threshold
+ int threshold = 0;
+
+ // lineart threshold curve for dynamic rasterization
+ int threshold_curve = 0;
+
+ // Disable interpolation for xres<yres
+ int disable_interpolation = 0;
+
+ // value for contrast enhancement in the [-100..100] range
+ int contrast = 0;
+
+ // value for brightness enhancement in the [-100..100] range
+ int brightness = 0;
+
+ // cache entries expiration time
+ int expiration_time = 0;
+
+ unsigned get_channels() const
+ {
+ if (scan_mode == ScanColorMode::COLOR_SINGLE_PASS)
+ return 3;
+ return 1;
+ }
+};
+
+std::ostream& operator<<(std::ostream& out, const Genesys_Settings& settings);
+
+
+struct SetupParams {
+
+ static constexpr unsigned NOT_SET = std::numeric_limits<unsigned>::max();
+
+ // resolution in x direction
+ unsigned xres = NOT_SET;
+ // resolution in y direction
+ unsigned yres = NOT_SET;
+ // start pixel in X direction, from dummy_pixel + 1
+ unsigned startx = NOT_SET;
+ // start pixel in Y direction, counted according to base_ydpi
+ unsigned starty = NOT_SET;
+ // the number of pixels in X direction. Note that each logical pixel may correspond to more
+ // than one CCD pixel, see CKSEL and GenesysSensor::ccd_pixels_per_system_pixel()
+ unsigned pixels = NOT_SET;
+
+ // the number of pixels in the X direction as requested by the frontend. This will be different
+ // from `pixels` if the X resolution requested by the frontend is different than the actual
+ // resolution. This is only needed to compute dev->total_bytes_to_read. If 0, then the value
+ // is the same as pixels.
+ // TODO: move the computation of total_bytes_to_read to a higher layer.
+ unsigned requested_pixels = 0;
+
+ // the number of pixels in Y direction
+ unsigned lines = NOT_SET;
+ // the depth of the scan in bits. Allowed are 1, 8, 16
+ unsigned depth = NOT_SET;
+ // the number of channels
+ unsigned channels = NOT_SET;
+
+ ScanMethod scan_method = static_cast<ScanMethod>(NOT_SET);
+
+ ScanColorMode scan_mode = static_cast<ScanColorMode>(NOT_SET);
+
+ ColorFilter color_filter = static_cast<ColorFilter>(NOT_SET);
+
+ ScanFlag flags;
+
+ unsigned get_requested_pixels() const
+ {
+ if (requested_pixels != 0) {
+ return requested_pixels;
+ }
+ return pixels;
+ }
+
+ void assert_valid() const
+ {
+ if (xres == NOT_SET || yres == NOT_SET || startx == NOT_SET || starty == NOT_SET ||
+ pixels == NOT_SET || lines == NOT_SET ||depth == NOT_SET || channels == NOT_SET ||
+ scan_method == static_cast<ScanMethod>(NOT_SET) ||
+ scan_mode == static_cast<ScanColorMode>(NOT_SET) ||
+ color_filter == static_cast<ColorFilter>(NOT_SET))
+ {
+ throw std::runtime_error("SetupParams are not valid");
+ }
+ }
+
+ bool operator==(const SetupParams& other) const
+ {
+ return xres == other.xres &&
+ yres == other.yres &&
+ startx == other.startx &&
+ starty == other.starty &&
+ pixels == other.pixels &&
+ requested_pixels == other.requested_pixels &&
+ lines == other.lines &&
+ depth == other.depth &&
+ channels == other.channels &&
+ scan_method == other.scan_method &&
+ scan_mode == other.scan_mode &&
+ color_filter == other.color_filter &&
+ flags == other.flags;
+ }
+};
+
+std::ostream& operator<<(std::ostream& out, const SetupParams& params);
+
+template<class Stream>
+void serialize(Stream& str, SetupParams& x)
+{
+ serialize(str, x.xres);
+ serialize(str, x.yres);
+ serialize(str, x.startx);
+ serialize(str, x.starty);
+ serialize(str, x.pixels);
+ serialize(str, x.requested_pixels);
+ serialize(str, x.lines);
+ serialize(str, x.depth);
+ serialize(str, x.channels);
+ serialize(str, x.scan_method);
+ serialize(str, x.scan_mode);
+ serialize(str, x.color_filter);
+ serialize(str, x.flags);
+}
+
+struct ScanSession {
+ SetupParams params;
+
+ // whether the session setup has been computed via compute_session()
+ bool computed = false;
+
+ // specifies the reduction (if any) of hardware dpi on the Genesys chip side.
+ // except gl646
+ unsigned hwdpi_divisor = 1;
+
+ // specifies the reduction (if any) of CCD effective dpi which is performed by latching the
+ // data coming from CCD in such a way that 1/2 or 3/4 of pixel data is ignored.
+ unsigned ccd_size_divisor = 1;
+
+ // the optical resolution of the scanner.
+ unsigned optical_resolution = 0;
+
+ // the number of pixels at the optical resolution, not including segmentation overhead.
+ unsigned optical_pixels = 0;
+
+ // the number of pixels at the optical resolution, including segmentation overhead.
+ // only on gl846, g847
+ unsigned optical_pixels_raw = 0;
+
+ // the resolution of the output data.
+ // gl843-only
+ unsigned output_resolution = 0;
+
+ // the number of pixels in output data (after desegmentation)
+ unsigned output_pixels = 0;
+
+ // the number of bytes in the output of a channel of a single line (after desegmentation)
+ unsigned output_channel_bytes = 0;
+
+ // the number of bytes in the output of a single line (after desegmentation)
+ unsigned output_line_bytes = 0;
+
+ // the number of bytes per line in the output data from the scanner (before desegmentation)
+ // Equal to output_line_bytes if sensor does not have segments
+ unsigned output_line_bytes_raw = 0;
+
+ // the number of bytes per line as requested by the frontend
+ unsigned output_line_bytes_requested = 0;
+
+ // the number of lines in the output of the scanner. This must be larger than the user
+ // requested number due to line staggering and color channel shifting.
+ unsigned output_line_count = 0;
+
+ // the total number of bytes to read from the scanner (before desegmentation)
+ unsigned output_total_bytes_raw = 0;
+
+ // the total number of bytes to read from the scanner (after desegmentation)
+ unsigned output_total_bytes = 0;
+
+ // the number of staggered lines (i.e. lines that overlap during scanning due to line being
+ // thinner than the CCD element)
+ unsigned num_staggered_lines = 0;
+
+ // the number of lines that color channels shift due to different physical positions of
+ // different color channels.
+ unsigned max_color_shift_lines = 0;
+
+ // actual line shift of the red color
+ unsigned color_shift_lines_r = 0;
+ // actual line shift of the green color
+ unsigned color_shift_lines_g = 0;
+ // actual line shift of the blue color
+ unsigned color_shift_lines_b = 0;
+
+ // the number of scanner segments used in the current scan
+ unsigned segment_count = 1;
+
+ // the physical pixel positions that are sent to the registers
+ unsigned pixel_startx = 0;
+ unsigned pixel_endx = 0;
+
+ // certain scanners require the logical pixel count to be multiplied on certain resolutions
+ unsigned pixel_count_multiplier = 1;
+
+ // Distance in pixels between consecutive pixels, e.g. between odd and even pixels. Note that
+ // the number of segments can be large.
+ // only on gl124, gl846, gl847
+ unsigned conseq_pixel_dist = 0;
+
+ // The number of "even" pixels to scan. This corresponds to the number of pixels that will be
+ // scanned from a single segment
+ // only on gl124, gl846, gl847
+ unsigned output_segment_pixel_group_count = 0;
+
+ // The number of bytes to skip at start of line during desegmentation.
+ // Currently it's always zero.
+ unsigned output_segment_start_offset = 0;
+
+ // the sizes of the corresponding buffers
+ size_t buffer_size_read = 0;
+ size_t buffer_size_lines = 0;
+ size_t buffer_size_shrink = 0;
+ size_t buffer_size_out = 0;
+
+ // whether to enable ledadd functionality
+ bool enable_ledadd = false;
+
+ // what pipeline modifications are needed
+ bool pipeline_needs_reorder = false;
+ bool pipeline_needs_ccd = false;
+ bool pipeline_needs_shrink = false;
+
+ void assert_computed() const
+ {
+ if (!computed) {
+ throw std::runtime_error("ScanSession is not computed");
+ }
+ }
+};
+
+std::ostream& operator<<(std::ostream& out, const ScanSession& session);
+
+std::ostream& operator<<(std::ostream& out, const SANE_Parameters& params);
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_SETTINGS_H
diff --git a/backend/genesys/static_init.cpp b/backend/genesys/static_init.cpp
new file mode 100644
index 0000000..c0f3748
--- /dev/null
+++ b/backend/genesys/static_init.cpp
@@ -0,0 +1,70 @@
+/* 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 "static_init.h"
+#include <vector>
+
+namespace genesys {
+
+static std::unique_ptr<std::vector<std::function<void()>>> s_functions_run_at_backend_exit;
+
+void add_function_to_run_at_backend_exit(const std::function<void()>& function)
+{
+ if (!s_functions_run_at_backend_exit)
+ s_functions_run_at_backend_exit.reset(new std::vector<std::function<void()>>());
+ s_functions_run_at_backend_exit->push_back(std::move(function));
+}
+
+void run_functions_at_backend_exit()
+{
+ for (auto it = s_functions_run_at_backend_exit->rbegin();
+ it != s_functions_run_at_backend_exit->rend(); ++it)
+ {
+ (*it)();
+ }
+ s_functions_run_at_backend_exit.reset();
+}
+
+} // namespace genesys
diff --git a/backend/genesys/static_init.h b/backend/genesys/static_init.h
new file mode 100644
index 0000000..3ffa62c
--- /dev/null
+++ b/backend/genesys/static_init.h
@@ -0,0 +1,88 @@
+/* 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_STATIC_INIT_H
+#define BACKEND_GENESYS_STATIC_INIT_H
+
+#include <functional>
+#include <memory>
+
+namespace genesys {
+
+void add_function_to_run_at_backend_exit(const std::function<void()>& function);
+
+// calls functions added via add_function_to_run_at_backend_exit() in reverse order of being
+// added.
+void run_functions_at_backend_exit();
+
+template<class T>
+class StaticInit {
+public:
+ StaticInit() = default;
+ StaticInit(const StaticInit&) = delete;
+ StaticInit& operator=(const StaticInit&) = delete;
+
+ template<class... Args>
+ void init(Args&& ... args)
+ {
+ ptr_ = std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+ add_function_to_run_at_backend_exit([this](){ deinit(); });
+ }
+
+ void deinit()
+ {
+ ptr_.reset();
+ }
+
+ const T* operator->() const { return ptr_.get(); }
+ T* operator->() { return ptr_.get(); }
+ const T& operator*() const { return *ptr_.get(); }
+ T& operator*() { return *ptr_.get(); }
+
+private:
+ std::unique_ptr<T> ptr_;
+};
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_STATIC_INIT_H
diff --git a/backend/genesys/status.cpp b/backend/genesys/status.cpp
new file mode 100644
index 0000000..7f883b0
--- /dev/null
+++ b/backend/genesys/status.cpp
@@ -0,0 +1,66 @@
+/* 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 "status.h"
+#include <iostream>
+
+namespace genesys {
+
+std::ostream& operator<<(std::ostream& out, Status status)
+{
+ out << "Status{\n"
+ << " replugged: " << (status.is_replugged ? "yes" : "no") << '\n'
+ << " is_buffer_empty: " << (status.is_buffer_empty ? "yes" : "no") << '\n'
+ << " is_feeding_finished: " << (status.is_feeding_finished ? "yes" : "no") << '\n'
+ << " is_scanning_finished: " << (status.is_scanning_finished ? "yes" : "no") << '\n'
+ << " is_at_home: " << (status.is_at_home ? "yes" : "no") << '\n'
+ << " is_lamp_on: " << (status.is_lamp_on ? "yes" : "no") << '\n'
+ << " is_front_end_busy: " << (status.is_front_end_busy ? "yes" : "no") << '\n'
+ << " is_motor_enabled: " << (status.is_motor_enabled ? "yes" : "no") << '\n'
+ << "}\n";
+ return out;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/status.h b/backend/genesys/status.h
new file mode 100644
index 0000000..91f4692
--- /dev/null
+++ b/backend/genesys/status.h
@@ -0,0 +1,68 @@
+/* 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_STATUS_H
+#define BACKEND_GENESYS_STATUS_H
+
+#include <iosfwd>
+
+namespace genesys {
+
+/// Represents the scanner status register
+struct Status
+{
+ bool is_replugged = false;
+ bool is_buffer_empty = false;
+ bool is_feeding_finished = false;
+ bool is_scanning_finished = false;
+ bool is_at_home = false;
+ bool is_lamp_on = false;
+ bool is_front_end_busy = false;
+ bool is_motor_enabled = false;
+};
+
+std::ostream& operator<<(std::ostream& out, Status status);
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_STATUS_H
diff --git a/backend/genesys/tables_frontend.cpp b/backend/genesys/tables_frontend.cpp
new file mode 100644
index 0000000..1edf32f
--- /dev/null
+++ b/backend/genesys/tables_frontend.cpp
@@ -0,0 +1,653 @@
+/* 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 "low.h"
+
+namespace genesys {
+
+StaticInit<std::vector<Genesys_Frontend>> s_frontends;
+
+void genesys_init_frontend_tables()
+{
+ s_frontends.init();
+
+ GenesysFrontendLayout wolfson_layout;
+ wolfson_layout.type = FrontendType::WOLFSON;
+ wolfson_layout.offset_addr = { 0x20, 0x21, 0x22 };
+ wolfson_layout.gain_addr = { 0x28, 0x29, 0x2a };
+
+ GenesysFrontendLayout analog_devices;
+ analog_devices.type = FrontendType::ANALOG_DEVICES;
+
+
+ Genesys_Frontend fe;
+ fe.id = AdcId::WOLFSON_UMAX;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x03 },
+ { 0x02, 0x05 },
+ { 0x03, 0x11 },
+ { 0x20, 0x80 },
+ { 0x21, 0x80 },
+ { 0x22, 0x80 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x02 },
+ { 0x29, 0x02 },
+ { 0x2a, 0x02 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::WOLFSON_ST12;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x03 },
+ { 0x02, 0x05 },
+ { 0x03, 0x03 },
+ { 0x20, 0xc8 },
+ { 0x21, 0xc8 },
+ { 0x22, 0xc8 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x04 },
+ { 0x29, 0x04 },
+ { 0x2a, 0x04 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::WOLFSON_ST24;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x03 },
+ { 0x02, 0x05 },
+ { 0x03, 0x21 },
+ { 0x20, 0xc8 },
+ { 0x21, 0xc8 },
+ { 0x22, 0xc8 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x06 },
+ { 0x29, 0x06 },
+ { 0x2a, 0x06 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::WOLFSON_5345;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x03 },
+ { 0x02, 0x05 },
+ { 0x03, 0x12 },
+ { 0x20, 0xb8 },
+ { 0x21, 0xb8 },
+ { 0x22, 0xb8 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x04 },
+ { 0x29, 0x04 },
+ { 0x2a, 0x04 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ // reg3=0x02 for 50-600 dpi, 0x32 (0x12 also works well) at 1200
+ fe = Genesys_Frontend();
+ fe.id = AdcId::WOLFSON_HP2400;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x03 },
+ { 0x02, 0x05 },
+ { 0x03, 0x02 },
+ { 0x20, 0xb4 },
+ { 0x21, 0xb6 },
+ { 0x22, 0xbc },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x06 },
+ { 0x29, 0x09 },
+ { 0x2a, 0x08 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::WOLFSON_HP2300;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x03 },
+ { 0x02, 0x04 },
+ { 0x03, 0x02 },
+ { 0x20, 0xbe },
+ { 0x21, 0xbe },
+ { 0x22, 0xbe },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x04 },
+ { 0x29, 0x04 },
+ { 0x2a, 0x04 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::CANON_LIDE_35;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x3d },
+ { 0x02, 0x08 },
+ { 0x03, 0x00 },
+ { 0x20, 0xe1 },
+ { 0x21, 0xe1 },
+ { 0x22, 0xe1 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x93 },
+ { 0x29, 0x93 },
+ { 0x2a, 0x93 },
+ };
+ fe.reg2 = {0x00, 0x19, 0x06};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::AD_XP200;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x58 },
+ { 0x01, 0x80 },
+ { 0x02, 0x00 },
+ { 0x03, 0x00 },
+ { 0x20, 0x09 },
+ { 0x21, 0x09 },
+ { 0x22, 0x09 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x09 },
+ { 0x29, 0x09 },
+ { 0x2a, 0x09 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::WOLFSON_XP300;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x35 },
+ { 0x02, 0x20 },
+ { 0x03, 0x14 },
+ { 0x20, 0xe1 },
+ { 0x21, 0xe1 },
+ { 0x22, 0xe1 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x93 },
+ { 0x29, 0x93 },
+ { 0x2a, 0x93 },
+ };
+ fe.reg2 = {0x07, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::WOLFSON_HP3670;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x03 },
+ { 0x02, 0x05 },
+ { 0x03, 0x32 },
+ { 0x20, 0xba },
+ { 0x21, 0xb8 },
+ { 0x22, 0xb8 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x06 },
+ { 0x29, 0x05 },
+ { 0x2a, 0x04 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::WOLFSON_DSM600;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x35 },
+ { 0x02, 0x20 },
+ { 0x03, 0x14 },
+ { 0x20, 0x85 },
+ { 0x21, 0x85 },
+ { 0x22, 0x85 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0xa0 },
+ { 0x29, 0xa0 },
+ { 0x2a, 0xa0 },
+ };
+ fe.reg2 = {0x07, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::CANON_LIDE_200;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x9d },
+ { 0x01, 0x91 },
+ { 0x02, 0x00 },
+ { 0x03, 0x00 },
+ { 0x20, 0x00 },
+ { 0x21, 0x3f },
+ { 0x22, 0x00 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x32 },
+ { 0x29, 0x04 },
+ { 0x2a, 0x00 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::CANON_LIDE_700F;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x9d },
+ { 0x01, 0x9e },
+ { 0x02, 0x00 },
+ { 0x03, 0x00 },
+ { 0x20, 0x00 },
+ { 0x21, 0x3f },
+ { 0x22, 0x00 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x2f },
+ { 0x29, 0x04 },
+ { 0x2a, 0x00 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::KVSS080;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x23 },
+ { 0x02, 0x24 },
+ { 0x03, 0x0f },
+ { 0x20, 0x80 },
+ { 0x21, 0x80 },
+ { 0x22, 0x80 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x4b },
+ { 0x29, 0x4b },
+ { 0x2a, 0x4b },
+ };
+ fe.reg2 = {0x00,0x00,0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::G4050;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x23 },
+ { 0x02, 0x24 },
+ { 0x03, 0x1f },
+ { 0x20, 0x45 },
+ { 0x21, 0x45 },
+ { 0x22, 0x45 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x4b },
+ { 0x29, 0x4b },
+ { 0x2a, 0x4b },
+ };
+ fe.reg2 = {0x00,0x00,0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::CANON_LIDE_110;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x80 },
+ { 0x01, 0x8a },
+ { 0x02, 0x23 },
+ { 0x03, 0x4c },
+ { 0x20, 0x00 },
+ { 0x21, 0x00 },
+ { 0x22, 0x00 },
+ { 0x24, 0x00 },
+ { 0x25, 0xca },
+ { 0x26, 0x94 },
+ { 0x28, 0x00 },
+ { 0x29, 0x00 },
+ { 0x2a, 0x00 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+ /** @brief GL124 special case
+ * for GL124 based scanners, this struct is "abused"
+ * in fact the fields are map like below to AFE registers
+ * (from Texas Instrument or alike ?)
+ */
+ fe = Genesys_Frontend();
+ fe.id = AdcId::CANON_LIDE_120;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x80 },
+ { 0x01, 0xa3 },
+ { 0x02, 0x2b },
+ { 0x03, 0x4c },
+ { 0x20, 0x00 },
+ { 0x21, 0x00 },
+ { 0x22, 0x00 },
+ { 0x24, 0x00 }, // actual address 0x05
+ { 0x25, 0xca }, // actual address 0x06
+ { 0x26, 0x95 }, // actual address 0x07
+ { 0x28, 0x00 },
+ { 0x29, 0x00 },
+ { 0x2a, 0x00 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::PLUSTEK_OPTICPRO_3600;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x70 },
+ { 0x01, 0x80 },
+ { 0x02, 0x00 },
+ { 0x03, 0x00 },
+ { 0x20, 0x00 },
+ { 0x21, 0x00 },
+ { 0x22, 0x00 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x3f },
+ { 0x29, 0x3d },
+ { 0x2a, 0x3d },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::PLUSTEK_OPTICFILM_7200I;
+ fe.layout = analog_devices;
+ fe.regs = {
+ { 0x00, 0xf8 },
+ { 0x01, 0x80 },
+ { 0x02, 0x0a },
+ { 0x03, 0x06 },
+ { 0x04, 0x0f },
+ { 0x05, 0x56 },
+ { 0x06, 0x64 },
+ { 0x07, 0x56 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::PLUSTEK_OPTICFILM_7300;
+ fe.layout = analog_devices;
+ fe.regs = {
+ { 0x00, 0xf8 },
+ { 0x01, 0x80 },
+ { 0x02, 0x10 },
+ { 0x03, 0x06 },
+ { 0x04, 0x06 },
+ { 0x05, 0x09 },
+ { 0x06, 0x0a },
+ { 0x07, 0x0102 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::PLUSTEK_OPTICFILM_7500I;
+ fe.layout = analog_devices;
+ fe.regs = {
+ { 0x00, 0xf8 },
+ { 0x01, 0x80 },
+ { 0x02, 0x1d },
+ { 0x03, 0x17 },
+ { 0x04, 0x13 },
+ { 0x05, 0x00 },
+ { 0x06, 0x00 },
+ { 0x07, 0x0111 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::CANON_4400F;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x23 },
+ { 0x02, 0x24 },
+ { 0x03, 0x2f },
+ { 0x20, 0x6d },
+ { 0x21, 0x67 },
+ { 0x22, 0x5b },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0xd8 },
+ { 0x29, 0xd1 },
+ { 0x2a, 0xb9 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::CANON_8400F;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x23 },
+ { 0x02, 0x24 },
+ { 0x03, 0x0f },
+ { 0x20, 0x60 },
+ { 0x21, 0x5c },
+ { 0x22, 0x6c },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x8a },
+ { 0x29, 0x9f },
+ { 0x2a, 0xc2 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::CANON_8600F;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x00 },
+ { 0x01, 0x23 },
+ { 0x02, 0x24 },
+ { 0x03, 0x2f },
+ { 0x20, 0x67 },
+ { 0x21, 0x69 },
+ { 0x22, 0x68 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0xdb },
+ { 0x29, 0xda },
+ { 0x2a, 0xd7 },
+ };
+ fe.reg2 = { 0x00, 0x00, 0x00 };
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::IMG101;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x78 },
+ { 0x01, 0xf0 },
+ { 0x02, 0x00 },
+ { 0x03, 0x00 },
+ { 0x20, 0x00 },
+ { 0x21, 0x00 },
+ { 0x22, 0x00 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x00 },
+ { 0x29, 0x00 },
+ { 0x2a, 0x00 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ fe = Genesys_Frontend();
+ fe.id = AdcId::PLUSTEK_OPTICBOOK_3800;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x78 },
+ { 0x01, 0xf0 },
+ { 0x02, 0x00 },
+ { 0x03, 0x00 },
+ { 0x20, 0x00 },
+ { 0x21, 0x00 },
+ { 0x22, 0x00 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x00 },
+ { 0x29, 0x00 },
+ { 0x2a, 0x00 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+
+
+ /* reg0: control 74 data, 70 no data
+ * reg3: offset
+ * reg6: gain
+ * reg0 , reg3, reg6 */
+ fe = Genesys_Frontend();
+ fe.id = AdcId::CANON_LIDE_80;
+ fe.layout = wolfson_layout;
+ fe.regs = {
+ { 0x00, 0x70 },
+ { 0x01, 0x16 },
+ { 0x02, 0x60 },
+ { 0x03, 0x00 },
+ { 0x20, 0x00 },
+ { 0x21, 0x00 },
+ { 0x22, 0x00 },
+ { 0x24, 0x00 },
+ { 0x25, 0x00 },
+ { 0x26, 0x00 },
+ { 0x28, 0x00 },
+ { 0x29, 0x00 },
+ { 0x2a, 0x00 },
+ };
+ fe.reg2 = {0x00, 0x00, 0x00};
+ s_frontends->push_back(fe);
+}
+
+} // namespace genesys
diff --git a/backend/genesys/tables_gpo.cpp b/backend/genesys/tables_gpo.cpp
new file mode 100644
index 0000000..2c9ad5e
--- /dev/null
+++ b/backend/genesys/tables_gpo.cpp
@@ -0,0 +1,415 @@
+/* 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 "low.h"
+
+namespace genesys {
+
+StaticInit<std::vector<Genesys_Gpo>> s_gpo;
+
+void genesys_init_gpo_tables()
+{
+ s_gpo.init();
+
+ Genesys_Gpo gpo;
+ gpo.id = GpioId::UMAX;
+ gpo.regs = {
+ { 0x66, 0x11 },
+ { 0x67, 0x00 },
+ { 0x68, 0x51 },
+ { 0x69, 0x20 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::ST12;
+ gpo.regs = {
+ { 0x66, 0x11 },
+ { 0x67, 0x00 },
+ { 0x68, 0x51 },
+ { 0x69, 0x20 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::ST24;
+ gpo.regs = {
+ { 0x66, 0x00 },
+ { 0x67, 0x00 },
+ { 0x68, 0x51 },
+ { 0x69, 0x20 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::MD_5345; // bits 11-12 are for bipolar V-ref input voltage
+ gpo.regs = {
+ { 0x66, 0x30 },
+ { 0x67, 0x18 },
+ { 0x68, 0xa0 },
+ { 0x69, 0x18 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::HP2400;
+ gpo.regs = {
+ { 0x66, 0x30 },
+ { 0x67, 0x00 },
+ { 0x68, 0x31 },
+ { 0x69, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::HP2300;
+ gpo.regs = {
+ { 0x66, 0x00 },
+ { 0x67, 0x00 },
+ { 0x68, 0x00 },
+ { 0x69, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::CANON_LIDE_35;
+ gpo.regs = {
+ { 0x6c, 0x02 },
+ { 0x6d, 0x80 },
+ { 0x6e, 0xef },
+ { 0x6f, 0x80 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::XP200;
+ gpo.regs = {
+ { 0x66, 0x30 },
+ { 0x67, 0x00 },
+ { 0x68, 0xb0 },
+ { 0x69, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::HP3670;
+ gpo.regs = {
+ { 0x66, 0x00 },
+ { 0x67, 0x00 },
+ { 0x68, 0x00 },
+ { 0x69, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::XP300;
+ gpo.regs = {
+ { 0x6c, 0x09 },
+ { 0x6d, 0xc6 },
+ { 0x6e, 0xbb },
+ { 0x6f, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::DP665;
+ gpo.regs = {
+ { 0x6c, 0x18 },
+ { 0x6d, 0x00 },
+ { 0x6e, 0xbb },
+ { 0x6f, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::DP685;
+ gpo.regs = {
+ { 0x6c, 0x3f },
+ { 0x6d, 0x46 },
+ { 0x6e, 0xfb },
+ { 0x6f, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::CANON_LIDE_200;
+ gpo.regs = {
+ { 0x6c, 0xfb }, // 0xfb when idle , 0xf9/0xe9 (1200) when scanning
+ { 0x6d, 0x20 },
+ { 0x6e, 0xff },
+ { 0x6f, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::CANON_LIDE_700F;
+ gpo.regs = {
+ { 0x6c, 0xdb },
+ { 0x6d, 0xff },
+ { 0x6e, 0xff },
+ { 0x6f, 0x80 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::KVSS080;
+ gpo.regs = {
+ { 0x6c, 0xf5 },
+ { 0x6d, 0x20 },
+ { 0x6e, 0x7e },
+ { 0x6f, 0xa1 },
+ { 0xa6, 0x06 },
+ { 0xa7, 0x0f },
+ { 0xa8, 0x00 },
+ { 0xa9, 0x08 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::G4050;
+ gpo.regs = {
+ { 0x6c, 0x20 },
+ { 0x6d, 0x00 },
+ { 0x6e, 0xfc },
+ { 0x6f, 0x00 },
+ { 0xa6, 0x08 },
+ { 0xa7, 0x1e },
+ { 0xa8, 0x3e },
+ { 0xa9, 0x06 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::HP_N6310;
+ gpo.regs = {
+ { 0x6c, 0xa3 },
+ { 0x6d, 0x00 },
+ { 0x6e, 0x7f },
+ { 0x6f, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::CANON_LIDE_110;
+ gpo.regs = {
+ { 0x6c, 0xfb },
+ { 0x6d, 0x20 },
+ { 0x6e, 0xff },
+ { 0x6f, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::CANON_LIDE_120;
+ gpo.regs = {
+ { 0x6c, 0xfb },
+ { 0x6d, 0x20 },
+ { 0x6e, 0xff },
+ { 0x6f, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::CANON_LIDE_210;
+ gpo.regs = {
+ { 0x6c, 0xfb },
+ { 0x6d, 0x20 },
+ { 0x6e, 0xff },
+ { 0x6f, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::PLUSTEK_OPTICPRO_3600;
+ gpo.regs = {
+ { 0x6c, 0x02 },
+ { 0x6d, 0x00 },
+ { 0x6e, 0x1e },
+ { 0x6f, 0x80 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::PLUSTEK_OPTICFILM_7200I;
+ gpo.regs = {
+ { 0x6c, 0x4c },
+ { 0x6d, 0x80 },
+ { 0x6e, 0x4c },
+ { 0x6f, 0x80 },
+ { 0xa6, 0x00 },
+ { 0xa7, 0x07 },
+ { 0xa8, 0x20 },
+ { 0xa9, 0x01 },
+ };
+ s_gpo->push_back(gpo);
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::PLUSTEK_OPTICFILM_7300;
+ gpo.regs = {
+ { 0x6c, 0x4c },
+ { 0x6d, 0x00 },
+ { 0x6e, 0x4c },
+ { 0x6f, 0x80 },
+ { 0xa6, 0x00 },
+ { 0xa7, 0x07 },
+ { 0xa8, 0x20 },
+ { 0xa9, 0x01 },
+ };
+ s_gpo->push_back(gpo);
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::PLUSTEK_OPTICFILM_7500I;
+ gpo.regs = {
+ { 0x6c, 0x4c },
+ { 0x6d, 0x00 },
+ { 0x6e, 0x4c },
+ { 0x6f, 0x80 },
+ { 0xa6, 0x00 },
+ { 0xa7, 0x07 },
+ { 0xa8, 0x20 },
+ { 0xa9, 0x01 },
+ };
+ s_gpo->push_back(gpo);
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::CANON_4400F;
+ gpo.regs = {
+ { 0x6c, 0x01 },
+ { 0x6d, 0x7f },
+ { 0x6e, 0xff },
+ { 0x6f, 0x00 },
+ { 0xa6, 0x00 },
+ { 0xa7, 0xff },
+ { 0xa8, 0x07 },
+ { 0xa9, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::CANON_8400F;
+ gpo.regs = {
+ { 0x6c, 0x9a },
+ { 0x6d, 0xdf },
+ { 0x6e, 0xfe },
+ { 0x6f, 0x60 },
+ { 0xa6, 0x00 },
+ { 0xa7, 0x03 },
+ { 0xa8, 0x00 },
+ { 0xa9, 0x02 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::CANON_8600F;
+ gpo.regs = {
+ { 0x6c, 0x20 },
+ { 0x6d, 0x7c },
+ { 0x6e, 0xff },
+ { 0x6f, 0x00 },
+ { 0xa6, 0x00 },
+ { 0xa7, 0xff },
+ { 0xa8, 0x00 },
+ { 0xa9, 0x00 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::IMG101;
+ gpo.regs = {
+ { 0x6c, 0x41 },
+ { 0x6d, 0xa4 },
+ { 0x6e, 0x13 },
+ { 0x6f, 0xa7 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::PLUSTEK_OPTICBOOK_3800;
+ gpo.regs = {
+ { 0x6c, 0x41 },
+ { 0x6d, 0xa4 },
+ { 0x6e, 0x13 },
+ { 0x6f, 0xa7 },
+ };
+ s_gpo->push_back(gpo);
+
+
+ gpo = Genesys_Gpo();
+ gpo.id = GpioId::CANON_LIDE_80;
+ gpo.regs = {
+ { 0x6c, 0x28 },
+ { 0x6d, 0x90 },
+ { 0x6e, 0x75 },
+ { 0x6f, 0x80 },
+ };
+ s_gpo->push_back(gpo);
+}
+
+} // namespace genesys
diff --git a/backend/genesys/tables_model.cpp b/backend/genesys/tables_model.cpp
new file mode 100644
index 0000000..0b3a0af
--- /dev/null
+++ b/backend/genesys/tables_model.cpp
@@ -0,0 +1,2958 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2003 Oliver Rauch
+ Copyright (C) 2003-2005 Henning Meier-Geinitz <henning@meier-geinitz.de>
+ Copyright (C) 2004, 2005 Gerhard Jaeger <gerhard@gjaeger.de>
+ Copyright (C) 2004-2013 Stéphane Voltz <stef.dev@free.fr>
+ Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
+ Copyright (C) 2007 Luke <iceyfor@gmail.com>
+ Copyright (C) 2010 Jack McGill <jmcgill85258@yahoo.com>
+ Copyright (C) 2010 Andrey Loginov <avloginov@gmail.com>,
+ xerox travelscan device entry
+ Copyright (C) 2010 Chris Berry <s0457957@sms.ed.ac.uk> and Michael Rickmann <mrickma@gwdg.de>
+ for Plustek Opticbook 3600 support
+ 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 "low.h"
+
+namespace genesys {
+
+StaticInit<std::vector<Genesys_USB_Device_Entry>> s_usb_devices;
+
+void genesys_init_usb_device_tables()
+{
+ s_usb_devices.init();
+
+ Genesys_Model model;
+ model.name = "umax-astra-4500";
+ model.vendor = "UMAX";
+ model.model = "Astra 4500";
+ model.model_id = ModelId::UMAX_ASTRA_4500;
+ model.asic_type = AsicType::GL646;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 300, 150, 75 },
+ { 2400, 1200, 600, 300, 150, 75 }
+ }
+ };
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 3.5;
+ model.y_offset = 7.5;
+ model.x_size = 218.0;
+ model.y_size = 299.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 1.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 8;
+ model.ld_shift_b = 16;
+
+ model.line_mode_color_order = ColorOrder::BGR;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_UMAX;
+ model.adc_id = AdcId::WOLFSON_UMAX;
+ model.gpio_id = GpioId::UMAX;
+ model.motor_id = MotorId::UMAX;
+ model.flags = GENESYS_FLAG_UNTESTED;
+ model.buttons = GENESYS_HAS_NO_BUTTONS;
+ model.shading_lines = 20;
+ model.shading_ta_lines = 0;
+ model.search_lines = 200;
+
+ s_usb_devices->emplace_back(0x0638, 0x0a10, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-lide-50";
+ model.vendor = "Canon";
+ model.model = "LiDE 35/40/50";
+ model.model_id = ModelId::CANON_LIDE_50;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 300, 200, 150, 75 },
+ { 2400, 1200, 600, 300, 200, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.42;
+ model.y_offset = 7.9;
+ model.x_size = 218.0;
+ model.y_size = 299.0;
+
+ model.y_offset_calib_white = 6.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CIS_CANON_LIDE_35;
+ model.adc_id = AdcId::CANON_LIDE_35;
+ model.gpio_id = GpioId::CANON_LIDE_35;
+ model.motor_id = MotorId::CANON_LIDE_35;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_WHITE_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW |
+ GENESYS_HAS_FILE_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_COPY_SW;
+ model.shading_lines = 280;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a9, 0x2213, model);
+
+
+ model = Genesys_Model();
+ model.name = "panasonic-kv-ss080";
+ model.vendor = "Panasonic";
+ model.model = "KV-SS080";
+ model.model_id = ModelId::PANASONIC_KV_SS080;
+ model.asic_type = AsicType::GL843;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, /* 500, 400,*/ 300, 200, 150, 100, 75 },
+ { 1200, 600, /* 500, 400, */ 300, 200, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 7.2;
+ model.y_offset = 14.7;
+ model.x_size = 217.7;
+ model.y_size = 300.0;
+
+ model.y_offset_calib_white = 9.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 0.0;
+ model.y_size_ta = 0.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 8;
+ model.ld_shift_b = 16;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_KVSS080;
+ model.adc_id = AdcId::KVSS080;
+ model.gpio_id = GpioId::KVSS080;
+ model.motor_id = MotorId::KVSS080;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 100;
+
+ s_usb_devices->emplace_back(0x04da, 0x100f, model);
+
+
+ model = Genesys_Model();
+ model.name = "hewlett-packard-scanjet-4850c";
+ model.vendor = "Hewlett Packard";
+ model.model = "ScanJet 4850C";
+ model.model_id = ModelId::HP_SCANJET_4850C;
+ model.asic_type = AsicType::GL843;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 2400, 1200, 600, 400, 300, 200, 150, 100 },
+ { 2400, 1200, 600, 400, 300, 200, 150, 100 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 7.9;
+ model.y_offset = 10.0;
+ model.x_size = 219.6;
+ model.y_size = 314.5;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 48;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_HP_4850C;
+ model.adc_id = AdcId::G4050;
+ model.gpio_id = GpioId::G4050;
+ model.motor_id = MotorId::G4050;
+ model.flags = GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_SHADING_REPARK |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 100;
+ s_usb_devices->emplace_back(0x03f0, 0x1b05, model);
+
+
+ model = Genesys_Model();
+ model.name = "hewlett-packard-scanjet-g4010";
+ model.vendor = "Hewlett Packard";
+ model.model = "ScanJet G4010";
+ model.model_id = ModelId::HP_SCANJET_G4010;
+ model.asic_type = AsicType::GL843;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 2400, 1200, 600, 400, 300, 200, 150, 100 },
+ { 2400, 1200, 600, 400, 300, 200, 150, 100 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 8.0;
+ model.y_offset = 13.00;
+ model.x_size = 217.9;
+ model.y_size = 315.0;
+
+ model.y_offset_calib_white = 3.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 48;
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_G4050;
+ model.adc_id = AdcId::G4050;
+ model.gpio_id = GpioId::G4050;
+ model.motor_id = MotorId::G4050;
+ model.flags = GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 100;
+
+ s_usb_devices->emplace_back(0x03f0, 0x4505, model);
+
+
+ model = Genesys_Model();
+ model.name = "hewlett-packard-scanjet-g4050";
+ model.vendor = "Hewlett Packard";
+ model.model = "ScanJet G4050";
+ model.model_id = ModelId::HP_SCANJET_G4050;
+ model.asic_type = AsicType::GL843;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 2400, 1200, 600, 400, 300, 200, 150, 100 },
+ { 2400, 1200, 600, 400, 300, 200, 150, 100 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 8.0;
+ model.y_offset = 10.00;
+ model.x_size = 217.9;
+ model.y_size = 315.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 8.0;
+ model.y_offset_ta = 13.00;
+ model.x_size_ta = 217.9;
+ model.y_size_ta = 250.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 40.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 48;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_G4050;
+ model.adc_id = AdcId::G4050;
+ model.gpio_id = GpioId::G4050;
+ model.motor_id = MotorId::G4050;
+ model.flags = GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 100;
+
+ s_usb_devices->emplace_back(0x03f0, 0x4605, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-canoscan-4400f";
+ model.vendor = "Canon";
+ model.model = "Canoscan 4400f";
+ model.model_id = ModelId::CANON_4400F;
+ model.asic_type = AsicType::GL843;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 300 },
+ { 1200, 600, 300 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 6.0;
+ model.y_offset = 12.00;
+ model.x_size = 215.9;
+ model.y_size = 297.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 8.0;
+ model.y_offset_ta = 13.00;
+ model.x_size_ta = 217.9;
+ model.y_size_ta = 250.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 40.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 96;
+ model.ld_shift_g = 48;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_CANON_4400F;
+ model.adc_id = AdcId::CANON_4400F;
+ model.gpio_id = GpioId::CANON_4400F;
+ model.motor_id = MotorId::CANON_4400F;
+ model.flags = GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_FULL_HWDPI_MODE |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_SHADING_REPARK;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 100;
+
+ s_usb_devices->emplace_back(0x04a9, 0x2228, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-canoscan-8400f";
+ model.vendor = "Canon";
+ model.model = "Canoscan 8400f";
+ model.model_id = ModelId::CANON_8400F;
+ model.asic_type = AsicType::GL843;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 3200, 1600, 800, 400 },
+ { 3200, 1600, 800, 400 },
+ }, {
+ { ScanMethod::TRANSPARENCY },
+ { 3200, 1600, 800, 400 },
+ { 3200, 1600, 800, 400 },
+ }, {
+ { ScanMethod::TRANSPARENCY_INFRARED },
+ { 3200, 1600, 800, 400 },
+ { 3200, 1600, 800, 400 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 3.5;
+ model.y_offset = 17.00;
+ model.x_size = 219.9;
+ model.y_size = 300.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 10.0;
+
+ model.x_offset_ta = 75.0;
+ model.y_offset_ta = 45.00;
+ model.x_size_ta = 75.0;
+ model.y_size_ta = 230.0;
+
+ model.y_offset_sensor_to_ta = 22.0;
+ model.y_offset_calib_white_ta = 25.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 48;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_CANON_8400F;
+ model.adc_id = AdcId::CANON_8400F;
+ model.gpio_id = GpioId::CANON_8400F;
+ model.motor_id = MotorId::CANON_8400F;
+ model.flags = GENESYS_FLAG_HAS_UTA |
+ GENESYS_FLAG_HAS_UTA_INFRARED |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_FULL_HWDPI_MODE |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_SHADING_REPARK;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 50;
+ model.search_lines = 100;
+
+ s_usb_devices->emplace_back(0x04a9, 0x221e, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-canoscan-8600f";
+ model.vendor = "Canon";
+ model.model = "Canoscan 8600f";
+ model.model_id = ModelId::CANON_8600F;
+ model.asic_type = AsicType::GL843;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 300 },
+ { 1200, 600, 300 },
+ }, {
+ { ScanMethod::TRANSPARENCY, ScanMethod::TRANSPARENCY_INFRARED },
+ { 4800, 2400, 1200, 600, 300 },
+ { 4800, 2400, 1200, 600, 300 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 24.0;
+ model.y_offset = 10.0;
+ model.x_size = 216.0;
+ model.y_size = 297.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 8.0;
+
+ model.x_offset_ta = 85.0;
+ model.y_offset_ta = 26.0;
+ model.x_size_ta = 70.0;
+ model.y_size_ta = 230.0;
+
+ model.y_offset_sensor_to_ta = 11.5;
+ model.y_offset_calib_white_ta = 14.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 48;
+ model.ld_shift_b = 96;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_CANON_8600F;
+ model.adc_id = AdcId::CANON_8600F;
+ model.gpio_id = GpioId::CANON_8600F;
+ model.motor_id = MotorId::CANON_8600F;
+ model.flags = GENESYS_FLAG_HAS_UTA |
+ GENESYS_FLAG_HAS_UTA_INFRARED |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_FULL_HWDPI_MODE |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_SHADING_REPARK;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_FILE_SW | GENESYS_HAS_COPY_SW;
+ model.shading_lines = 50;
+ model.shading_ta_lines = 50;
+ model.search_lines = 100;
+
+ s_usb_devices->emplace_back(0x04a9, 0x2229, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-lide-100";
+ model.vendor = "Canon";
+ model.model = "LiDE 100";
+ model.model_id = ModelId::CANON_LIDE_100;
+ model.asic_type = AsicType::GL847;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 2400, 1200, 600, 300, 200, 150, 100, 75 },
+ { 4800, 2400, 1200, 600, 300, 200, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 1.1;
+ model.y_offset = 8.3;
+ model.x_size = 216.07;
+ model.y_size = 299.0;
+
+ model.y_offset_calib_white = 1.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CIS_CANON_LIDE_100;
+ model.adc_id = AdcId::CANON_LIDE_200;
+ model.gpio_id = GpioId::CANON_LIDE_200;
+ model.motor_id = MotorId::CANON_LIDE_100;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_SIS_SENSOR |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_SHADING_REPARK |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW |
+ GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_FILE_SW;
+ model.shading_lines = 50;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a9, 0x1904, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-lide-110";
+ model.vendor = "Canon";
+ model.model = "LiDE 110";
+ model.model_id = ModelId::CANON_LIDE_110;
+ model.asic_type = AsicType::GL124;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 2400, 1200, 600, /* 400,*/ 300, 150, 100, 75 },
+ { 4800, 2400, 1200, 600, /* 400,*/ 300, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 2.2;
+ model.y_offset = 9.0;
+ model.x_size = 216.70;
+ model.y_size = 300.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CIS_CANON_LIDE_110;
+ model.adc_id = AdcId::CANON_LIDE_110;
+ model.gpio_id = GpioId::CANON_LIDE_110;
+ model.motor_id = MotorId::CANON_LIDE_110;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_SHADING_REPARK |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW |
+ GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_FILE_SW;
+ model.shading_lines = 25;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a9, 0x1909, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-lide-120";
+ model.vendor = "Canon";
+ model.model = "LiDE 120";
+ model.model_id = ModelId::CANON_LIDE_120;
+ model.asic_type = AsicType::GL124;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 2400, 1200, 600, 300, 150, 100, 75 },
+ { 4800, 2400, 1200, 600, 300, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.0;
+ model.y_offset = 8.0;
+ model.x_size = 216.0;
+ model.y_size = 300.0;
+
+ model.y_offset_calib_white = 1.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+ model.line_mode_color_order = ColorOrder::RGB;
+ model.is_cis = true;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CIS_CANON_LIDE_120;
+ model.adc_id = AdcId::CANON_LIDE_120;
+ model.gpio_id = GpioId::CANON_LIDE_120;
+ model.motor_id = MotorId::CANON_LIDE_120;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_SHADING_REPARK |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW |
+ GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_FILE_SW;
+ model.shading_lines = 50;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a9, 0x190e, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-lide-210";
+ model.vendor = "Canon";
+ model.model = "LiDE 210";
+ model.model_id = ModelId::CANON_LIDE_210;
+ model.asic_type = AsicType::GL124;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ // BUG: 4800 resolution crashes
+ { /*4800,*/ 2400, 1200, 600, /* 400,*/ 300, 150, 100, 75 },
+ { /*4800,*/ 2400, 1200, 600, /* 400,*/ 300, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 2.2;
+ model.y_offset = 8.7;
+ model.x_size = 216.70;
+ model.y_size = 297.5;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CIS_CANON_LIDE_210;
+ model.adc_id = AdcId::CANON_LIDE_110;
+ model.gpio_id = GpioId::CANON_LIDE_210;
+ model.motor_id = MotorId::CANON_LIDE_210;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_SHADING_REPARK |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW |
+ GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_FILE_SW |
+ GENESYS_HAS_EXTRA_SW;
+ model.shading_lines = 60;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a9, 0x190a, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-lide-220";
+ model.vendor = "Canon";
+ model.model = "LiDE 220";
+ model.model_id = ModelId::CANON_LIDE_220;
+ model.asic_type = AsicType::GL124; // or a compatible one
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ // BUG: 4800 resolution crashes
+ { /*4800,*/ 2400, 1200, 600, 300, 150, 100, 75 },
+ { /*4800,*/ 2400, 1200, 600, 300, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 2.2;
+ model.y_offset = 8.7;
+ model.x_size = 216.70;
+ model.y_size = 297.5;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+ model.is_cis = true;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CIS_CANON_LIDE_220;
+ model.adc_id = AdcId::CANON_LIDE_110;
+ model.gpio_id = GpioId::CANON_LIDE_210;
+ model.motor_id = MotorId::CANON_LIDE_210;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_SHADING_REPARK |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW |
+ GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_FILE_SW |
+ GENESYS_HAS_EXTRA_SW;
+ model.shading_lines = 60;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a9, 0x190f, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-5600f";
+ model.vendor = "Canon";
+ model.model = "5600F";
+ model.model_id = ModelId::CANON_5600F;
+ model.asic_type = AsicType::GL847;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 4800, 2400, 1200, 600, 400, 300, 200, 150, 100, 75 },
+ { 4800, 2400, 1200, 600, 400, 300, 200, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 1.1;
+ model.y_offset = 8.3;
+ model.x_size = 216.07;
+ model.y_size = 299.0;
+
+ model.y_offset_calib_white = 3.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CIS_CANON_LIDE_200;
+ model.adc_id = AdcId::CANON_LIDE_200;
+ model.gpio_id = GpioId::CANON_LIDE_200;
+ model.motor_id = MotorId::CANON_LIDE_200;
+ model.flags = GENESYS_FLAG_UNTESTED |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_SIS_SENSOR |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW |
+ GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_FILE_SW;
+ model.shading_lines = 50;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a9, 0x1906, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-lide-700f";
+ model.vendor = "Canon";
+ model.model = "LiDE 700F";
+ model.model_id = ModelId::CANON_LIDE_700F;
+ model.asic_type = AsicType::GL847;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 4800, 2400, 1200, 600, 300, 200, 150, 100, 75 },
+ { 4800, 2400, 1200, 600, 300, 200, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 3.1;
+ model.y_offset = 8.1;
+ model.x_size = 216.07;
+ model.y_size = 297.0;
+
+ model.y_offset_calib_white = 1.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CIS_CANON_LIDE_700F;
+ model.adc_id = AdcId::CANON_LIDE_700F;
+ model.gpio_id = GpioId::CANON_LIDE_700F;
+ model.motor_id = MotorId::CANON_LIDE_700;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_SIS_SENSOR |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_SHADING_REPARK |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW |
+ GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_FILE_SW;
+ model.shading_lines = 70;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a9, 0x1907, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-lide-200";
+ model.vendor = "Canon";
+ model.model = "LiDE 200";
+ model.model_id = ModelId::CANON_LIDE_200;
+ model.asic_type = AsicType::GL847;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 4800, 2400, 1200, 600, 300, 200, 150, 100, 75 },
+ { 4800, 2400, 1200, 600, 300, 200, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 1.1;
+ model.y_offset = 8.3;
+ model.x_size = 216.07;
+ model.y_size = 299.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+ model.is_cis = true;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CIS_CANON_LIDE_200;
+ model.adc_id = AdcId::CANON_LIDE_200;
+ model.gpio_id = GpioId::CANON_LIDE_200;
+ model.motor_id = MotorId::CANON_LIDE_200;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_SIS_SENSOR |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_SHADING_REPARK |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW |
+ GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_FILE_SW;
+ model.shading_lines = 50;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a9, 0x1905, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-lide-60";
+ model.vendor = "Canon";
+ model.model = "LiDE 60";
+ model.model_id = ModelId::CANON_LIDE_60;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 300, 150, 75 },
+ { 2400, 1200, 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.42;
+ model.y_offset = 7.9;
+ model.x_size = 218.0;
+ model.y_size = 299.0;
+
+ model.y_offset_calib_white = 6.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CIS_CANON_LIDE_35;
+ model.adc_id = AdcId::CANON_LIDE_35;
+ model.gpio_id = GpioId::CANON_LIDE_35;
+ model.motor_id = MotorId::CANON_LIDE_35;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_WHITE_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+
+ model.buttons = GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_SCAN_SW |
+ GENESYS_HAS_FILE_SW |
+ GENESYS_HAS_EMAIL_SW;
+ model.shading_lines = 300;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+ // this is completely untested
+ s_usb_devices->emplace_back(0x04a9, 0x221c, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-lide-80";
+ model.vendor = "Canon";
+ model.model = "LiDE 80";
+ model.model_id = ModelId::CANON_LIDE_80;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 300, 150, 100, 75 },
+ { 2400, 1200, 600, 300, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+ model.x_offset = 0.42;
+ model.y_offset = 7.90;
+ model.x_size = 216.07;
+ model.y_size = 299.0;
+
+ model.y_offset_calib_white = 4.5;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CIS_CANON_LIDE_80;
+ model.adc_id = AdcId::CANON_LIDE_80;
+ model.gpio_id = GpioId::CANON_LIDE_80;
+ model.motor_id = MotorId::CANON_LIDE_80;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_WHITE_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW |
+ GENESYS_HAS_FILE_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_COPY_SW;
+ model.shading_lines = 160;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a9, 0x2214, model);
+
+
+ model = Genesys_Model();
+ model.name = "hewlett-packard-scanjet-2300c";
+ model.vendor = "Hewlett Packard";
+ model.model = "ScanJet 2300c";
+ model.model_id = ModelId::HP_SCANJET_2300C;
+ model.asic_type = AsicType::GL646;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 1200, 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 2.0;
+ model.y_offset = 7.5;
+ model.x_size = 215.9;
+ model.y_size = 295.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 1.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 16;
+ model.ld_shift_g = 8;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_HP2300;
+ model.adc_id = AdcId::WOLFSON_HP2300;
+ model.gpio_id = GpioId::HP2300;
+ model.motor_id = MotorId::HP2300;
+ model.flags = GENESYS_FLAG_14BIT_GAMMA |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_SEARCH_START |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_COPY_SW;
+ model.shading_lines = 40;
+ model.shading_ta_lines = 0;
+ model.search_lines = 132;
+
+ s_usb_devices->emplace_back(0x03f0, 0x0901, model);
+
+
+ model = Genesys_Model();
+ model.name = "hewlett-packard-scanjet-2400c";
+ model.vendor = "Hewlett Packard";
+ model.model = "ScanJet 2400c";
+ model.model_id = ModelId::HP_SCANJET_2400C;
+ model.asic_type = AsicType::GL646;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 300, 150, 100, 50 },
+ { 1200, 600, 300, 150, 100, 50 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 6.5;
+ model.y_offset = 2.5;
+ model.x_size = 220.0;
+ model.y_size = 297.2;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 1.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 48;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_HP2400;
+ model.adc_id = AdcId::WOLFSON_HP2400;
+ model.gpio_id = GpioId::HP2400;
+ model.motor_id = MotorId::HP2400;
+ model.flags = GENESYS_FLAG_14BIT_GAMMA |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_COPY_SW | GENESYS_HAS_EMAIL_SW | GENESYS_HAS_SCAN_SW;
+ model.shading_lines = 20;
+ model.shading_ta_lines = 0;
+ model.search_lines = 132;
+
+ s_usb_devices->emplace_back(0x03f0, 0x0a01, model);
+
+
+ model = Genesys_Model();
+ model.name = "visioneer-strobe-xp200";
+ model.vendor = "Visioneer";
+ model.model = "Strobe XP200";
+ model.model_id = ModelId::VISIONEER_STROBE_XP200;
+ model.asic_type = AsicType::GL646;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 200, 100, 75 },
+ { 600, 300, 200, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.5;
+ model.y_offset = 16.0;
+ model.x_size = 215.9;
+ model.y_size = 297.2;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CIS_XP200;
+ model.adc_id = AdcId::AD_XP200;
+ model.gpio_id = GpioId::XP200;
+ model.motor_id = MotorId::XP200;
+ model.flags = GENESYS_FLAG_14BIT_GAMMA |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE;
+ model.shading_lines = 120;
+ model.shading_ta_lines = 0;
+ model.search_lines = 132;
+
+ s_usb_devices->emplace_back(0x04a7, 0x0426, model);
+
+
+ model = Genesys_Model();
+ model.name = "hewlett-packard-scanjet-3670";
+ model.vendor = "Hewlett Packard";
+ model.model = "ScanJet 3670";
+ model.model_id = ModelId::HP_SCANJET_3670;
+ model.asic_type = AsicType::GL646;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 300, 150, 100, 75, 50 },
+ { 1200, 600, 300, 150, 100, 75, 50 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 8.5;
+ model.y_offset = 11.0;
+ model.x_size = 215.9;
+ model.y_size = 300.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 1.0;
+
+ model.x_offset_ta = 104.0;
+ model.y_offset_ta = 55.6;
+ model.x_size_ta = 25.6;
+ model.y_size_ta = 78.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 76.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 48;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_HP3670;
+ model.adc_id = AdcId::WOLFSON_HP3670;
+ model.gpio_id = GpioId::HP3670;
+ model.motor_id = MotorId::HP3670;
+ model.flags = GENESYS_FLAG_14BIT_GAMMA |
+ GENESYS_FLAG_XPA |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_COPY_SW | GENESYS_HAS_EMAIL_SW | GENESYS_HAS_SCAN_SW;
+ model.shading_lines = 20;
+ model.shading_ta_lines = 0;
+ model.search_lines = 200;
+
+ s_usb_devices->emplace_back(0x03f0, 0x1405, model);
+
+
+ model = Genesys_Model();
+ model.name = "plustek-opticpro-st12";
+ model.vendor = "Plustek";
+ model.model = "OpticPro ST12";
+ model.model_id = ModelId::PLUSTEK_OPTICPRO_ST12;
+ model.asic_type = AsicType::GL646;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 1200, 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 3.5;
+ model.y_offset = 7.5;
+ model.x_size = 218.0;
+ model.y_size = 299.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 1.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 8;
+ model.ld_shift_b = 16;
+
+ model.line_mode_color_order = ColorOrder::BGR;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_ST12;
+ model.adc_id = AdcId::WOLFSON_ST12;
+ model.gpio_id = GpioId::ST12;
+ model.motor_id = MotorId::UMAX;
+ model.flags = GENESYS_FLAG_UNTESTED | GENESYS_FLAG_14BIT_GAMMA;
+ model.buttons = GENESYS_HAS_NO_BUTTONS;
+ model.shading_lines = 20;
+ model.shading_ta_lines = 0;
+ model.search_lines = 200;
+
+ s_usb_devices->emplace_back(0x07b3, 0x0600, model);
+
+ model = Genesys_Model();
+ model.name = "plustek-opticpro-st24";
+ model.vendor = "Plustek";
+ model.model = "OpticPro ST24";
+ model.model_id = ModelId::PLUSTEK_OPTICPRO_ST24;
+ model.asic_type = AsicType::GL646;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 300, 150, 75 },
+ { 2400, 1200, 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 3.5;
+ model.y_offset = 7.5;
+ model.x_size = 218.0;
+ model.y_size = 299.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 1.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 8;
+ model.ld_shift_b = 16;
+
+ model.line_mode_color_order = ColorOrder::BGR;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_ST24;
+ model.adc_id = AdcId::WOLFSON_ST24;
+ model.gpio_id = GpioId::ST24;
+ model.motor_id = MotorId::ST24;
+ model.flags = GENESYS_FLAG_UNTESTED |
+ GENESYS_FLAG_14BIT_GAMMA |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_SEARCH_START |
+ GENESYS_FLAG_OFFSET_CALIBRATION;
+ model.buttons = GENESYS_HAS_NO_BUTTONS;
+ model.shading_lines = 20;
+ model.shading_ta_lines = 0;
+ model.search_lines = 200;
+
+ s_usb_devices->emplace_back(0x07b3, 0x0601, model);
+
+ model = Genesys_Model();
+ model.name = "medion-md5345-model";
+ model.vendor = "Medion";
+ model.model = "MD5345/MD6228/MD6471";
+ model.model_id = ModelId::MEDION_MD5345;
+ model.asic_type = AsicType::GL646;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 400, 300, 200, 150, 100, 75, 50 },
+ { 2400, 1200, 600, 400, 300, 200, 150, 100, 75, 50 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.30;
+ model.y_offset = 0.80;
+ model.x_size = 220.0;
+ model.y_size = 296.4;
+
+ model.y_offset_calib_white = 0.00;
+ model.x_offset_calib_black = 0.00;
+
+ model.x_offset_ta = 0.00;
+ model.y_offset_ta = 0.00;
+ model.x_size_ta = 0.00;
+ model.y_size_ta = 0.00;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.00;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 48;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 0;
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_5345;
+ model.adc_id = AdcId::WOLFSON_5345;
+ model.gpio_id = GpioId::MD_5345;
+ model.motor_id = MotorId::MD_5345;
+ model.flags = GENESYS_FLAG_14BIT_GAMMA |
+ GENESYS_FLAG_SEARCH_START |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_SHADING_NO_MOVE |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_POWER_SW |
+ GENESYS_HAS_OCR_SW |
+ GENESYS_HAS_SCAN_SW;
+ model.shading_lines = 40;
+ model.shading_ta_lines = 0;
+ model.search_lines = 200;
+
+ s_usb_devices->emplace_back(0x0461, 0x0377, model);
+
+ model = Genesys_Model();
+ model.name = "visioneer-strobe-xp300";
+ model.vendor = "Visioneer";
+ model.model = "Strobe XP300";
+ model.model_id = ModelId::VISIONEER_STROBE_XP300;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.0;
+ model.y_offset = 1.0;
+ model.x_size = 435.0;
+ model.y_size = 511;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 26.5;
+ // this is larger than needed -- accounts for second sensor head, which is a calibration item
+ model.eject_feed = 0.0;
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CCD_XP300;
+ model.adc_id = AdcId::WOLFSON_XP300;
+ model.gpio_id = GpioId::XP300;
+ model.motor_id = MotorId::XP300;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a7, 0x0474, model);
+
+ model = Genesys_Model();
+ model.name = "syscan-docketport-665";
+ model.vendor = "Syscan/Ambir";
+ model.model = "DocketPORT 665";
+ model.model_id = ModelId::SYSCAN_DOCKETPORT_665;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 1200, 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.0;
+ model.y_offset = 0.0;
+ model.x_size = 108.0;
+ model.y_size = 511;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 17.5;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CCD_DP665;
+ model.adc_id = AdcId::WOLFSON_XP300;
+ model.gpio_id = GpioId::DP665;
+ model.motor_id = MotorId::DP665;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x0a82, 0x4803, model);
+
+ model = Genesys_Model();
+ model.name = "visioneer-roadwarrior";
+ model.vendor = "Visioneer";
+ model.model = "Readwarrior";
+ model.model_id = ModelId::VISIONEER_ROADWARRIOR;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 1200, 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.0;
+ model.y_offset = 0.0;
+ model.x_size = 220.0;
+ model.y_size = 511;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 16.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CCD_ROADWARRIOR;
+ model.adc_id = AdcId::WOLFSON_XP300;
+ model.gpio_id = GpioId::DP665;
+ model.motor_id = MotorId::ROADWARRIOR;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_DARK_CALIBRATION;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a7, 0x0494, model);
+
+ model = Genesys_Model();
+ model.name = "syscan-docketport-465";
+ model.vendor = "Syscan";
+ model.model = "DocketPORT 465";
+ model.model_id = ModelId::SYSCAN_DOCKETPORT_465;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 1200, 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.0;
+ model.y_offset = 0.0;
+ model.x_size = 220.0;
+ model.y_size = 511;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 16.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CCD_ROADWARRIOR;
+ model.adc_id = AdcId::WOLFSON_XP300;
+ model.gpio_id = GpioId::DP665;
+ model.motor_id = MotorId::ROADWARRIOR;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_NO_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_UNTESTED;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW;
+ model.shading_lines = 300;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x0a82, 0x4802, model);
+
+
+ model = Genesys_Model();
+ model.name = "visioneer-xp100-revision3";
+ model.vendor = "Visioneer";
+ model.model = "XP100 Revision 3";
+ model.model_id = ModelId::VISIONEER_STROBE_XP100_REVISION3;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 1200, 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.0;
+ model.y_offset = 0.0;
+ model.x_size = 220.0;
+ model.y_size = 511;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 16.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CCD_ROADWARRIOR;
+ model.adc_id = AdcId::WOLFSON_XP300;
+ model.gpio_id = GpioId::DP665;
+ model.motor_id = MotorId::ROADWARRIOR;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_DARK_CALIBRATION;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a7, 0x049b, model);
+
+ model = Genesys_Model();
+ model.name = "pentax-dsmobile-600";
+ model.vendor = "Pentax";
+ model.model = "DSmobile 600";
+ model.model_id = ModelId::PENTAX_DSMOBILE_600;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 1200, 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.0;
+ model.y_offset = 0.0;
+ model.x_size = 220.0;
+ model.y_size = 511;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 16.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CCD_DSMOBILE600;
+ model.adc_id = AdcId::WOLFSON_DSM600;
+ model.gpio_id = GpioId::DP665;
+ model.motor_id = MotorId::DSMOBILE_600;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_DARK_CALIBRATION;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x0a17, 0x3210, model);
+ // clone, only usb id is different
+ s_usb_devices->emplace_back(0x04f9, 0x2038, model);
+
+ model = Genesys_Model();
+ model.name = "syscan-docketport-467";
+ model.vendor = "Syscan";
+ model.model = "DocketPORT 467";
+ model.model_id = ModelId::SYSCAN_DOCKETPORT_467;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 1200, 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.0;
+ model.y_offset = 0.0;
+ model.x_size = 220.0;
+ model.y_size = 511;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 16.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CCD_DSMOBILE600;
+ model.adc_id = AdcId::WOLFSON_DSM600;
+ model.gpio_id = GpioId::DP665;
+ model.motor_id = MotorId::DSMOBILE_600;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_DARK_CALIBRATION;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x1dcc, 0x4812, model);
+
+ model = Genesys_Model();
+ model.name = "syscan-docketport-685";
+ model.vendor = "Syscan/Ambir";
+ model.model = "DocketPORT 685";
+ model.model_id = ModelId::SYSCAN_DOCKETPORT_685;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.0;
+ model.y_offset = 1.0;
+ model.x_size = 212.0;
+ model.y_size = 500;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 26.5;
+ // this is larger than needed -- accounts for second sensor head, which is a calibration item
+ model.eject_feed = 0.0;
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CCD_DP685;
+ model.adc_id = AdcId::WOLFSON_DSM600;
+ model.gpio_id = GpioId::DP685;
+ model.motor_id = MotorId::XP300;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_DARK_CALIBRATION;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+
+ s_usb_devices->emplace_back(0x0a82, 0x480c, model);
+
+
+ model = Genesys_Model();
+ model.name = "syscan-docketport-485";
+ model.vendor = "Syscan/Ambir";
+ model.model = "DocketPORT 485";
+ model.model_id = ModelId::SYSCAN_DOCKETPORT_485;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.0;
+ model.y_offset = 1.0;
+ model.x_size = 435.0;
+ model.y_size = 511;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 26.5;
+ // this is larger than needed -- accounts for second sensor head, which is a calibration item
+ model.eject_feed = 0.0;
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CCD_XP300;
+ model.adc_id = AdcId::WOLFSON_XP300;
+ model.gpio_id = GpioId::XP300;
+ model.motor_id = MotorId::XP300;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_DARK_CALIBRATION;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x0a82, 0x4800, model);
+
+
+ model = Genesys_Model();
+ model.name = "dct-docketport-487";
+ model.vendor = "DCT";
+ model.model = "DocketPORT 487";
+ model.model_id = ModelId::DCT_DOCKETPORT_487;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.0;
+ model.y_offset = 1.0;
+ model.x_size = 435.0;
+ model.y_size = 511;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 26.5;
+ // this is larger than needed -- accounts for second sensor head, which is a calibration item
+ model.eject_feed = 0.0;
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CCD_XP300;
+ model.adc_id = AdcId::WOLFSON_XP300;
+ model.gpio_id = GpioId::XP300;
+ model.motor_id = MotorId::XP300;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_UNTESTED;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x1dcc, 0x4810, model);
+
+
+ model = Genesys_Model();
+ model.name = "visioneer-7100-model";
+ model.vendor = "Visioneer";
+ model.model = "OneTouch 7100";
+ model.model_id = ModelId::VISIONEER_7100;
+ model.asic_type = AsicType::GL646;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 400, 300, 200, 150, 100, 75, 50 },
+ { 2400, 1200, 600, 400, 300, 200, 150, 100, 75, 50 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 4.00;
+ model.y_offset = 0.80;
+ model.x_size = 215.9;
+ model.y_size = 296.4;
+
+ model.y_offset_calib_white = 0.00;
+ model.x_offset_calib_black = 0.00;
+
+ model.x_offset_ta = 0.00;
+ model.y_offset_ta = 0.00;
+ model.x_size_ta = 0.00;
+ model.y_size_ta = 0.00;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.00;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 48;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 0;
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_5345;
+ model.adc_id = AdcId::WOLFSON_5345;
+ model.gpio_id = GpioId::MD_5345;
+ model.motor_id = MotorId::MD_5345;
+ model.flags = GENESYS_FLAG_14BIT_GAMMA |
+ GENESYS_FLAG_SEARCH_START |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_POWER_SW |
+ GENESYS_HAS_OCR_SW |
+ GENESYS_HAS_SCAN_SW;
+ model.shading_lines = 40;
+ model.shading_ta_lines = 0;
+ model.search_lines = 200;
+
+ s_usb_devices->emplace_back(0x04a7, 0x0229, model);
+
+
+ model = Genesys_Model();
+ model.name = "xerox-2400-model";
+ model.vendor = "Xerox";
+ model.model = "OneTouch 2400";
+ model.model_id = ModelId::XEROX_2400;
+ model.asic_type = AsicType::GL646;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 400, 300, 200, 150, 100, 75, 50 },
+ { 2400, 1200, 600, 400, 300, 200, 150, 100, 75, 50 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 4.00;
+ model.y_offset = 0.80;
+ model.x_size = 215.9;
+ model.y_size = 296.4;
+
+ model.y_offset_calib_white = 0.00;
+ model.x_offset_calib_black = 0.00;
+
+ model.x_offset_ta = 0.00;
+ model.y_offset_ta = 0.00;
+ model.x_size_ta = 0.00;
+ model.y_size_ta = 0.00;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.00;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 48;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 0;
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_5345;
+ model.adc_id = AdcId::WOLFSON_5345;
+ model.gpio_id = GpioId::MD_5345;
+ model.motor_id = MotorId::MD_5345;
+ model.flags = GENESYS_FLAG_14BIT_GAMMA |
+ GENESYS_FLAG_SEARCH_START |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_COPY_SW |
+ GENESYS_HAS_EMAIL_SW |
+ GENESYS_HAS_POWER_SW |
+ GENESYS_HAS_OCR_SW |
+ GENESYS_HAS_SCAN_SW;
+ model.shading_lines = 40;
+ model.shading_ta_lines = 0;
+ model.search_lines = 200;
+
+ s_usb_devices->emplace_back(0x0461, 0x038b, model);
+
+
+ model = Genesys_Model();
+ model.name = "xerox-travelscanner";
+ model.vendor = "Xerox";
+ model.model = "Travelscanner 100";
+ model.model_id = ModelId::XEROX_TRAVELSCANNER_100;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 600, 300, 150, 75 },
+ { 1200, 600, 300, 150, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 4.0;
+ model.y_offset = 0.0;
+ model.x_size = 220.0;
+ model.y_size = 511;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 16.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = true;
+ model.is_sheetfed = true;
+ model.sensor_id = SensorId::CCD_ROADWARRIOR;
+ model.adc_id = AdcId::WOLFSON_XP300;
+ model.gpio_id = GpioId::DP665;
+ model.motor_id = MotorId::ROADWARRIOR;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_DARK_CALIBRATION;
+ model.buttons = GENESYS_HAS_SCAN_SW | GENESYS_HAS_PAGE_LOADED_SW | GENESYS_HAS_CALIBRATE;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 400;
+
+ s_usb_devices->emplace_back(0x04a7, 0x04ac, model);
+
+
+ model = Genesys_Model();
+ model.name = "plustek-opticbook-3600";
+ model.vendor = "PLUSTEK";
+ model.model = "OpticBook 3600";
+ model.model_id = ModelId::PLUSTEK_OPTICPRO_3600;
+ model.asic_type = AsicType::GL841;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { /*1200,*/ 600, 400, 300, 200, 150, 100, 75 },
+ { /*2400,*/ 1200, 600, 400, 300, 200, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 0.42;
+ model.y_offset = 6.75;
+ model.x_size = 216.0;
+ model.y_size = 297.0;
+
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 0.0;
+ model.y_size_ta = 0.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 48;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_PLUSTEK_OPTICPRO_3600;
+ model.adc_id = AdcId::PLUSTEK_OPTICPRO_3600;
+ model.gpio_id = GpioId::PLUSTEK_OPTICPRO_3600;
+ model.motor_id = MotorId::PLUSTEK_OPTICPRO_3600;
+ model.flags = GENESYS_FLAG_UNTESTED | // not fully working yet
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION;
+ model.buttons = GENESYS_HAS_NO_BUTTONS;
+ model.shading_lines = 7;
+ model.shading_ta_lines = 0;
+ model.search_lines = 200;
+
+ s_usb_devices->emplace_back(0x07b3, 0x0900, model);
+
+
+ model = Genesys_Model();
+ model.name = "plustek-opticfilm-7200i";
+ model.vendor = "PLUSTEK";
+ model.model = "OpticFilm 7200i";
+ model.model_id = ModelId::PLUSTEK_OPTICFILM_7200I;
+ model.asic_type = AsicType::GL843;
+
+ model.resolutions = {
+ {
+ { ScanMethod::TRANSPARENCY, ScanMethod::TRANSPARENCY_INFRARED },
+ { 7200, 3600, 1800, 900 },
+ { 7200, 3600, 1800, 900 },
+ }
+ };
+
+ model.bpp_gray_values = { 16 };
+ model.bpp_color_values = { 16 };
+ model.default_method = ScanMethod::TRANSPARENCY;
+
+ model.x_offset = 0.0;
+ model.y_offset = 0.0;
+ model.x_size = 36.0;
+ model.y_size = 44.0;
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 6.5;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 29.0;
+ model.x_size_ta = 36.0;
+ model.y_size_ta = 24.0;
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_black_ta = 6.5;
+ model.y_offset_calib_white_ta = 0.0;
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 12;
+ model.ld_shift_b = 24;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+
+ model.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7200I;
+ model.adc_id = AdcId::PLUSTEK_OPTICFILM_7200I;
+ model.gpio_id = GpioId::PLUSTEK_OPTICFILM_7200I;
+ model.motor_id = MotorId::PLUSTEK_OPTICFILM_7200I;
+
+ model.flags = GENESYS_FLAG_HAS_UTA |
+ GENESYS_FLAG_HAS_UTA_INFRARED |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_HAS_NO_BUTTONS |
+ GENESYS_FLAG_SHADING_REPARK |
+ GENESYS_FLAG_CALIBRATION_HOST_SIDE |
+ GENESYS_FLAG_16BIT_DATA_INVERTED;
+
+ model.shading_lines = 7;
+ model.shading_ta_lines = 50;
+ model.search_lines = 200;
+ s_usb_devices->emplace_back(0x07b3, 0x0c04, model);
+
+
+ model = Genesys_Model();
+ model.name = "plustek-opticfilm-7300";
+ model.vendor = "PLUSTEK";
+ model.model = "OpticFilm 7300";
+ model.model_id = ModelId::PLUSTEK_OPTICFILM_7300;
+ model.asic_type = AsicType::GL843;
+
+ model.resolutions = {
+ {
+ { ScanMethod::TRANSPARENCY },
+ { 7200, 3600, 1800, 900 },
+ { 7200, 3600, 1800, 900 },
+ }
+ };
+
+ model.bpp_gray_values = { 16 };
+ model.bpp_color_values = { 16 };
+ model.default_method = ScanMethod::TRANSPARENCY;
+
+ model.x_offset = 0.0;
+ model.y_offset = 0.0;
+ model.x_size = 36.0;
+ model.y_size = 44.0;
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 6.5;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 29.0;
+ model.x_size_ta = 36.0;
+ model.y_size_ta = 24.0;
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_black_ta = 6.5;
+ model.y_offset_calib_white_ta = 0.0;
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 12;
+ model.ld_shift_b = 24;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+
+ model.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7300;
+ model.adc_id = AdcId::PLUSTEK_OPTICFILM_7300;
+ model.gpio_id = GpioId::PLUSTEK_OPTICFILM_7300;
+ model.motor_id = MotorId::PLUSTEK_OPTICFILM_7300;
+
+ model.flags = GENESYS_FLAG_HAS_UTA |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_HAS_NO_BUTTONS |
+ GENESYS_FLAG_SHADING_REPARK |
+ GENESYS_FLAG_CALIBRATION_HOST_SIDE;
+
+ model.shading_lines = 7;
+ model.shading_ta_lines = 50;
+ model.search_lines = 200;
+ s_usb_devices->emplace_back(0x07b3, 0x0c12, model);
+
+
+ model = Genesys_Model();
+ model.name = "plustek-opticfilm-7500i";
+ model.vendor = "PLUSTEK";
+ model.model = "OpticFilm 7500i";
+ model.model_id = ModelId::PLUSTEK_OPTICFILM_7500I;
+ model.asic_type = AsicType::GL843;
+
+ model.resolutions = {
+ {
+ { ScanMethod::TRANSPARENCY, ScanMethod::TRANSPARENCY_INFRARED },
+ { 7200, 3600, 1800, 900 },
+ { 7200, 3600, 1800, 900 },
+ }
+ };
+
+ model.bpp_gray_values = { 16 };
+ model.bpp_color_values = { 16 };
+ model.default_method = ScanMethod::TRANSPARENCY;
+
+ model.x_offset = 0.0;
+ model.y_offset = 0.0;
+ model.x_size = 36.0;
+ model.y_size = 44.0;
+ model.y_offset_calib_white = 0.0;
+ model.x_offset_calib_black = 6.5;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 29.0;
+ model.x_size_ta = 36.0;
+ model.y_size_ta = 24.0;
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_black_ta = 6.5;
+ model.y_offset_calib_white_ta = 0.0;
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 12;
+ model.ld_shift_b = 24;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+
+ model.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7500I;
+ model.adc_id = AdcId::PLUSTEK_OPTICFILM_7500I;
+ model.gpio_id = GpioId::PLUSTEK_OPTICFILM_7500I;
+ model.motor_id = MotorId::PLUSTEK_OPTICFILM_7500I;
+
+ model.flags = GENESYS_FLAG_HAS_UTA |
+ GENESYS_FLAG_HAS_UTA_INFRARED |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_HAS_NO_BUTTONS |
+ GENESYS_FLAG_SHADING_REPARK |
+ GENESYS_FLAG_CALIBRATION_HOST_SIDE;
+
+ model.shading_lines = 7;
+ model.shading_ta_lines = 50;
+ model.search_lines = 200;
+ s_usb_devices->emplace_back(0x07b3, 0x0c13, model);
+
+
+ model = Genesys_Model();
+ model.name = "hewlett-packard-scanjet-N6310";
+ model.vendor = "Hewlett Packard";
+ model.model = "ScanJet N6310";
+ model.model_id = ModelId::HP_SCANJET_N6310;
+ model.asic_type = AsicType::GL847;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 2400, 1200, 600, 400, 300, 200, 150, 100, 75 },
+ { 2400, 1200, 600, 400, 300, 200, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 6;
+ model.y_offset = 2;
+ model.x_size = 216;
+ model.y_size = 511;
+
+ model.y_offset_calib_white = 3.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 100.0;
+ model.y_size_ta = 100.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0;
+
+ model.post_scan = 0;
+ model.eject_feed = 0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 0;
+ model.ld_shift_b = 0;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_HP_N6310;
+ model.adc_id = AdcId::CANON_LIDE_200; // Not defined yet for N6310
+ model.gpio_id = GpioId::HP_N6310;
+ model.motor_id = MotorId::CANON_LIDE_200; // Not defined yet for N6310
+ model.flags = GENESYS_FLAG_UNTESTED |
+ GENESYS_FLAG_14BIT_GAMMA |
+ GENESYS_FLAG_DARK_CALIBRATION |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_NO_CALIBRATION;
+
+ model.buttons = GENESYS_HAS_NO_BUTTONS;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 100;
+
+ s_usb_devices->emplace_back(0x03f0, 0x4705, model);
+
+
+ model = Genesys_Model();
+ model.name = "plustek-opticbook-3800";
+ model.vendor = "PLUSTEK";
+ model.model = "OpticBook 3800";
+ model.model_id = ModelId::PLUSTEK_OPTICBOOK_3800;
+ model.asic_type = AsicType::GL845;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 300, 150, 100, 75 },
+ { 1200, 600, 300, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 7.2;
+ model.y_offset = 14.7;
+ model.x_size = 217.7;
+ model.y_size = 300.0;
+
+ model.y_offset_calib_white = 9.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 0.0;
+ model.y_size_ta = 0.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 48;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_PLUSTEK_OPTICBOOK_3800;
+ model.adc_id = AdcId::PLUSTEK_OPTICBOOK_3800;
+ model.gpio_id = GpioId::PLUSTEK_OPTICBOOK_3800;
+ model.motor_id = MotorId::PLUSTEK_OPTICBOOK_3800;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA;
+ model.buttons = GENESYS_HAS_NO_BUTTONS; // TODO there are 4 buttons to support
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 100;
+
+ s_usb_devices->emplace_back(0x07b3, 0x1300, model);
+
+
+ model = Genesys_Model();
+ model.name = "canon-image-formula-101";
+ model.vendor = "Canon";
+ model.model = "Image Formula 101";
+ model.model_id = ModelId::CANON_IMAGE_FORMULA_101;
+ model.asic_type = AsicType::GL846;
+
+ model.resolutions = {
+ {
+ { ScanMethod::FLATBED },
+ { 1200, 600, 300, 150, 100, 75 },
+ { 1200, 600, 300, 150, 100, 75 },
+ }
+ };
+
+ model.bpp_gray_values = { 8, 16 };
+ model.bpp_color_values = { 8, 16 };
+
+ model.x_offset = 7.2;
+ model.y_offset = 14.7;
+ model.x_size = 217.7;
+ model.y_size = 300.0;
+
+ model.y_offset_calib_white = 9.0;
+ model.x_offset_calib_black = 0.0;
+
+ model.x_offset_ta = 0.0;
+ model.y_offset_ta = 0.0;
+ model.x_size_ta = 0.0;
+ model.y_size_ta = 0.0;
+
+ model.y_offset_sensor_to_ta = 0.0;
+ model.y_offset_calib_white_ta = 0.0;
+
+ model.post_scan = 0.0;
+ model.eject_feed = 0.0;
+
+ model.ld_shift_r = 0;
+ model.ld_shift_g = 24;
+ model.ld_shift_b = 48;
+
+ model.line_mode_color_order = ColorOrder::RGB;
+
+ model.is_cis = false;
+ model.is_sheetfed = false;
+ model.sensor_id = SensorId::CCD_IMG101;
+ model.adc_id = AdcId::IMG101;
+ model.gpio_id = GpioId::IMG101;
+ model.motor_id = MotorId::IMG101;
+ model.flags = GENESYS_FLAG_SKIP_WARMUP |
+ GENESYS_FLAG_OFFSET_CALIBRATION |
+ GENESYS_FLAG_CUSTOM_GAMMA |
+ GENESYS_FLAG_UNTESTED;
+ model.buttons = GENESYS_HAS_NO_BUTTONS ;
+ model.shading_lines = 100;
+ model.shading_ta_lines = 0;
+ model.search_lines = 100;
+
+ s_usb_devices->emplace_back(0x1083, 0x162e, model);
+ }
+
+} // namespace genesys
diff --git a/backend/genesys/tables_motor.cpp b/backend/genesys/tables_motor.cpp
new file mode 100644
index 0000000..2484d2d
--- /dev/null
+++ b/backend/genesys/tables_motor.cpp
@@ -0,0 +1,325 @@
+/* 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 "low.h"
+
+namespace genesys {
+
+StaticInit<std::vector<Genesys_Motor>> s_motors;
+
+void genesys_init_motor_tables()
+{
+ s_motors.init();
+
+ Genesys_Motor motor;
+ motor.id = MotorId::UMAX;
+ motor.base_ydpi = 1200;
+ motor.optical_ydpi = 2400;
+ motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128));
+ motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::MD_5345; // MD5345/6228/6471
+ motor.base_ydpi = 1200;
+ motor.optical_ydpi = 2400;
+ motor.slopes.push_back(MotorSlope::create_from_steps(2000, 1375, 128));
+ motor.slopes.push_back(MotorSlope::create_from_steps(2000, 1375, 128));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::ST24;
+ motor.base_ydpi = 2400;
+ motor.optical_ydpi = 2400;
+ motor.slopes.push_back(MotorSlope::create_from_steps(2289, 2100, 128));
+ motor.slopes.push_back(MotorSlope::create_from_steps(2289, 2100, 128));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::HP3670;
+ motor.base_ydpi = 1200;
+ motor.optical_ydpi = 1200;
+ motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128));
+ motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::HP2400;
+ motor.base_ydpi = 1200;
+ motor.optical_ydpi = 1200;
+ motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128));
+ motor.slopes.push_back(MotorSlope::create_from_steps(11000, 3000, 128));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::HP2300;
+ motor.base_ydpi = 600;
+ motor.optical_ydpi = 1200;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3200, 1200, 128));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3200, 1200, 128));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::CANON_LIDE_35;
+ motor.base_ydpi = 1200;
+ motor.optical_ydpi = 2400;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1400, 60));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::XP200;
+ motor.base_ydpi = 600;
+ motor.optical_ydpi = 600;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::XP300;
+ motor.base_ydpi = 300;
+ motor.optical_ydpi = 600;
+ // works best with GPIO10, GPIO14 off
+ motor.slopes.push_back(MotorSlope::create_from_steps(3700, 3700, 2));
+ motor.slopes.push_back(MotorSlope::create_from_steps(11000, 11000, 2));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::DP665;
+ motor.base_ydpi = 750;
+ motor.optical_ydpi = 1500;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3000, 2500, 10));
+ motor.slopes.push_back(MotorSlope::create_from_steps(11000, 11000, 2));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::ROADWARRIOR;
+ motor.base_ydpi = 750;
+ motor.optical_ydpi = 1500;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3000, 2600, 10));
+ motor.slopes.push_back(MotorSlope::create_from_steps(11000, 11000, 2));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::DSMOBILE_600;
+ motor.base_ydpi = 750;
+ motor.optical_ydpi = 1500;
+ motor.slopes.push_back(MotorSlope::create_from_steps(6666, 3700, 8));
+ motor.slopes.push_back(MotorSlope::create_from_steps(6666, 3700, 8));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::CANON_LIDE_100;
+ motor.base_ydpi = 1200;
+ motor.optical_ydpi = 6400;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 127));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1500, 127));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3 * 2712, 3 * 2712, 16));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::CANON_LIDE_200;
+ motor.base_ydpi = 1200;
+ motor.optical_ydpi = 6400;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 127));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1500, 127));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3 * 2712, 3 * 2712, 16));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::CANON_LIDE_700;
+ motor.base_ydpi = 1200;
+ motor.optical_ydpi = 6400;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 127));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1500, 127));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3 * 2712, 3 * 2712, 16));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::KVSS080;
+ motor.base_ydpi = 1200;
+ motor.optical_ydpi = 1200;
+ motor.slopes.push_back(MotorSlope::create_from_steps(22222, 500, 246));
+ motor.slopes.push_back(MotorSlope::create_from_steps(22222, 500, 246));
+ motor.slopes.push_back(MotorSlope::create_from_steps(22222, 500, 246));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::G4050;
+ motor.base_ydpi = 2400;
+ motor.optical_ydpi = 9600;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::CANON_4400F;
+ motor.base_ydpi = 2400;
+ motor.optical_ydpi = 9600;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::CANON_8400F;
+ motor.base_ydpi = 1600;
+ motor.optical_ydpi = 6400;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::CANON_8600F;
+ motor.base_ydpi = 2400;
+ motor.optical_ydpi = 9600;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3961, 240, 246));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::CANON_LIDE_110;
+ motor.base_ydpi = 4800;
+ motor.optical_ydpi = 9600;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 256));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::CANON_LIDE_120;
+ motor.base_ydpi = 4800;
+ motor.optical_ydpi = 9600;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 256));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::CANON_LIDE_210;
+ motor.base_ydpi = 4800;
+ motor.optical_ydpi = 9600;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3000, 1000, 256));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::PLUSTEK_OPTICPRO_3600;
+ motor.base_ydpi = 1200;
+ motor.optical_ydpi = 2400;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3500, 3250, 60));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::PLUSTEK_OPTICFILM_7200I;
+ motor.base_ydpi = 3600;
+ motor.optical_ydpi = 3600;
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::PLUSTEK_OPTICFILM_7300;
+ motor.base_ydpi = 3600;
+ motor.optical_ydpi = 3600;
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::PLUSTEK_OPTICFILM_7500I;
+ motor.base_ydpi = 3600;
+ motor.optical_ydpi = 3600;
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::IMG101;
+ motor.base_ydpi = 600;
+ motor.optical_ydpi = 1200;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3500, 3250, 60));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::PLUSTEK_OPTICBOOK_3800;
+ motor.base_ydpi = 600;
+ motor.optical_ydpi = 1200;
+ motor.slopes.push_back(MotorSlope::create_from_steps(3500, 1300, 60));
+ motor.slopes.push_back(MotorSlope::create_from_steps(3500, 3250, 60));
+ s_motors->push_back(std::move(motor));
+
+
+ motor = Genesys_Motor();
+ motor.id = MotorId::CANON_LIDE_80;
+ motor.base_ydpi = 2400;
+ motor.optical_ydpi = 4800; // 9600
+ motor.slopes.push_back(MotorSlope::create_from_steps(9560, 1912, 31));
+ s_motors->push_back(std::move(motor));
+}
+
+} // namespace genesys
diff --git a/backend/genesys/tables_motor_profile.cpp b/backend/genesys/tables_motor_profile.cpp
new file mode 100644
index 0000000..18f7271
--- /dev/null
+++ b/backend/genesys/tables_motor_profile.cpp
@@ -0,0 +1,380 @@
+/* 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 "low.h"
+
+namespace genesys {
+
+StaticInit<std::vector<Motor_Profile>> gl843_motor_profiles;
+
+void genesys_init_motor_profile_tables_gl843()
+{
+ gl843_motor_profiles.init();
+
+ auto profile = Motor_Profile();
+ profile.motor_id = MotorId::KVSS080;
+ profile.exposure = 8000;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(44444, 500, 489);
+ gl843_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::G4050;
+ profile.exposure = 8016;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(7842, 320, 602);
+ gl843_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::G4050;
+ profile.exposure = 15624;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(9422, 254, 1004);
+ gl843_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::G4050;
+ profile.exposure = 42752;
+ profile.step_type = StepType::QUARTER;
+ profile.slope = MotorSlope::create_from_steps(42752, 1706, 610);
+ gl843_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::G4050;
+ profile.exposure = 56064;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(28032, 2238, 604);
+ gl843_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_4400F;
+ profile.exposure = 11640;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(49152, 484, 1014);
+ gl843_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_8400F;
+ profile.exposure = 50000;
+ profile.step_type = StepType::QUARTER;
+ profile.slope = MotorSlope::create_from_steps(8743, 300, 794);
+ gl843_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_8600F;
+ profile.exposure = 0x59d8;
+ profile.step_type = StepType::QUARTER;
+ // FIXME: if the exposure is lower then we'll select another motor
+ profile.slope = MotorSlope::create_from_steps(54612, 1500, 219);
+ gl843_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::PLUSTEK_OPTICFILM_7200I;
+ profile.exposure = 0;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(39682, 1191, 15);
+ gl843_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::PLUSTEK_OPTICFILM_7300;
+ profile.exposure = 0x2f44;
+ profile.step_type = StepType::QUARTER;
+ profile.slope = MotorSlope::create_from_steps(31250, 1512, 6);
+ gl843_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::PLUSTEK_OPTICFILM_7500I;
+ profile.exposure = 0;
+ profile.step_type = StepType::QUARTER;
+ profile.slope = MotorSlope::create_from_steps(31250, 1375, 7);
+ gl843_motor_profiles->push_back(profile);
+}
+
+StaticInit<std::vector<Motor_Profile>> gl846_motor_profiles;
+
+void genesys_init_motor_profile_tables_gl846()
+{
+ gl846_motor_profiles.init();
+
+ auto profile = Motor_Profile();
+ profile.motor_id = MotorId::IMG101;
+ profile.exposure = 11000;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(22000, 1000, 1017);
+
+ gl846_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::PLUSTEK_OPTICBOOK_3800;
+ profile.exposure = 11000;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(22000, 1000, 1017);
+ gl846_motor_profiles->push_back(profile);
+}
+
+/**
+ * database of motor profiles
+ */
+
+StaticInit<std::vector<Motor_Profile>> gl847_motor_profiles;
+
+void genesys_init_motor_profile_tables_gl847()
+{
+ gl847_motor_profiles.init();
+
+ auto profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_100;
+ profile.exposure = 2848;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(46876, 534, 255);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_100;
+ profile.exposure = 1424;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(46876, 534, 255);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_100;
+ profile.exposure = 1432;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(46876, 534, 255);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_100;
+ profile.exposure = 2712;
+ profile.step_type = StepType::QUARTER;
+ profile.slope = MotorSlope::create_from_steps(46876, 534, 279);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_100;
+ profile.exposure = 5280;
+ profile.step_type = StepType::EIGHTH;
+ profile.slope = MotorSlope::create_from_steps(31680, 534, 247);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_200;
+ profile.exposure = 2848;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(46876, 534, 255);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_200;
+ profile.exposure = 1424;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(46876, 534, 255);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_200;
+ profile.exposure = 1432;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(46876, 534, 255);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_200;
+ profile.exposure = 2712;
+ profile.step_type = StepType::QUARTER;
+ profile.slope = MotorSlope::create_from_steps(46876, 534, 279);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_200;
+ profile.exposure = 5280;
+ profile.step_type = StepType::EIGHTH;
+ profile.slope = MotorSlope::create_from_steps(31680, 534, 247);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_200;
+ profile.exposure = 10416;
+ profile.step_type = StepType::EIGHTH;
+ profile.slope = MotorSlope::create_from_steps(31680, 534, 247);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_700;
+ profile.exposure = 2848;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(46876, 534, 255);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_700;
+ profile.exposure = 1424;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(46876, 534, 255);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_700;
+ profile.exposure = 1504;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(46876, 534, 255);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_700;
+ profile.exposure = 2696;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(46876, 2022, 127);
+ gl847_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_700;
+ profile.exposure = 10576;
+ profile.step_type = StepType::EIGHTH;
+ profile.slope = MotorSlope::create_from_steps(46876, 15864, 2);
+ gl847_motor_profiles->push_back(profile);
+}
+
+StaticInit<std::vector<Motor_Profile>> gl124_motor_profiles;
+
+void genesys_init_motor_profile_tables_gl124()
+{
+ gl124_motor_profiles.init();
+
+ // NEXT LPERIOD=PREVIOUS*2-192
+ Motor_Profile profile;
+ profile.motor_id = MotorId::CANON_LIDE_110;
+ profile.exposure = 2768;
+ profile.step_type = StepType::FULL;
+ profile.slope = MotorSlope::create_from_steps(62496, 335, 255);
+ gl124_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_110;
+ profile.exposure = 5360;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(62496, 335, 469);
+ gl124_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_110;
+ profile.exposure = 10528;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(62496, 2632, 3);
+ gl124_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_110;
+ profile.exposure = 20864;
+ profile.step_type = StepType::QUARTER;
+ profile.slope = MotorSlope::create_from_steps(62496, 10432, 3);
+ gl124_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_120;
+ profile.exposure = 4608;
+ profile.step_type = StepType::FULL;
+ profile.slope = MotorSlope::create_from_steps(62496, 864, 127);
+ gl124_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_120;
+ profile.exposure = 5360;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(62496, 2010, 63);
+ gl124_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_120;
+ profile.exposure = 10528;
+ profile.step_type = StepType::QUARTER;
+ profile.slope = MotorSlope::create_from_steps(62464, 2632, 3);
+ gl124_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_120;
+ profile.exposure = 20864;
+ profile.step_type = StepType::QUARTER;
+ profile.slope = MotorSlope::create_from_steps(62592, 10432, 5);
+ gl124_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_210;
+ profile.exposure = 2768;
+ profile.step_type = StepType::FULL;
+ profile.slope = MotorSlope::create_from_steps(62496, 335, 255);
+ gl124_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_210;
+ profile.exposure = 5360;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(62496, 335, 469);
+ gl124_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_210;
+ profile.exposure = 10528;
+ profile.step_type = StepType::HALF;
+ profile.slope = MotorSlope::create_from_steps(62496, 2632, 3);
+ gl124_motor_profiles->push_back(profile);
+
+ profile = Motor_Profile();
+ profile.motor_id = MotorId::CANON_LIDE_210;
+ profile.exposure = 20864;
+ profile.step_type = StepType::QUARTER;
+ profile.slope = MotorSlope::create_from_steps(62496, 10432, 4);
+ gl124_motor_profiles->push_back(profile);
+}
+
+void genesys_init_motor_profile_tables()
+{
+ genesys_init_motor_profile_tables_gl843();
+ genesys_init_motor_profile_tables_gl846();
+ genesys_init_motor_profile_tables_gl847();
+ genesys_init_motor_profile_tables_gl124();
+}
+
+} // namespace genesys
diff --git a/backend/genesys/tables_sensor.cpp b/backend/genesys/tables_sensor.cpp
new file mode 100644
index 0000000..bbbe441
--- /dev/null
+++ b/backend/genesys/tables_sensor.cpp
@@ -0,0 +1,3668 @@
+/* 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 "low.h"
+
+namespace genesys {
+
+inline unsigned default_get_logical_hwdpi(const Genesys_Sensor& sensor, unsigned xres)
+{
+ if (sensor.logical_dpihw_override)
+ return sensor.logical_dpihw_override;
+
+ // can't be below 600 dpi
+ if (xres <= 600) {
+ return 600;
+ }
+ if (xres <= static_cast<unsigned>(sensor.optical_res) / 4) {
+ return sensor.optical_res / 4;
+ }
+ if (xres <= static_cast<unsigned>(sensor.optical_res) / 2) {
+ return sensor.optical_res / 2;
+ }
+ return sensor.optical_res;
+}
+
+inline unsigned get_sensor_optical_with_ccd_divisor(const Genesys_Sensor& sensor, unsigned xres)
+{
+ unsigned hwres = sensor.optical_res / sensor.get_ccd_size_divisor_for_dpi(xres);
+
+ if (xres <= hwres / 4) {
+ return hwres / 4;
+ }
+ if (xres <= hwres / 2) {
+ return hwres / 2;
+ }
+ return hwres;
+}
+
+inline unsigned default_get_ccd_size_divisor_for_dpi(const Genesys_Sensor& sensor, unsigned xres)
+{
+ if (sensor.ccd_size_divisor >= 4 && xres * 4 <= static_cast<unsigned>(sensor.optical_res)) {
+ return 4;
+ }
+ if (sensor.ccd_size_divisor >= 2 && xres * 2 <= static_cast<unsigned>(sensor.optical_res)) {
+ return 2;
+ }
+ return 1;
+}
+
+inline unsigned get_ccd_size_divisor_exact(const Genesys_Sensor& sensor, unsigned xres)
+{
+ (void) xres;
+ return sensor.ccd_size_divisor;
+}
+
+inline unsigned get_ccd_size_divisor_gl124(const Genesys_Sensor& sensor, unsigned xres)
+{
+ // we have 2 domains for ccd: xres below or above half ccd max dpi
+ if (xres <= 300 && sensor.ccd_size_divisor > 1) {
+ return 2;
+ }
+ return 1;
+}
+
+inline unsigned default_get_hwdpi_divisor_for_dpi(const Genesys_Sensor& sensor, unsigned xres)
+{
+ return sensor.optical_res / default_get_logical_hwdpi(sensor, xres);
+}
+
+StaticInit<std::vector<Genesys_Sensor>> s_sensors;
+
+void genesys_init_sensor_tables()
+{
+ s_sensors.init();
+
+ Genesys_Sensor sensor;
+
+ sensor = Genesys_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.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_ST12;
+ sensor.optical_res = 600;
+ sensor.black_pixels = 48;
+ sensor.dummy_pixel = 85;
+ sensor.ccd_start_xoffset = 152;
+ sensor.sensor_pixels = 5416;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 230;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.custom_regs = {
+ { 0x08, 0x02 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x06 },
+ { 0x0b, 0x04 },
+ { 0x16, 0x2b },
+ { 0x17, 0x08 },
+ { 0x18, 0x20 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x0c },
+ { 0x1d, 0x03 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_ST24;
+ 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, 0x0e },
+ { 0x09, 0x0c },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x0c },
+ { 0x16, 0x33 },
+ { 0x17, 0x08 },
+ { 0x18, 0x31 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x17 },
+ { 0x53, 0x03 },
+ { 0x54, 0x07 },
+ { 0x55, 0x0b },
+ { 0x56, 0x0f },
+ { 0x57, 0x13 },
+ { 0x58, 0x03 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_5345;
+ sensor.optical_res = 1200;
+ sensor.ccd_size_divisor = 2;
+ sensor.black_pixels = 48;
+ sensor.dummy_pixel = 16;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10872;
+ sensor.fau_gain_white_ref = 190;
+ sensor.gain_white_ref = 190;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.stagger_config = StaggerConfig{ 1200, 4 }; // FIXME: may be incorrect
+ sensor.custom_base_regs = {
+ { 0x08, 0x0d },
+ { 0x09, 0x0f },
+ { 0x0a, 0x11 },
+ { 0x0b, 0x13 },
+ { 0x16, 0x0b },
+ { 0x17, 0x0a },
+ { 0x18, 0x30 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x03 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x23 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 },
+ };
+ sensor.gamma = { 2.38f, 2.35f, 2.34f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ unsigned exposure_lperiod;
+ unsigned ccd_size_divisor;
+ GenesysRegisterSettingSet custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 50 }, 12000, 2, {
+ { 0x08, 0x00 },
+ { 0x09, 0x05 },
+ { 0x0a, 0x06 },
+ { 0x0b, 0x08 },
+ { 0x16, 0x0b },
+ { 0x17, 0x0a },
+ { 0x18, 0x28 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x03 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 75 }, 11000, 2, {
+ { 0x08, 0x00 },
+ { 0x09, 0x05 },
+ { 0x0a, 0x06 },
+ { 0x0b, 0x08 },
+ { 0x16, 0x0b },
+ { 0x17, 0x0a },
+ { 0x18, 0x28 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x03 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 100 }, 11000, 2, {
+ { 0x08, 0x00 },
+ { 0x09, 0x05 },
+ { 0x0a, 0x06 },
+ { 0x0b, 0x08 },
+ { 0x16, 0x0b },
+ { 0x17, 0x0a },
+ { 0x18, 0x28 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x03 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 150 }, 11000, 2, {
+ { 0x08, 0x00 },
+ { 0x09, 0x05 },
+ { 0x0a, 0x06 },
+ { 0x0b, 0x08 },
+ { 0x16, 0x0b },
+ { 0x17, 0x0a },
+ { 0x18, 0x28 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x03 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 200 }, 11000, 2, {
+ { 0x08, 0x00 },
+ { 0x09, 0x05 },
+ { 0x0a, 0x06 },
+ { 0x0b, 0x08 },
+ { 0x16, 0x0b },
+ { 0x17, 0x0a },
+ { 0x18, 0x28 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x03 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 300 }, 11000, 2, {
+ { 0x08, 0x00 },
+ { 0x09, 0x05 },
+ { 0x0a, 0x06 },
+ { 0x0b, 0x08 },
+ { 0x16, 0x0b },
+ { 0x17, 0x0a },
+ { 0x18, 0x28 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x03 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 400 }, 11000, 2, {
+ { 0x08, 0x00 },
+ { 0x09, 0x05 },
+ { 0x0a, 0x06 },
+ { 0x0b, 0x08 },
+ { 0x16, 0x0b },
+ { 0x17, 0x0a },
+ { 0x18, 0x28 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x03 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 600 }, 11000, 2, {
+ { 0x08, 0x00 },
+ { 0x09, 0x05 },
+ { 0x0a, 0x06 },
+ { 0x0b, 0x08 },
+ { 0x16, 0x0b },
+ { 0x17, 0x0a },
+ { 0x18, 0x28 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x03 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 1200 }, 11000, 1, {
+ { 0x08, 0x0d },
+ { 0x09, 0x0f },
+ { 0x0a, 0x11 },
+ { 0x0b, 0x13 },
+ { 0x16, 0x0b },
+ { 0x17, 0x0a },
+ { 0x18, 0x30 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x03 },
+ { 0x52, 0x03 },
+ { 0x53, 0x07 },
+ { 0x54, 0x0b },
+ { 0x55, 0x0f },
+ { 0x56, 0x13 },
+ { 0x57, 0x17 },
+ { 0x58, 0x23 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ };
+
+ for (const CustomSensorSettings& setting : custom_settings)
+ {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.ccd_size_divisor = setting.ccd_size_divisor;
+ sensor.custom_regs = setting.custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_HP2400;
+ sensor.optical_res = 1200;
+ sensor.black_pixels = 48;
+ sensor.dummy_pixel = 15;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10872;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.stagger_config = StaggerConfig{1200, 4}; // FIXME: may be incorrect
+ sensor.custom_base_regs = {
+ { 0x08, 0x14 },
+ { 0x09, 0x15 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0xbf },
+ { 0x17, 0x08 },
+ { 0x18, 0x3f },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0f },
+ { 0x54, 0x13 },
+ { 0x55, 0x17 },
+ { 0x56, 0x03 },
+ { 0x57, 0x07 },
+ { 0x58, 0x63 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x0e },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 },
+ };
+ sensor.gamma = { 2.1f, 2.1f, 2.1f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ unsigned exposure_lperiod;
+ GenesysRegisterSettingSet custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 50 }, 7211, {
+ { 0x08, 0x14 },
+ { 0x09, 0x15 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0xbf },
+ { 0x17, 0x08 },
+ { 0x18, 0x3f },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0f },
+ { 0x54, 0x13 },
+ { 0x55, 0x17 },
+ { 0x56, 0x03 },
+ { 0x57, 0x07 },
+ { 0x58, 0x63 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 100 }, 7211, {
+ { 0x08, 0x14 },
+ { 0x09, 0x15 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0xbf },
+ { 0x17, 0x08 },
+ { 0x18, 0x3f },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0f },
+ { 0x54, 0x13 },
+ { 0x55, 0x17 },
+ { 0x56, 0x03 },
+ { 0x57, 0x07 },
+ { 0x58, 0x63 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 150 }, 7211, {
+ { 0x08, 0x14 },
+ { 0x09, 0x15 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0xbf },
+ { 0x17, 0x08 },
+ { 0x18, 0x3f },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0f },
+ { 0x54, 0x13 },
+ { 0x55, 0x17 },
+ { 0x56, 0x03 },
+ { 0x57, 0x07 },
+ { 0x58, 0x63 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 300 }, 8751, {
+ { 0x08, 0x14 },
+ { 0x09, 0x15 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0xbf },
+ { 0x17, 0x08 },
+ { 0x18, 0x3f },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0f },
+ { 0x54, 0x13 },
+ { 0x55, 0x17 },
+ { 0x56, 0x03 },
+ { 0x57, 0x07 },
+ { 0x58, 0x63 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 600 }, 18760, {
+ { 0x08, 0x0e },
+ { 0x09, 0x0f },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0xbf },
+ { 0x17, 0x08 },
+ { 0x18, 0x31 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x03 },
+ { 0x53, 0x07 },
+ { 0x54, 0x0b },
+ { 0x55, 0x0f },
+ { 0x56, 0x13 },
+ { 0x57, 0x17 },
+ { 0x58, 0x23 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 1200 }, 21749, {
+ { 0x08, 0x02 },
+ { 0x09, 0x04 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0xbf },
+ { 0x17, 0x08 },
+ { 0x18, 0x30 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0xc0 },
+ { 0x1d, 0x42 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0f },
+ { 0x54, 0x13 },
+ { 0x55, 0x17 },
+ { 0x56, 0x03 },
+ { 0x57, 0x07 },
+ { 0x58, 0x63 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x0e },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ };
+
+ for (const CustomSensorSettings& setting : custom_settings)
+ {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.custom_regs = setting.custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_HP2300;
+ sensor.optical_res = 600;
+ sensor.ccd_size_divisor = 2;
+ sensor.black_pixels = 48;
+ sensor.dummy_pixel = 20;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 5368;
+ sensor.fau_gain_white_ref = 180;
+ sensor.gain_white_ref = 180;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.custom_base_regs = {
+ { 0x08, 0x16 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x01 },
+ { 0x0b, 0x03 },
+ { 0x16, 0xb7 },
+ { 0x17, 0x0a },
+ { 0x18, 0x20 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x6a },
+ { 0x1b, 0x8a },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x05 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x06 },
+ { 0x5c, 0x0b },
+ { 0x5d, 0x10 },
+ { 0x5e, 0x16 },
+ };
+ sensor.gamma = { 2.1f, 2.1f, 2.1f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ unsigned exposure_lperiod;
+ unsigned ccd_size_divisor;
+ GenesysRegisterSettingSet custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 75 }, 4480, 2, {
+ { 0x08, 0x16 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x01 },
+ { 0x0b, 0x03 },
+ { 0x16, 0xb7 },
+ { 0x17, 0x0a },
+ { 0x18, 0x20 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x6a },
+ { 0x1b, 0x8a },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x85 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x06 },
+ { 0x5c, 0x0b },
+ { 0x5d, 0x10 },
+ { 0x5e, 0x16 }
+ }
+ },
+ { { 150 }, 4350, 2, {
+ { 0x08, 0x16 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x01 },
+ { 0x0b, 0x03 },
+ { 0x16, 0xb7 },
+ { 0x17, 0x0a },
+ { 0x18, 0x20 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x6a },
+ { 0x1b, 0x8a },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x85 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x06 },
+ { 0x5c, 0x0b },
+ { 0x5d, 0x10 },
+ { 0x5e, 0x16 }
+ }
+ },
+ { { 300 }, 4350, 2, {
+ { 0x08, 0x16 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x01 },
+ { 0x0b, 0x03 },
+ { 0x16, 0xb7 },
+ { 0x17, 0x0a },
+ { 0x18, 0x20 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x6a },
+ { 0x1b, 0x8a },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x85 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x06 },
+ { 0x5c, 0x0b },
+ { 0x5d, 0x10 },
+ { 0x5e, 0x16 }
+ }
+ },
+ { { 600 }, 8700, 1, {
+ { 0x08, 0x01 },
+ { 0x09, 0x03 },
+ { 0x0a, 0x04 },
+ { 0x0b, 0x06 },
+ { 0x16, 0xb7 },
+ { 0x17, 0x0a },
+ { 0x18, 0x20 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x6a },
+ { 0x1b, 0x8a },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x05 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x06 },
+ { 0x5c, 0x0b },
+ { 0x5d, 0x10 },
+ { 0x5e, 0x16 }
+ }
+ },
+ };
+
+ for (const CustomSensorSettings& setting : custom_settings)
+ {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.ccd_size_divisor = setting.ccd_size_divisor;
+ sensor.custom_regs = setting.custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CIS_CANON_LIDE_35;
+ sensor.optical_res = 1200;
+ sensor.ccd_size_divisor = 2;
+ sensor.black_pixels = 87;
+ sensor.dummy_pixel = 87;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10400;
+ sensor.fau_gain_white_ref = 0;
+ sensor.gain_white_ref = 0;
+ sensor.exposure = { 0x0400, 0x0400, 0x0400 };
+ sensor.custom_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0x00 },
+ { 0x17, 0x02 },
+ { 0x18, 0x00 },
+ { 0x19, 0x50 },
+ { 0x1a, 0x00 }, // TODO: 1a-1d: these do no harm, but may be neccessery for CCD
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x05 }, // [GB](HI|LOW) not needed for cis
+ { 0x53, 0x07 },
+ { 0x54, 0x00 },
+ { 0x55, 0x00 },
+ { 0x56, 0x00 },
+ { 0x57, 0x00 },
+ { 0x58, 0x3a },
+ { 0x59, 0x03 },
+ { 0x5a, 0x40 },
+ { 0x5b, 0x00 }, // TODO: 5b-5e
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CIS_XP200;
+ sensor.optical_res = 600;
+ sensor.black_pixels = 5;
+ sensor.dummy_pixel = 38;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 5200;
+ sensor.fau_gain_white_ref = 200;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x1450, 0x0c80, 0x0a28 };
+ sensor.custom_base_regs = {
+ { 0x08, 0x16 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x01 },
+ { 0x0b, 0x03 },
+ { 0x16, 0xb7 },
+ { 0x17, 0x0a },
+ { 0x18, 0x20 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x6a },
+ { 0x1b, 0x8a },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x05 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x06 },
+ { 0x5c, 0x0b },
+ { 0x5d, 0x10 },
+ { 0x5e, 0x16 },
+ };
+ sensor.custom_regs = {
+ { 0x08, 0x06 },
+ { 0x09, 0x07 },
+ { 0x0a, 0x0a },
+ { 0x0b, 0x04 },
+ { 0x16, 0x24 },
+ { 0x17, 0x04 },
+ { 0x18, 0x00 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x0a },
+ { 0x1b, 0x0a },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x11 },
+ { 0x52, 0x08 },
+ { 0x53, 0x02 },
+ { 0x54, 0x00 },
+ { 0x55, 0x00 },
+ { 0x56, 0x00 },
+ { 0x57, 0x00 },
+ { 0x58, 0x1a },
+ { 0x59, 0x51 },
+ { 0x5a, 0x00 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ };
+ sensor.gamma = { 2.1f, 2.1f, 2.1f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ std::vector<unsigned> channels;
+ unsigned exposure_lperiod;
+ SensorExposure exposure;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 75 }, { 3 }, 5700, { 0x1644, 0x0c80, 0x092e } },
+ { { 100 }, { 3 }, 5700, { 0x1644, 0x0c80, 0x092e } },
+ { { 200 }, { 3 }, 5700, { 0x1644, 0x0c80, 0x092e } },
+ { { 300 }, { 3 }, 9000, { 0x1644, 0x0c80, 0x092e } },
+ { { 600 }, { 3 }, 16000, { 0x1644, 0x0c80, 0x092e } },
+ { { 75 }, { 1 }, 16000, { 0x050a, 0x0fa0, 0x1010 } },
+ { { 100 }, { 1 }, 7800, { 0x050a, 0x0fa0, 0x1010 } },
+ { { 200 }, { 1 }, 11000, { 0x050a, 0x0fa0, 0x1010 } },
+ { { 300 }, { 1 }, 13000, { 0x050a, 0x0fa0, 0x1010 } },
+ { { 600 }, { 1 }, 24000, { 0x050a, 0x0fa0, 0x1010 } },
+ };
+
+ for (const CustomSensorSettings& setting : custom_settings)
+ {
+ sensor.resolutions = setting.resolutions;
+ sensor.channels = setting.channels;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.exposure = setting.exposure;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_HP3670;
+ sensor.optical_res = 1200;
+ sensor.black_pixels = 48;
+ sensor.dummy_pixel = 16;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10872;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0, 0, 0 };
+ sensor.stagger_config = StaggerConfig{1200, 4}; // FIXME: may be incorrect
+ sensor.custom_base_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x0a },
+ { 0x0a, 0x0b },
+ { 0x0b, 0x0d },
+ { 0x16, 0x33 },
+ { 0x17, 0x07 },
+ { 0x18, 0x20 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0xc0 },
+ { 0x1d, 0x43 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x00 },
+ { 0x5a, 0x15 },
+ { 0x5b, 0x05 },
+ { 0x5c, 0x0a },
+ { 0x5d, 0x0f },
+ { 0x5e, 0x00 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ unsigned exposure_lperiod;
+ GenesysRegisterSettingSet custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 50 }, 5758, {
+ { 0x08, 0x00 },
+ { 0x09, 0x0a },
+ { 0x0a, 0x0b },
+ { 0x0b, 0x0d },
+ { 0x16, 0x33 },
+ { 0x17, 0x07 },
+ { 0x18, 0x33 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x02 },
+ { 0x1b, 0x13 },
+ { 0x1c, 0xc0 },
+ { 0x1d, 0x43 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x15 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x05 },
+ { 0x5c, 0x0a },
+ { 0x5d, 0x0f },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 75 }, 4879, {
+ { 0x08, 0x00 },
+ { 0x09, 0x0a },
+ { 0x0a, 0x0b },
+ { 0x0b, 0x0d },
+ { 0x16, 0x33 },
+ { 0x17, 0x07 },
+ { 0x18, 0x33 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x02 },
+ { 0x1b, 0x13 },
+ { 0x1c, 0xc0 },
+ { 0x1d, 0x43 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x15 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x05 },
+ { 0x5c, 0x0a },
+ { 0x5d, 0x0f },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 100 }, 4487, {
+ { 0x08, 0x00 },
+ { 0x09, 0x0a },
+ { 0x0a, 0x0b },
+ { 0x0b, 0x0d },
+ { 0x16, 0x33 },
+ { 0x17, 0x07 },
+ { 0x18, 0x33 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x02 },
+ { 0x1b, 0x13 },
+ { 0x1c, 0xc0 },
+ { 0x1d, 0x43 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x15 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x05 },
+ { 0x5c, 0x0a },
+ { 0x5d, 0x0f },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 150 }, 4879, {
+ { 0x08, 0x00 },
+ { 0x09, 0x0a },
+ { 0x0a, 0x0b },
+ { 0x0b, 0x0d },
+ { 0x16, 0x33 },
+ { 0x17, 0x07 },
+ { 0x18, 0x33 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x02 },
+ { 0x1b, 0x13 },
+ { 0x1c, 0xc0 },
+ { 0x1d, 0x43 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x15 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x05 },
+ { 0x5c, 0x0a },
+ { 0x5d, 0x0f },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 300 }, 4503, {
+ { 0x08, 0x00 },
+ { 0x09, 0x0a },
+ { 0x0a, 0x0b },
+ { 0x0b, 0x0d },
+ { 0x16, 0x33 },
+ { 0x17, 0x07 },
+ { 0x18, 0x33 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x02 },
+ { 0x1b, 0x13 },
+ { 0x1c, 0xc0 },
+ { 0x1d, 0x43 },
+ { 0x52, 0x0f },
+ { 0x53, 0x13 },
+ { 0x54, 0x17 },
+ { 0x55, 0x03 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0b },
+ { 0x58, 0x83 },
+ { 0x59, 0x15 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x05 },
+ { 0x5c, 0x0a },
+ { 0x5d, 0x0f },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 600 }, 10251, {
+ { 0x08, 0x00 },
+ { 0x09, 0x05 },
+ { 0x0a, 0x06 },
+ { 0x0b, 0x08 },
+ { 0x16, 0x33 },
+ { 0x17, 0x07 },
+ { 0x18, 0x31 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x02 },
+ { 0x1b, 0x0e },
+ { 0x1c, 0xc0 },
+ { 0x1d, 0x43 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0f },
+ { 0x54, 0x13 },
+ { 0x55, 0x17 },
+ { 0x56, 0x03 },
+ { 0x57, 0x07 },
+ { 0x58, 0x63 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x02 },
+ { 0x5c, 0x0e },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ { { 1200 }, 12750, {
+ { 0x08, 0x0d },
+ { 0x09, 0x0f },
+ { 0x0a, 0x11 },
+ { 0x0b, 0x13 },
+ { 0x16, 0x2b },
+ { 0x17, 0x07 },
+ { 0x18, 0x30 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0xc0 },
+ { 0x1d, 0x43 },
+ { 0x52, 0x03 },
+ { 0x53, 0x07 },
+ { 0x54, 0x0b },
+ { 0x55, 0x0f },
+ { 0x56, 0x13 },
+ { 0x57, 0x17 },
+ { 0x58, 0x23 },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc1 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x00 }
+ }
+ },
+ };
+
+ for (const CustomSensorSettings& setting : custom_settings)
+ {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.custom_regs = setting.custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_DP665;
+ sensor.optical_res = 600;
+ sensor.black_pixels = 27;
+ sensor.dummy_pixel = 27;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 2496;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x1100, 0x1100, 0x1100 };
+ sensor.custom_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0x00 },
+ { 0x17, 0x02 },
+ { 0x18, 0x04 },
+ { 0x19, 0x50 },
+ { 0x1a, 0x10 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x20 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x04 }, // [GB](HI|LOW) not needed for cis
+ { 0x53, 0x05 },
+ { 0x54, 0x00 },
+ { 0x55, 0x00 },
+ { 0x56, 0x00 },
+ { 0x57, 0x00 },
+ { 0x58, 0x54 },
+ { 0x59, 0x03 },
+ { 0x5a, 0x00 },
+ { 0x5b, 0x00 }, // TODO: 5b-5e
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x01 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_ROADWARRIOR;
+ sensor.optical_res = 600;
+ sensor.black_pixels = 27;
+ sensor.dummy_pixel = 27;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 5200;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x1100, 0x1100, 0x1100 };
+ sensor.custom_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0x00 },
+ { 0x17, 0x02 },
+ { 0x18, 0x04 },
+ { 0x19, 0x50 },
+ { 0x1a, 0x10 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x20 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x04 }, // [GB](HI|LOW) not needed for cis
+ { 0x53, 0x05 },
+ { 0x54, 0x00 },
+ { 0x55, 0x00 },
+ { 0x56, 0x00 },
+ { 0x57, 0x00 },
+ { 0x58, 0x54 },
+ { 0x59, 0x03 },
+ { 0x5a, 0x00 },
+ { 0x5b, 0x00 }, // TODO: 5b-5e
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x01 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_DSMOBILE600;
+ sensor.optical_res = 600;
+ sensor.black_pixels = 28;
+ sensor.dummy_pixel = 28;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 5200;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x1544, 0x1544, 0x1544 };
+ sensor.custom_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0x00 },
+ { 0x17, 0x02 },
+ { 0x18, 0x04 },
+ { 0x19, 0x50 },
+ { 0x1a, 0x10 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x20 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x04 }, // [GB](HI|LOW) not needed for cis
+ { 0x53, 0x05 },
+ { 0x54, 0x00 },
+ { 0x55, 0x00 },
+ { 0x56, 0x00 },
+ { 0x57, 0x00 },
+ { 0x58, 0x54 },
+ { 0x59, 0x03 },
+ { 0x5a, 0x00 },
+ { 0x5b, 0x00 }, // TODO: 5b-5e
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x01 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_XP300;
+ sensor.optical_res = 600;
+ sensor.black_pixels = 27;
+ sensor.dummy_pixel = 27;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10240;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x1100, 0x1100, 0x1100 };
+ sensor.custom_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0x00 },
+ { 0x17, 0x02 },
+ { 0x18, 0x04 },
+ { 0x19, 0x50 },
+ { 0x1a, 0x10 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x20 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x04 }, // [GB](HI|LOW) not needed for cis
+ { 0x53, 0x05 },
+ { 0x54, 0x00 },
+ { 0x55, 0x00 },
+ { 0x56, 0x00 },
+ { 0x57, 0x00 },
+ { 0x58, 0x54 },
+ { 0x59, 0x03 },
+ { 0x5a, 0x00 },
+ { 0x5b, 0x00 }, // TODO: 5b-5e
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x01 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_DP685;
+ sensor.optical_res = 600;
+ sensor.black_pixels = 27;
+ sensor.dummy_pixel = 27;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 5020;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x1100, 0x1100, 0x1100 };
+ sensor.custom_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0x00 },
+ { 0x17, 0x02 },
+ { 0x18, 0x04 },
+ { 0x19, 0x50 },
+ { 0x1a, 0x10 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x20 },
+ { 0x1d, 0x02 },
+ { 0x52, 0x04 }, // [GB](HI|LOW) not needed for cis
+ { 0x53, 0x05 },
+ { 0x54, 0x00 },
+ { 0x55, 0x00 },
+ { 0x56, 0x00 },
+ { 0x57, 0x00 },
+ { 0x58, 0x54 },
+ { 0x59, 0x03 },
+ { 0x5a, 0x00 },
+ { 0x5b, 0x00 }, // TODO: 5b-5e
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x01 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CIS_CANON_LIDE_200;
+ sensor.optical_res = 4800;
+ sensor.black_pixels = 87*4;
+ sensor.dummy_pixel = 16*4;
+ sensor.ccd_start_xoffset = 320*8;
+ sensor.sensor_pixels = 5136*8;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.gamma = { 2.2f, 2.2f, 2.2f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ int exposure_lperiod;
+ SensorExposure exposure;
+ unsigned segment_size;
+ std::vector<unsigned> segment_order;
+ GenesysRegisterSettingSet custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ // Note: Windows driver uses 1424 lperiod and enables dummy line (0x17)
+ { { 75, 100, 150, 200 }, 2848, { 304, 203, 180 }, 5136, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ // Note: Windows driver uses 788 lperiod and enables dummy line (0x17)
+ { { 300, 400 }, 1424, { 304, 203, 180 }, 5136, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 600 }, 1432, { 492, 326, 296 }, 5136, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 1200 }, 2712, { 935, 592, 538 }, 5136, { 0, 1 }, {
+ { 0x16, 0x10 }, { 0x17, 0x08 }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 2400 }, 5280, { 1777, 1125, 979 }, 5136, { 0, 2, 1, 3 }, {
+ { 0x16, 0x10 }, { 0x17, 0x06 }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 4800 }, 10416, { 3377, 2138, 1780 }, 5136, { 0, 2, 4, 6, 1, 3, 5, 7 }, {
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ }
+ };
+
+ for (const auto& setting : custom_settings) {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.exposure = setting.exposure;
+ sensor.segment_size = setting.segment_size;
+ sensor.segment_order = setting.segment_order;
+ sensor.custom_regs = setting.custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CIS_CANON_LIDE_700F;
+ sensor.optical_res = 4800;
+ sensor.black_pixels = 73*8; // black pixels 73 at 600 dpi
+ sensor.dummy_pixel = 16*8;
+ // 384 at 600 dpi
+ sensor.ccd_start_xoffset = 384*8;
+ // 8x5570 segments, 5187+1 for rounding
+ sensor.sensor_pixels = 5188*8;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ int exposure_lperiod;
+ SensorExposure exposure;
+ unsigned segment_size;
+ std::vector<unsigned> segment_order;
+ GenesysRegisterSettingSet custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 75, 100, 150, 200 }, 2848, { 465, 310, 239 }, 5187, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x0c }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 300 }, 1424, { 465, 310, 239 }, 5187, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x0c }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 600 }, 1504, { 465, 310, 239 }, 5187, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x0c }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 1200 }, 2696, { 1464, 844, 555 }, 5187, { 0, 1 }, {
+ { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 2400 }, 10576, { 2798, 1558, 972 }, 5187, { 0, 1, 2, 3 }, {
+ { 0x16, 0x10 }, { 0x17, 0x08 }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 4800 }, 10576, { 2798, 1558, 972 }, 5187, { 0, 1, 4, 5, 2, 3, 6, 7 }, {
+ { 0x16, 0x10 }, { 0x17, 0x06 }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x07 }, { 0x53, 0x03 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x87 },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0xf9 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ }
+ };
+
+ for (const auto& setting : custom_settings) {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.exposure = setting.exposure;
+ sensor.segment_size = setting.segment_size;
+ sensor.segment_order = setting.segment_order;
+ sensor.custom_regs = setting.custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CIS_CANON_LIDE_100;
+ sensor.optical_res = 2400;
+ sensor.black_pixels = 87*4;
+ sensor.dummy_pixel = 16*4;
+ sensor.ccd_start_xoffset = 320*4;
+ sensor.sensor_pixels = 5136*4;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x01c1, 0x0126, 0x00e5 };
+ sensor.gamma = { 2.2f, 2.2f, 2.2f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ int exposure_lperiod;
+ SensorExposure exposure;
+ unsigned segment_size;
+ std::vector<unsigned> segment_order;
+ GenesysRegisterSettingSet custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 75, 100, 150, 200 }, 2304, { 423, 294, 242 }, 5136, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 300 }, 1728, { 423, 294, 242 }, 5136, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 600 }, 1432, { 423, 294, 242 }, 5136, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x0a }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ },
+ },
+ { { 1200 }, 2712, { 791, 542, 403 }, 5136, {0, 1}, {
+ { 0x16, 0x10 }, { 0x17, 0x08 }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ },
+ { { 2400 }, 5280, { 1504, 1030, 766 }, 5136, {0, 2, 1, 3}, {
+ { 0x16, 0x10 }, { 0x17, 0x06 }, { 0x18, 0x00 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x04 },
+ { 0x52, 0x03 }, { 0x53, 0x07 }, { 0x54, 0x00 }, { 0x55, 0x00 },
+ { 0x56, 0x00 }, { 0x57, 0x00 }, { 0x58, 0x2a }, { 0x59, 0xe1 }, { 0x5a, 0x55 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ }
+ }
+ };
+
+ for (const auto& setting : custom_settings) {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.exposure = setting.exposure;
+ sensor.segment_size = setting.segment_size;
+ sensor.segment_order = setting.segment_order;
+ sensor.custom_regs = setting.custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_KVSS080;
+ sensor.optical_res = 600;
+ sensor.black_pixels = 38;
+ sensor.dummy_pixel = 38;
+ sensor.ccd_start_xoffset = 152;
+ sensor.sensor_pixels = 5376;
+ sensor.fau_gain_white_ref = 160;
+ sensor.gain_white_ref = 160;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.exposure_lperiod = 8000;
+ sensor.custom_regs = {
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 },
+ { 0x77, 0x00 }, { 0x78, 0xff }, { 0x79, 0xff },
+ { 0x7a, 0x03 }, { 0x7b, 0xff }, { 0x7c, 0xff },
+ { 0x0c, 0x00 },
+ { 0x70, 0x01 },
+ { 0x71, 0x03 },
+ { 0x9e, 0x00 },
+ { 0xaa, 0x00 },
+ { 0x16, 0x33 },
+ { 0x17, 0x1c },
+ { 0x18, 0x00 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x2c },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x20 },
+ { 0x1d, 0x04 },
+ { 0x52, 0x0c },
+ { 0x53, 0x0f },
+ { 0x54, 0x00 },
+ { 0x55, 0x03 },
+ { 0x56, 0x06 },
+ { 0x57, 0x09 },
+ { 0x58, 0x6b },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc0 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_G4050;
+ sensor.optical_res = 4800;
+ sensor.black_pixels = 50*8;
+ // 31 at 600 dpi dummy_pixels 58 at 1200
+ sensor.dummy_pixel = 58;
+ sensor.ccd_start_xoffset = 152;
+ sensor.sensor_pixels = 5360*8;
+ sensor.fau_gain_white_ref = 160;
+ sensor.gain_white_ref = 160;
+ sensor.exposure = { 0x2c09, 0x22b8, 0x10f0 };
+ sensor.stagger_config = StaggerConfig{ 2400, 4 }; // FIXME: may be incorrect
+ sensor.custom_regs = {};
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ int exposure_lperiod;
+ ScanMethod method;
+ GenesysRegisterSettingSet extra_custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 100, 150, 200, 300, 400, 600 }, 8016, ScanMethod::FLATBED, {
+ { 0x74, 0x00 }, { 0x75, 0x01 }, { 0x76, 0xff },
+ { 0x77, 0x03 }, { 0x78, 0xff }, { 0x79, 0xff },
+ { 0x7a, 0x03 }, { 0x7b, 0xff }, { 0x7c, 0xff },
+ { 0x0c, 0x00 },
+ { 0x70, 0x00 },
+ { 0x71, 0x02 },
+ { 0x9e, 0x00 },
+ { 0xaa, 0x00 },
+ { 0x16, 0x33 },
+ { 0x17, 0x0c },
+ { 0x18, 0x00 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x08 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0e },
+ { 0x54, 0x11 },
+ { 0x55, 0x02 },
+ { 0x56, 0x05 },
+ { 0x57, 0x08 },
+ { 0x58, 0x63 },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ }
+ },
+ { { 1200 }, 56064, ScanMethod::FLATBED, {
+ { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff },
+ { 0x77, 0x00 }, { 0x78, 0x01 }, { 0x79, 0xff },
+ { 0x7a, 0x00 }, { 0x7b, 0x01 }, { 0x7c, 0xff },
+ { 0x0c, 0x20 },
+ { 0x70, 0x08 },
+ { 0x71, 0x0c },
+ { 0x9e, 0xc0 },
+ { 0xaa, 0x05 },
+ { 0x16, 0x3b },
+ { 0x17, 0x0c },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x38 },
+ { 0x1b, 0x10 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x08 },
+ { 0x52, 0x02 },
+ { 0x53, 0x05 },
+ { 0x54, 0x08 },
+ { 0x55, 0x0b },
+ { 0x56, 0x0e },
+ { 0x57, 0x11 },
+ { 0x58, 0x1b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ }
+ },
+ { { 2400 }, 56064, ScanMethod::FLATBED, {
+ { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 },
+ { 0x0c, 0x20 },
+ { 0x70, 0x08 },
+ { 0x71, 0x0a },
+ { 0x9e, 0xc0 },
+ { 0xaa, 0x05 },
+ { 0x16, 0x3b },
+ { 0x17, 0x0c },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x38 },
+ { 0x1b, 0x10 },
+ { 0x1c, 0xc0 },
+ { 0x1d, 0x08 },
+ { 0x52, 0x02 },
+ { 0x53, 0x05 },
+ { 0x54, 0x08 },
+ { 0x55, 0x0b },
+ { 0x56, 0x0e },
+ { 0x57, 0x11 },
+ { 0x58, 0x1b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ }
+ },
+ { { 4800 }, 42752, ScanMethod::FLATBED, {
+ { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 },
+ { 0x0c, 0x21 },
+ { 0x70, 0x08 },
+ { 0x71, 0x0a },
+ { 0x9e, 0xc0 },
+ { 0xaa, 0x07 },
+ { 0x16, 0x3b },
+ { 0x17, 0x0c },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x38 },
+ { 0x1b, 0x10 },
+ { 0x1c, 0xc1 },
+ { 0x1d, 0x08 },
+ { 0x52, 0x02 },
+ { 0x53, 0x05 },
+ { 0x54, 0x08 },
+ { 0x55, 0x0b },
+ { 0x56, 0x0e },
+ { 0x57, 0x11 },
+ { 0x58, 0x1b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ }
+ },
+ { ResolutionFilter::ANY, 15624, ScanMethod::TRANSPARENCY, {
+ { 0x74, 0x00 }, { 0x75, 0x1c }, { 0x76, 0x7f },
+ { 0x77, 0x03 }, { 0x78, 0xff }, { 0x79, 0xff },
+ { 0x7a, 0x03 }, { 0x7b, 0xff }, { 0x7c, 0xff },
+ { 0x0c, 0x00 },
+ { 0x70, 0x00 },
+ { 0x71, 0x02 },
+ { 0x9e, 0x00 },
+ { 0xaa, 0x00 },
+ { 0x16, 0x33 },
+ { 0x17, 0x4c },
+ { 0x18, 0x01 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x08 },
+ { 0x52, 0x0e },
+ { 0x53, 0x11 },
+ { 0x54, 0x02 },
+ { 0x55, 0x05 },
+ { 0x56, 0x08 },
+ { 0x57, 0x0b },
+ { 0x58, 0x6b },
+ { 0x59, 0x00 },
+ { 0x5a, 0xc0 },
+ }
+ }
+ };
+
+ auto base_custom_regs = sensor.custom_regs;
+ for (const CustomSensorSettings& setting : custom_settings)
+ {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.method = setting.method;
+ sensor.custom_regs = base_custom_regs;
+ sensor.custom_regs.merge(setting.extra_custom_regs);
+ s_sensors->push_back(sensor);
+ }
+ }
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_HP_4850C;
+ sensor.optical_res = 4800;
+ sensor.black_pixels = 100;
+ sensor.dummy_pixel = 58;
+ sensor.ccd_start_xoffset = 152;
+ sensor.sensor_pixels = 5360*8;
+ sensor.fau_gain_white_ref = 160;
+ sensor.gain_white_ref = 160;
+ sensor.exposure = { 0x2c09, 0x22b8, 0x10f0 };
+ sensor.stagger_config = StaggerConfig{ 2400, 4 }; // FIXME: may be incorrect
+ sensor.custom_regs = {};
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ int exposure_lperiod;
+ ScanMethod method;
+ GenesysRegisterSettingSet extra_custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 100, 150, 200, 300, 400, 600 }, 8016, ScanMethod::FLATBED, {
+ { 0x0c, 0x00 },
+ { 0x16, 0x33 }, { 0x17, 0x0c }, { 0x18, 0x00 }, { 0x19, 0x2a },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x00 }, { 0x1d, 0x08 },
+ { 0x52, 0x0b }, { 0x53, 0x0e }, { 0x54, 0x11 }, { 0x55, 0x02 },
+ { 0x56, 0x05 }, { 0x57, 0x08 }, { 0x58, 0x63 }, { 0x59, 0x00 }, { 0x5a, 0x40 },
+ { 0x70, 0x00 }, { 0x71, 0x02 },
+ { 0x74, 0x00 }, { 0x75, 0x01 }, { 0x76, 0xff },
+ { 0x77, 0x03 }, { 0x78, 0xff }, { 0x79, 0xff },
+ { 0x7a, 0x03 }, { 0x7b, 0xff }, { 0x7c, 0xff },
+ { 0x9e, 0x00 },
+ { 0xaa, 0x00 },
+ }
+ },
+ { { 1200 }, 56064, ScanMethod::FLATBED, {
+ { 0x0c, 0x20 },
+ { 0x16, 0x3b }, { 0x17, 0x0c }, { 0x18, 0x10 }, { 0x19, 0x2a },
+ { 0x1a, 0x38 }, { 0x1b, 0x10 }, { 0x1c, 0x00 }, { 0x1d, 0x08 },
+ { 0x52, 0x02 }, { 0x53, 0x05 }, { 0x54, 0x08 }, { 0x55, 0x0b },
+ { 0x56, 0x0e }, { 0x57, 0x11 }, { 0x58, 0x1b }, { 0x59, 0x00 }, { 0x5a, 0x40 },
+ { 0x70, 0x08 }, { 0x71, 0x0c },
+ { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff },
+ { 0x77, 0x00 }, { 0x78, 0x01 }, { 0x79, 0xff },
+ { 0x7a, 0x00 }, { 0x7b, 0x01 }, { 0x7c, 0xff },
+ { 0x9e, 0xc0 },
+ { 0xaa, 0x05 },
+ }
+ },
+ { { 2400 }, 56064, ScanMethod::FLATBED, {
+ { 0x0c, 0x20 },
+ { 0x16, 0x3b }, { 0x17, 0x0c }, { 0x18, 0x10 }, { 0x19, 0x2a },
+ { 0x1a, 0x38 }, { 0x1b, 0x10 }, { 0x1c, 0xc0 }, { 0x1d, 0x08 },
+ { 0x52, 0x02 }, { 0x53, 0x05 }, { 0x54, 0x08 }, { 0x55, 0x0b },
+ { 0x56, 0x0e }, { 0x57, 0x11 }, { 0x58, 0x1b }, { 0x59, 0x00 }, { 0x5a, 0x40 },
+ { 0x70, 0x08 }, { 0x71, 0x0a },
+ { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 },
+ { 0x9e, 0xc0 },
+ { 0xaa, 0x05 },
+ }
+ },
+ { { 4800 }, 42752, ScanMethod::FLATBED, {
+ { 0x0c, 0x21 },
+ { 0x16, 0x3b }, { 0x17, 0x0c }, { 0x18, 0x10 }, { 0x19, 0x2a },
+ { 0x1a, 0x38 }, { 0x1b, 0x10 }, { 0x1c, 0xc1 }, { 0x1d, 0x08 },
+ { 0x52, 0x02 }, { 0x53, 0x05 }, { 0x54, 0x08 }, { 0x55, 0x0b },
+ { 0x56, 0x0e }, { 0x57, 0x11 }, { 0x58, 0x1b }, { 0x59, 0x00 }, { 0x5a, 0x40 },
+ { 0x70, 0x08 }, { 0x71, 0x0a },
+ { 0x74, 0x0f }, { 0x75, 0xff }, { 0x76, 0xff },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 },
+ { 0x9e, 0xc0 },
+ { 0xaa, 0x07 },
+ }
+ },
+ { ResolutionFilter::ANY, 15624, ScanMethod::TRANSPARENCY, {
+ { 0x0c, 0x00 },
+ { 0x16, 0x33 }, { 0x17, 0x4c }, { 0x18, 0x01 }, { 0x19, 0x2a },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x00 }, { 0x1d, 0x08 },
+ { 0x52, 0x0e }, { 0x53, 0x11 }, { 0x54, 0x02 }, { 0x55, 0x05 },
+ { 0x56, 0x08 }, { 0x57, 0x0b }, { 0x58, 0x6b }, { 0x59, 0x00 }, { 0x5a, 0xc0 },
+ { 0x70, 0x00 }, { 0x71, 0x02 },
+ { 0x74, 0x00 }, { 0x75, 0x1c }, { 0x76, 0x7f },
+ { 0x77, 0x03 }, { 0x78, 0xff }, { 0x79, 0xff },
+ { 0x7a, 0x03 }, { 0x7b, 0xff }, { 0x7c, 0xff },
+ { 0x9e, 0x00 },
+ { 0xaa, 0x00 },
+ }
+ }
+ };
+
+ auto base_custom_regs = sensor.custom_regs;
+ for (const CustomSensorSettings& setting : custom_settings)
+ {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.method = setting.method;
+ sensor.custom_regs = base_custom_regs;
+ sensor.custom_regs.merge(setting.extra_custom_regs);
+ s_sensors->push_back(sensor);
+ }
+ }
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_CANON_4400F;
+ sensor.optical_res = 4800;
+ sensor.ccd_size_divisor = 4;
+ sensor.black_pixels = 50*8;
+ // 31 at 600 dpi, 58 at 1200 dpi
+ sensor.dummy_pixel = 20;
+ sensor.ccd_start_xoffset = 152;
+ // 5360 max at 600 dpi
+ sensor.sensor_pixels = 5700 * 8;
+ sensor.fau_gain_white_ref = 160;
+ sensor.gain_white_ref = 160;
+ sensor.exposure = { 0x9c40, 0x9c40, 0x9c40 };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = get_sensor_optical_with_ccd_divisor;
+ sensor.get_register_hwdpi_fun = [](const Genesys_Sensor&, unsigned) { return 4800; };
+ sensor.get_hwdpi_divisor_fun = [](const Genesys_Sensor&, unsigned) { return 1; };
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ int exposure_lperiod;
+ std::vector<ScanMethod> methods;
+ GenesysRegisterSettingSet extra_custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 300, 600, 1200 }, 11640, { ScanMethod::FLATBED }, {
+ { 0x16, 0x13 },
+ { 0x17, 0x0a },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x6b },
+ { 0x52, 0x0a },
+ { 0x53, 0x0d },
+ { 0x54, 0x00 },
+ { 0x55, 0x03 },
+ { 0x56, 0x06 },
+ { 0x57, 0x08 },
+ { 0x58, 0x5b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x01 }, { 0x73, 0x03 },
+ { 0x74, 0x00 }, { 0x75, 0xf8 }, { 0x76, 0x38 },
+ { 0x77, 0x00 }, { 0x78, 0xfc }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0xa4 },
+ { 0x9e, 0x2d },
+ }
+ },
+ { { 300, 600, 1200 }, 33300, { ScanMethod::TRANSPARENCY }, {
+ { 0x16, 0x13 },
+ { 0x17, 0x0a },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x6b },
+ { 0x52, 0x0a },
+ { 0x53, 0x0d },
+ { 0x54, 0x00 },
+ { 0x55, 0x03 },
+ { 0x56, 0x06 },
+ { 0x57, 0x08 },
+ { 0x58, 0x5b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x00 }, { 0x73, 0x02 },
+ { 0x74, 0x00 }, { 0x75, 0xf8 }, { 0x76, 0x38 },
+ { 0x77, 0x00 }, { 0x78, 0xfc }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0xa4 },
+ { 0x9e, 0x2d },
+ }
+ },
+ { { 2400 }, 33300, { ScanMethod::TRANSPARENCY }, {
+ { 0x16, 0x13 },
+ { 0x17, 0x0a },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x01 },
+ { 0x1d, 0x75 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0d },
+ { 0x54, 0x00 },
+ { 0x55, 0x03 },
+ { 0x56, 0x06 },
+ { 0x57, 0x09 },
+ { 0x58, 0x53 },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x02 }, { 0x73, 0x04 },
+ { 0x74, 0x00 }, { 0x75, 0xff }, { 0x76, 0x00 },
+ { 0x77, 0x00 }, { 0x78, 0xff }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x54 }, { 0x7c, 0x92 },
+ { 0x9e, 0x2d },
+ }
+ },
+ { { 4800 }, 33300, { ScanMethod::TRANSPARENCY }, {
+ { 0x16, 0x13 },
+ { 0x17, 0x0a },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x61 },
+ { 0x1d, 0x75 },
+ { 0x52, 0x02 },
+ { 0x53, 0x05 },
+ { 0x54, 0x08 },
+ { 0x55, 0x0b },
+ { 0x56, 0x0d },
+ { 0x57, 0x0f },
+ { 0x58, 0x1b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x08 }, { 0x71, 0x0a }, { 0x72, 0x0a }, { 0x73, 0x0c },
+ { 0x74, 0x00 }, { 0x75, 0xff }, { 0x76, 0xff },
+ { 0x77, 0x00 }, { 0x78, 0xff }, { 0x79, 0xff },
+ { 0x7a, 0x00 }, { 0x7b, 0x54 }, { 0x7c, 0x92 },
+ { 0x9e, 0x2d },
+ }
+ }
+ };
+
+ for (const CustomSensorSettings& setting : custom_settings)
+ {
+ for (auto method : setting.methods) {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.method = method;
+ sensor.custom_regs = setting.extra_custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_CANON_8400F;
+ sensor.optical_res = 3200;
+ sensor.register_dpihw_override = 4800;
+ sensor.ccd_size_divisor = 1;
+ sensor.black_pixels = 50*8;
+ // 31 at 600 dpi, 58 at 1200 dpi
+ sensor.dummy_pixel = 20;
+ sensor.ccd_start_xoffset = 152;
+ sensor.sensor_pixels = 27200;
+ sensor.fau_gain_white_ref = 160;
+ sensor.gain_white_ref = 160;
+ sensor.exposure = { 0x9c40, 0x9c40, 0x9c40 };
+ sensor.stagger_config = StaggerConfig{ 3200, 6 };
+ sensor.custom_regs = {};
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = get_sensor_optical_with_ccd_divisor;
+ sensor.get_register_hwdpi_fun = [](const Genesys_Sensor&, unsigned) { return 4800; };
+ sensor.get_hwdpi_divisor_fun = [](const Genesys_Sensor&, unsigned) { return 1; };
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ unsigned dpiset_override;
+ unsigned pixel_count_multiplier;
+ int exposure_lperiod;
+ std::vector<ScanMethod> methods;
+ GenesysRegisterSettingSet extra_custom_regs;
+ GenesysRegisterSettingSet custom_fe_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 400 }, 2400, 1, 7200, { ScanMethod::FLATBED }, {
+ { 0x16, 0x33 },
+ { 0x17, 0x0c },
+ { 0x18, 0x13 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x84 },
+ { 0x1e, 0xa0 },
+ { 0x52, 0x0d },
+ { 0x53, 0x10 },
+ { 0x54, 0x01 },
+ { 0x55, 0x04 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0a },
+ { 0x58, 0x6b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x01 }, { 0x71, 0x02 }, { 0x72, 0x03 }, { 0x73, 0x04 },
+ { 0x74, 0x00 }, { 0x75, 0x0e }, { 0x76, 0x3f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x01 }, { 0x7b, 0xb6 }, { 0x7c, 0xdb },
+ { 0x80, 0x2a },
+ }, {}
+ },
+ { { 800 }, 4800, 1, 7200, { ScanMethod::FLATBED }, {
+ { 0x16, 0x33 },
+ { 0x17, 0x0c },
+ { 0x18, 0x13 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x84 },
+ { 0x1e, 0xa0 },
+ { 0x52, 0x0d },
+ { 0x53, 0x10 },
+ { 0x54, 0x01 },
+ { 0x55, 0x04 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0a },
+ { 0x58, 0x6b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x01 }, { 0x71, 0x02 }, { 0x72, 0x03 }, { 0x73, 0x04 },
+ { 0x74, 0x00 }, { 0x75, 0x0e }, { 0x76, 0x3f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x01 }, { 0x7b, 0xb6 }, { 0x7c, 0xdb },
+ { 0x80, 0x20 },
+ }, {}
+ },
+ { { 1600 }, 4800, 1, 14400, { ScanMethod::FLATBED }, {
+ { 0x16, 0x33 },
+ { 0x17, 0x0c },
+ { 0x18, 0x11 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x84 },
+ { 0x1e, 0xa1 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0e },
+ { 0x54, 0x11 },
+ { 0x55, 0x02 },
+ { 0x56, 0x05 },
+ { 0x57, 0x08 },
+ { 0x58, 0x63 },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x01 }, { 0x71, 0x02 }, { 0x72, 0x02 }, { 0x73, 0x03 },
+ { 0x74, 0x00 }, { 0x75, 0x01 }, { 0x76, 0xff },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x02 }, { 0x7b, 0x49 }, { 0x7c, 0x24 },
+ { 0x80, 0x28 },
+ }, {
+ { 0x03, 0x1f },
+ }
+ },
+ { { 3200 }, 4800, 1, 28800, { ScanMethod::FLATBED }, {
+ { 0x16, 0x33 },
+ { 0x17, 0x0c },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x20 },
+ { 0x1d, 0x84 },
+ { 0x1e, 0xa1 },
+ { 0x52, 0x02 },
+ { 0x53, 0x05 },
+ { 0x54, 0x08 },
+ { 0x55, 0x0b },
+ { 0x56, 0x0e },
+ { 0x57, 0x11 },
+ { 0x58, 0x1b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x09 }, { 0x71, 0x0a }, { 0x72, 0x0b }, { 0x73, 0x0c },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x02 }, { 0x7b, 0x49 }, { 0x7c, 0x24 },
+ { 0x80, 0x2b },
+ }, {
+ { 0x03, 0x1f },
+ },
+ },
+ { { 400 }, 2400, 1, 14400, { ScanMethod::TRANSPARENCY,
+ ScanMethod::TRANSPARENCY_INFRARED }, {
+ { 0x16, 0x33 },
+ { 0x17, 0x0c },
+ { 0x18, 0x13 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x84 },
+ { 0x1e, 0xa0 },
+ { 0x52, 0x0d },
+ { 0x53, 0x10 },
+ { 0x54, 0x01 },
+ { 0x55, 0x04 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0a },
+ { 0x58, 0x6b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x01 }, { 0x71, 0x02 }, { 0x72, 0x03 }, { 0x73, 0x04 },
+ { 0x74, 0x00 }, { 0x75, 0x0e }, { 0x76, 0x3f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x01 }, { 0x7b, 0xb6 }, { 0x7c, 0xdb },
+ { 0x80, 0x20 },
+ }, {}
+ },
+ { { 800 }, 4800, 1, 14400, { ScanMethod::TRANSPARENCY,
+ ScanMethod::TRANSPARENCY_INFRARED }, {
+ { 0x16, 0x33 },
+ { 0x17, 0x0c },
+ { 0x18, 0x13 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x84 },
+ { 0x1e, 0xa0 },
+ { 0x52, 0x0d },
+ { 0x53, 0x10 },
+ { 0x54, 0x01 },
+ { 0x55, 0x04 },
+ { 0x56, 0x07 },
+ { 0x57, 0x0a },
+ { 0x58, 0x6b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x01 }, { 0x71, 0x02 }, { 0x72, 0x03 }, { 0x73, 0x04 },
+ { 0x74, 0x00 }, { 0x75, 0x0e }, { 0x76, 0x3f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x01 }, { 0x7b, 0xb6 }, { 0x7c, 0xdb },
+ { 0x80, 0x20 },
+ }, {}
+ },
+ { { 1600 }, 4800, 1, 28800, { ScanMethod::TRANSPARENCY,
+ ScanMethod::TRANSPARENCY_INFRARED }, {
+ { 0x16, 0x33 },
+ { 0x17, 0x0c },
+ { 0x18, 0x11 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x84 },
+ { 0x1e, 0xa0 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0e },
+ { 0x54, 0x11 },
+ { 0x55, 0x02 },
+ { 0x56, 0x05 },
+ { 0x57, 0x08 },
+ { 0x58, 0x63 },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x00 }, { 0x71, 0x01 }, { 0x72, 0x02 }, { 0x73, 0x03 },
+ { 0x74, 0x00 }, { 0x75, 0x01 }, { 0x76, 0xff },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x02 }, { 0x7b, 0x49 }, { 0x7c, 0x24 },
+ { 0x80, 0x29 },
+ }, {
+ { 0x03, 0x1f },
+ },
+ },
+ { { 3200 }, 4800, 1, 28800, { ScanMethod::TRANSPARENCY,
+ ScanMethod::TRANSPARENCY_INFRARED }, {
+ { 0x16, 0x33 },
+ { 0x17, 0x0c },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x20 },
+ { 0x1d, 0x84 },
+ { 0x1e, 0xa0 },
+ { 0x52, 0x02 },
+ { 0x53, 0x05 },
+ { 0x54, 0x08 },
+ { 0x55, 0x0b },
+ { 0x56, 0x0e },
+ { 0x57, 0x11 },
+ { 0x58, 0x1b },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ { 0x70, 0x09 }, { 0x71, 0x0a }, { 0x72, 0x0b }, { 0x73, 0x0c },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x02 }, { 0x7b, 0x49 }, { 0x7c, 0x24 },
+ { 0x80, 0x2b },
+ }, {
+ { 0x03, 0x1f },
+ },
+ },
+ };
+
+ for (const CustomSensorSettings& setting : custom_settings)
+ {
+ for (auto method : setting.methods) {
+ sensor.resolutions = setting.resolutions;
+ sensor.dpiset_override = setting.dpiset_override;
+ sensor.pixel_count_multiplier = setting.pixel_count_multiplier;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.method = method;
+ sensor.custom_regs = setting.extra_custom_regs;
+ sensor.custom_fe_regs = setting.custom_fe_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_CANON_8600F;
+ sensor.optical_res = 4800;
+ sensor.ccd_size_divisor = 4;
+ sensor.black_pixels = 31;
+ sensor.dummy_pixel = 20;
+ sensor.ccd_start_xoffset = 0; // not used at the moment
+ // 11372 pixels at 1200 dpi
+ sensor.sensor_pixels = 11372*4;
+ sensor.fau_gain_white_ref = 160;
+ sensor.gain_white_ref = 160;
+ sensor.exposure = { 0x9c40, 0x9c40, 0x9c40 };
+ sensor.stagger_config = StaggerConfig{4800, 8};
+ sensor.custom_regs = {};
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = get_sensor_optical_with_ccd_divisor;
+ sensor.get_register_hwdpi_fun = [](const Genesys_Sensor&, unsigned) { return 4800; };
+ sensor.get_hwdpi_divisor_fun = [](const Genesys_Sensor&, unsigned) { return 1; };
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ int exposure_lperiod;
+ std::vector<ScanMethod> methods;
+ GenesysRegisterSettingSet extra_custom_regs;
+ GenesysRegisterSettingSet custom_fe_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 300, 600, 1200 }, 24000, { ScanMethod::FLATBED }, {
+ { 0x0c, 0x00 },
+ { 0x16, 0x13 }, { 0x17, 0x0a }, { 0x18, 0x10 }, { 0x19, 0x2a },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x00 }, { 0x1d, 0x6b },
+ { 0x52, 0x0c }, { 0x53, 0x0f }, { 0x54, 0x00 }, { 0x55, 0x03 },
+ { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x02 }, { 0x73, 0x04 },
+ { 0x56, 0x06 }, { 0x57, 0x09 }, { 0x58, 0x6b }, { 0x59, 0x00 }, { 0x5a, 0x40 },
+ { 0x74, 0x03 }, { 0x75, 0xf0 }, { 0x76, 0xf0 },
+ { 0x77, 0x03 }, { 0x78, 0xfe }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0x49 },
+ { 0x9e, 0x2d },
+ { 0xaa, 0x00 },
+ },
+ {},
+ },
+ { { 300, 600, 1200 }, 45000, { ScanMethod::TRANSPARENCY,
+ ScanMethod::TRANSPARENCY_INFRARED }, {
+ { 0x0c, 0x00 },
+ { 0x16, 0x13 }, { 0x17, 0x0a }, { 0x18, 0x10 }, { 0x19, 0x2a },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x00 }, { 0x1d, 0x6b },
+ { 0x52, 0x0c }, { 0x53, 0x0f }, { 0x54, 0x00 }, { 0x55, 0x03 },
+ { 0x56, 0x06 }, { 0x57, 0x09 }, { 0x58, 0x6b }, { 0x59, 0x00 }, { 0x5a, 0x40 },
+ { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x02 }, { 0x73, 0x04 },
+ { 0x74, 0x03 }, { 0x75, 0xf0 }, { 0x76, 0xf0 },
+ { 0x77, 0x03 }, { 0x78, 0xfe }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0x49 },
+ { 0x9e, 0x2d },
+ { 0xaa, 0x00 },
+ },
+ {},
+ },
+ { { 2400 }, 45000, { ScanMethod::TRANSPARENCY,
+ ScanMethod::TRANSPARENCY_INFRARED }, {
+ { 0x0c, 0x00 },
+ { 0x16, 0x13 }, { 0x17, 0x15 }, { 0x18, 0x10 }, { 0x19, 0x2a },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x01 }, { 0x1d, 0x75 },
+ { 0x52, 0x0c }, { 0x53, 0x0f }, { 0x54, 0x00 }, { 0x55, 0x03 },
+ { 0x56, 0x06 }, { 0x57, 0x09 }, { 0x58, 0x6b }, { 0x59, 0x00 }, { 0x5a, 0x40 },
+ { 0x70, 0x00 }, { 0x71, 0x02 }, { 0x72, 0x02 }, { 0x73, 0x04 },
+ { 0x74, 0x03 }, { 0x75, 0xfe }, { 0x76, 0x00 },
+ { 0x77, 0x03 }, { 0x78, 0xfe }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0x49 },
+ { 0x9e, 0x2d },
+ { 0xaa, 0x00 },
+ },
+ {},
+ },
+ { { 4800 }, 45000, { ScanMethod::TRANSPARENCY,
+ ScanMethod::TRANSPARENCY_INFRARED }, {
+ { 0x0c, 0x00 },
+ { 0x16, 0x13 }, { 0x17, 0x15 }, { 0x18, 0x10 }, { 0x19, 0x2a },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x61 }, { 0x1d, 0x75 },
+ { 0x52, 0x03 }, { 0x53, 0x06 }, { 0x54, 0x09 }, { 0x55, 0x0c },
+ { 0x56, 0x0f }, { 0x57, 0x00 }, { 0x58, 0x23 }, { 0x59, 0x00 }, { 0x5a, 0x40 },
+ { 0x70, 0x0a }, { 0x71, 0x0c }, { 0x72, 0x0c }, { 0x73, 0x0e },
+ { 0x74, 0x03 }, { 0x75, 0xff }, { 0x76, 0xff },
+ { 0x77, 0x03 }, { 0x78, 0xff }, { 0x79, 0xff },
+ { 0x7a, 0x00 }, { 0x7b, 0x92 }, { 0x7c, 0x49 },
+ { 0x9e, 0x2d },
+ { 0xaa, 0x00 },
+ },
+ { { 0x03, 0x1f },
+ },
+ },
+ };
+
+ for (const CustomSensorSettings& setting : custom_settings) {
+ for (auto method : setting.methods) {
+ sensor.resolutions = setting.resolutions;
+ sensor.method = method;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.custom_regs = setting.extra_custom_regs;
+ sensor.custom_fe_regs = setting.custom_fe_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_HP_N6310;
+ sensor.optical_res = 2400;
+ // sensor.ccd_size_divisor = 2; Possibly half CCD, needs checking
+ sensor.black_pixels = 96;
+ sensor.dummy_pixel = 26;
+ sensor.ccd_start_xoffset = 128;
+ sensor.sensor_pixels = 42720;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 230;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.custom_regs = {
+ { 0x16, 0x33 },
+ { 0x17, 0x0c },
+ { 0x18, 0x02 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x30 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x08 },
+ { 0x52, 0x0b },
+ { 0x53, 0x0e },
+ { 0x54, 0x11 },
+ { 0x55, 0x02 },
+ { 0x56, 0x05 },
+ { 0x57, 0x08 },
+ { 0x58, 0x63 },
+ { 0x59, 0x00 },
+ { 0x5a, 0x40 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CIS_CANON_LIDE_110;
+ sensor.optical_res = 2400;
+ sensor.ccd_size_divisor = 2;
+ sensor.black_pixels = 87;
+ sensor.dummy_pixel = 16;
+ sensor.ccd_start_xoffset = 303;
+ sensor.sensor_pixels = 5168*4;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.gamma = { 2.2f, 2.2f, 2.2f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_gl124;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ int exposure_lperiod;
+ SensorExposure exposure;
+ std::vector<unsigned> segment_order;
+ GenesysRegisterSettingSet custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 75, 100, 150 }, 4608, { 462, 609, 453 }, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x0c },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ { 0x70, 0x06 }, { 0x71, 0x08 }, { 0x72, 0x08 }, { 0x73, 0x0a },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x65 },
+ { 0x93, 0x00 }, { 0x94, 0x0a }, { 0x95, 0x18 },
+ { 0x96, 0x00 }, { 0x97, 0x9a },
+ { 0x98, 0x21 },
+ }
+ },
+ { { 300 }, 4608, { 462, 609, 453 }, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x0c },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ { 0x70, 0x06 }, { 0x71, 0x08 }, { 0x72, 0x08 }, { 0x73, 0x0a },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x65 },
+ { 0x93, 0x00 }, { 0x94, 0x0a }, { 0x95, 0x18 },
+ { 0x96, 0x00 }, { 0x97, 0x9a },
+ { 0x98, 0x21 },
+ }
+ },
+ { { 600 }, 5360, { 823, 1117, 805 }, std::vector<unsigned>{}, {
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x0a },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ { 0x70, 0x06 }, { 0x71, 0x08 }, { 0x72, 0x08 }, { 0x73, 0x0a },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x65 },
+ { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 },
+ { 0x96, 0x00 }, { 0x97, 0xa3 },
+ { 0x98, 0x21 },
+ },
+ },
+ { { 1200 }, 10528, { 6071, 6670, 6042 }, { 0, 1 }, {
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 },{ 0x20, 0x08 },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ { 0x70, 0x06 }, { 0x71, 0x08 }, { 0x72, 0x08 }, { 0x73, 0x0a },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x12 }, { 0x89, 0x47 },
+ { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 },
+ { 0x96, 0x00 }, { 0x97, 0xa3 },
+ { 0x98, 0x22 },
+ }
+ },
+ { { 2400 }, 20864, { 7451, 8661, 7405 }, { 0, 2, 1, 3 }, {
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x06 },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ { 0x70, 0x06 }, { 0x71, 0x08 }, { 0x72, 0x08 }, { 0x73, 0x0a },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x12 }, { 0x89, 0x47 },
+ { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 },
+ { 0x96, 0x00 }, { 0x97, 0xa3 },
+ { 0x98, 0x24 },
+ }
+ }
+ };
+
+ for (const auto& setting : custom_settings) {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.exposure = setting.exposure;
+ sensor.segment_order = setting.segment_order;
+ sensor.custom_regs = setting.custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CIS_CANON_LIDE_120;
+ sensor.optical_res = 2400;
+ sensor.ccd_size_divisor = 2;
+ sensor.black_pixels = 87;
+ sensor.dummy_pixel = 16;
+ sensor.ccd_start_xoffset = 303;
+ // SEGCNT at 600 DPI by number of segments
+ sensor.sensor_pixels = 5104*4;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.gamma = { 2.2f, 2.2f, 2.2f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_gl124;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ int exposure_lperiod;
+ SensorExposure exposure;
+ std::vector<unsigned> segment_order;
+ GenesysRegisterSettingSet custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 75, 100, 150, 300 }, 4608, { 1244, 1294, 1144 }, std::vector<unsigned>{}, {
+ { 0x16, 0x15 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x02 },
+ { 0x52, 0x04 }, { 0x53, 0x06 }, { 0x54, 0x00 }, { 0x55, 0x02 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x3a }, { 0x5b, 0x00 }, { 0x5c, 0x00 },
+ { 0x61, 0x20 },
+ { 0x70, 0x00 }, { 0x71, 0x1f }, { 0x72, 0x08 }, { 0x73, 0x0a },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x5e },
+ { 0x93, 0x00 }, { 0x94, 0x09 }, { 0x95, 0xf8 },
+ { 0x96, 0x00 }, { 0x97, 0x70 },
+ { 0x98, 0x21 },
+ },
+ },
+ { { 600 }, 5360, { 2394, 2444, 2144 }, std::vector<unsigned>{}, {
+ { 0x16, 0x11 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x02 },
+ { 0x52, 0x04 }, { 0x53, 0x06 }, { 0x54, 0x00 }, { 0x55, 0x02 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x3a }, { 0x5b, 0x00 }, { 0x5c, 0x00 },
+ { 0x61, 0x20 },
+ { 0x70, 0x1f }, { 0x71, 0x1f }, { 0x72, 0x08 }, { 0x73, 0x0a },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x5e },
+ { 0x93, 0x00 }, { 0x94, 0x13 }, { 0x95, 0xf0 },
+ { 0x96, 0x00 }, { 0x97, 0x8b },
+ { 0x98, 0x21 },
+ },
+ },
+ { { 1200 }, 10528, { 4694, 4644, 4094 }, std::vector<unsigned>{}, {
+ { 0x16, 0x15 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x02 },
+ { 0x52, 0x04 }, { 0x53, 0x06 }, { 0x54, 0x00 }, { 0x55, 0x02 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x3a }, { 0x5b, 0x00 }, { 0x5c, 0x00 },
+ { 0x61, 0x20 },
+ { 0x70, 0x1f }, { 0x71, 0x1f }, { 0x72, 0x08 }, { 0x73, 0x0a },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x5e },
+ { 0x93, 0x00 }, { 0x94, 0x27 }, { 0x95, 0xe0 },
+ { 0x96, 0x00 }, { 0x97, 0xc0 },
+ { 0x98, 0x21 },
+ },
+ },
+ { { 2400 }, 20864, { 8944, 8144, 7994 }, std::vector<unsigned>{}, {
+ { 0x16, 0x11 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x20, 0x02 },
+ { 0x52, 0x04 }, { 0x53, 0x06 }, { 0x54, 0x00 }, { 0x55, 0x02 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x3a }, { 0x5b, 0x00 }, { 0x5c, 0x00 },
+ { 0x61, 0x20 },
+ { 0x70, 0x00 }, { 0x71, 0x1f }, { 0x72, 0x08 }, { 0x73, 0x0a },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x5e },
+ { 0x93, 0x00 }, { 0x94, 0x4f }, { 0x95, 0xc0 },
+ { 0x96, 0x01 }, { 0x97, 0x2a },
+ { 0x98, 0x21 },
+ }
+ },
+ };
+
+ for (const auto& setting : custom_settings) {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.exposure = setting.exposure;
+ sensor.segment_order = setting.segment_order;
+ sensor.custom_regs = setting.custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CIS_CANON_LIDE_210;
+ sensor.optical_res = 2400;
+ sensor.ccd_size_divisor = 2;
+ sensor.black_pixels = 87;
+ sensor.dummy_pixel = 16;
+ sensor.ccd_start_xoffset = 303;
+ sensor.sensor_pixels = 5168*4;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.gamma = { 2.2f, 2.2f, 2.2f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_gl124;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ int exposure_lperiod;
+ SensorExposure exposure;
+ std::vector<unsigned> segment_order;
+ GenesysRegisterSettingSet custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 75, 100, 150, 300 }, 2768, { 388, 574, 393 }, std::vector<unsigned>{}, {
+ // { 0x16, 0x00 }, // FIXME: check if default value is different
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x0c },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ // { 0x70, 0x00 }, // FIXME: check if default value is different
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x65 },
+ { 0x93, 0x00 }, { 0x94, 0x0a }, { 0x95, 0x18 },
+ { 0x96, 0x00 }, { 0x97, 0x9a },
+ { 0x98, 0x21 },
+ }
+ },
+ { { 600 }, 5360, { 388, 574, 393 }, std::vector<unsigned>{}, {
+ // { 0x16, 0x00 }, // FIXME: check if default value is different
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x0a },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ // { 0x70, 0x00 }, // FIXME: check if default value is different
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x65 },
+ { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 },
+ { 0x96, 0x00 }, { 0x97, 0xa3 },
+ { 0x98, 0x21 },
+ }
+ },
+ { { 1200 }, 10528, { 388, 574, 393 }, {0, 1}, {
+ // { 0x16, 0x00 }, // FIXME: check if default value is different
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x08 },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ // { 0x70, 0x00 }, // FIXME: check if default value is different
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x65 },
+ { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 },
+ { 0x96, 0x00 }, { 0x97, 0xa3 },
+ { 0x98, 0x22 },
+ },
+ },
+ { { 2400 }, 20864, { 6839, 8401, 6859 }, {0, 2, 1, 3}, {
+ // { 0x16, 0x00 }, // FIXME: check if default value is different
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x06 },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ // { 0x70, 0x00 }, // FIXME: check if default value is different
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x1e },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x12 }, { 0x89, 0x47 },
+ { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 },
+ { 0x96, 0x00 }, { 0x97, 0xa3 },
+ { 0x98, 0x24 },
+ },
+ }
+ };
+
+ for (const auto& setting : custom_settings) {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.exposure = setting.exposure;
+ sensor.segment_order = setting.segment_order;
+ sensor.custom_regs = setting.custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CIS_CANON_LIDE_220;
+ sensor.optical_res = 2400;
+ sensor.ccd_size_divisor = 2;
+ sensor.black_pixels = 87;
+ sensor.dummy_pixel = 16;
+ sensor.ccd_start_xoffset = 303;
+ sensor.sensor_pixels = 5168*4;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.gamma = { 2.2f, 2.2f, 2.2f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_gl124;
+
+ {
+ struct CustomSensorSettings {
+ ResolutionFilter resolutions;
+ int exposure_lperiod;
+ SensorExposure exposure;
+ std::vector<unsigned> segment_order;
+ GenesysRegisterSettingSet custom_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 75, 100, 150, 300 }, 2768, { 388, 574, 393 }, std::vector<unsigned>{}, {
+ // { 0x16, 0x00 }, // FIXME: check if default value is different
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x0c },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ // { 0x70, 0x00 }, // FIXME: check if default value is different
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x65 },
+ { 0x93, 0x00 }, { 0x94, 0x0a }, { 0x95, 0x18 },
+ { 0x96, 0x00 }, { 0x97, 0x9a },
+ { 0x98, 0x21 },
+ }
+ },
+ { { 600 }, 5360, { 388, 574, 393 }, std::vector<unsigned>{}, {
+ // { 0x16, 0x00 }, // FIXME: check if default value is different
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x0a },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ // { 0x70, 0x00 }, // FIXME: check if default value is different
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x65 },
+ { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 },
+ { 0x96, 0x00 }, { 0x97, 0xa3 },
+ { 0x98, 0x21 },
+ }
+ },
+ { { 1200 }, 10528, { 388, 574, 393 }, {0, 1}, {
+ // { 0x16, 0x00 }, // FIXME: check if default value is different
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x08 },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ // { 0x70, 0x00 }, // FIXME: check if default value is different
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x00 }, { 0x89, 0x65 },
+ { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 },
+ { 0x96, 0x00 }, { 0x97, 0xa3 },
+ { 0x98, 0x22 },
+ }
+ },
+ { { 2400 }, 20864, { 6839, 8401, 6859 }, {0, 2, 1, 3}, {
+ // { 0x16, 0x00 }, // FIXME: check if default value is different
+ { 0x16, 0x10 }, { 0x17, 0x04 }, { 0x18, 0x00 }, { 0x19, 0x01 },
+ { 0x1a, 0x30 }, { 0x1b, 0x00 }, { 0x1c, 0x02 }, { 0x1d, 0x01 }, { 0x20, 0x06 },
+ { 0x52, 0x00 }, { 0x53, 0x02 }, { 0x54, 0x04 }, { 0x55, 0x06 },
+ { 0x56, 0x04 }, { 0x57, 0x04 }, { 0x58, 0x04 }, { 0x59, 0x04 },
+ { 0x5a, 0x1a }, { 0x5b, 0x00 }, { 0x5c, 0xc0 },
+ { 0x61, 0x20 },
+ // { 0x70, 0x00 }, // FIXME: check if default value is different
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x0f },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ { 0x88, 0x12 }, { 0x89, 0x47 },
+ { 0x93, 0x00 }, { 0x94, 0x14 }, { 0x95, 0x30 },
+ { 0x96, 0x00 }, { 0x97, 0xa3 },
+ { 0x98, 0x24 },
+ },
+ }
+ };
+
+ for (const auto& setting : custom_settings) {
+ sensor.resolutions = setting.resolutions;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.exposure = setting.exposure;
+ sensor.segment_order = setting.segment_order;
+ sensor.custom_regs = setting.custom_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_PLUSTEK_OPTICPRO_3600;
+ sensor.optical_res = 1200;
+ sensor.ccd_size_divisor = 2;
+ sensor.black_pixels = 87;
+ sensor.dummy_pixel = 87;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10100;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 230;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.custom_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x00 },
+ { 0x0b, 0x00 },
+ { 0x16, 0x33 },
+ { 0x17, 0x0b },
+ { 0x18, 0x11 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0xc4 },
+ { 0x52, 0x07 }, // [GB](HI|LOW) not needed for cis
+ { 0x53, 0x0a },
+ { 0x54, 0x0c },
+ { 0x55, 0x00 },
+ { 0x56, 0x02 },
+ { 0x57, 0x06 },
+ { 0x58, 0x22 },
+ { 0x59, 0x69 },
+ { 0x5a, 0x40 },
+ { 0x5b, 0x00 }, // TODO: 5b-5e
+ { 0x5c, 0x00 },
+ { 0x5d, 0x00 },
+ { 0x5e, 0x02 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7200I;
+ sensor.optical_res = 7200;
+ sensor.register_dpihw_override = 1200;
+ sensor.black_pixels = 88; // TODO
+ sensor.dummy_pixel = 20;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10200; // TODO
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 230;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.stagger_config = StaggerConfig{7200, 4};
+ sensor.custom_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x00 },
+ { 0x16, 0x23 },
+ { 0x17, 0x0c },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x21 },
+ { 0x1d, 0x84 },
+ { 0x52, 0x0a },
+ { 0x53, 0x0d },
+ { 0x54, 0x10 },
+ { 0x55, 0x01 },
+ { 0x56, 0x04 },
+ { 0x57, 0x07 },
+ { 0x58, 0x3a },
+ { 0x59, 0x81 },
+ { 0x5a, 0xc0 },
+ { 0x70, 0x0a },
+ { 0x71, 0x0b },
+ { 0x72, 0x0c },
+ { 0x73, 0x0d },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact;
+ {
+ struct CustomSensorSettings
+ {
+ ResolutionFilter resolutions;
+ ScanMethod method;
+ unsigned ccd_size_divisor;
+ unsigned logical_dpihw_override;
+ unsigned pixel_count_multiplier;
+ unsigned exposure_lperiod;
+ unsigned dpiset_override;
+ GenesysRegisterSettingSet custom_fe_regs;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 900 }, ScanMethod::TRANSPARENCY, 1, 900, 8, 0x2538, 150, {} },
+ { { 1800 }, ScanMethod::TRANSPARENCY, 1, 1800, 4, 0x2538, 300, {} },
+ { { 3600 }, ScanMethod::TRANSPARENCY, 1, 3600, 2, 0x2538, 600, {} },
+ { { 7200 }, ScanMethod::TRANSPARENCY, 1, 7200, 1, 0x19c8, 1200, {
+ { 0x02, 0x1b },
+ { 0x03, 0x14 },
+ { 0x04, 0x20 },
+ }
+ },
+ { { 900 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 900, 8, 0x1f54, 150, {} },
+ { { 1800 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 1800, 4, 0x1f54, 300, {} },
+ { { 3600 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 3600, 2, 0x1f54, 600, {} },
+ { { 7200 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 7200, 1, 0x1f54, 1200, {} },
+ };
+
+ for (const CustomSensorSettings& setting : custom_settings) {
+ sensor.resolutions = setting.resolutions;
+ sensor.method = setting.method;
+ sensor.ccd_size_divisor = setting.ccd_size_divisor;
+ sensor.logical_dpihw_override = setting.logical_dpihw_override;
+ sensor.pixel_count_multiplier = setting.pixel_count_multiplier;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.dpiset_override = setting.dpiset_override;
+ sensor.custom_fe_regs = setting.custom_fe_regs;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7300;
+ sensor.optical_res = 7200;
+ sensor.method = ScanMethod::TRANSPARENCY;
+ sensor.register_dpihw_override = 1200;
+ sensor.black_pixels = 88; // TODO
+ sensor.dummy_pixel = 20;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10200; // TODO
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 230;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.exposure_lperiod = 0x2f44;
+ sensor.stagger_config = StaggerConfig{7200, 4};
+ sensor.custom_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x00 },
+ { 0x16, 0x27 },
+ { 0x17, 0x0c },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x20 },
+ { 0x1d, 0x84 },
+ { 0x52, 0x0a },
+ { 0x53, 0x0d },
+ { 0x54, 0x0f },
+ { 0x55, 0x01 },
+ { 0x56, 0x04 },
+ { 0x57, 0x07 },
+ { 0x58, 0x31 },
+ { 0x59, 0x79 },
+ { 0x5a, 0xc0 },
+ { 0x70, 0x0c },
+ { 0x71, 0x0d },
+ { 0x72, 0x0e },
+ { 0x73, 0x0f },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact;
+ {
+ struct CustomSensorSettings
+ {
+ ResolutionFilter resolutions;
+ unsigned ccd_size_divisor;
+ unsigned logical_dpihw_override;
+ unsigned pixel_count_multiplier;
+ unsigned dpiset_override;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 900 }, 1, 900, 8, 150 },
+ { { 1800 }, 1, 1800, 4, 300 },
+ { { 3600 }, 1, 3600, 2, 600 },
+ { { 7200 }, 1, 7200, 1, 1200 },
+ };
+
+ for (const CustomSensorSettings& setting : custom_settings) {
+ sensor.resolutions = setting.resolutions;
+ sensor.ccd_size_divisor = setting.ccd_size_divisor;
+ sensor.logical_dpihw_override = setting.logical_dpihw_override;
+ sensor.pixel_count_multiplier = setting.pixel_count_multiplier;
+ sensor.dpiset_override = setting.dpiset_override;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_PLUSTEK_OPTICFILM_7500I;
+ sensor.optical_res = 7200;
+ sensor.register_dpihw_override = 1200;
+ sensor.black_pixels = 88; // TODO
+ sensor.dummy_pixel = 20;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10200; // TODO
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 230;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.stagger_config = StaggerConfig{7200, 4};
+ sensor.custom_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x00 },
+ { 0x0a, 0x00 },
+ { 0x16, 0x27 },
+ { 0x17, 0x0c },
+ { 0x18, 0x10 },
+ { 0x19, 0x2a },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x20 },
+ { 0x1d, 0x84 },
+ { 0x52, 0x0a },
+ { 0x53, 0x0d },
+ { 0x54, 0x0f },
+ { 0x55, 0x01 },
+ { 0x56, 0x04 },
+ { 0x57, 0x07 },
+ { 0x58, 0x31 },
+ { 0x59, 0x79 },
+ { 0x5a, 0xc0 },
+ { 0x70, 0x0c },
+ { 0x71, 0x0d },
+ { 0x72, 0x0e },
+ { 0x73, 0x0f },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x00 },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x00 },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x00 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = get_ccd_size_divisor_exact;
+ {
+ struct CustomSensorSettings
+ {
+ ResolutionFilter resolutions;
+ ScanMethod method;
+ unsigned ccd_size_divisor;
+ unsigned logical_dpihw_override;
+ unsigned pixel_count_multiplier;
+ unsigned exposure_lperiod;
+ unsigned dpiset_override;
+ };
+
+ CustomSensorSettings custom_settings[] = {
+ { { 900 }, ScanMethod::TRANSPARENCY, 1, 900, 8, 0x2f44, 150 },
+ { { 1800 }, ScanMethod::TRANSPARENCY, 1, 1800, 4, 0x2f44, 300 },
+ { { 3600 }, ScanMethod::TRANSPARENCY, 1, 3600, 2, 0x2f44, 600 },
+ { { 7200 }, ScanMethod::TRANSPARENCY, 1, 7200, 1, 0x2f44, 1200 },
+ { { 900 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 900, 8, 0x2af8, 150 },
+ { { 1800 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 1800, 4, 0x2af8, 300 },
+ { { 3600 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 3600, 2, 0x2af8, 600 },
+ { { 7200 }, ScanMethod::TRANSPARENCY_INFRARED, 1, 7200, 1, 0x2af8, 1200 },
+ };
+
+ for (const CustomSensorSettings& setting : custom_settings) {
+ sensor.resolutions = setting.resolutions;
+ sensor.method = setting.method;
+ sensor.ccd_size_divisor = setting.ccd_size_divisor;
+ sensor.logical_dpihw_override = setting.logical_dpihw_override;
+ sensor.pixel_count_multiplier = setting.pixel_count_multiplier;
+ sensor.exposure_lperiod = setting.exposure_lperiod;
+ sensor.dpiset_override = setting.dpiset_override;
+ s_sensors->push_back(sensor);
+ }
+ }
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_IMG101;
+ sensor.resolutions = { 75, 100, 150, 300, 600, 1200 };
+ sensor.exposure_lperiod = 11000;
+ sensor.segment_size = 5136;
+ sensor.segment_order = {0, 1};
+ sensor.optical_res = 1200;
+ sensor.black_pixels = 31;
+ sensor.dummy_pixel = 31;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10800;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0x0000, 0x0000, 0x0000 };
+ sensor.custom_regs = {
+ { 0x16, 0xbb }, { 0x17, 0x13 }, { 0x18, 0x10 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x20 }, { 0x1d, 0x06 },
+ { 0x52, 0x02 }, { 0x53, 0x04 }, { 0x54, 0x06 }, { 0x55, 0x08 },
+ { 0x56, 0x0a }, { 0x57, 0x00 }, { 0x58, 0x59 }, { 0x59, 0x31 }, { 0x5a, 0x40 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ };
+ sensor.gamma = { 1.7f, 1.7f, 1.7f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CCD_PLUSTEK_OPTICBOOK_3800;
+ sensor.resolutions = { 75, 100, 150, 300, 600, 1200 };
+ sensor.exposure_lperiod = 11000;
+ sensor.optical_res = 1200;
+ sensor.black_pixels = 31;
+ sensor.dummy_pixel = 31;
+ sensor.ccd_start_xoffset = 0;
+ sensor.sensor_pixels = 10200;
+ sensor.fau_gain_white_ref = 210;
+ sensor.gain_white_ref = 200;
+ sensor.exposure = { 0, 0, 0 };
+ sensor.custom_regs = {
+ { 0x16, 0xbb }, { 0x17, 0x13 }, { 0x18, 0x10 }, { 0x19, 0xff },
+ { 0x1a, 0x34 }, { 0x1b, 0x00 }, { 0x1c, 0x20 }, { 0x1d, 0x06 },
+ { 0x52, 0x02 }, { 0x53, 0x04 }, { 0x54, 0x06 }, { 0x55, 0x08 },
+ { 0x56, 0x0a }, { 0x57, 0x00 }, { 0x58, 0x59 }, { 0x59, 0x31 }, { 0x5a, 0x40 },
+ { 0x74, 0x00 }, { 0x75, 0x00 }, { 0x76, 0x3c },
+ { 0x77, 0x00 }, { 0x78, 0x00 }, { 0x79, 0x9f },
+ { 0x7a, 0x00 }, { 0x7b, 0x00 }, { 0x7c, 0x55 },
+ };
+ sensor.gamma = { 1.7f, 1.7f, 1.7f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+
+
+ sensor = Genesys_Sensor();
+ sensor.sensor_id = SensorId::CIS_CANON_LIDE_80;
+ sensor.optical_res = 1200; // real hardware limit is 2400
+ sensor.ccd_size_divisor = 2;
+ sensor.black_pixels = 20;
+ sensor.dummy_pixel = 6;
+ // tuned to give 3*8 multiple startx coordinate during shading calibration
+ sensor.ccd_start_xoffset = 34; // 14=>3, 20=>2
+ // 10400, too wide=>10288 in shading data 10240~
+ // 10208 too short for shading, max shading data = 10240 pixels, endpix-startpix=10208
+ sensor.sensor_pixels = 10240;
+ sensor.fau_gain_white_ref = 150;
+ sensor.gain_white_ref = 150;
+ // maps to 0x70-0x73 for GL841
+ sensor.exposure = { 0x1000, 0x1000, 0x0500 };
+ sensor.custom_regs = {
+ { 0x08, 0x00 },
+ { 0x09, 0x05 },
+ { 0x0a, 0x07 },
+ { 0x0b, 0x09 },
+ { 0x16, 0x00 },
+ { 0x17, 0x01 },
+ { 0x18, 0x00 },
+ { 0x19, 0x06 },
+ { 0x1a, 0x00 },
+ { 0x1b, 0x00 },
+ { 0x1c, 0x00 },
+ { 0x1d, 0x04 },
+ { 0x52, 0x03 },
+ { 0x53, 0x07 },
+ { 0x54, 0x00 },
+ { 0x55, 0x00 },
+ { 0x56, 0x00 },
+ { 0x57, 0x00 },
+ { 0x58, 0x29 },
+ { 0x59, 0x69 },
+ { 0x5a, 0x55 },
+ { 0x5b, 0x00 },
+ { 0x5c, 0x00 },
+ { 0x5d, 0x20 },
+ { 0x5e, 0x41 },
+ };
+ sensor.gamma = { 1.0f, 1.0f, 1.0f };
+ sensor.get_logical_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_register_hwdpi_fun = default_get_logical_hwdpi;
+ sensor.get_hwdpi_divisor_fun = default_get_hwdpi_divisor_for_dpi;
+ sensor.get_ccd_size_divisor_fun = default_get_ccd_size_divisor_for_dpi;
+ s_sensors->push_back(sensor);
+}
+
+} // namespace genesys
diff --git a/backend/genesys/test_scanner_interface.cpp b/backend/genesys/test_scanner_interface.cpp
new file mode 100644
index 0000000..12f726f
--- /dev/null
+++ b/backend/genesys/test_scanner_interface.cpp
@@ -0,0 +1,229 @@
+/* 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 "test_scanner_interface.h"
+#include "device.h"
+#include <cstring>
+
+namespace genesys {
+
+TestScannerInterface::TestScannerInterface(Genesys_Device* dev) : dev_{dev}
+{
+ // initialize status registers
+ if (dev_->model->asic_type == AsicType::GL124) {
+ write_register(0x101, 0x00);
+ } else {
+ write_register(0x41, 0x00);
+ }
+ if (dev_->model->asic_type == AsicType::GL841 ||
+ dev_->model->asic_type == AsicType::GL843 ||
+ dev_->model->asic_type == AsicType::GL845 ||
+ dev_->model->asic_type == AsicType::GL846 ||
+ dev_->model->asic_type == AsicType::GL847)
+ {
+ write_register(0x40, 0x00);
+ }
+
+ // initialize other registers that we read on init
+ if (dev_->model->asic_type == AsicType::GL124) {
+ write_register(0x33, 0x00);
+ write_register(0xbd, 0x00);
+ write_register(0xbe, 0x00);
+ write_register(0x100, 0x00);
+ }
+
+ if (dev_->model->asic_type == AsicType::GL845 ||
+ dev_->model->asic_type == AsicType::GL846 ||
+ dev_->model->asic_type == AsicType::GL847)
+ {
+ write_register(0xbd, 0x00);
+ write_register(0xbe, 0x00);
+
+ write_register(0xd0, 0x00);
+ write_register(0xd1, 0x01);
+ write_register(0xd2, 0x02);
+ write_register(0xd3, 0x03);
+ write_register(0xd4, 0x04);
+ write_register(0xd5, 0x05);
+ write_register(0xd6, 0x06);
+ write_register(0xd7, 0x07);
+ write_register(0xd8, 0x08);
+ write_register(0xd9, 0x09);
+ }
+}
+
+TestScannerInterface::~TestScannerInterface() = default;
+
+bool TestScannerInterface::is_mock() const
+{
+ return true;
+}
+
+std::uint8_t TestScannerInterface::read_register(std::uint16_t address)
+{
+ return cached_regs_.get(address);
+}
+
+void TestScannerInterface::write_register(std::uint16_t address, std::uint8_t value)
+{
+ cached_regs_.update(address, value);
+}
+
+void TestScannerInterface::write_registers(const Genesys_Register_Set& regs)
+{
+ cached_regs_.update(regs);
+}
+
+
+void TestScannerInterface::write_0x8c(std::uint8_t index, std::uint8_t value)
+{
+ (void) index;
+ (void) value;
+}
+
+void TestScannerInterface::bulk_read_data(std::uint8_t addr, std::uint8_t* data, std::size_t size)
+{
+ (void) addr;
+ std::memset(data, 0, size);
+}
+
+void TestScannerInterface::bulk_write_data(std::uint8_t addr, std::uint8_t* data, std::size_t size)
+{
+ (void) addr;
+ (void) data;
+ (void) size;
+}
+
+void TestScannerInterface::write_buffer(std::uint8_t type, std::uint32_t addr, std::uint8_t* data,
+ std::size_t size, Flags flags)
+{
+ (void) type;
+ (void) addr;
+ (void) data;
+ (void) size;
+ (void) flags;
+}
+
+void TestScannerInterface::write_gamma(std::uint8_t type, std::uint32_t addr, std::uint8_t* data,
+ std::size_t size, Flags flags)
+{
+ (void) type;
+ (void) addr;
+ (void) data;
+ (void) size;
+ (void) flags;
+}
+
+void TestScannerInterface::write_ahb(std::uint32_t addr, std::uint32_t size, std::uint8_t* data)
+{
+ (void) addr;
+ (void) size;
+ (void) data;
+}
+
+std::uint16_t TestScannerInterface::read_fe_register(std::uint8_t address)
+{
+ return cached_fe_regs_.get(address);
+}
+
+void TestScannerInterface::write_fe_register(std::uint8_t address, std::uint16_t value)
+{
+ cached_fe_regs_.update(address, value);
+}
+
+IUsbDevice& TestScannerInterface::get_usb_device()
+{
+ return usb_dev_;
+}
+
+void TestScannerInterface::sleep_us(unsigned microseconds)
+{
+ (void) microseconds;
+}
+
+void TestScannerInterface::record_slope_table(unsigned table_nr,
+ const std::vector<std::uint16_t>& steps)
+{
+ slope_tables_[table_nr] = steps;
+}
+
+std::map<unsigned, std::vector<std::uint16_t>>& TestScannerInterface::recorded_slope_tables()
+{
+ return slope_tables_;
+}
+
+void TestScannerInterface::record_progress_message(const char* msg)
+{
+ last_progress_message_ = msg;
+}
+
+const std::string& TestScannerInterface::last_progress_message() const
+{
+ return last_progress_message_;
+}
+
+void TestScannerInterface::record_key_value(const std::string& key, const std::string& value)
+{
+ key_values_[key] = value;
+}
+
+std::map<std::string, std::string>& TestScannerInterface::recorded_key_values()
+{
+ return key_values_;
+}
+
+void TestScannerInterface::test_checkpoint(const std::string& name)
+{
+ if (checkpoint_callback_) {
+ checkpoint_callback_(*dev_, *this, name);
+ }
+}
+
+void TestScannerInterface::set_checkpoint_callback(TestCheckpointCallback callback)
+{
+ checkpoint_callback_ = callback;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/test_scanner_interface.h b/backend/genesys/test_scanner_interface.h
new file mode 100644
index 0000000..acf0f6d
--- /dev/null
+++ b/backend/genesys/test_scanner_interface.h
@@ -0,0 +1,122 @@
+/* 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_TEST_SCANNER_INTERFACE_H
+#define BACKEND_GENESYS_TEST_SCANNER_INTERFACE_H
+
+#include "scanner_interface.h"
+#include "register_cache.h"
+#include "test_usb_device.h"
+#include "test_settings.h"
+
+#include <map>
+
+namespace genesys {
+
+class TestScannerInterface : public ScannerInterface
+{
+public:
+ TestScannerInterface(Genesys_Device* dev);
+
+ ~TestScannerInterface() override;
+
+ bool is_mock() const override;
+
+ const RegisterCache<std::uint8_t>& cached_regs() const { return cached_regs_; }
+ const RegisterCache<std::uint16_t>& cached_fe_regs() const { return cached_fe_regs_; }
+
+ std::uint8_t read_register(std::uint16_t address) override;
+ void write_register(std::uint16_t address, std::uint8_t value) override;
+ void write_registers(const Genesys_Register_Set& regs) override;
+
+ void write_0x8c(std::uint8_t index, std::uint8_t value) override;
+ void bulk_read_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) override;
+ void bulk_write_data(std::uint8_t addr, std::uint8_t* data, std::size_t size) override;
+
+ void write_buffer(std::uint8_t type, std::uint32_t addr, std::uint8_t* data,
+ std::size_t size, Flags flags) override;
+ void write_gamma(std::uint8_t type, std::uint32_t addr, std::uint8_t* data,
+ std::size_t size, Flags flags) override;
+ void write_ahb(std::uint32_t addr, std::uint32_t size, std::uint8_t* data) override;
+
+ std::uint16_t read_fe_register(std::uint8_t address) override;
+ void write_fe_register(std::uint8_t address, std::uint16_t value) override;
+
+ IUsbDevice& get_usb_device() override;
+
+ void sleep_us(unsigned microseconds) override;
+
+ void record_progress_message(const char* msg) override;
+
+ const std::string& last_progress_message() const;
+
+ void record_slope_table(unsigned table_nr, const std::vector<std::uint16_t>& steps) override;
+
+ std::map<unsigned, std::vector<std::uint16_t>>& recorded_slope_tables();
+
+ void record_key_value(const std::string& key, const std::string& value) override;
+
+ std::map<std::string, std::string>& recorded_key_values();
+
+ void test_checkpoint(const std::string& name) override;
+
+ void set_checkpoint_callback(TestCheckpointCallback callback);
+
+private:
+ Genesys_Device* dev_;
+
+ RegisterCache<std::uint8_t> cached_regs_;
+ RegisterCache<std::uint16_t> cached_fe_regs_;
+ TestUsbDevice usb_dev_;
+
+ TestCheckpointCallback checkpoint_callback_;
+
+ std::map<unsigned, std::vector<std::uint16_t>> slope_tables_;
+
+ std::string last_progress_message_;
+ std::map<std::string, std::string> key_values_;
+};
+
+} // namespace genesys
+
+#endif
diff --git a/backend/genesys/test_settings.cpp b/backend/genesys/test_settings.cpp
new file mode 100644
index 0000000..425f09c
--- /dev/null
+++ b/backend/genesys/test_settings.cpp
@@ -0,0 +1,106 @@
+/* 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 "test_settings.h"
+
+namespace genesys {
+
+namespace {
+
+bool s_testing_mode = false;
+std::uint16_t s_vendor_id = 0;
+std::uint16_t s_product_id = 0;
+TestCheckpointCallback s_checkpoint_callback;
+
+} // namespace
+
+bool is_testing_mode()
+{
+ return s_testing_mode;
+}
+
+void disable_testing_mode()
+{
+ s_testing_mode = false;
+ s_vendor_id = 0;
+ s_product_id = 0;
+
+}
+
+void enable_testing_mode(std::uint16_t vendor_id, std::uint16_t product_id,
+ TestCheckpointCallback checkpoint_callback)
+{
+ s_testing_mode = true;
+ s_vendor_id = vendor_id;
+ s_product_id = product_id;
+ s_checkpoint_callback = checkpoint_callback;
+}
+
+std::uint16_t get_testing_vendor_id()
+{
+ return s_vendor_id;
+}
+
+std::uint16_t get_testing_product_id()
+{
+ return s_product_id;
+}
+
+std::string get_testing_device_name()
+{
+ std::string name;
+ unsigned max_size = 50;
+ name.resize(max_size);
+ name.resize(std::snprintf(&name.front(), max_size, "test device:0x%04x:0x%04x",
+ s_vendor_id, s_product_id));
+ return name;
+}
+
+TestCheckpointCallback get_testing_checkpoint_callback()
+{
+ return s_checkpoint_callback;
+}
+
+} // namespace genesys
diff --git a/backend/genesys/test_settings.h b/backend/genesys/test_settings.h
new file mode 100644
index 0000000..8ac03e0
--- /dev/null
+++ b/backend/genesys/test_settings.h
@@ -0,0 +1,70 @@
+/* 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_TEST_SETTINGS_H
+#define BACKEND_GENESYS_TEST_SETTINGS_H
+
+#include "scanner_interface.h"
+#include "register_cache.h"
+#include "test_usb_device.h"
+#include <functional>
+
+namespace genesys {
+
+using TestCheckpointCallback = std::function<void(const Genesys_Device&,
+ TestScannerInterface&,
+ const std::string&)>;
+
+bool is_testing_mode();
+void disable_testing_mode();
+void enable_testing_mode(std::uint16_t vendor_id, std::uint16_t product_id,
+ TestCheckpointCallback checkpoint_callback);
+std::uint16_t get_testing_vendor_id();
+std::uint16_t get_testing_product_id();
+std::string get_testing_device_name();
+TestCheckpointCallback get_testing_checkpoint_callback();
+
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_TEST_SETTINGS_H
diff --git a/backend/genesys/test_usb_device.cpp b/backend/genesys/test_usb_device.cpp
new file mode 100644
index 0000000..de2399e
--- /dev/null
+++ b/backend/genesys/test_usb_device.cpp
@@ -0,0 +1,141 @@
+/* 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 "test_usb_device.h"
+#include "low.h"
+
+namespace genesys {
+
+TestUsbDevice::TestUsbDevice(std::uint16_t vendor, std::uint16_t product) :
+ vendor_{vendor},
+ product_{product}
+{
+}
+
+TestUsbDevice::~TestUsbDevice()
+{
+ if (is_open()) {
+ DBG(DBG_error, "TestUsbDevice not closed; closing automatically");
+ close();
+ }
+}
+
+void TestUsbDevice::open(const char* dev_name)
+{
+ DBG_HELPER(dbg);
+
+ if (is_open()) {
+ throw SaneException("device already open");
+ }
+ name_ = dev_name;
+ is_open_ = true;
+}
+
+void TestUsbDevice::clear_halt()
+{
+ DBG_HELPER(dbg);
+ assert_is_open();
+}
+
+void TestUsbDevice::reset()
+{
+ DBG_HELPER(dbg);
+ assert_is_open();
+}
+
+void TestUsbDevice::close()
+{
+ DBG_HELPER(dbg);
+ assert_is_open();
+
+ is_open_ = false;
+ name_ = "";
+}
+
+void TestUsbDevice::get_vendor_product(int& vendor, int& product)
+{
+ DBG_HELPER(dbg);
+ assert_is_open();
+ vendor = vendor_;
+ product = product_;
+}
+
+void TestUsbDevice::control_msg(int rtype, int reg, int value, int index, int length,
+ std::uint8_t* data)
+{
+ (void) reg;
+ (void) value;
+ (void) index;
+ DBG_HELPER(dbg);
+ assert_is_open();
+ if (rtype == REQUEST_TYPE_IN) {
+ std::memset(data, 0, length);
+ }
+}
+
+void TestUsbDevice::bulk_read(std::uint8_t* buffer, std::size_t* size)
+{
+
+ DBG_HELPER(dbg);
+ assert_is_open();
+ std::memset(buffer, 0, *size);
+}
+
+void TestUsbDevice::bulk_write(const std::uint8_t* buffer, std::size_t* size)
+{
+ (void) buffer;
+ (void) size;
+ DBG_HELPER(dbg);
+ assert_is_open();
+}
+
+void TestUsbDevice::assert_is_open() const
+{
+ if (!is_open()) {
+ throw SaneException("device not open");
+ }
+}
+
+} // namespace genesys
diff --git a/backend/genesys/test_usb_device.h b/backend/genesys/test_usb_device.h
new file mode 100644
index 0000000..abbd78a
--- /dev/null
+++ b/backend/genesys/test_usb_device.h
@@ -0,0 +1,85 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+#ifndef BACKEND_GENESYS_TEST_USB_DEVICE_H
+#define BACKEND_GENESYS_TEST_USB_DEVICE_H
+
+#include "usb_device.h"
+
+namespace genesys {
+
+class TestUsbDevice : public IUsbDevice {
+public:
+ TestUsbDevice(std::uint16_t vendor, std::uint16_t product);
+ TestUsbDevice() = default;
+
+ ~TestUsbDevice() override;
+
+ bool is_open() const override { return is_open_; }
+
+ const std::string& name() const override { return name_; }
+
+ void open(const char* dev_name) override;
+
+ void clear_halt() override;
+ void reset() override;
+ void close() override;
+
+ void get_vendor_product(int& vendor, int& product) override;
+
+ void control_msg(int rtype, int reg, int value, int index, int length,
+ std::uint8_t* data) override;
+ void bulk_read(std::uint8_t* buffer, std::size_t* size) override;
+ void bulk_write(const std::uint8_t* buffer, std::size_t* size) override;
+private:
+ void assert_is_open() const;
+
+ std::string name_;
+ bool is_open_ = false;
+ std::uint16_t vendor_ = 0;
+ std::uint16_t product_ = 0;
+};
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_TEST_USB_DEVICE_H
diff --git a/backend/genesys/usb_device.cpp b/backend/genesys/usb_device.cpp
new file mode 100644
index 0000000..2d02219
--- /dev/null
+++ b/backend/genesys/usb_device.cpp
@@ -0,0 +1,147 @@
+/* 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 "usb_device.h"
+
+namespace genesys {
+
+IUsbDevice::~IUsbDevice() = default;
+
+UsbDevice::~UsbDevice()
+{
+ if (is_open()) {
+ DBG(DBG_error, "UsbDevice not closed; closing automatically");
+ close();
+ }
+}
+
+void UsbDevice::open(const char* dev_name)
+{
+ DBG_HELPER(dbg);
+
+ if (is_open()) {
+ throw SaneException("device already open");
+ }
+ int device_num = 0;
+
+ dbg.status("open device");
+ TIE(sanei_usb_open(dev_name, &device_num));
+
+ name_ = dev_name;
+ device_num_ = device_num;
+ is_open_ = true;
+}
+
+void UsbDevice::clear_halt()
+{
+ DBG_HELPER(dbg);
+ assert_is_open();
+ TIE(sanei_usb_clear_halt(device_num_));
+}
+
+void UsbDevice::reset()
+{
+ DBG_HELPER(dbg);
+ assert_is_open();
+ TIE(sanei_usb_reset(device_num_));
+}
+
+void UsbDevice::close()
+{
+ DBG_HELPER(dbg);
+ assert_is_open();
+
+ // we can't do much if closing fails, so we close the device on our side regardless of the
+ // function succeeds
+ int device_num = device_num_;
+
+ set_not_open();
+ sanei_usb_close(device_num);
+}
+
+void UsbDevice::get_vendor_product(int& vendor, int& product)
+{
+ DBG_HELPER(dbg);
+ assert_is_open();
+ TIE(sanei_usb_get_vendor_product(device_num_, &vendor, &product));
+}
+
+void UsbDevice::control_msg(int rtype, int reg, int value, int index, int length,
+ std::uint8_t* data)
+{
+ DBG_HELPER(dbg);
+ assert_is_open();
+ TIE(sanei_usb_control_msg(device_num_, rtype, reg, value, index, length, data));
+}
+
+void UsbDevice::bulk_read(std::uint8_t* buffer, std::size_t* size)
+{
+ DBG_HELPER(dbg);
+ assert_is_open();
+ TIE(sanei_usb_read_bulk(device_num_, buffer, size));
+}
+
+void UsbDevice::bulk_write(const std::uint8_t* buffer, std::size_t* size)
+{
+ DBG_HELPER(dbg);
+ assert_is_open();
+ TIE(sanei_usb_write_bulk(device_num_, buffer, size));
+}
+
+void UsbDevice::assert_is_open() const
+{
+ if (!is_open()) {
+ throw SaneException("device not open");
+ }
+}
+
+void UsbDevice::set_not_open()
+{
+ device_num_ = 0;
+ is_open_ = false;
+ name_ = "";
+}
+
+} // namespace genesys
diff --git a/backend/genesys/usb_device.h b/backend/genesys/usb_device.h
new file mode 100644
index 0000000..265c57c
--- /dev/null
+++ b/backend/genesys/usb_device.h
@@ -0,0 +1,118 @@
+/* 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_USB_DEVICE_H
+#define BACKEND_GENESYS_USB_DEVICE_H
+
+#include "error.h"
+#include "../include/sane/sanei_usb.h"
+
+#include <cstdint>
+#include <string>
+
+namespace genesys {
+
+class IUsbDevice {
+public:
+ IUsbDevice() = default;
+
+ IUsbDevice(const IUsbDevice& other) = delete;
+ IUsbDevice& operator=(const IUsbDevice&) = delete;
+
+ virtual ~IUsbDevice();
+
+ virtual bool is_open() const = 0;
+
+ virtual const std::string& name() const = 0;
+
+ virtual void open(const char* dev_name) = 0;
+
+ virtual void clear_halt() = 0;
+ virtual void reset() = 0;
+ virtual void close() = 0;
+
+ virtual void get_vendor_product(int& vendor, int& product) = 0;
+
+ virtual void control_msg(int rtype, int reg, int value, int index, int length,
+ std::uint8_t* data) = 0;
+ virtual void bulk_read(std::uint8_t* buffer, std::size_t* size) = 0;
+ virtual void bulk_write(const std::uint8_t* buffer, std::size_t* size) = 0;
+
+};
+
+class UsbDevice : public IUsbDevice {
+public:
+ UsbDevice() = default;
+
+ ~UsbDevice() override;
+
+ bool is_open() const override { return is_open_; }
+
+ const std::string& name() const override { return name_; }
+
+ void open(const char* dev_name) override;
+
+ void clear_halt() override;
+ void reset() override;
+ void close() override;
+
+ void get_vendor_product(int& vendor, int& product) override;
+
+ void control_msg(int rtype, int reg, int value, int index, int length,
+ std::uint8_t* data) override;
+ void bulk_read(std::uint8_t* buffer, std::size_t* size) override;
+ void bulk_write(const std::uint8_t* buffer, std::size_t* size) override;
+
+private:
+
+ void assert_is_open() const;
+ void set_not_open();
+
+ std::string name_;
+ bool is_open_ = false;
+ int device_num_ = 0;
+};
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_USB_DEVICE_H
diff --git a/backend/genesys/utilities.h b/backend/genesys/utilities.h
new file mode 100644
index 0000000..1e268b5
--- /dev/null
+++ b/backend/genesys/utilities.h
@@ -0,0 +1,180 @@
+/* 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_UTILITIES_H
+#define BACKEND_GENESYS_UTILITIES_H
+
+#include "error.h"
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <vector>
+
+namespace genesys {
+
+template<class T>
+void compute_array_percentile_approx(T* result, const T* data,
+ std::size_t line_count, std::size_t elements_per_line,
+ float percentile)
+{
+ if (line_count == 0) {
+ throw SaneException("invalid line count");
+ }
+
+ if (line_count == 1) {
+ std::copy(data, data + elements_per_line, result);
+ return;
+ }
+
+ std::vector<T> column_elems;
+ column_elems.resize(line_count, 0);
+
+ std::size_t select_elem = std::min(static_cast<std::size_t>(line_count * percentile),
+ line_count - 1);
+
+ auto select_it = column_elems.begin() + select_elem;
+
+ for (std::size_t ix = 0; ix < elements_per_line; ++ix) {
+ for (std::size_t iy = 0; iy < line_count; ++iy) {
+ column_elems[iy] = data[iy * elements_per_line + ix];
+ }
+
+ std::nth_element(column_elems.begin(), select_it, column_elems.end());
+
+ *result++ = *select_it;
+ }
+}
+
+template<class Char, class Traits>
+class BasicStreamStateSaver
+{
+public:
+ explicit BasicStreamStateSaver(std::basic_ios<Char, Traits>& stream) :
+ stream_{stream}
+ {
+ flags_ = stream_.flags();
+ width_ = stream_.width();
+ precision_ = stream_.precision();
+ fill_ = stream_.fill();
+ }
+
+ ~BasicStreamStateSaver()
+ {
+ stream_.flags(flags_);
+ stream_.width(width_);
+ stream_.precision(precision_);
+ stream_.fill(fill_);
+ }
+
+ BasicStreamStateSaver(const BasicStreamStateSaver&) = delete;
+ BasicStreamStateSaver& operator=(const BasicStreamStateSaver&) = delete;
+
+private:
+ std::basic_ios<Char, Traits>& stream_;
+ std::ios_base::fmtflags flags_;
+ std::streamsize width_ = 0;
+ std::streamsize precision_ = 0;
+ Char fill_ = ' ';
+};
+
+using StreamStateSaver = BasicStreamStateSaver<char, std::char_traits<char>>;
+
+template<class T>
+std::string format_indent_braced_list(unsigned indent, const T& x)
+{
+ std::string indent_str(indent, ' ');
+ std::ostringstream out;
+ out << x;
+ auto formatted_str = out.str();
+ if (formatted_str.empty()) {
+ return formatted_str;
+ }
+
+ std::string out_str;
+ for (std::size_t i = 0; i < formatted_str.size(); ++i) {
+ out_str += formatted_str[i];
+
+ if (formatted_str[i] == '\n' &&
+ i < formatted_str.size() - 1 &&
+ formatted_str[i + 1] != '\n')
+ {
+ out_str += indent_str;
+ }
+ }
+ return out_str;
+}
+
+template<class T>
+std::string format_vector_unsigned(unsigned indent, const std::vector<T>& arg)
+{
+ std::ostringstream out;
+ std::string indent_str(indent, ' ');
+
+ out << "std::vector<T>{ ";
+ for (const auto& el : arg) {
+ out << indent_str << static_cast<unsigned>(el) << "\n";
+ }
+ out << "}";
+ return out.str();
+}
+
+template<class T>
+std::string format_vector_indent_braced(unsigned indent, const char* type,
+ const std::vector<T>& arg)
+{
+ if (arg.empty()) {
+ return "{}";
+ }
+ std::string indent_str(indent, ' ');
+ std::stringstream out;
+ out << "std::vector<" << type << ">{\n";
+ for (const auto& item : arg) {
+ out << indent_str << format_indent_braced_list(indent, item) << '\n';
+ }
+ out << "}";
+ return out.str();
+}
+
+} // namespace genesys
+
+#endif // BACKEND_GENESYS_UTILITIES_H