sane-project-backends/backend/genesys/genesys.cpp

6370 wiersze
217 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/>.
*/
/*
* 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<std::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<std::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::GL845 ||
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,
std::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<std::uint8_t> shading_data(pixels_per_line * 4 * channels, 0);
std::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<std::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<std::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::GL843)
{
// FIXME: remove the above check
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, &regs, 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, &regs, 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, &regs, 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, &regs, 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, &regs, 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, &regs, 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::GL841 ||
dev.model->asic_type == AsicType::GL842 ||
dev.model->asic_type == AsicType::GL843)
{
std::vector<std::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 {
// 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::GL841 ||
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
}
ScanSession session;
session.params.xres = resolution;
session.params.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 = 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, &regs, session);
std::uint16_t exp[3];
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 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] });
dev.interface->write_registers(regs);
dbg.log(DBG_info, "starting line reading");
dev.cmd_set->begin_scan(&dev, calib_sensor, &regs, 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);
} else if (dev.model->asic_type == AsicType::GL124) {
scanner_stop_action(dev);
} else {
scanner_stop_action(dev);
dev.cmd_set->move_back_home(&dev, true);
}
return { exp[0], exp[1], exp[2] };
}
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::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::GL841 ||
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,
std::uint32_t exposure_time,
const std::vector<std::uint16_t>& slope_table,
unsigned acceleration_steps,
unsigned move_steps,
unsigned buffer_acceleration_steps,
std::uint32_t* out_z1, std::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;
std::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<std::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);
std::uint32_t pixels_per_line;
std::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);
}
std::size_t size;
std::uint32_t pixels_per_line;
unsigned int x;
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<std::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);
std::uint16_t* average_white = dev->white_average_data.data() + start_offset * channels;
std::uint16_t* average_dark = dev->dark_average_data.data() + start_offset * channels;
for (x = 0; x < pixels_per_line * channels; x++)
{
std::uint32_t dark = 0xffff;
std::uint32_t white = 0;
for (std::size_t y = 0; y < dev->calib_session.params.lines; y++)
{
std::uint32_t 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;
}
std::uint32_t dif = white - dark;
dark = dark + dif / 8;
white = white - dif / 8;
std::uint32_t dark_count = 0;
std::uint32_t dark_sum = 0;
std::uint32_t white_count = 0;
std::uint32_t white_sum = 0;
for (std::size_t y = 0; y < dev->calib_session.params.lines; y++)
{
std::uint32_t 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,
std::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,
std::uint8_t* shading_data,
unsigned int pixels_per_line,
unsigned int channels,
ColorOrder color_order,
int offset,
unsigned int coeff,
unsigned int target)
{
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 */
// contain 16bit words in little endian
std::uint8_t* 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,
std::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)
{
std::uint32_t i;
std::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 (unsigned c = 0; c < channels; c++) {
/* shading data is larger than pixels_per_line so offset can be neglected */
for (unsigned 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 */
// contains 16bit words in little endian
std::uint8_t* 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 (unsigned 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,
std::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];
std::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;
}
std::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<std::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);
std::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<std::uint8_t> first_line(total_size);
std::vector<std::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, &regs, 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;
}
// 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<std::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