summaryrefslogtreecommitdiff
path: root/backend/genesys/low.cpp
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2020-03-30 21:30:45 +0200
committerJörg Frings-Fürst <debian@jff-webhosting.net>2020-03-30 21:30:45 +0200
commitee770c2346eb37e0dcb8b6cf3eaacf3d8efd6bbc (patch)
tree58f05092be1a17a939e861f8cadcda1b6ca2ecef /backend/genesys/low.cpp
parent0da9e21872802cfc6e975b1ebaf9efb9e5934d84 (diff)
parentfef76e17ed4c607ea73b81279f9ef1d7121be900 (diff)
Merge branch 'release/experimental/1.0.29-1_experimental1'experimental/1.0.29-1_experimental1
Diffstat (limited to 'backend/genesys/low.cpp')
-rw-r--r--backend/genesys/low.cpp1994
1 files changed, 1994 insertions, 0 deletions
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