kopia lustrzana https://gitlab.com/sane-project/backends
				
				
				
			
		
			
				
	
	
		
			6492 wiersze
		
	
	
		
			221 KiB
		
	
	
	
		
			C++
		
	
	
			
		
		
	
	
			6492 wiersze
		
	
	
		
			221 KiB
		
	
	
	
		
			C++
		
	
	
| /* 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, see <https://www.gnu.org/licenses/>.
 | |
| 
 | |
|    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 "gl124_registers.h"
 | |
| #include "gl841_registers.h"
 | |
| #include "gl842_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 <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, not used
 | |
|     // SANE_VALUE_SCAN_MODE_LINEART, not used
 | |
|     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 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 = {
 | |
|     float_to_fixed(0),     // minimum
 | |
|     float_to_fixed(100),   // maximum
 | |
|     float_to_fixed(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(const 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(const 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(const 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(const 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(const 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 (auto& sensor : *s_sensors) {
 | |
|         if (dev->model->sensor_id == sensor.sensor_id && sensor.method == 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 (dev->model->asic_type == AsicType::GL845 ||
 | |
|         dev->model->asic_type == AsicType::GL846 ||
 | |
|         dev->model->asic_type == AsicType::GL847 ||
 | |
|         dev->model->asic_type == AsicType::GL124)
 | |
|     {
 | |
|         bool memory_layout_found = false;
 | |
|         for (const auto& memory_layout : *s_memory_layout) {
 | |
|             if (memory_layout.models.matches(dev->model->model_id)) {
 | |
|                 dev->memory_layout = memory_layout;
 | |
|                 memory_layout_found = true;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         if (!memory_layout_found) {
 | |
|             throw SaneException("Could not find memory layout");
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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));
 | |
|     }
 | |
| }
 | |
| 
 | |
| /** @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 (has_flag(dev->model->flags, ModelFlag::GAMMA_14BIT)) {
 | |
|             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 transferred.
 | |
|  */
 | |
| SANE_Int sanei_genesys_exposure_time2(Genesys_Device * dev, const MotorProfile& profile, float ydpi,
 | |
|                                       int endpixel, int exposure_by_led)
 | |
| {
 | |
|   int exposure_by_ccd = endpixel + 32;
 | |
|     unsigned max_speed_motor_w = profile.slope.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;
 | |
|     }
 | |
| 
 | |
|     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
 | |
| 
 | |
|    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 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;
 | |
|     }
 | |
| 
 | |
|     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->cmd_set->has_send_shading_data()) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|   DBG(DBG_proc, "%s (pixels_per_line = %d)\n", __func__, pixels_per_line);
 | |
| 
 | |
|     unsigned channels = dev->settings.get_channels();
 | |
| 
 | |
|   // 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 (unsigned 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);
 | |
| }
 | |
| 
 | |
| 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::GL841: {
 | |
|             dev.interface->write_register(gl841::REG_0x0D,
 | |
|                                           gl841::REG_0x0D_CLRLNCNT);
 | |
|             break;
 | |
|         }
 | |
|         case AsicType::GL842: {
 | |
|             dev.interface->write_register(gl842::REG_0x0D,
 | |
|                                           gl842::REG_0x0D_CLRLNCNT);
 | |
|             break;
 | |
|         }
 | |
|         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_send_slope_table(Genesys_Device* dev, const Genesys_Sensor& sensor, unsigned table_nr,
 | |
|                               const std::vector<uint16_t>& slope_table)
 | |
| {
 | |
|     DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %zu", table_nr, slope_table.size());
 | |
| 
 | |
|     unsigned max_table_nr = 0;
 | |
|     switch (dev->model->asic_type) {
 | |
|         case AsicType::GL646: {
 | |
|             max_table_nr = 2;
 | |
|             break;
 | |
|         }
 | |
|         case AsicType::GL841:
 | |
|         case AsicType::GL842:
 | |
|         case AsicType::GL843:
 | |
|         case AsicType::GL845:
 | |
|         case AsicType::GL846:
 | |
|         case AsicType::GL847:
 | |
|         case AsicType::GL124: {
 | |
|             max_table_nr = 4;
 | |
|             break;
 | |
|         }
 | |
|         default:
 | |
|             throw SaneException("Unsupported ASIC type");
 | |
|     }
 | |
| 
 | |
|     if (table_nr > max_table_nr) {
 | |
|         throw SaneException("invalid table number %d", table_nr);
 | |
|     }
 | |
| 
 | |
|     std::vector<uint8_t> table;
 | |
|     table.reserve(slope_table.size() * 2);
 | |
|     for (std::size_t i = 0; i < slope_table.size(); i++) {
 | |
|         table.push_back(slope_table[i] & 0xff);
 | |
|         table.push_back(slope_table[i] >> 8);
 | |
|     }
 | |
|     if (dev->model->asic_type == AsicType::GL841 ||
 | |
|         dev->model->model_id == ModelId::CANON_LIDE_90)
 | |
|     {
 | |
|         // BUG: do this on all gl842 scanners
 | |
|         auto max_table_size = get_slope_table_max_size(dev->model->asic_type);
 | |
|         table.reserve(max_table_size * 2);
 | |
|         while (table.size() < max_table_size * 2) {
 | |
|             table.push_back(slope_table.back() & 0xff);
 | |
|             table.push_back(slope_table.back() >> 8);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (dev->interface->is_mock()) {
 | |
|         dev->interface->record_slope_table(table_nr, slope_table);
 | |
|     }
 | |
| 
 | |
|     switch (dev->model->asic_type) {
 | |
|         case AsicType::GL646: {
 | |
|             unsigned dpihw = dev->reg.find_reg(0x05).value >> 6;
 | |
|             unsigned start_address = 0;
 | |
|             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");
 | |
|             }
 | |
|             dev->interface->write_buffer(0x3c, start_address + table_nr * 0x100, table.data(),
 | |
|                                          table.size());
 | |
|             break;
 | |
|         }
 | |
|         case AsicType::GL841:
 | |
|         case AsicType::GL842: {
 | |
|             unsigned start_address = 0;
 | |
|             switch (sensor.register_dpihw) {
 | |
|                 case 600: start_address = 0x08000; break;
 | |
|                 case 1200: start_address = 0x10000; break;
 | |
|                 case 2400: start_address = 0x20000; break;
 | |
|                 default: throw SaneException("Unexpected dpihw");
 | |
|             }
 | |
|             dev->interface->write_buffer(0x3c, start_address + table_nr * 0x200, table.data(),
 | |
|                                          table.size());
 | |
|             break;
 | |
|         }
 | |
|         case AsicType::GL843: {
 | |
|             // 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(),
 | |
|                                         table.size());
 | |
|             break;
 | |
|         }
 | |
|         case AsicType::GL845:
 | |
|         case AsicType::GL846:
 | |
|         case AsicType::GL847:
 | |
|         case AsicType::GL124: {
 | |
|             // slope table addresses are fixed
 | |
|             dev->interface->write_ahb(0x10000000 + 0x4000 * table_nr, table.size(),
 | |
|                                       table.data());
 | |
|             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 status = scanner_read_status(dev);
 | |
|             auto reg = dev.interface->read_register(gl841::REG_0x40);
 | |
| 
 | |
|             return (!(reg & gl841::REG_0x40_DATAENB) && !(reg & gl841::REG_0x40_MOTMFLG) &&
 | |
|                     !status.is_motor_enabled);
 | |
|         }
 | |
|         case AsicType::GL842: {
 | |
|             auto status = scanner_read_status(dev);
 | |
|             auto reg = dev.interface->read_register(gl842::REG_0x40);
 | |
| 
 | |
|             return (!(reg & gl842::REG_0x40_DATAENB) && !(reg & gl842::REG_0x40_MOTMFLG) &&
 | |
|                     !status.is_motor_enabled);
 | |
|         }
 | |
|         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_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->asic_type != AsicType::GL841 &&
 | |
|         dev.model->asic_type != AsicType::GL843)
 | |
|     {
 | |
|         regs_set_exposure(dev.model->asic_type, regs, sensor.exposure);
 | |
|     }
 | |
| 
 | |
|     dev.segment_order = sensor.segment_order;
 | |
| }
 | |
| 
 | |
| void scanner_stop_action(Genesys_Device& dev)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
| 
 | |
|     switch (dev.model->asic_type) {
 | |
|         case AsicType::GL841:
 | |
|         case AsicType::GL842:
 | |
|         case AsicType::GL843:
 | |
|         case AsicType::GL845:
 | |
|         case AsicType::GL846:
 | |
|         case AsicType::GL847:
 | |
|         case AsicType::GL124:
 | |
|             break;
 | |
|         default:
 | |
|             throw SaneException("Unsupported asic type");
 | |
|     }
 | |
| 
 | |
|     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::GL842:
 | |
|         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) &&
 | |
|                                (!has_flag(dev.model->flags, ModelFlag::UTA_NO_SECONDARY_MOTOR));
 | |
| 
 | |
|     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 = 50;
 | |
|     session.params.lines = 3;
 | |
|     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::GREEN;
 | |
|     session.params.contrast_adjustment = dev.settings.contrast;
 | |
|     session.params.brightness_adjustment = dev.settings.brightness;
 | |
| 
 | |
|     session.params.flags = ScanFlag::DISABLE_SHADING |
 | |
|                            ScanFlag::DISABLE_GAMMA |
 | |
|                            ScanFlag::FEEDING |
 | |
|                            ScanFlag::IGNORE_STAGGER_OFFSET |
 | |
|                            ScanFlag::IGNORE_COLOR_OFFSET;
 | |
| 
 | |
|     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,
 | |
|                           sanei_genesys_fixup_exposure({0, 0, 0}));
 | |
|     }
 | |
|     scanner_clear_scan_and_feed_counts(dev);
 | |
| 
 | |
|     dev.interface->write_registers(local_reg);
 | |
|     if (uses_secondary_head) {
 | |
|         dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY_AND_SECONDARY);
 | |
|     }
 | |
| 
 | |
|     try {
 | |
|         scanner_start_action(dev, true);
 | |
|     } catch (...) {
 | |
|         catch_all_exceptions(__func__, [&]() {
 | |
|             dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY);
 | |
|         });
 | |
|         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);
 | |
|         }
 | |
| 
 | |
|         scanner_stop_action(dev);
 | |
|         if (uses_secondary_head) {
 | |
|             dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY);
 | |
|         }
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     // wait until feed count reaches the required value
 | |
|     if (dev.model->model_id == ModelId::CANON_LIDE_700F) {
 | |
|         dev.cmd_set->update_home_sensor_gpio(dev);
 | |
|     }
 | |
| 
 | |
|     // 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);
 | |
|     }
 | |
| 
 | |
|     scanner_stop_action(dev);
 | |
|     if (uses_secondary_head) {
 | |
|         dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY);
 | |
|     }
 | |
| 
 | |
|     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_to_ta(Genesys_Device& dev)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
| 
 | |
|     unsigned feed = static_cast<unsigned>((dev.model->y_offset_sensor_to_ta * dev.motor.base_ydpi) /
 | |
|                                            MM_PER_INCH);
 | |
|     scanner_move(dev, dev.model->default_method, feed, Direction::FORWARD);
 | |
| }
 | |
| 
 | |
| 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::GL841:
 | |
|         case AsicType::GL842:
 | |
|         case AsicType::GL843:
 | |
|         case AsicType::GL845:
 | |
|         case AsicType::GL846:
 | |
|         case AsicType::GL847:
 | |
|         case AsicType::GL124:
 | |
|             break;
 | |
|         default:
 | |
|             throw SaneException("Unsupported asic type");
 | |
|     }
 | |
| 
 | |
|     if (dev.model->is_sheetfed) {
 | |
|         dbg.vlog(DBG_proc, "sheetfed scanner, skipping going back home");
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     // 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) &&
 | |
|             (!has_flag(dev.model->flags, ModelFlag::UTA_NO_SECONDARY_MOTOR)))
 | |
|     {
 | |
|         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);
 | |
|     }
 | |
| 
 | |
|     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;
 | |
|     }
 | |
| 
 | |
|     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 = 0;
 | |
|     session.params.starty = 40000;
 | |
|     session.params.pixels = 50;
 | |
|     session.params.lines = 3;
 | |
|     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.contrast_adjustment = dev.settings.contrast;
 | |
|     session.params.brightness_adjustment = dev.settings.brightness;
 | |
| 
 | |
|     session.params.flags =  ScanFlag::DISABLE_SHADING |
 | |
|                             ScanFlag::DISABLE_GAMMA |
 | |
|                             ScanFlag::IGNORE_STAGGER_OFFSET |
 | |
|                             ScanFlag::IGNORE_COLOR_OFFSET |
 | |
|                             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;
 | |
|     }
 | |
| 
 | |
|     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(ScanHeadId::PRIMARY | ScanHeadId::SECONDARY);
 | |
|         throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for scanhead to go home");
 | |
|     }
 | |
|     dbg.log(DBG_info, "scanhead is still moving");
 | |
| }
 | |
| 
 | |
| namespace {
 | |
|     bool should_use_secondary_motor_mode(Genesys_Device& dev)
 | |
|     {
 | |
|         bool should_use = !dev.is_head_pos_known(ScanHeadId::SECONDARY) ||
 | |
|                           !dev.is_head_pos_known(ScanHeadId::PRIMARY) ||
 | |
|                           dev.head_pos(ScanHeadId::SECONDARY) > dev.head_pos(ScanHeadId::PRIMARY);
 | |
|         bool supports = dev.model->model_id == ModelId::CANON_8600F;
 | |
|         return should_use && supports;
 | |
|     }
 | |
| 
 | |
|     void handle_motor_position_after_move_back_home_ta(Genesys_Device& dev, MotorMode motor_mode)
 | |
|     {
 | |
|         if (motor_mode == MotorMode::SECONDARY) {
 | |
|             dev.set_head_pos_zero(ScanHeadId::SECONDARY);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         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);
 | |
|         }
 | |
|     }
 | |
| } // namespace
 | |
| 
 | |
| void scanner_move_back_home_ta(Genesys_Device& dev)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
| 
 | |
|     switch (dev.model->asic_type) {
 | |
|         case AsicType::GL842:
 | |
|         case AsicType::GL843:
 | |
|         case AsicType::GL845:
 | |
|             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.is_head_pos_known(ScanHeadId::PRIMARY) &&
 | |
|         dev.head_pos(ScanHeadId::SECONDARY) > 1000 &&
 | |
|         dev.head_pos(ScanHeadId::SECONDARY) <= dev.head_pos(ScanHeadId::PRIMARY))
 | |
|     {
 | |
|         // 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 = 0;
 | |
|     session.params.starty = 40000;
 | |
|     session.params.pixels = 50;
 | |
|     session.params.lines = 3;
 | |
|     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::GREEN;
 | |
|     session.params.contrast_adjustment = dev.settings.contrast;
 | |
|     session.params.brightness_adjustment = dev.settings.brightness;
 | |
| 
 | |
|     session.params.flags =  ScanFlag::DISABLE_SHADING |
 | |
|                             ScanFlag::DISABLE_GAMMA |
 | |
|                             ScanFlag::IGNORE_STAGGER_OFFSET |
 | |
|                             ScanFlag::IGNORE_COLOR_OFFSET |
 | |
|                             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);
 | |
| 
 | |
|     auto motor_mode = should_use_secondary_motor_mode(dev) ? MotorMode::SECONDARY
 | |
|                                                            : MotorMode::PRIMARY_AND_SECONDARY;
 | |
| 
 | |
|     dev.cmd_set->set_motor_mode(dev, local_reg, motor_mode);
 | |
| 
 | |
|     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");
 | |
| 
 | |
|         handle_motor_position_after_move_back_home_ta(dev, motor_mode);
 | |
| 
 | |
|         scanner_stop_action(dev);
 | |
|         dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY);
 | |
|         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");
 | |
| 
 | |
|             handle_motor_position_after_move_back_home_ta(dev, motor_mode);
 | |
| 
 | |
|             scanner_stop_action(dev);
 | |
|             dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         dev.interface->sleep_ms(100);
 | |
|     }
 | |
| 
 | |
|     throw SaneException("Timeout waiting for XPA lamp to park");
 | |
| }
 | |
| 
 | |
| void scanner_search_strip(Genesys_Device& dev, bool forward, bool black)
 | |
| {
 | |
|     DBG_HELPER_ARGS(dbg, "%s %s", black ? "black" : "white", forward ? "forward" : "reverse");
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL841 && !black && forward) {
 | |
|         dev.frontend.set_gain(0, 0xff);
 | |
|         dev.frontend.set_gain(1, 0xff);
 | |
|         dev.frontend.set_gain(2, 0xff);
 | |
|     }
 | |
| 
 | |
|     // 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();
 | |
|     unsigned channels = 1;
 | |
| 
 | |
|     auto& sensor = sanei_genesys_find_sensor(&dev, dpi, channels, dev.settings.scan_method);
 | |
|     dev.cmd_set->set_fe(&dev, sensor, AFE_SET);
 | |
|     scanner_stop_action(dev);
 | |
| 
 | |
| 
 | |
|     // shading calibration is done with dev.motor.base_ydpi
 | |
|     unsigned lines = static_cast<unsigned>(dev.model->y_size_calib_mm * dpi / MM_PER_INCH);
 | |
|     if (dev.model->asic_type == AsicType::GL841) {
 | |
|         lines = 10; // TODO: use dev.model->search_lines
 | |
|         lines = static_cast<unsigned>((lines * dpi) / MM_PER_INCH);
 | |
|     }
 | |
| 
 | |
|     unsigned pixels = dev.model->x_size_calib_mm * dpi / MM_PER_INCH;
 | |
| 
 | |
|     dev.set_head_pos_zero(ScanHeadId::PRIMARY);
 | |
| 
 | |
|     unsigned length = 20;
 | |
|     if (dev.model->asic_type == AsicType::GL841) {
 | |
|         // 20 cm max length for calibration sheet
 | |
|         length = static_cast<unsigned>(((200 * dpi) / MM_PER_INCH) / lines);
 | |
|     }
 | |
| 
 | |
|     auto 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.contrast_adjustment = dev.settings.contrast;
 | |
|     session.params.brightness_adjustment = dev.settings.brightness;
 | |
|     session.params.flags = ScanFlag::DISABLE_SHADING |
 | |
|                            ScanFlag::DISABLE_GAMMA;
 | |
|     if (dev.model->asic_type != AsicType::GL841 && !forward) {
 | |
|         session.params.flags |= ScanFlag::REVERSE;
 | |
|     }
 | |
|     compute_session(&dev, session, sensor);
 | |
| 
 | |
|     dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session);
 | |
| 
 | |
|     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");
 | |
|         scanner_stop_action(dev);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     wait_until_buffer_non_empty(&dev);
 | |
| 
 | |
|     // now we're on target, we can read data
 | |
|     auto image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
 | |
| 
 | |
|     scanner_stop_action(dev);
 | |
| 
 | |
|     unsigned pass = 0;
 | |
|     if (dbg_log_image_data()) {
 | |
|         char title[80];
 | |
|         std::sprintf(title, "gl_search_strip_%s_%s%02d.tiff",
 | |
|                      black ? "black" : "white", forward ? "fwd" : "bwd", pass);
 | |
|         write_tiff_file(title, image);
 | |
|     }
 | |
| 
 | |
|     // loop until strip is found or maximum pass number done
 | |
|     bool found = false;
 | |
|     while (pass < length && !found) {
 | |
|         dev.interface->write_registers(local_reg);
 | |
| 
 | |
|         // now start scan
 | |
|         dev.cmd_set->begin_scan(&dev, sensor, &local_reg, true);
 | |
| 
 | |
|         wait_until_buffer_non_empty(&dev);
 | |
| 
 | |
|         // now we're on target, we can read data
 | |
|         image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
 | |
| 
 | |
|         scanner_stop_action(dev);
 | |
| 
 | |
|         if (dbg_log_image_data()) {
 | |
|             char title[80];
 | |
|             std::sprintf(title, "gl_search_strip_%s_%s%02d.tiff",
 | |
|                          black ? "black" : "white",
 | |
|                          forward ? "fwd" : "bwd", static_cast<int>(pass));
 | |
|             write_tiff_file(title, image);
 | |
|         }
 | |
| 
 | |
|         unsigned white_level = 90;
 | |
|         unsigned black_level = 60;
 | |
| 
 | |
|         std::size_t count = 0;
 | |
|         // 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 ame color
 | |
|         if (forward) {
 | |
| 
 | |
|             for (std::size_t y = 0; y < image.get_height() && !found; y++) {
 | |
|                 count = 0;
 | |
| 
 | |
|                 // count of white/black pixels depending on the color searched
 | |
|                 for (std::size_t x = 0; x < image.get_width(); x++) {
 | |
| 
 | |
|                     // when searching for black, detect white pixels
 | |
|                     if (black && image.get_raw_channel(x, y, 0) > white_level) {
 | |
|                         count++;
 | |
|                     }
 | |
| 
 | |
|                     // when searching for white, detect black pixels
 | |
|                     if (!black && image.get_raw_channel(x, y, 0) < 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
 | |
| 
 | |
|                 auto found_percentage = (count * 100 / image.get_width());
 | |
|                 if (found_percentage < 3) {
 | |
|                     found = 1;
 | |
|                     DBG(DBG_data, "%s: strip found forward during pass %d at line %zu\n", __func__,
 | |
|                         pass, y);
 | |
|                 } else {
 | |
|                     DBG(DBG_data, "%s: pixels=%zu, count=%zu (%zu%%)\n", __func__,
 | |
|                         image.get_width(), count, found_percentage);
 | |
|                 }
 | |
|             }
 | |
|         } else {
 | |
|             /*  since calibration scans are done forward, we need the whole area
 | |
|                 to be of the required color when searching backward
 | |
|             */
 | |
|             count = 0;
 | |
|             for (std::size_t y = 0; y < image.get_height(); y++) {
 | |
|                 // count of white/black pixels depending on the color searched
 | |
|                 for (std::size_t x = 0; x < image.get_width(); x++) {
 | |
|                     // when searching for black, detect white pixels
 | |
|                     if (black && image.get_raw_channel(x, y, 0) > white_level) {
 | |
|                         count++;
 | |
|                     }
 | |
|                     // when searching for white, detect black pixels
 | |
|                     if (!black && image.get_raw_channel(x, y, 0) < 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
 | |
|             auto found_percentage = count * 100 / (image.get_width() * image.get_height());
 | |
|             if (found_percentage < 3) {
 | |
|                 found = 1;
 | |
|                 DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass);
 | |
|             } else {
 | |
|                 DBG(DBG_data, "%s: pixels=%zu, count=%zu (%zu%%)\n", __func__, image.get_width(),
 | |
|                     count, found_percentage);
 | |
|             }
 | |
|         }
 | |
|         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");
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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];
 | |
| }
 | |
| 
 | |
| bool should_calibrate_only_active_area(const Genesys_Device& dev,
 | |
|                                        const Genesys_Settings& settings)
 | |
| {
 | |
|     if (settings.scan_method == ScanMethod::TRANSPARENCY ||
 | |
|         settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
 | |
|     {
 | |
|         if (dev.model->model_id == ModelId::CANON_4400F && settings.xres >= 4800) {
 | |
|             return true;
 | |
|         }
 | |
|         if (dev.model->model_id == ModelId::CANON_8600F && settings.xres == 4800) {
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| void scanner_offset_calibration(Genesys_Device& dev, const Genesys_Sensor& sensor,
 | |
|                                 Genesys_Register_Set& regs)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL842 &&
 | |
|         dev.frontend.layout.type != FrontendType::WOLFSON)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL843 &&
 | |
|         dev.frontend.layout.type != FrontendType::WOLFSON)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL845 ||
 | |
|         dev.model->asic_type == AsicType::GL846)
 | |
|     {
 | |
|         // no gain nor offset for AKM AFE
 | |
|         std::uint8_t reg04 = dev.interface->read_register(gl846::REG_0x04);
 | |
|         if ((reg04 & gl846::REG_0x04_FESET) == 0x02) {
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
|     if (dev.model->asic_type == AsicType::GL847) {
 | |
|         // no gain nor offset for AKM AFE
 | |
|         std::uint8_t reg04 = dev.interface->read_register(gl847::REG_0x04);
 | |
|         if ((reg04 & gl847::REG_0x04_FESET) == 0x02) {
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL124) {
 | |
|         std::uint8_t reg0a = dev.interface->read_register(gl124::REG_0x0A);
 | |
|         if (((reg0a & gl124::REG_0x0A_SIFSEL) >> gl124::REG_0x0AS_SIFSEL) == 3) {
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     unsigned target_pixels = dev.model->x_size_calib_mm * sensor.full_resolution / MM_PER_INCH;
 | |
|     unsigned start_pixel = 0;
 | |
|     unsigned black_pixels = (sensor.black_pixels * sensor.full_resolution) / sensor.full_resolution;
 | |
| 
 | |
|     unsigned channels = 3;
 | |
|     unsigned lines = 1;
 | |
|     unsigned resolution = sensor.full_resolution;
 | |
| 
 | |
|     const Genesys_Sensor* calib_sensor = &sensor;
 | |
|     if (dev.model->asic_type == AsicType::GL843) {
 | |
|         lines = 8;
 | |
| 
 | |
|         // compute divider factor to compute final pixels number
 | |
|         const auto& dpihw_sensor = sanei_genesys_find_sensor(&dev, dev.settings.xres, channels,
 | |
|                                                              dev.settings.scan_method);
 | |
|         resolution = dpihw_sensor.shading_resolution;
 | |
|         unsigned factor = sensor.full_resolution / resolution;
 | |
| 
 | |
|         calib_sensor = &sanei_genesys_find_sensor(&dev, resolution, channels,
 | |
|                                                   dev.settings.scan_method);
 | |
| 
 | |
|         target_pixels = dev.model->x_size_calib_mm * resolution / MM_PER_INCH;
 | |
|         black_pixels = calib_sensor->black_pixels / factor;
 | |
| 
 | |
|         if (should_calibrate_only_active_area(dev, dev.settings)) {
 | |
|             float offset = dev.model->x_offset_ta;
 | |
|             start_pixel = static_cast<int>((offset * calib_sensor->get_optical_resolution()) / MM_PER_INCH);
 | |
| 
 | |
|             float size = dev.model->x_size_ta;
 | |
|             target_pixels = static_cast<int>((size * calib_sensor->get_optical_resolution()) / MM_PER_INCH);
 | |
|         }
 | |
| 
 | |
|         if (dev.model->model_id == ModelId::CANON_4400F &&
 | |
|             dev.settings.scan_method == ScanMethod::FLATBED)
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (dev.model->model_id == ModelId::CANON_5600F) {
 | |
|         // FIXME: use same approach as for GL843 scanners
 | |
|         lines = 8;
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL847) {
 | |
|         calib_sensor = &sanei_genesys_find_sensor(&dev, resolution, channels,
 | |
|                                                   dev.settings.scan_method);
 | |
|     }
 | |
| 
 | |
|     ScanFlag flags = ScanFlag::DISABLE_SHADING |
 | |
|                      ScanFlag::DISABLE_GAMMA |
 | |
|                      ScanFlag::SINGLE_LINE |
 | |
|                      ScanFlag::IGNORE_STAGGER_OFFSET |
 | |
|                      ScanFlag::IGNORE_COLOR_OFFSET;
 | |
| 
 | |
|     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 = dev.model->asic_type == AsicType::GL843 ? ColorFilter::RED
 | |
|                                                                           : dev.settings.color_filter;
 | |
|     session.params.contrast_adjustment = dev.settings.contrast;
 | |
|     session.params.brightness_adjustment = dev.settings.brightness;
 | |
|     session.params.flags = flags;
 | |
|     compute_session(&dev, session, *calib_sensor);
 | |
| 
 | |
|     dev.cmd_set->init_regs_for_scan_session(&dev, *calib_sensor, ®s, session);
 | |
| 
 | |
|     unsigned output_pixels = session.output_pixels;
 | |
| 
 | |
|     sanei_genesys_set_motor_power(regs, false);
 | |
| 
 | |
|     int top[3], bottom[3];
 | |
|     int topavg[3], bottomavg[3], avg[3];
 | |
| 
 | |
|     // 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, ®s, true);
 | |
| 
 | |
|     if (is_testing_mode()) {
 | |
|         dev.interface->test_checkpoint("offset_calibration");
 | |
|         if (dev.model->asic_type == AsicType::GL842 ||
 | |
|             dev.model->asic_type == AsicType::GL843)
 | |
|         {
 | |
|             scanner_stop_action_no_move(dev, regs);
 | |
|         }
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     Image first_line;
 | |
|     if (dev.model->asic_type == AsicType::GL842 ||
 | |
|         dev.model->asic_type == AsicType::GL843)
 | |
|     {
 | |
|         first_line = read_unshuffled_image_from_scanner(&dev, session,
 | |
|                                                         session.output_total_bytes_raw);
 | |
|         scanner_stop_action_no_move(dev, regs);
 | |
|     } else {
 | |
|         first_line = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
 | |
| 
 | |
|         if (dev.model->model_id == ModelId::CANON_5600F) {
 | |
|             scanner_stop_action_no_move(dev, regs);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (dbg_log_image_data()) {
 | |
|         char fn[40];
 | |
|         std::snprintf(fn, 40, "gl843_bottom_offset_%03d_%03d_%03d.tiff",
 | |
|                       bottom[0], bottom[1], bottom[2]);
 | |
|         write_tiff_file(fn, first_line);
 | |
|     }
 | |
| 
 | |
|     for (unsigned ch = 0; ch < 3; ch++) {
 | |
|         bottomavg[ch] = dark_average_channel(first_line, black_pixels, ch);
 | |
|         DBG(DBG_info, "%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, ®s, true);
 | |
| 
 | |
|     Image second_line;
 | |
|     if (dev.model->asic_type == AsicType::GL842 ||
 | |
|         dev.model->asic_type == AsicType::GL843)
 | |
|     {
 | |
|         second_line = read_unshuffled_image_from_scanner(&dev, session,
 | |
|                                                          session.output_total_bytes_raw);
 | |
|         scanner_stop_action_no_move(dev, regs);
 | |
|     } else {
 | |
|         second_line = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
 | |
| 
 | |
|         if (dev.model->model_id == ModelId::CANON_5600F) {
 | |
|             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_info, "%s: top avg %d=%d\n", __func__, ch, topavg[ch]);
 | |
|     }
 | |
| 
 | |
|     unsigned pass = 0;
 | |
| 
 | |
|     std::vector<std::uint8_t> debug_image;
 | |
|     std::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++;
 | |
| 
 | |
|         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, ®s, true);
 | |
| 
 | |
|         if (dev.model->asic_type == AsicType::GL842 ||
 | |
|             dev.model->asic_type == AsicType::GL843)
 | |
|         {
 | |
|             second_line = read_unshuffled_image_from_scanner(&dev, session,
 | |
|                                                              session.output_total_bytes_raw);
 | |
|             scanner_stop_action_no_move(dev, regs);
 | |
|         } else {
 | |
|             second_line = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
 | |
| 
 | |
|             if (dev.model->model_id == ModelId::CANON_5600F) {
 | |
|                 scanner_stop_action_no_move(dev, regs);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (dbg_log_image_data()) {
 | |
|             char title[100];
 | |
|             std::snprintf(title, 100, "lines: %d pixels_per_line: %d offsets[0..2]: %d %d %d\n",
 | |
|                           lines, output_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_log_image_data()) {
 | |
|         sanei_genesys_write_file("gl_offset_all_desc.txt",
 | |
|                                  reinterpret_cast<const std::uint8_t*>(debug_image_info.data()),
 | |
|                                  debug_image_info.size());
 | |
|         write_tiff_file("gl_offset_all.tiff", debug_image.data(), session.params.depth, channels,
 | |
|                         output_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));
 | |
| }
 | |
| 
 | |
| /*  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 scanner_coarse_gain_calibration(Genesys_Device& dev, const Genesys_Sensor& sensor,
 | |
|                                      Genesys_Register_Set& regs, unsigned dpi)
 | |
| {
 | |
|     DBG_HELPER_ARGS(dbg, "dpi = %d", dpi);
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL842 &&
 | |
|         dev.frontend.layout.type != FrontendType::WOLFSON)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL843 &&
 | |
|         dev.frontend.layout.type != FrontendType::WOLFSON)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL845 ||
 | |
|         dev.model->asic_type == AsicType::GL846)
 | |
|     {
 | |
|         // no gain nor offset for AKM AFE
 | |
|         std::uint8_t reg04 = dev.interface->read_register(gl846::REG_0x04);
 | |
|         if ((reg04 & gl846::REG_0x04_FESET) == 0x02) {
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL847) {
 | |
|         // no gain nor offset for AKM AFE
 | |
|         std::uint8_t reg04 = dev.interface->read_register(gl847::REG_0x04);
 | |
|         if ((reg04 & gl847::REG_0x04_FESET) == 0x02) {
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL124) {
 | |
|         // no gain nor offset for TI AFE
 | |
|         std::uint8_t reg0a = dev.interface->read_register(gl124::REG_0x0A);
 | |
|         if (((reg0a & gl124::REG_0x0A_SIFSEL) >> gl124::REG_0x0AS_SIFSEL) == 3) {
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL841) {
 | |
|         // feed to white strip if needed
 | |
|         if (dev.model->y_offset_calib_white > 0) {
 | |
|             unsigned move = static_cast<unsigned>(
 | |
|                     (dev.model->y_offset_calib_white * (dev.motor.base_ydpi)) / MM_PER_INCH);
 | |
|             scanner_move(dev, dev.model->default_method, move, Direction::FORWARD);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // coarse gain calibration is always done in color mode
 | |
|     unsigned channels = 3;
 | |
| 
 | |
|     unsigned resolution = sensor.full_resolution;
 | |
|     if (dev.model->asic_type == AsicType::GL841) {
 | |
|         const auto& dpihw_sensor = sanei_genesys_find_sensor(&dev, dev.settings.xres, channels,
 | |
|                                                              dev.settings.scan_method);
 | |
|         resolution = dpihw_sensor.shading_resolution;
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL842 ||
 | |
|         dev.model->asic_type == AsicType::GL843)
 | |
|     {
 | |
|         const auto& dpihw_sensor = sanei_genesys_find_sensor(&dev, dpi, channels,
 | |
|                                                              dev.settings.scan_method);
 | |
|         resolution = dpihw_sensor.shading_resolution;
 | |
|     }
 | |
| 
 | |
|     float coeff = 1;
 | |
| 
 | |
|     // Follow CKSEL
 | |
|     if (dev.model->sensor_id == SensorId::CCD_KVSS080 ||
 | |
|         dev.model->asic_type == AsicType::GL845 ||
 | |
|         dev.model->asic_type == AsicType::GL846 ||
 | |
|         dev.model->asic_type == AsicType::GL847 ||
 | |
|         dev.model->asic_type == AsicType::GL124)
 | |
|     {
 | |
|         if (dev.settings.xres < sensor.full_resolution) {
 | |
|             coeff = 0.9f;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     unsigned lines = 10;
 | |
|     if (dev.model->asic_type == AsicType::GL841) {
 | |
|         lines = 1;
 | |
|     }
 | |
| 
 | |
|     const Genesys_Sensor* calib_sensor = &sensor;
 | |
|     if (dev.model->asic_type == AsicType::GL841 ||
 | |
|         dev.model->asic_type == AsicType::GL842 ||
 | |
|         dev.model->asic_type == AsicType::GL843 ||
 | |
|         dev.model->asic_type == AsicType::GL847)
 | |
|     {
 | |
|         calib_sensor = &sanei_genesys_find_sensor(&dev, resolution, channels,
 | |
|                                                   dev.settings.scan_method);
 | |
|     }
 | |
| 
 | |
|     ScanFlag flags = ScanFlag::DISABLE_SHADING |
 | |
|                      ScanFlag::DISABLE_GAMMA |
 | |
|                      ScanFlag::SINGLE_LINE |
 | |
|                      ScanFlag::IGNORE_STAGGER_OFFSET |
 | |
|                      ScanFlag::IGNORE_COLOR_OFFSET;
 | |
| 
 | |
|     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 = dev.model->asic_type == AsicType::GL841 ? dev.settings.yres : resolution;
 | |
|     session.params.startx = 0;
 | |
|     session.params.starty = 0;
 | |
|     session.params.pixels = dev.model->x_size_calib_mm * resolution / MM_PER_INCH;
 | |
|     session.params.lines = lines;
 | |
|     session.params.depth = dev.model->asic_type == AsicType::GL841 ? 16 : 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.contrast_adjustment = dev.settings.contrast;
 | |
|     session.params.brightness_adjustment = dev.settings.brightness;
 | |
|     session.params.flags = flags;
 | |
|     compute_session(&dev, session, *calib_sensor);
 | |
| 
 | |
|     std::size_t pixels = session.output_pixels;
 | |
| 
 | |
|     try {
 | |
|         dev.cmd_set->init_regs_for_scan_session(&dev, *calib_sensor, ®s, session);
 | |
|     } catch (...) {
 | |
|         if (dev.model->asic_type != AsicType::GL841) {
 | |
|             catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); });
 | |
|         }
 | |
|         throw;
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type != AsicType::GL841) {
 | |
|         sanei_genesys_set_motor_power(regs, false);
 | |
|     }
 | |
| 
 | |
|     dev.interface->write_registers(regs);
 | |
| 
 | |
|     if (dev.model->asic_type != AsicType::GL841) {
 | |
|         dev.cmd_set->set_fe(&dev, *calib_sensor, AFE_SET);
 | |
|     }
 | |
|     dev.cmd_set->begin_scan(&dev, *calib_sensor, ®s, true);
 | |
| 
 | |
|     if (is_testing_mode()) {
 | |
|         dev.interface->test_checkpoint("coarse_gain_calibration");
 | |
|         scanner_stop_action(dev);
 | |
|         dev.cmd_set->move_back_home(&dev, true);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     Image image;
 | |
|     if (dev.model->asic_type == AsicType::GL842 ||
 | |
|         dev.model->asic_type == AsicType::GL843)
 | |
|     {
 | |
|         image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes_raw);
 | |
|     } else if (dev.model->asic_type == AsicType::GL124) {
 | |
|         // BUG: we probably want to read whole image, not just first line
 | |
|         image = read_unshuffled_image_from_scanner(&dev, session, session.output_line_bytes);
 | |
|     } else {
 | |
|         image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL842 ||
 | |
|         dev.model->asic_type == AsicType::GL843)
 | |
|     {
 | |
|         scanner_stop_action_no_move(dev, regs);
 | |
|     }
 | |
| 
 | |
|     if (dbg_log_image_data()) {
 | |
|         write_tiff_file("gl_coarse_gain.tiff", image);
 | |
|     }
 | |
| 
 | |
|     for (unsigned ch = 0; ch < channels; ch++) {
 | |
|         float curr_output = 0;
 | |
|         float target_value = 0;
 | |
| 
 | |
|         if (dev.model->asic_type == AsicType::GL842 ||
 | |
|             dev.model->asic_type == AsicType::GL843)
 | |
|         {
 | |
|             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(image.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());
 | |
|             curr_output = static_cast<float>(values[unsigned((values.size() - 1) * 0.95)]);
 | |
|             target_value = calib_sensor->gain_white_ref * coeff;
 | |
| 
 | |
|         } else if (dev.model->asic_type == AsicType::GL841) {
 | |
|             // FIXME: use the GL843 approach
 | |
|             unsigned max = 0;
 | |
|             for (std::size_t x = 0; x < image.get_width(); x++) {
 | |
|                 auto value = image.get_raw_channel(x, 0, ch);
 | |
|                 if (value > max) {
 | |
|                     max = value;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             curr_output = max;
 | |
|             target_value = 65535.0f;
 | |
|         } else {
 | |
|             // FIXME: use the GL843 approach
 | |
|             auto width = image.get_width();
 | |
| 
 | |
|             std::uint64_t total = 0;
 | |
|             for (std::size_t x = width / 4; x < (width * 3 / 4); x++) {
 | |
|                 total += image.get_raw_channel(x, 0, ch);
 | |
|             }
 | |
| 
 | |
|             curr_output = total / (width / 2);
 | |
|             target_value = calib_sensor->gain_white_ref * coeff;
 | |
|         }
 | |
| 
 | |
|         std::uint8_t out_gain = compute_frontend_gain(curr_output, target_value,
 | |
|                                                       dev.frontend.layout.type);
 | |
|         dev.frontend.set_gain(ch, out_gain);
 | |
| 
 | |
|         DBG(DBG_proc, "%s: channel %d, curr=%f, target=%f, out_gain:%d\n", __func__, ch,
 | |
|             curr_output, target_value, out_gain);
 | |
| 
 | |
|         if (dev.model->asic_type == AsicType::GL841 &&
 | |
|             target_value / curr_output > 30)
 | |
|         {
 | |
|             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");
 | |
|             throw SaneException(SANE_STATUS_JAMMED, "scanning head is locked");
 | |
|         }
 | |
| 
 | |
|         dbg.vlog(DBG_info, "gain=(%d, %d, %d)", dev.frontend.get_gain(0), dev.frontend.get_gain(1),
 | |
|                  dev.frontend.get_gain(2));
 | |
|     }
 | |
| 
 | |
|     if (dev.model->is_cis) {
 | |
|         std::uint8_t min_gain = std::min({dev.frontend.get_gain(0),
 | |
|                                           dev.frontend.get_gain(1),
 | |
|                                           dev.frontend.get_gain(2)});
 | |
| 
 | |
|         dev.frontend.set_gain(0, min_gain);
 | |
|         dev.frontend.set_gain(1, min_gain);
 | |
|         dev.frontend.set_gain(2, min_gain);
 | |
|     }
 | |
| 
 | |
|     dbg.vlog(DBG_info, "final gain=(%d, %d, %d)", dev.frontend.get_gain(0),
 | |
|              dev.frontend.get_gain(1), dev.frontend.get_gain(2));
 | |
| 
 | |
|     scanner_stop_action(dev);
 | |
| 
 | |
|     dev.cmd_set->move_back_home(&dev, true);
 | |
| }
 | |
| 
 | |
| namespace gl124 {
 | |
|     void move_to_calibration_area(Genesys_Device* dev, const Genesys_Sensor& sensor,
 | |
|                                   Genesys_Register_Set& regs);
 | |
| } // namespace gl124
 | |
| 
 | |
| SensorExposure scanner_led_calibration(Genesys_Device& dev, const Genesys_Sensor& sensor,
 | |
|                                        Genesys_Register_Set& regs)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
| 
 | |
|     float move = 0;
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL841) {
 | |
|         if (dev.model->y_offset_calib_white > 0) {
 | |
|             move = (dev.model->y_offset_calib_white * (dev.motor.base_ydpi)) / MM_PER_INCH;
 | |
|             scanner_move(dev, dev.model->default_method, static_cast<unsigned>(move),
 | |
|                          Direction::FORWARD);
 | |
|         }
 | |
|     } else if (dev.model->asic_type == AsicType::GL842 ||
 | |
|                dev.model->asic_type == AsicType::GL843)
 | |
|     {
 | |
|         // do nothing
 | |
|     } else if (dev.model->asic_type == AsicType::GL845 ||
 | |
|                dev.model->asic_type == AsicType::GL846 ||
 | |
|                dev.model->asic_type == AsicType::GL847)
 | |
|     {
 | |
|         move = 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);
 | |
|         }
 | |
|     } else if (dev.model->asic_type == AsicType::GL124) {
 | |
|         gl124::move_to_calibration_area(&dev, sensor, regs);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     unsigned channels = 3;
 | |
|     unsigned resolution = sensor.shading_resolution;
 | |
|     const auto& calib_sensor = sanei_genesys_find_sensor(&dev, resolution, channels,
 | |
|                                                          dev.settings.scan_method);
 | |
| 
 | |
|     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)
 | |
|     {
 | |
|         regs = dev.reg; // FIXME: apply this to all ASICs
 | |
|     }
 | |
| 
 | |
|     unsigned yres = resolution;
 | |
|     if (dev.model->asic_type == AsicType::GL841) {
 | |
|         yres = dev.settings.yres; // FIXME: remove this
 | |
|     }
 | |
| 
 | |
|     ScanSession session;
 | |
|     session.params.xres = resolution;
 | |
|     session.params.yres = yres;
 | |
|     session.params.startx = 0;
 | |
|     session.params.starty = 0;
 | |
|     session.params.pixels = dev.model->x_size_calib_mm * resolution / MM_PER_INCH;
 | |
|     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.contrast_adjustment = dev.settings.contrast;
 | |
|     session.params.brightness_adjustment = dev.settings.brightness;
 | |
|     session.params.flags = ScanFlag::DISABLE_SHADING |
 | |
|                            ScanFlag::DISABLE_GAMMA |
 | |
|                            ScanFlag::SINGLE_LINE |
 | |
|                            ScanFlag::IGNORE_STAGGER_OFFSET |
 | |
|                            ScanFlag::IGNORE_COLOR_OFFSET;
 | |
|     compute_session(&dev, session, calib_sensor);
 | |
| 
 | |
|     dev.cmd_set->init_regs_for_scan_session(&dev, calib_sensor, ®s, session);
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL841) {
 | |
|         dev.interface->write_registers(regs); // FIXME: remove this
 | |
|     }
 | |
| 
 | |
|     std::uint16_t exp[3];
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL841) {
 | |
|         exp[0] = sensor.exposure.red;
 | |
|         exp[1] = sensor.exposure.green;
 | |
|         exp[2] = sensor.exposure.blue;
 | |
|     } else {
 | |
|         exp[0] = calib_sensor.exposure.red;
 | |
|         exp[1] = calib_sensor.exposure.green;
 | |
|         exp[2] = calib_sensor.exposure.blue;
 | |
|     }
 | |
| 
 | |
|     std::uint16_t target = sensor.gain_white_ref * 256;
 | |
| 
 | |
|     std::uint16_t min_exposure = 500; // only gl841
 | |
|     std::uint16_t max_exposure = ((exp[0] + exp[1] + exp[2]) / 3) * 2; // only gl841
 | |
| 
 | |
|     std::uint16_t top[3] = {};
 | |
|     std::uint16_t bottom[3] = {};
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL845 ||
 | |
|         dev.model->asic_type == AsicType::GL846)
 | |
|     {
 | |
|         bottom[0] = 29000;
 | |
|         bottom[1] = 29000;
 | |
|         bottom[2] = 29000;
 | |
| 
 | |
|         top[0] = 41000;
 | |
|         top[1] = 51000;
 | |
|         top[2] = 51000;
 | |
|     } else if (dev.model->asic_type == AsicType::GL847) {
 | |
|         bottom[0] = 28000;
 | |
|         bottom[1] = 28000;
 | |
|         bottom[2] = 28000;
 | |
| 
 | |
|         top[0] = 32000;
 | |
|         top[1] = 32000;
 | |
|         top[2] = 32000;
 | |
|     }
 | |
| 
 | |
|     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)
 | |
|     {
 | |
|         sanei_genesys_set_motor_power(regs, false);
 | |
|     }
 | |
| 
 | |
|     bool acceptable = false;
 | |
|     for (unsigned i_test = 0; i_test < 100 && !acceptable; ++i_test) {
 | |
|         regs_set_exposure(dev.model->asic_type, regs, { exp[0], exp[1], exp[2] });
 | |
| 
 | |
|         if (dev.model->asic_type == AsicType::GL841) {
 | |
|             // FIXME: remove
 | |
|             dev.interface->write_register(0x10, (exp[0] >> 8) & 0xff);
 | |
|             dev.interface->write_register(0x11, exp[0] & 0xff);
 | |
|             dev.interface->write_register(0x12, (exp[1] >> 8) & 0xff);
 | |
|             dev.interface->write_register(0x13, exp[1] & 0xff);
 | |
|             dev.interface->write_register(0x14, (exp[2] >> 8) & 0xff);
 | |
|             dev.interface->write_register(0x15, exp[2] & 0xff);
 | |
|         }
 | |
| 
 | |
|         dev.interface->write_registers(regs);
 | |
| 
 | |
|         dbg.log(DBG_info, "starting line reading");
 | |
|         dev.cmd_set->begin_scan(&dev, calib_sensor, ®s, true);
 | |
| 
 | |
|         if (is_testing_mode()) {
 | |
|             dev.interface->test_checkpoint("led_calibration");
 | |
|             if (dev.model->asic_type == AsicType::GL841) {
 | |
|                 scanner_stop_action(dev);
 | |
|                 dev.cmd_set->move_back_home(&dev, true);
 | |
|                 return { exp[0], exp[1], exp[2] };
 | |
|             } else if (dev.model->asic_type == AsicType::GL124) {
 | |
|                 scanner_stop_action(dev);
 | |
|                 return calib_sensor.exposure;
 | |
|             } else {
 | |
|                 scanner_stop_action(dev);
 | |
|                 dev.cmd_set->move_back_home(&dev, true);
 | |
|                 return calib_sensor.exposure;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         auto image = read_unshuffled_image_from_scanner(&dev, session, session.output_line_bytes);
 | |
| 
 | |
|         scanner_stop_action(dev);
 | |
| 
 | |
|         if (dbg_log_image_data()) {
 | |
|             char fn[30];
 | |
|             std::snprintf(fn, 30, "gl_led_%02d.tiff", i_test);
 | |
|             write_tiff_file(fn, image);
 | |
|         }
 | |
| 
 | |
|         int avg[3];
 | |
|         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.vlog(DBG_info, "average: %d, %d, %d", avg[0], avg[1], avg[2]);
 | |
| 
 | |
|         acceptable = true;
 | |
| 
 | |
|         if (dev.model->asic_type == AsicType::GL841) {
 | |
|             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 (unsigned i = 0; i < 3; i++) {
 | |
|                     // we accept +- 2% delta from target
 | |
|                     if (std::abs(avg[i] - target) > target / 50) {
 | |
|                         exp[i] = (exp[i] * target) / avg[i];
 | |
|                         acceptable = false;
 | |
|                     }
 | |
|                 }
 | |
|             } else {
 | |
|                 if (!acceptable) {
 | |
|                     unsigned 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
 | |
|                     */
 | |
|                     unsigned 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;
 | |
|                     }
 | |
| 
 | |
|                 }
 | |
|             }
 | |
|         } else if (dev.model->asic_type == AsicType::GL845 ||
 | |
|                    dev.model->asic_type == AsicType::GL846)
 | |
|         {
 | |
|             for (unsigned i = 0; i < 3; i++) {
 | |
|                 if (avg[i] < bottom[i]) {
 | |
|                     if (avg[i] != 0) {
 | |
|                         exp[i] = (exp[i] * bottom[i]) / avg[i];
 | |
|                     } else {
 | |
|                         exp[i] *= 10;
 | |
|                     }
 | |
|                     acceptable = false;
 | |
|                 }
 | |
|                 if (avg[i] > top[i]) {
 | |
|                     if (avg[i] != 0) {
 | |
|                         exp[i] = (exp[i] * top[i]) / avg[i];
 | |
|                     } else {
 | |
|                         exp[i] *= 10;
 | |
|                     }
 | |
|                     acceptable = false;
 | |
|                 }
 | |
|             }
 | |
|         } else if (dev.model->asic_type == AsicType::GL847) {
 | |
|             for (unsigned i = 0; i < 3; i++) {
 | |
|                 if (avg[i] < bottom[i] || avg[i] > top[i]) {
 | |
|                     auto target = (bottom[i] + top[i]) / 2;
 | |
|                     if (avg[i] != 0) {
 | |
|                         exp[i] = (exp[i] * target) / avg[i];
 | |
|                     } else {
 | |
|                         exp[i] *= 10;
 | |
|                     }
 | |
| 
 | |
|                     acceptable = false;
 | |
|                 }
 | |
|             }
 | |
|         } else if (dev.model->asic_type == AsicType::GL124) {
 | |
|             for (unsigned i = 0; i < 3; i++) {
 | |
|                 // we accept +- 2% delta from target
 | |
|                 if (std::abs(avg[i] - target) > target / 50) {
 | |
|                     float prev_weight = 0.5;
 | |
|                     if (avg[i] != 0) {
 | |
|                         exp[i] = exp[i] * prev_weight + ((exp[i] * target) / avg[i]) * (1 - prev_weight);
 | |
|                     } else {
 | |
|                         exp[i] = exp[i] * prev_weight + (exp[i] * 10) * (1 - prev_weight);
 | |
|                     }
 | |
|                     acceptable = false;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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)
 | |
|     {
 | |
|         // set these values as final ones for scan
 | |
|         regs_set_exposure(dev.model->asic_type, dev.reg, { exp[0], exp[1], exp[2] });
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL841 ||
 | |
|         dev.model->asic_type == AsicType::GL842 ||
 | |
|         dev.model->asic_type == AsicType::GL843)
 | |
|     {
 | |
|         dev.cmd_set->move_back_home(&dev, true);
 | |
|     }
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL845 ||
 | |
|         dev.model->asic_type == AsicType::GL846 ||
 | |
|         dev.model->asic_type == AsicType::GL847)
 | |
|     {
 | |
|         if (move > 20) {
 | |
|             dev.cmd_set->move_back_home(&dev, true);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     dbg.vlog(DBG_info,"acceptable exposure: %d, %d, %d\n", exp[0], exp[1], exp[2]);
 | |
| 
 | |
|     return { exp[0], exp[1], exp[2] };
 | |
| }
 | |
| 
 | |
| 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)
 | |
| {
 | |
|     // 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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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,
 | |
|                                              Genesys_Register_Set& local_reg,
 | |
|                                              std::vector<std::uint16_t>& out_average_data,
 | |
|                                              bool is_dark, const std::string& log_filename_prefix)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
| 
 | |
|     if (dev->model->asic_type == AsicType::GL646) {
 | |
|         dev->cmd_set->init_regs_for_shading(dev, sensor, local_reg);
 | |
|         local_reg = dev->reg;
 | |
|     } else {
 | |
|         local_reg = dev->reg;
 | |
|         dev->cmd_set->init_regs_for_shading(dev, sensor, local_reg);
 | |
|         dev->interface->write_registers(local_reg);
 | |
|     }
 | |
| 
 | |
|     debug_dump(DBG_info, dev->calib_session);
 | |
| 
 | |
|   size_t size;
 | |
|   uint32_t pixels_per_line;
 | |
| 
 | |
|     if (dev->model->asic_type == AsicType::GL842 ||
 | |
|         dev->model->asic_type == AsicType::GL843 ||
 | |
|         dev->model->model_id == ModelId::CANON_5600F)
 | |
|     {
 | |
|         pixels_per_line = dev->calib_session.output_pixels;
 | |
|     } else {
 | |
|         // BUG: this selects incorrect pixel number
 | |
|         pixels_per_line = dev->calib_session.params.pixels;
 | |
|     }
 | |
|     unsigned channels = dev->calib_session.params.channels;
 | |
| 
 | |
|     // BUG: we are using wrong pixel number here
 | |
|     unsigned start_offset =
 | |
|             dev->calib_session.params.startx * sensor.full_resolution / dev->calib_session.params.xres;
 | |
|     unsigned out_pixels_per_line = pixels_per_line + start_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. Note the extra line when computing size.
 | |
|     if (dev->model->asic_type == AsicType::GL842 ||
 | |
|         dev->model->asic_type == AsicType::GL843 ||
 | |
|         dev->model->model_id == ModelId::CANON_5600F)
 | |
|     {
 | |
|         size = dev->calib_session.output_total_bytes_raw;
 | |
|     } else {
 | |
|         size = channels * 2 * pixels_per_line * (dev->calib_session.params.lines + 1);
 | |
|     }
 | |
| 
 | |
|   std::vector<uint16_t> calibration_data(size / 2);
 | |
| 
 | |
|     // 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, local_reg, false);
 | |
|     } else {
 | |
|         sanei_genesys_set_lamp_power(dev, sensor, local_reg, true);
 | |
|     }
 | |
|     sanei_genesys_set_motor_power(local_reg, true);
 | |
| 
 | |
|     dev->interface->write_registers(local_reg);
 | |
| 
 | |
|     if (is_dark) {
 | |
|         // wait some time to let lamp to get dark
 | |
|         dev->interface->sleep_ms(200);
 | |
|     } else if (has_flag(dev->model->flags, ModelFlag::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, &local_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, &local_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, &local_reg, true);
 | |
| 
 | |
|     if (has_flag(dev->model->flags, ModelFlag::SWAP_16BIT_DATA)) {
 | |
|         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;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (has_flag(dev->model->flags, ModelFlag::INVERT_PIXEL_DATA)) {
 | |
|         for (std::size_t i = 0; i < size / 2; ++i) {
 | |
|             calibration_data[i] = 0xffff - calibration_data[i];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     std::fill(out_average_data.begin(),
 | |
|               out_average_data.begin() + start_offset * channels, 0);
 | |
| 
 | |
|     compute_array_percentile_approx(out_average_data.data() +
 | |
|                                         start_offset * channels,
 | |
|                                     calibration_data.data(),
 | |
|                                     dev->calib_session.params.lines, pixels_per_line * channels,
 | |
|                                     0.5f);
 | |
| 
 | |
|     if (dbg_log_image_data()) {
 | |
|         write_tiff_file(log_filename_prefix + "_shading.tiff", calibration_data.data(), 16,
 | |
|                         channels, pixels_per_line, dev->calib_session.params.lines);
 | |
|         write_tiff_file(log_filename_prefix + "_average.tiff", out_average_data.data(), 16,
 | |
|                         channels, out_pixels_per_line, 1);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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_dark_shading_by_dummy_pixel(Genesys_Device* dev, const Genesys_Sensor& sensor)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
|   uint32_t pixels_per_line;
 | |
|   uint32_t skip, xend;
 | |
|   int dummy1, dummy2, dummy3;	/* dummy black average per channel */
 | |
| 
 | |
|     if (dev->model->asic_type == AsicType::GL842 ||
 | |
|         dev->model->asic_type == AsicType::GL843)
 | |
|     {
 | |
|         pixels_per_line = dev->calib_session.output_pixels;
 | |
|     } else {
 | |
|         pixels_per_line = dev->calib_session.params.pixels;
 | |
|     }
 | |
| 
 | |
|     unsigned channels = dev->calib_session.params.channels;
 | |
| 
 | |
|     // BUG: we are using wrong pixel number here
 | |
|     unsigned start_offset =
 | |
|             dev->calib_session.params.startx * sensor.full_resolution / dev->calib_session.params.xres;
 | |
| 
 | |
|     unsigned out_pixels_per_line = pixels_per_line + start_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.full_resolution / 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_dark_shading_by_constant(Genesys_Device& dev)
 | |
| {
 | |
|     dev.dark_average_data.clear();
 | |
|     dev.dark_average_data.resize(dev.average_size, 0x0101);
 | |
| }
 | |
| 
 | |
| static void genesys_repark_sensor_before_shading(Genesys_Device* dev)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
|     if (has_flag(dev->model->flags, ModelFlag::SHADING_REPARK)) {
 | |
|         dev->cmd_set->move_back_home(dev, true);
 | |
| 
 | |
|         if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
 | |
|             dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
 | |
|         {
 | |
|             scanner_move_to_ta(*dev);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void genesys_repark_sensor_after_white_shading(Genesys_Device* dev)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
|     if (has_flag(dev->model->flags, ModelFlag::SHADING_REPARK)) {
 | |
|         dev->cmd_set->move_back_home(dev, true);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void genesys_host_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);
 | |
| 
 | |
|     if (is_dark && dev.settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) {
 | |
|         // FIXME: dark shading currently not supported on infrared transparency scans
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     auto local_reg = dev.reg;
 | |
|     dev.cmd_set->init_regs_for_shading(&dev, sensor, local_reg);
 | |
| 
 | |
|     auto& session = dev.calib_session;
 | |
|     debug_dump(DBG_info, session);
 | |
| 
 | |
|     // 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, local_reg, false);
 | |
|     } else {
 | |
|         sanei_genesys_set_lamp_power(&dev, sensor, local_reg, true);
 | |
|     }
 | |
|     sanei_genesys_set_motor_power(local_reg, true);
 | |
| 
 | |
|     dev.interface->write_registers(local_reg);
 | |
| 
 | |
|     if (is_dark) {
 | |
|         // wait some time to let lamp to get dark
 | |
|         dev.interface->sleep_ms(200);
 | |
|     } else if (has_flag(dev.model->flags, ModelFlag::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, &local_reg, start_motor);
 | |
| 
 | |
|     if (is_testing_mode()) {
 | |
|         dev.interface->test_checkpoint(is_dark ? "host_dark_shading_calibration"
 | |
|                                                : "host_white_shading_calibration");
 | |
|         dev.cmd_set->end_scan(&dev, &local_reg, true);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     Image image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes_raw);
 | |
|     scanner_stop_action(dev);
 | |
| 
 | |
|     auto start_offset = session.params.startx;
 | |
|     auto out_pixels_per_line = start_offset + session.output_pixels;
 | |
| 
 | |
|     // FIXME: we set this during both dark and white calibration. A cleaner approach should
 | |
|     // probably be used
 | |
|     dev.average_size = session.params.channels * out_pixels_per_line;
 | |
| 
 | |
|     out_average_data.clear();
 | |
|     out_average_data.resize(dev.average_size);
 | |
| 
 | |
|     std::fill(out_average_data.begin(),
 | |
|               out_average_data.begin() + start_offset * session.params.channels, 0);
 | |
| 
 | |
|     compute_array_percentile_approx(out_average_data.data() +
 | |
|                                         start_offset * session.params.channels,
 | |
|                                     reinterpret_cast<std::uint16_t*>(image.get_row_ptr(0)),
 | |
|                                     session.params.lines,
 | |
|                                     session.output_pixels * session.params.channels,
 | |
|                                     0.5f);
 | |
| 
 | |
|     if (dbg_log_image_data()) {
 | |
|         write_tiff_file(log_filename_prefix + "_host_shading.tiff", image);
 | |
|         write_tiff_file(log_filename_prefix + "_host_average.tiff", out_average_data.data(), 16,
 | |
|                         session.params.channels, out_pixels_per_line, 1);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void genesys_dark_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
 | |
|                                              Genesys_Register_Set& local_reg)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
|     if (has_flag(dev->model->flags, ModelFlag::HOST_SIDE_CALIBRATION_COMPLETE_SCAN)) {
 | |
|         genesys_host_shading_calibration_impl(*dev, sensor, dev->dark_average_data, true,
 | |
|                                               "gl_black");
 | |
|     } else {
 | |
|         genesys_shading_calibration_impl(dev, sensor, local_reg, dev->dark_average_data, true,
 | |
|                                          "gl_black");
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void genesys_white_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor,
 | |
|                                               Genesys_Register_Set& local_reg)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
|     if (has_flag(dev->model->flags, ModelFlag::HOST_SIDE_CALIBRATION_COMPLETE_SCAN)) {
 | |
|         genesys_host_shading_calibration_impl(*dev, sensor, dev->white_average_data, false,
 | |
|                                               "gl_white");
 | |
|     } else {
 | |
|         genesys_shading_calibration_impl(dev, sensor, local_reg, 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,
 | |
|                                                    Genesys_Register_Set& local_reg)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
| 
 | |
|     if (dev->model->asic_type == AsicType::GL646) {
 | |
|         dev->cmd_set->init_regs_for_shading(dev, sensor, local_reg);
 | |
|         local_reg = dev->reg;
 | |
|     } else {
 | |
|         local_reg = dev->reg;
 | |
|         dev->cmd_set->init_regs_for_shading(dev, sensor, local_reg);
 | |
|         dev->interface->write_registers(local_reg);
 | |
|     }
 | |
| 
 | |
|   size_t size;
 | |
|   uint32_t pixels_per_line;
 | |
|   unsigned int x;
 | |
|   uint32_t dark, white, dark_sum, white_sum, dark_count, white_count, col,
 | |
|     dif;
 | |
| 
 | |
|     if (dev->model->asic_type == AsicType::GL842 ||
 | |
|         dev->model->asic_type == AsicType::GL843)
 | |
|     {
 | |
|         pixels_per_line = dev->calib_session.output_pixels;
 | |
|     } else {
 | |
|         pixels_per_line = dev->calib_session.params.pixels;
 | |
|     }
 | |
| 
 | |
|     unsigned channels = dev->calib_session.params.channels;
 | |
| 
 | |
|     // BUG: we are using wrong pixel number here
 | |
|     unsigned start_offset =
 | |
|             dev->calib_session.params.startx * sensor.full_resolution / dev->calib_session.params.xres;
 | |
| 
 | |
|     unsigned out_pixels_per_line = pixels_per_line + start_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->model->asic_type == AsicType::GL842 ||
 | |
|         dev->model->asic_type == AsicType::GL843)
 | |
|     {
 | |
|         size = dev->calib_session.output_total_bytes_raw;
 | |
|     } else {
 | |
|         // FIXME: on GL841 this is different than dev->calib_session.output_total_bytes_raw,
 | |
|         // needs checking
 | |
|         size = channels * 2 * pixels_per_line * dev->calib_session.params.lines;
 | |
|     }
 | |
| 
 | |
|   std::vector<uint8_t> calibration_data(size);
 | |
| 
 | |
|     // turn on motor and lamp power
 | |
|     sanei_genesys_set_lamp_power(dev, sensor, local_reg, true);
 | |
|     sanei_genesys_set_motor_power(local_reg, true);
 | |
| 
 | |
|     dev->interface->write_registers(local_reg);
 | |
| 
 | |
|     dev->cmd_set->begin_scan(dev, sensor, &local_reg, false);
 | |
| 
 | |
|     if (is_testing_mode()) {
 | |
|         dev->interface->test_checkpoint("dark_white_shading_calibration");
 | |
|         dev->cmd_set->end_scan(dev, &local_reg, true);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size);
 | |
| 
 | |
|     dev->cmd_set->end_scan(dev, &local_reg, true);
 | |
| 
 | |
|     if (dbg_log_image_data()) {
 | |
|         if (dev->model->is_cis) {
 | |
|             write_tiff_file("gl_black_white_shading.tiff", calibration_data.data(),
 | |
|                             16, 1, pixels_per_line*channels,
 | |
|                             dev->calib_session.params.lines);
 | |
|         } else {
 | |
|             write_tiff_file("gl_black_white_shading.tiff", calibration_data.data(),
 | |
|                             16, channels, pixels_per_line,
 | |
|                             dev->calib_session.params.lines);
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     std::fill(dev->dark_average_data.begin(),
 | |
|               dev->dark_average_data.begin() + start_offset * channels, 0);
 | |
|     std::fill(dev->white_average_data.begin(),
 | |
|               dev->white_average_data.begin() + start_offset * channels, 0);
 | |
| 
 | |
|     uint16_t* average_white = dev->white_average_data.data() +
 | |
|                               start_offset * channels;
 | |
|     uint16_t* average_dark = dev->dark_average_data.data() +
 | |
|                              start_offset * channels;
 | |
| 
 | |
|   for (x = 0; x < pixels_per_line * channels; x++)
 | |
|     {
 | |
|       dark = 0xffff;
 | |
|       white = 0;
 | |
| 
 | |
|             for (std::size_t y = 0; y < dev->calib_session.params.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_session.params.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_log_image_data()) {
 | |
|         write_tiff_file("gl_white_average.tiff", dev->white_average_data.data(), 16, channels,
 | |
|                         out_pixels_per_line, 1);
 | |
|         write_tiff_file("gl_dark_average.tiff", dev->dark_average_data.data(), 16, 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.full_resolution > sensor.get_optical_resolution()) {
 | |
|         res *= 2;
 | |
|     }
 | |
| 
 | |
|     // this should be evenly dividable
 | |
|     basepixels = sensor.full_resolution / 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)		/* contiguous 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.full_resolution > sensor.get_optical_resolution()) {
 | |
|         x *= 2;	// scanner is using half-ccd mode
 | |
|     }
 | |
|     basepixels = sensor.full_resolution / 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 (sensor.use_host_side_calib) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|   uint32_t pixels_per_line;
 | |
|   int o;
 | |
|   unsigned int length;		/**> number of shading calibration data words */
 | |
|   unsigned int factor;
 | |
|   unsigned int coeff, target_code, words_per_color = 0;
 | |
| 
 | |
| 
 | |
|     // BUG: we are using wrong pixel number here
 | |
|     unsigned start_offset =
 | |
|             dev->calib_session.params.startx * sensor.full_resolution / dev->calib_session.params.xres;
 | |
| 
 | |
|     if (dev->model->asic_type == AsicType::GL842 ||
 | |
|         dev->model->asic_type == AsicType::GL843)
 | |
|     {
 | |
|         pixels_per_line = dev->calib_session.output_pixels + start_offset;
 | |
|     } else {
 | |
|         pixels_per_line = dev->calib_session.params.pixels + start_offset;
 | |
|     }
 | |
| 
 | |
|     unsigned channels = dev->calib_session.params.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);
 | |
| 
 | |
|     if (!dev->calib_session.computed) {
 | |
|         genesys_send_offset_and_shading(dev, sensor, shading_data.data(), length);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|   /* 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->reg)) {
 | |
|         coeff = 0x4000;
 | |
|     } else {
 | |
|         coeff = 0x2000;
 | |
|     }
 | |
| 
 | |
|   /* compute avg factor */
 | |
|     if (dev->settings.xres > sensor.full_resolution) {
 | |
|         factor = 1;
 | |
|     } else {
 | |
|         factor = sensor.full_resolution / 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_DOCKETPORT_487:
 | |
|     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.full_resolution / 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.full_resolution/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_7200:
 | |
|     case SensorId::CCD_PLUSTEK_OPTICFILM_7200I:
 | |
|     case SensorId::CCD_PLUSTEK_OPTICFILM_7300:
 | |
|         case SensorId::CCD_PLUSTEK_OPTICFILM_7400:
 | |
|     case SensorId::CCD_PLUSTEK_OPTICFILM_7500I:
 | |
|         case SensorId::CCD_PLUSTEK_OPTICFILM_8200I:
 | |
|       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:
 | |
|         case SensorId::CCD_CANON_5600F:
 | |
|         /* 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:
 | |
|         case SensorId::CIS_CANON_LIDE_60:
 | |
|             case SensorId::CIS_CANON_LIDE_90:
 | |
|       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: contiguous 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 there 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->calib_session = cache.session;
 | |
|           dev->average_size = cache.average_size;
 | |
| 
 | |
|           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->session = dev->calib_session;
 | |
| 
 | |
| #ifdef HAVE_SYS_TIME_H
 | |
|     gettimeofday(&time, nullptr);
 | |
|   found_cache_it->last_calibration = time.tv_sec;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static void genesys_flatbed_calibration(Genesys_Device* dev, Genesys_Sensor& sensor)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
|     uint32_t pixels_per_line;
 | |
| 
 | |
|     unsigned coarse_res = sensor.full_resolution;
 | |
|     if (dev->settings.yres <= sensor.full_resolution / 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;
 | |
|     }
 | |
| 
 | |
|     auto local_reg = dev->initial_regs;
 | |
| 
 | |
|     if (!has_flag(dev->model->flags, ModelFlag::DISABLE_ADC_CALIBRATION)) {
 | |
|         // do ADC calibration first.
 | |
|         dev->interface->record_progress_message("offset_calibration");
 | |
|         dev->cmd_set->offset_calibration(dev, sensor, local_reg);
 | |
| 
 | |
|         dev->interface->record_progress_message("coarse_gain_calibration");
 | |
|         dev->cmd_set->coarse_gain_calibration(dev, sensor, local_reg, coarse_res);
 | |
|     }
 | |
| 
 | |
|     if (dev->model->is_cis &&
 | |
|         !has_flag(dev->model->flags, ModelFlag::DISABLE_EXPOSURE_CALIBRATION))
 | |
|     {
 | |
|         // ADC now sends correct data, we can configure the exposure for the LEDs
 | |
|         dev->interface->record_progress_message("led_calibration");
 | |
|         switch (dev->model->asic_type) {
 | |
|             case AsicType::GL124:
 | |
|             case AsicType::GL841:
 | |
|             case AsicType::GL845:
 | |
|             case AsicType::GL846:
 | |
|             case AsicType::GL847: {
 | |
|                 auto calib_exposure = dev->cmd_set->led_calibration(dev, sensor, local_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, local_reg);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!has_flag(dev->model->flags, ModelFlag::DISABLE_ADC_CALIBRATION)) {
 | |
|             // recalibrate ADC again for the new LED exposure
 | |
|             dev->interface->record_progress_message("offset_calibration");
 | |
|             dev->cmd_set->offset_calibration(dev, sensor, local_reg);
 | |
| 
 | |
|             dev->interface->record_progress_message("coarse_gain_calibration");
 | |
|             dev->cmd_set->coarse_gain_calibration(dev, sensor, local_reg, coarse_res);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   /* we always use sensor pixel number when the ASIC can't handle multi-segments sensor */
 | |
|     if (!has_flag(dev->model->flags, ModelFlag::SIS_SENSOR)) {
 | |
|         pixels_per_line = static_cast<std::uint32_t>((dev->model->x_size * dev->settings.xres) /
 | |
|                                                      MM_PER_INCH);
 | |
|     } else {
 | |
|         pixels_per_line = static_cast<std::uint32_t>((dev->model->x_size_calib_mm * dev->settings.xres)
 | |
|                                                       / MM_PER_INCH);
 | |
|     }
 | |
| 
 | |
|     // 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)
 | |
|     {
 | |
|         scanner_move_to_ta(*dev);
 | |
|     }
 | |
| 
 | |
|     // shading calibration
 | |
|     if (!has_flag(dev->model->flags, ModelFlag::DISABLE_SHADING_CALIBRATION)) {
 | |
|         if (has_flag(dev->model->flags, ModelFlag::DARK_WHITE_CALIBRATION)) {
 | |
|             dev->interface->record_progress_message("genesys_dark_white_shading_calibration");
 | |
|             genesys_dark_white_shading_calibration(dev, sensor, local_reg);
 | |
|         } else {
 | |
|             DBG(DBG_proc, "%s : genesys_dark_shading_calibration local_reg ", __func__);
 | |
|             debug_dump(DBG_proc, local_reg);
 | |
| 
 | |
|             if (has_flag(dev->model->flags, ModelFlag::DARK_CALIBRATION)) {
 | |
|                 dev->interface->record_progress_message("genesys_dark_shading_calibration");
 | |
|                 genesys_dark_shading_calibration(dev, sensor, local_reg);
 | |
|                 genesys_repark_sensor_before_shading(dev);
 | |
|             }
 | |
| 
 | |
|             dev->interface->record_progress_message("genesys_white_shading_calibration");
 | |
|             genesys_white_shading_calibration(dev, sensor, local_reg);
 | |
| 
 | |
|             genesys_repark_sensor_after_white_shading(dev);
 | |
| 
 | |
|             if (!has_flag(dev->model->flags, ModelFlag::DARK_CALIBRATION)) {
 | |
|                 if (has_flag(dev->model->flags, ModelFlag::USE_CONSTANT_FOR_DARK_CALIBRATION)) {
 | |
|                     genesys_dark_shading_by_constant(*dev);
 | |
|                 } else {
 | |
|                     genesys_dark_shading_by_dummy_pixel(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;
 | |
| 
 | |
|     auto local_reg = dev->initial_regs;
 | |
| 
 | |
|     // first step, load document
 | |
|     dev->cmd_set->load_document(dev);
 | |
| 
 | |
|     unsigned coarse_res = sensor.full_resolution;
 | |
| 
 | |
|   /* the afe needs to sends valid data even before calibration */
 | |
| 
 | |
|   /* go to a white area */
 | |
|     try {
 | |
|         scanner_search_strip(*dev, forward, false);
 | |
|     } catch (...) {
 | |
|         catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
 | |
|         throw;
 | |
|     }
 | |
| 
 | |
|     if (!has_flag(dev->model->flags, ModelFlag::DISABLE_ADC_CALIBRATION)) {
 | |
|         // do ADC calibration first.
 | |
|         dev->interface->record_progress_message("offset_calibration");
 | |
|         dev->cmd_set->offset_calibration(dev, sensor, local_reg);
 | |
| 
 | |
|         dev->interface->record_progress_message("coarse_gain_calibration");
 | |
|         dev->cmd_set->coarse_gain_calibration(dev, sensor, local_reg, coarse_res);
 | |
|     }
 | |
| 
 | |
|     if (dev->model->is_cis &&
 | |
|         !has_flag(dev->model->flags, ModelFlag::DISABLE_EXPOSURE_CALIBRATION))
 | |
|     {
 | |
|         // ADC now sends correct data, we can configure the exposure for the LEDs
 | |
|         dev->interface->record_progress_message("led_calibration");
 | |
|         dev->cmd_set->led_calibration(dev, sensor, local_reg);
 | |
| 
 | |
|         if (!has_flag(dev->model->flags, ModelFlag::DISABLE_ADC_CALIBRATION)) {
 | |
|             // recalibrate ADC again for the new LED exposure
 | |
|             dev->interface->record_progress_message("offset_calibration");
 | |
|             dev->cmd_set->offset_calibration(dev, sensor, local_reg);
 | |
| 
 | |
|             dev->interface->record_progress_message("coarse_gain_calibration");
 | |
|             dev->cmd_set->coarse_gain_calibration(dev, sensor, local_reg, coarse_res);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   /* search for a full width black strip and then do a 16 bit scan to
 | |
|    * gather black shading data */
 | |
|     if (has_flag(dev->model->flags, ModelFlag::DARK_CALIBRATION)) {
 | |
|         // seek black/white reverse/forward
 | |
|         try {
 | |
|             scanner_search_strip(*dev, forward, true);
 | |
|         } catch (...) {
 | |
|             catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
 | |
|             throw;
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             genesys_dark_shading_calibration(dev, sensor, local_reg);
 | |
|         } catch (...) {
 | |
|             catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
 | |
|             throw;
 | |
|         }
 | |
|         forward = false;
 | |
|     }
 | |
| 
 | |
| 
 | |
|   /* go to a white area */
 | |
|     try {
 | |
|         scanner_search_strip(*dev, forward, false);
 | |
|     } catch (...) {
 | |
|         catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
 | |
|         throw;
 | |
|     }
 | |
| 
 | |
|   genesys_repark_sensor_before_shading(dev);
 | |
| 
 | |
|     try {
 | |
|         genesys_white_shading_calibration(dev, sensor, local_reg);
 | |
|         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_dark_shading_by_dummy_pixel() ?
 | |
|     if (!has_flag(dev->model->flags, ModelFlag::DARK_CALIBRATION)) {
 | |
|         genesys_dark_shading_by_constant(*dev);
 | |
|     }
 | |
| 
 | |
|   /* 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.full_resolution;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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;
 | |
| 
 | |
|   const auto& sensor = sanei_genesys_find_sensor_any(dev);
 | |
| 
 | |
|     dev->cmd_set->init_regs_for_warmup(dev, sensor, &dev->reg);
 | |
|     dev->interface->write_registers(dev->reg);
 | |
| 
 | |
|     auto total_pixels =  dev->session.output_pixels;
 | |
|     auto total_size = dev->session.output_line_bytes;
 | |
|     auto channels = dev->session.params.channels;
 | |
|     auto lines = dev->session.output_line_count;
 | |
| 
 | |
|   std::vector<uint8_t> first_line(total_size);
 | |
|   std::vector<uint8_t> second_line(total_size);
 | |
| 
 | |
|     do {
 | |
|         first_line = second_line;
 | |
| 
 | |
|         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);
 | |
| 
 | |
|         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
 | |
|         double first_average = 0;
 | |
|         double second_average = 0;
 | |
|         for (unsigned 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];
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         first_average /= total_pixels;
 | |
|         second_average /= total_pixels;
 | |
| 
 | |
|         if (dbg_log_image_data()) {
 | |
|             write_tiff_file("gl_warmup1.tiff", first_line.data(), dev->session.params.depth,
 | |
|                             channels, total_size / (lines * channels), lines);
 | |
|             write_tiff_file("gl_warmup2.tiff", second_line.data(), dev->session.params.depth,
 | |
|                             channels, total_size / (lines * channels), lines);
 | |
|         }
 | |
| 
 | |
|         DBG(DBG_info, "%s: average 1 = %.2f, average 2 = %.2f\n", __func__, first_average,
 | |
|             second_average);
 | |
| 
 | |
|         float average_difference = std::fabs(first_average - second_average) / second_average;
 | |
|         if (second_average > 0 && average_difference < 0.005)
 | |
|         {
 | |
|             dbg.vlog(DBG_info, "difference: %f, exiting", average_difference);
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         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);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void init_regs_for_scan(Genesys_Device& dev, const Genesys_Sensor& sensor,
 | |
|                                Genesys_Register_Set& regs)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
|     debug_dump(DBG_info, dev.settings);
 | |
| 
 | |
|     auto session = dev.cmd_set->calculate_scan_session(&dev, sensor, dev.settings);
 | |
| 
 | |
|     if (dev.model->asic_type == AsicType::GL124 ||
 | |
|         dev.model->asic_type == AsicType::GL845 ||
 | |
|         dev.model->asic_type == AsicType::GL846 ||
 | |
|         dev.model->asic_type == AsicType::GL847)
 | |
|     {
 | |
|         /*  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 && session.params.starty > 700) {
 | |
|             scanner_move(dev, dev.model->default_method,
 | |
|                          static_cast<unsigned>(session.params.starty - 500),
 | |
|                          Direction::FORWARD);
 | |
|             session.params.starty = 500;
 | |
|         }
 | |
|         compute_session(&dev, session, sensor);
 | |
|     }
 | |
| 
 | |
|     dev.cmd_set->init_regs_for_scan_session(&dev, sensor, ®s, session);
 | |
| }
 | |
| 
 | |
| // 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 to 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 (has_flag(dev->model->flags, ModelFlag::WARMUP) &&
 | |
|         (dev->settings.scan_method != ScanMethod::TRANSPARENCY_INFRARED))
 | |
|     {
 | |
|         if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
 | |
|             dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
 | |
|         {
 | |
|             scanner_move_to_ta(*dev);
 | |
|         }
 | |
| 
 | |
|         genesys_warmup_lamp(dev);
 | |
|     }
 | |
| 
 | |
|   /* set top left x and y values by scanning the internals if flatbed scanners */
 | |
|     if (!dev->model->is_sheetfed) {
 | |
|         // 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)
 | |
|     {
 | |
|         scanner_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.
 | |
|         // also don't run calibration for those scanners where all passes are disabled
 | |
|         bool shading_disabled =
 | |
|                 has_flag(dev->model->flags, ModelFlag::DISABLE_ADC_CALIBRATION) &&
 | |
|                 has_flag(dev->model->flags, ModelFlag::DISABLE_EXPOSURE_CALIBRATION) &&
 | |
|                 has_flag(dev->model->flags, ModelFlag::DISABLE_SHADING_CALIBRATION);
 | |
|         if (!shading_disabled && !dev->model->is_sheetfed) {
 | |
|             genesys_scanner_calibration(dev, sensor);
 | |
|             genesys_save_calibration(dev, sensor);
 | |
|         } else {
 | |
|           DBG(DBG_warn, "%s: no calibration done\n", __func__);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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)
 | |
|     {
 | |
|         scanner_move_to_ta(*dev);
 | |
|     }
 | |
| 
 | |
|     init_regs_for_scan(*dev, sensor, dev->reg);
 | |
| 
 | |
|   /* 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() &&
 | |
|         !has_flag(dev->model->flags, ModelFlag::DISABLE_SHADING_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 sufficient -- 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);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* 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;
 | |
| 
 | |
|     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 immediately in case scanner can handle it
 | |
|        * so we save time */
 | |
|         if (!dev->model->is_sheetfed && !has_flag(dev->model->flags, ModelFlag::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");
 | |
|     }
 | |
| 
 | |
|     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 {
 | |
|         if (dev->model->is_sheetfed) {
 | |
|             dev->cmd_set->detect_document_end(dev);
 | |
|         }
 | |
| 
 | |
|         if (dev->total_bytes_read + *len > dev->total_bytes_to_read) {
 | |
|             *len = dev->total_bytes_to_read - dev->total_bytes_read;
 | |
|         }
 | |
| 
 | |
|         dev->pipeline_buffer.get_data(*len, destination);
 | |
|         dev->total_bytes_read += *len;
 | |
|     }
 | |
| 
 | |
|   /* 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 Genesys_Settings calculate_scan_settings(Genesys_Scanner* s)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
| 
 | |
|     const auto* dev = s->dev;
 | |
|     Genesys_Settings settings;
 | |
|     settings.scan_method = s->scan_method;
 | |
|     settings.scan_mode = option_string_to_scan_color_mode(s->mode);
 | |
| 
 | |
|     settings.depth = s->bit_depth;
 | |
| 
 | |
|     if (settings.depth > 8) {
 | |
|         settings.depth = 16;
 | |
|     } else if (settings.depth < 8) {
 | |
|         settings.depth = 1;
 | |
|     }
 | |
| 
 | |
|     const auto& resolutions = dev->model->get_resolution_settings(settings.scan_method);
 | |
| 
 | |
|     settings.xres = pick_resolution(resolutions.resolutions_x, s->resolution, "X");
 | |
|     settings.yres = pick_resolution(resolutions.resolutions_y, s->resolution, "Y");
 | |
| 
 | |
|     settings.tl_x = fixed_to_float(s->pos_top_left_x);
 | |
|     settings.tl_y = fixed_to_float(s->pos_top_left_y);
 | |
|     float br_x = fixed_to_float(s->pos_bottom_right_x);
 | |
|     float br_y = fixed_to_float(s->pos_bottom_right_y);
 | |
| 
 | |
|     settings.lines = static_cast<unsigned>(((br_y - settings.tl_y) * settings.yres) /
 | |
|                                             MM_PER_INCH);
 | |
| 
 | |
| 
 | |
|     unsigned pixels_per_line = static_cast<unsigned>(((br_x - settings.tl_x) * settings.xres) /
 | |
|                                                      MM_PER_INCH);
 | |
| 
 | |
|     const auto& sensor = sanei_genesys_find_sensor(dev, settings.xres, settings.get_channels(),
 | |
|                                                    settings.scan_method);
 | |
| 
 | |
|     pixels_per_line = session_adjust_output_pixels(pixels_per_line, *dev, sensor,
 | |
|                                                    settings.xres, settings.yres, true);
 | |
| 
 | |
|     unsigned xres_factor = s->resolution / settings.xres;
 | |
|     settings.pixels = pixels_per_line;
 | |
|     settings.requested_pixels = pixels_per_line * xres_factor;
 | |
| 
 | |
|     if (s->color_filter == "Red") {
 | |
|         settings.color_filter = ColorFilter::RED;
 | |
|     } else if (s->color_filter == "Green") {
 | |
|         settings.color_filter = ColorFilter::GREEN;
 | |
|     } else if (s->color_filter == "Blue") {
 | |
|         settings.color_filter = ColorFilter::BLUE;
 | |
|     } else {
 | |
|         settings.color_filter = ColorFilter::NONE;
 | |
|     }
 | |
| 
 | |
|     if (s->color_filter == "None") {
 | |
|         settings.true_gray = 1;
 | |
|     } else {
 | |
|         settings.true_gray = 0;
 | |
|     }
 | |
| 
 | |
|     // brightness and contrast only for for 8 bit scans
 | |
|     if (s->bit_depth == 8) {
 | |
|         settings.contrast = (s->contrast * 127) / 100;
 | |
|         settings.brightness = (s->brightness * 127) / 100;
 | |
|     } else {
 | |
|         settings.contrast = 0;
 | |
|         settings.brightness = 0;
 | |
|     }
 | |
| 
 | |
|     settings.expiration_time = s->expiration_time;
 | |
| 
 | |
|     return settings;
 | |
| }
 | |
| 
 | |
| static SANE_Parameters calculate_scan_parameters(const Genesys_Device& dev,
 | |
|                                                  const Genesys_Settings& settings)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
| 
 | |
|     auto sensor = sanei_genesys_find_sensor(&dev, settings.xres, settings.get_channels(),
 | |
|                                             settings.scan_method);
 | |
|     auto session = dev.cmd_set->calculate_scan_session(&dev, sensor, settings);
 | |
|     auto pipeline = build_image_pipeline(dev, session, 0, false);
 | |
| 
 | |
|     SANE_Parameters params;
 | |
|     if (settings.scan_mode == ScanColorMode::GRAY) {
 | |
|         params.format = SANE_FRAME_GRAY;
 | |
|     } else {
 | |
|         params.format = SANE_FRAME_RGB;
 | |
|     }
 | |
|     // only single-pass scanning supported
 | |
|     params.last_frame = true;
 | |
|     params.depth = settings.depth;
 | |
|     params.lines = pipeline.get_output_height();
 | |
|     params.pixels_per_line = pipeline.get_output_width();
 | |
|     params.bytes_per_line = pipeline.get_output_row_bytes();
 | |
| 
 | |
|     return params;
 | |
| }
 | |
| 
 | |
| static void calc_parameters(Genesys_Scanner* s)
 | |
| {
 | |
|     DBG_HELPER(dbg);
 | |
| 
 | |
|     s->dev->settings = calculate_scan_settings(s);
 | |
|     s->params = calculate_scan_parameters(*s->dev, s->dev->settings);
 | |
| }
 | |
| 
 | |
| 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 ModelFlag::GAMMA_14BIT
 | |
|  * 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 (has_flag(scanner->dev->model->flags, ModelFlag::GAMMA_14BIT)) {
 | |
| 	  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 = float_to_fixed(0.0);
 | |
|     range.max = float_to_fixed(size);
 | |
|     range.quant = float_to_fixed(0.0);
 | |
|     return range;
 | |
| }
 | |
| 
 | |
| /** @brief generate calibration cache file nam
 | |
|  * Generates the calibration cache file name to use.
 | |
|  * Tries to store the cache 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.vendorId == currdev->vendorId && dev.productId == currdev->productId) {
 | |
|                 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(s.dev->model->x_size);
 | |
|         s.opt_y_range = create_range(s.dev->model->y_size);
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|         s.opt_x_range = create_range(s.dev->model->x_size_ta);
 | |
|         s.opt_y_range = create_range(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;
 | |
|     const 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(model->x_size);
 | |
|     s->opt_y_range = create_range(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 (!has_flag(model->flags, ModelFlag::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 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;
 | |
| 
 | |
|   /* 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 run 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;
 | |
|     }
 | |
| 
 | |
|     // transparency/scan_film button
 | |
|     s->opt[OPT_TRANSP_SW].name = "transparency";
 | |
|     s->opt[OPT_TRANSP_SW].title = SANE_I18N ("Transparency button");
 | |
|     s->opt[OPT_TRANSP_SW].desc = SANE_I18N ("Transparency button");
 | |
|     s->opt[OPT_TRANSP_SW].type = SANE_TYPE_BOOL;
 | |
|     s->opt[OPT_TRANSP_SW].unit = SANE_UNIT_NONE;
 | |
|     if (model->buttons & GENESYS_HAS_TRANSP_SW) {
 | |
|         s->opt[OPT_TRANSP_SW].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
 | |
|     } else {
 | |
|         s->opt[OPT_TRANSP_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;
 | |
| }
 | |
| 
 | |
| const UsbDeviceEntry& get_matching_usb_dev(std::uint16_t vendor_id, std::uint16_t product_id,
 | |
|                                            std::uint16_t bcd_device)
 | |
| {
 | |
|     for (auto& usb_dev : *s_usb_devices) {
 | |
|         if (usb_dev.matches(vendor_id, product_id, bcd_device)) {
 | |
|             return usb_dev;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     throw SaneException("vendor 0x%x product 0x%x (bcdDevice 0x%x) "
 | |
|                         "is not supported by this backend",
 | |
|                         vendor_id, product_id, bcd_device);
 | |
| }
 | |
| 
 | |
| static Genesys_Device* attach_usb_device(const char* devname,
 | |
|                                          std::uint16_t vendor_id, std::uint16_t product_id,
 | |
|                                          std::uint16_t bcd_device)
 | |
| {
 | |
|     const auto& usb_dev = get_matching_usb_dev(vendor_id, product_id, bcd_device);
 | |
| 
 | |
|     s_devices->emplace_back();
 | |
|     Genesys_Device* dev = &s_devices->back();
 | |
|     dev->file_name = devname;
 | |
|     dev->vendorId = vendor_id;
 | |
|     dev->productId = product_id;
 | |
|     dev->model = &usb_dev.model();
 | |
|     dev->usb_mode = 0; // i.e. unset
 | |
|     dev->already_initialized = false;
 | |
|     return dev;
 | |
| }
 | |
| 
 | |
| static bool s_attach_device_by_name_evaluate_bcd_device = false;
 | |
| 
 | |
| 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);
 | |
| 
 | |
|     auto vendor_id = usb_dev.get_vendor_id();
 | |
|     auto product_id = usb_dev.get_product_id();
 | |
|     auto bcd_device = UsbDeviceEntry::BCD_DEVICE_NOT_SET;
 | |
|     if (s_attach_device_by_name_evaluate_bcd_device) {
 | |
|         // when the device is already known before scanning, we don't want to call get_bcd_device()
 | |
|         // when iterating devices, as that will interfere with record/replay during testing.
 | |
|         bcd_device = usb_dev.get_bcd_device();
 | |
|     }
 | |
|     usb_dev.close();
 | |
| 
 | |
|   /* KV-SS080 is an auxiliary device which requires a master device to be here */
 | |
|     if (vendor_id == 0x04da && product_id == 0x100f) {
 | |
|         present = false;
 | |
|         sanei_usb_find_devices(vendor_id, 0x1006, check_present);
 | |
|         sanei_usb_find_devices(vendor_id, 0x1007, check_present);
 | |
|         sanei_usb_find_devices(vendor_id, 0x1010, check_present);
 | |
|         if (present == false) {
 | |
|             throw SaneException("master device not present");
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Genesys_Device* dev = attach_usb_device(devname, vendor_id, product_id, bcd_device);
 | |
| 
 | |
|     DBG(DBG_info, "%s: found %u flatbed scanner %u at %s\n", __func__, vendor_id, product_id,
 | |
|         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,
 | |
|                       void __sane_unused__ *data) 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(),
 | |
|                           get_testing_bcd_device());
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|   SANEI_Config config;
 | |
| 
 | |
|     // set configuration options structure : no option for this backend
 | |
|     config.descriptors = nullptr;
 | |
|     config.values = nullptr;
 | |
|   config.count = 0;
 | |
| 
 | |
|     auto status = sanei_configure_attach(GENESYS_CONFIG_FILE, &config,
 | |
|                                          config_attach_genesys, NULL);
 | |
|     if (status == SANE_STATUS_ACCESS_DENIED) {
 | |
|         dbg.vlog(DBG_error0, "Critical error: Couldn't access configuration file '%s'",
 | |
|                  GENESYS_CONFIG_FILE);
 | |
|     }
 | |
|     TIE(status);
 | |
| 
 | |
|     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 = 32;
 | |
| 
 | |
| 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);
 | |
| }
 | |
| 
 | |
| /* -------------------------- 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();
 | |
|     }
 | |
| 
 | |
|   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_memory_layout_tables();
 | |
|     genesys_init_motor_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 already connected scanners
 | |
|     s_attach_device_by_name_evaluate_bcd_device = false;
 | |
|     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();
 | |
|     }
 | |
|     s_attach_device_by_name_evaluate_bcd_device = true;
 | |
|     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->file_name.c_str());
 | |
|         } 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 (is_testing_mode()) {
 | |
|         // during testing we need to initialize dev->model before test scanner interface is created
 | |
|         // as that it needs to know what type of chip it needs to mimic.
 | |
|         auto vendor_id = get_testing_vendor_id();
 | |
|         auto product_id = get_testing_product_id();
 | |
|         auto bcd_device = get_testing_bcd_device();
 | |
| 
 | |
|         dev->model = &get_matching_usb_dev(vendor_id, product_id, bcd_device).model();
 | |
| 
 | |
|         auto interface = std::unique_ptr<TestScannerInterface>{
 | |
|                 new TestScannerInterface{dev, vendor_id, product_id, bcd_device}};
 | |
|         interface->set_checkpoint_callback(get_testing_checkpoint_callback());
 | |
|         dev->interface = std::move(interface);
 | |
| 
 | |
|         dev->interface->get_usb_device().open(dev->file_name.c_str());
 | |
|     } else {
 | |
|         dev->interface = std::unique_ptr<ScannerInterfaceUsb>{new ScannerInterfaceUsb{dev}};
 | |
| 
 | |
|         dbg.vstatus("open device '%s'", dev->file_name.c_str());
 | |
|         dev->interface->get_usb_device().open(dev->file_name.c_str());
 | |
|         dbg.clear();
 | |
| 
 | |
|         auto bcd_device = dev->interface->get_usb_device().get_bcd_device();
 | |
| 
 | |
|         dev->model = &get_matching_usb_dev(dev->vendorId, dev->productId, bcd_device).model();
 | |
|     }
 | |
| 
 | |
|     dbg.vlog(DBG_info, "Opened device %s", dev->model->name);
 | |
| 
 | |
|     if (has_flag(dev->model->flags, ModelFlag::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");
 | |
|     }
 | |
| 
 | |
|   s_scanners->push_back(Genesys_Scanner());
 | |
|   auto* s = &s_scanners->back();
 | |
| 
 | |
|     s->dev = dev;
 | |
|     s->scanning = false;
 | |
|     dev->parking = false;
 | |
|     dev->read_active = false;
 | |
|     dev->force_calibration = 0;
 | |
|     dev->line_count = 0;
 | |
| 
 | |
|   *handle = s;
 | |
| 
 | |
|     if (!dev->already_initialized) {
 | |
|         sanei_genesys_init_structs (dev);
 | |
|     }
 | |
| 
 | |
|     dev->cmd_set = create_cmd_set(dev->model->asic_type);
 | |
| 
 | |
|     init_options(s);
 | |
| 
 | |
|     DBG_INIT();
 | |
| 
 | |
|     // 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
 | |
|     dev->cmd_set->update_hardware_sensors (s);
 | |
| }
 | |
| 
 | |
| 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 */
 | |
|     }
 | |
| 
 | |
|     auto* dev = it->dev;
 | |
| 
 | |
|     // eject document for sheetfed scanners
 | |
|     if (dev->model->is_sheetfed) {
 | |
|         catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
 | |
|     } else {
 | |
|         // in case scanner is parking, wait for the head to reach home position
 | |
|         if (dev->parking) {
 | |
|             sanei_genesys_wait_for_home(dev);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // enable power saving before leaving
 | |
|     dev->cmd_set->save_power(dev, true);
 | |
| 
 | |
|     // here is the place to store calibration cache
 | |
|     if (dev->force_calibration == 0 && !is_testing_mode()) {
 | |
|         catch_all_exceptions(__func__, [&](){ write_calibration(dev->calibration_cache,
 | |
|                                                                 dev->calib_file); });
 | |
|     }
 | |
| 
 | |
|     dev->already_initialized = false;
 | |
|     dev->clear();
 | |
| 
 | |
|     // LAMP OFF : same register across all the ASICs */
 | |
|     dev->interface->write_register(0x03, 0x00);
 | |
| 
 | |
|     catch_all_exceptions(__func__, [&](){ dev->interface->get_usb_device().clear_halt(); });
 | |
| 
 | |
|     // we need this to avoid these ASIC getting stuck in bulk writes
 | |
|     catch_all_exceptions(__func__, [&](){ dev->interface->get_usb_device().reset(); });
 | |
| 
 | |
|     // not freeing dev because it's in the dev list
 | |
|     catch_all_exceptions(__func__, [&](){ 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", fixed_to_float(*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);
 | |
|     auto* dev = s->dev;
 | |
|   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(dev, dev->settings.xres, dev->settings.get_channels(),
 | |
|                                  dev->settings.scan_method))
 | |
|     {
 | |
|         sensor = &sanei_genesys_find_sensor(dev, dev->settings.xres,
 | |
|                                             dev->settings.get_channels(),
 | |
|                                             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_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_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(dev, *sensor, GENESYS_RED);
 | |
|             } else if (s->color_filter == "Blue") {
 | |
|                 gamma_table = get_gamma_table(dev, *sensor, GENESYS_BLUE);
 | |
|             } else {
 | |
|                 gamma_table = get_gamma_table(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(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(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(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:
 | |
|     case OPT_TRANSP_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 = dev->cmd_set->calculate_scan_session(dev, *sensor, dev->settings);
 | |
| 
 | |
|             for (auto& cache : dev->calibration_cache) {
 | |
|                 if (sanei_genesys_is_compatible_calibration(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);
 | |
|     auto dev = s->dev;
 | |
| 
 | |
|     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;
 | |
|     }
 | |
| 
 | |
|     dev->calibration_cache = std::move(new_calibration);
 | |
|     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);
 | |
| 
 | |
|     auto* dev = s->dev;
 | |
| 
 | |
|   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_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;
 | |
|     /* 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_CONTRAST);
 | |
|           DISABLE(OPT_BRIGHTNESS);
 | |
|             } else {
 | |
|           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_GRAY) {
 | |
|                 if (dev->model->asic_type != AsicType::GL646 || !dev->model->is_cis) {
 | |
|                     ENABLE(OPT_COLOR_FILTER);
 | |
|                 }
 | |
|                 create_bpp_list(s, dev->model->bpp_gray_values);
 | |
|                 s->bit_depth = dev->model->bpp_gray_values[0];
 | |
|             } else {
 | |
|                 DISABLE(OPT_COLOR_FILTER);
 | |
|                 create_bpp_list(s, dev->model->bpp_color_values);
 | |
|                 s->bit_depth = 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 (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);
 | |
|                     dev->cmd_set->set_powersaving(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);
 | |
|             }
 | |
|             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 : 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);
 | |
| 
 | |
|             dev->gamma_override_tables[GENESYS_RED].resize(option_size);
 | |
|             dev->gamma_override_tables[GENESYS_GREEN].resize(option_size);
 | |
|             dev->gamma_override_tables[GENESYS_BLUE].resize(option_size);
 | |
|             for (i = 0; i < option_size; i++) {
 | |
|                 dev->gamma_override_tables[GENESYS_RED][i] = table[i];
 | |
|                 dev->gamma_override_tables[GENESYS_GREEN][i] = table[i];
 | |
|                 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);
 | |
|             dev->gamma_override_tables[GENESYS_RED].resize(option_size);
 | |
|             for (i = 0; i < option_size; i++) {
 | |
|                 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);
 | |
|             dev->gamma_override_tables[GENESYS_GREEN].resize(option_size);
 | |
|             for (i = 0; i < option_size; i++) {
 | |
|                 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);
 | |
|             dev->gamma_override_tables[GENESYS_BLUE].resize(option_size);
 | |
|             for (i = 0; i < option_size; i++) {
 | |
|                 dev->gamma_override_tables[GENESYS_BLUE][i] = table[i];
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case OPT_CALIBRATE: {
 | |
|             auto& sensor = sanei_genesys_find_sensor_for_write(dev, dev->settings.xres,
 | |
|                                                                dev->settings.get_channels(),
 | |
|                                                                dev->settings.scan_method);
 | |
|             catch_all_exceptions(__func__, [&]()
 | |
|             {
 | |
|             dev->cmd_set->save_power(dev, false);
 | |
|             genesys_scanner_calibration(dev, sensor);
 | |
|             });
 | |
|             catch_all_exceptions(__func__, [&]()
 | |
|             {
 | |
|             dev->cmd_set->save_power(dev, true);
 | |
|             });
 | |
|             *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
 | |
|             break;
 | |
|         }
 | |
|         case OPT_CLEAR_CALIBRATION: {
 | |
|             dev->calibration_cache.clear();
 | |
| 
 | |
|             // remove file
 | |
|             unlink(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: {
 | |
|             dev->force_calibration = 1;
 | |
|             dev->calibration_cache.clear();
 | |
|             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: {
 | |
|             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);
 | |
|     auto* dev = s->dev;
 | |
| 
 | |
|   /* don't recompute parameters once data reading is active, ie during scan */
 | |
|     if (!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 (dev->model->is_sheetfed &&
 | |
|             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);
 | |
|     auto* dev = s->dev;
 | |
| 
 | |
|     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");
 | |
|     }
 | |
| 
 | |
|     // fetch stored calibration
 | |
|     if (dev->force_calibration == 0) {
 | |
|         auto path = calibration_filename(dev);
 | |
|         s->calibration_file = path;
 | |
|         dev->calib_file = path;
 | |
|         DBG(DBG_info, "%s: Calibration filename set to:\n", __func__);
 | |
|         DBG(DBG_info, "%s: >%s<\n", __func__, dev->calib_file.c_str());
 | |
| 
 | |
|         catch_all_exceptions(__func__, [&]()
 | |
|         {
 | |
|             sanei_genesys_read_calibration(dev->calibration_cache, dev->calib_file);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     // 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(dev, s->lamp_off);
 | |
| 
 | |
|     s->scanning = true;
 | |
| }
 | |
| 
 | |
| SANE_GENESYS_API_LINKAGE
 | |
| SANE_Status sane_start(SANE_Handle handle)
 | |
| {
 | |
|     return wrap_exceptions_to_status_code(__func__, [=]()
 | |
|     {
 | |
|         sane_start_impl(handle);
 | |
|     });
 | |
| }
 | |
| 
 | |
| // returns SANE_STATUS_GOOD if there are more data, SANE_STATUS_EOF otherwise
 | |
| SANE_Status 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);
 | |
|   size_t local_len;
 | |
| 
 | |
|     if (!s) {
 | |
|         throw SaneException("handle is nullptr");
 | |
|     }
 | |
| 
 | |
|     auto* 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 immediately in case scanner can handle it
 | |
|        * so we save time */
 | |
|         if (!dev->model->is_sheetfed && !has_flag(dev->model->flags, ModelFlag::MUST_WAIT) &&
 | |
|             !dev->parking)
 | |
|         {
 | |
|             dev->cmd_set->move_back_home(dev, false);
 | |
|             dev->parking = true;
 | |
|         }
 | |
|         return SANE_STATUS_EOF;
 | |
|     }
 | |
| 
 | |
|   local_len = max_len;
 | |
| 
 | |
|     genesys_read_ordered_data(dev, buf, &local_len);
 | |
| 
 | |
|   *len = local_len;
 | |
|     if (local_len > static_cast<std::size_t>(max_len)) {
 | |
|         dbg.log(DBG_error, "error: returning incorrect length");
 | |
|     }
 | |
|   DBG(DBG_proc, "%s: %d bytes returned\n", __func__, *len);
 | |
|     return SANE_STATUS_GOOD;
 | |
| }
 | |
| 
 | |
| 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_return(__func__, [=]()
 | |
|     {
 | |
|         return 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);
 | |
|     auto* dev = s->dev;
 | |
| 
 | |
|     s->scanning = false;
 | |
|     dev->read_active = false;
 | |
| 
 | |
|     // no need to end scan if we are parking the head
 | |
|     if (!dev->parking) {
 | |
|         dev->cmd_set->end_scan(dev, &dev->reg, true);
 | |
|     }
 | |
| 
 | |
|     // park head if flatbed scanner
 | |
|     if (!dev->model->is_sheetfed) {
 | |
|         if (!dev->parking) {
 | |
|             dev->cmd_set->move_back_home(dev, has_flag(dev->model->flags, ModelFlag::MUST_WAIT));
 | |
|             dev->parking = !has_flag(dev->model->flags, ModelFlag::MUST_WAIT);
 | |
|         }
 | |
|     } else {
 | |
|         // in case of sheetfed scanners, we have to eject the document if still present
 | |
|         dev->cmd_set->eject_document(dev);
 | |
|     }
 | |
| 
 | |
|     // enable power saving mode unless we are parking ....
 | |
|     if (!dev->parking) {
 | |
|         dev->cmd_set->save_power(dev, true);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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;
 | |
|     case OPT_TRANSP_SW: return BUTTON_TRANSP_SW;
 | |
|     default: throw std::runtime_error("Unknown option to convert to button index");
 | |
|     }
 | |
| }
 | |
| 
 | |
| } // namespace genesys
 |