kopia lustrzana https://gitlab.com/sane-project/backends
6450 wiersze
221 KiB
C++
6450 wiersze
221 KiB
C++
/* sane - Scanner Access Now Easy.
|
|
|
|
Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
|
|
Copyright (C) 2004, 2005 Gerhard Jaeger <gerhard@gjaeger.de>
|
|
Copyright (C) 2004-2016 Stéphane Voltz <stef.dev@free.fr>
|
|
Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org>
|
|
Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com>
|
|
Copyright (C) 2007 Luke <iceyfor@gmail.com>
|
|
Copyright (C) 2010 Chris Berry <s0457957@sms.ed.ac.uk> and Michael Rickmann <mrickma@gwdg.de>
|
|
for Plustek Opticbook 3600 support
|
|
|
|
Dynamic rasterization code was taken from the epjistsu backend by
|
|
m. allan noah <kitno455 at gmail dot com>
|
|
|
|
Software processing for deskew, crop and dspeckle are inspired by allan's
|
|
noah work in the fujitsu backend
|
|
|
|
This file is part of the SANE package.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
As a special exception, the authors of SANE give permission for
|
|
additional uses of the libraries contained in this release of SANE.
|
|
|
|
The exception is that, if you link a SANE library with other files
|
|
to produce an executable, this does not by itself cause the
|
|
resulting executable to be covered by the GNU General Public
|
|
License. Your use of that executable is in no way restricted on
|
|
account of linking the SANE library code into it.
|
|
|
|
This exception does not, however, invalidate any other reasons why
|
|
the executable file might be covered by the GNU General Public
|
|
License.
|
|
|
|
If you submit changes to SANE to the maintainers to be included in
|
|
a subsequent release, you agree by submitting the changes that
|
|
those changes may be distributed with this exception intact.
|
|
|
|
If you write modifications of your own for SANE, it is your choice
|
|
whether to permit this exception to apply to your modifications.
|
|
If you do not wish that, delete this exception notice.
|
|
*/
|
|
|
|
/*
|
|
* SANE backend for Genesys Logic GL646/GL841/GL842/GL843/GL846/GL847/GL124 based scanners
|
|
*/
|
|
|
|
#define DEBUG_NOT_STATIC
|
|
|
|
#include "genesys.h"
|
|
#include "gl124_registers.h"
|
|
#include "gl841_registers.h"
|
|
#include "gl842_registers.h"
|
|
#include "gl843_registers.h"
|
|
#include "gl846_registers.h"
|
|
#include "gl847_registers.h"
|
|
#include "usb_device.h"
|
|
#include "utilities.h"
|
|
#include "scanner_interface_usb.h"
|
|
#include "test_scanner_interface.h"
|
|
#include "test_settings.h"
|
|
#include "../include/sane/sanei_config.h"
|
|
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <list>
|
|
#include <numeric>
|
|
#include <exception>
|
|
#include <vector>
|
|
|
|
#ifndef SANE_GENESYS_API_LINKAGE
|
|
#define SANE_GENESYS_API_LINKAGE extern "C"
|
|
#endif
|
|
|
|
namespace genesys {
|
|
|
|
// Data that we allocate to back SANE_Device objects in s_sane_devices
|
|
struct SANE_Device_Data
|
|
{
|
|
std::string name;
|
|
};
|
|
|
|
namespace {
|
|
StaticInit<std::list<Genesys_Scanner>> s_scanners;
|
|
StaticInit<std::vector<SANE_Device>> s_sane_devices;
|
|
StaticInit<std::vector<SANE_Device_Data>> s_sane_devices_data;
|
|
StaticInit<std::vector<SANE_Device*>> s_sane_devices_ptrs;
|
|
StaticInit<std::list<Genesys_Device>> s_devices;
|
|
|
|
// Maximum time for lamp warm-up
|
|
constexpr unsigned WARMUP_TIME = 65;
|
|
} // namespace
|
|
|
|
static SANE_String_Const mode_list[] = {
|
|
SANE_VALUE_SCAN_MODE_COLOR,
|
|
SANE_VALUE_SCAN_MODE_GRAY,
|
|
// SANE_TITLE_HALFTONE, not used
|
|
// SANE_VALUE_SCAN_MODE_LINEART, not used
|
|
nullptr
|
|
};
|
|
|
|
static SANE_String_Const color_filter_list[] = {
|
|
SANE_I18N ("Red"),
|
|
SANE_I18N ("Green"),
|
|
SANE_I18N ("Blue"),
|
|
nullptr
|
|
};
|
|
|
|
static SANE_String_Const cis_color_filter_list[] = {
|
|
SANE_I18N ("Red"),
|
|
SANE_I18N ("Green"),
|
|
SANE_I18N ("Blue"),
|
|
SANE_I18N ("None"),
|
|
nullptr
|
|
};
|
|
|
|
static SANE_Range time_range = {
|
|
0, /* minimum */
|
|
60, /* maximum */
|
|
0 /* quantization */
|
|
};
|
|
|
|
static const SANE_Range u12_range = {
|
|
0, /* minimum */
|
|
4095, /* maximum */
|
|
0 /* quantization */
|
|
};
|
|
|
|
static const SANE_Range u14_range = {
|
|
0, /* minimum */
|
|
16383, /* maximum */
|
|
0 /* quantization */
|
|
};
|
|
|
|
static const SANE_Range u16_range = {
|
|
0, /* minimum */
|
|
65535, /* maximum */
|
|
0 /* quantization */
|
|
};
|
|
|
|
static const SANE_Range percentage_range = {
|
|
float_to_fixed(0), // minimum
|
|
float_to_fixed(100), // maximum
|
|
float_to_fixed(1) // quantization
|
|
};
|
|
|
|
/**
|
|
* range for brightness and contrast
|
|
*/
|
|
static const SANE_Range enhance_range = {
|
|
-100, /* minimum */
|
|
100, /* maximum */
|
|
1 /* quantization */
|
|
};
|
|
|
|
/**
|
|
* range for expiration time
|
|
*/
|
|
static const SANE_Range expiration_range = {
|
|
-1, /* minimum */
|
|
30000, /* maximum */
|
|
1 /* quantization */
|
|
};
|
|
|
|
const Genesys_Sensor& sanei_genesys_find_sensor_any(const Genesys_Device* dev)
|
|
{
|
|
DBG_HELPER(dbg);
|
|
for (const auto& sensor : *s_sensors) {
|
|
if (dev->model->sensor_id == sensor.sensor_id) {
|
|
return sensor;
|
|
}
|
|
}
|
|
throw std::runtime_error("Given device does not have sensor defined");
|
|
}
|
|
|
|
Genesys_Sensor* find_sensor_impl(const Genesys_Device* dev, unsigned dpi, unsigned channels,
|
|
ScanMethod scan_method)
|
|
{
|
|
DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels,
|
|
static_cast<unsigned>(scan_method));
|
|
for (auto& sensor : *s_sensors) {
|
|
if (dev->model->sensor_id == sensor.sensor_id && sensor.resolutions.matches(dpi) &&
|
|
sensor.matches_channel_count(channels) && sensor.method == scan_method)
|
|
{
|
|
return &sensor;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool sanei_genesys_has_sensor(const Genesys_Device* dev, unsigned dpi, unsigned channels,
|
|
ScanMethod scan_method)
|
|
{
|
|
DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels,
|
|
static_cast<unsigned>(scan_method));
|
|
return find_sensor_impl(dev, dpi, channels, scan_method) != nullptr;
|
|
}
|
|
|
|
const Genesys_Sensor& sanei_genesys_find_sensor(const Genesys_Device* dev, unsigned dpi,
|
|
unsigned channels, ScanMethod scan_method)
|
|
{
|
|
DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels,
|
|
static_cast<unsigned>(scan_method));
|
|
const auto* sensor = find_sensor_impl(dev, dpi, channels, scan_method);
|
|
if (sensor)
|
|
return *sensor;
|
|
throw std::runtime_error("Given device does not have sensor defined");
|
|
}
|
|
|
|
Genesys_Sensor& sanei_genesys_find_sensor_for_write(Genesys_Device* dev, unsigned dpi,
|
|
unsigned channels,
|
|
ScanMethod scan_method)
|
|
{
|
|
DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels,
|
|
static_cast<unsigned>(scan_method));
|
|
auto* sensor = find_sensor_impl(dev, dpi, channels, scan_method);
|
|
if (sensor)
|
|
return *sensor;
|
|
throw std::runtime_error("Given device does not have sensor defined");
|
|
}
|
|
|
|
|
|
std::vector<std::reference_wrapper<const Genesys_Sensor>>
|
|
sanei_genesys_find_sensors_all(const Genesys_Device* dev, ScanMethod scan_method)
|
|
{
|
|
DBG_HELPER_ARGS(dbg, "scan_method: %d", static_cast<unsigned>(scan_method));
|
|
std::vector<std::reference_wrapper<const Genesys_Sensor>> ret;
|
|
for (auto& sensor : *s_sensors) {
|
|
if (dev->model->sensor_id == sensor.sensor_id && sensor.method == scan_method) {
|
|
ret.push_back(sensor);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::vector<std::reference_wrapper<Genesys_Sensor>>
|
|
sanei_genesys_find_sensors_all_for_write(Genesys_Device* dev, ScanMethod scan_method)
|
|
{
|
|
DBG_HELPER_ARGS(dbg, "scan_method: %d", static_cast<unsigned>(scan_method));
|
|
std::vector<std::reference_wrapper<Genesys_Sensor>> ret;
|
|
for (auto& sensor : *s_sensors) {
|
|
if (dev->model->sensor_id == sensor.sensor_id && sensor.method == scan_method) {
|
|
ret.push_back(sensor);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void sanei_genesys_init_structs (Genesys_Device * dev)
|
|
{
|
|
DBG_HELPER(dbg);
|
|
|
|
bool gpo_ok = false;
|
|
bool motor_ok = false;
|
|
bool fe_ok = false;
|
|
|
|
/* initialize the GPO data stuff */
|
|
for (const auto& gpo : *s_gpo) {
|
|
if (dev->model->gpio_id == gpo.id) {
|
|
dev->gpo = gpo;
|
|
gpo_ok = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// initialize the motor data stuff
|
|
for (const auto& motor : *s_motors) {
|
|
if (dev->model->motor_id == motor.id) {
|
|
dev->motor = motor;
|
|
motor_ok = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (const auto& frontend : *s_frontends) {
|
|
if (dev->model->adc_id == frontend.id) {
|
|
dev->frontend_initial = frontend;
|
|
dev->frontend = frontend;
|
|
fe_ok = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dev->model->asic_type == AsicType::GL845 ||
|
|
dev->model->asic_type == AsicType::GL846 ||
|
|
dev->model->asic_type == AsicType::GL847 ||
|
|
dev->model->asic_type == AsicType::GL124)
|
|
{
|
|
bool memory_layout_found = false;
|
|
for (const auto& memory_layout : *s_memory_layout) {
|
|
if (memory_layout.models.matches(dev->model->model_id)) {
|
|
dev->memory_layout = memory_layout;
|
|
memory_layout_found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!memory_layout_found) {
|
|
throw SaneException("Could not find memory layout");
|
|
}
|
|
}
|
|
|
|
if (!motor_ok || !gpo_ok || !fe_ok) {
|
|
throw SaneException("bad description(s) for fe/gpo/motor=%d/%d/%d\n",
|
|
static_cast<unsigned>(dev->model->sensor_id),
|
|
static_cast<unsigned>(dev->model->gpio_id),
|
|
static_cast<unsigned>(dev->model->motor_id));
|
|
}
|
|
}
|
|
|
|
/** @brief computes gamma table
|
|
* Generates a gamma table of the given length within 0 and the given
|
|
* maximum value
|
|
* @param gamma_table gamma table to fill
|
|
* @param size size of the table
|
|
* @param maximum value allowed for gamma
|
|
* @param gamma_max maximum gamma value
|
|
* @param gamma gamma to compute values
|
|
* @return a gamma table filled with the computed values
|
|
* */
|
|
void sanei_genesys_create_gamma_table(std::vector<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::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::GL841 &&
|
|
dev.model->asic_type != AsicType::GL843)
|
|
{
|
|
regs_set_exposure(dev.model->asic_type, regs, sensor.exposure);
|
|
}
|
|
|
|
dev.segment_order = sensor.segment_order;
|
|
}
|
|
|
|
void scanner_stop_action(Genesys_Device& dev)
|
|
{
|
|
DBG_HELPER(dbg);
|
|
|
|
switch (dev.model->asic_type) {
|
|
case AsicType::GL841:
|
|
case AsicType::GL842:
|
|
case AsicType::GL843:
|
|
case AsicType::GL845:
|
|
case AsicType::GL846:
|
|
case AsicType::GL847:
|
|
case AsicType::GL124:
|
|
break;
|
|
default:
|
|
throw SaneException("Unsupported asic type");
|
|
}
|
|
|
|
dev.cmd_set->update_home_sensor_gpio(dev);
|
|
|
|
if (scanner_is_motor_stopped(dev)) {
|
|
DBG(DBG_info, "%s: already stopped\n", __func__);
|
|
return;
|
|
}
|
|
|
|
scanner_stop_action_no_move(dev, dev.reg);
|
|
|
|
if (is_testing_mode()) {
|
|
return;
|
|
}
|
|
|
|
for (unsigned i = 0; i < 10; ++i) {
|
|
if (scanner_is_motor_stopped(dev)) {
|
|
return;
|
|
}
|
|
|
|
dev.interface->sleep_ms(100);
|
|
}
|
|
|
|
throw SaneException(SANE_STATUS_IO_ERROR, "could not stop motor");
|
|
}
|
|
|
|
void scanner_stop_action_no_move(Genesys_Device& dev, genesys::Genesys_Register_Set& regs)
|
|
{
|
|
switch (dev.model->asic_type) {
|
|
case AsicType::GL646:
|
|
case AsicType::GL841:
|
|
case AsicType::GL842:
|
|
case AsicType::GL843:
|
|
case AsicType::GL845:
|
|
case AsicType::GL846:
|
|
case AsicType::GL847:
|
|
case AsicType::GL124:
|
|
break;
|
|
default:
|
|
throw SaneException("Unsupported asic type");
|
|
}
|
|
|
|
regs_set_optical_off(dev.model->asic_type, regs);
|
|
// same across all supported ASICs
|
|
dev.interface->write_register(0x01, regs.get8(0x01));
|
|
|
|
// looks like certain scanners lock up if we try to scan immediately after stopping previous
|
|
// action.
|
|
dev.interface->sleep_ms(100);
|
|
}
|
|
|
|
void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, Direction direction)
|
|
{
|
|
DBG_HELPER_ARGS(dbg, "steps=%d direction=%d", steps, static_cast<unsigned>(direction));
|
|
|
|
auto local_reg = dev.reg;
|
|
|
|
unsigned resolution = dev.model->get_resolution_settings(scan_method).get_min_resolution_y();
|
|
|
|
const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 3, scan_method);
|
|
|
|
bool uses_secondary_head = (scan_method == ScanMethod::TRANSPARENCY ||
|
|
scan_method == ScanMethod::TRANSPARENCY_INFRARED) &&
|
|
(!has_flag(dev.model->flags, ModelFlag::UTA_NO_SECONDARY_MOTOR));
|
|
|
|
bool uses_secondary_pos = uses_secondary_head &&
|
|
dev.model->default_method == ScanMethod::FLATBED;
|
|
|
|
if (!dev.is_head_pos_known(ScanHeadId::PRIMARY)) {
|
|
throw SaneException("Unknown head position");
|
|
}
|
|
if (uses_secondary_pos && !dev.is_head_pos_known(ScanHeadId::SECONDARY)) {
|
|
throw SaneException("Unknown head position");
|
|
}
|
|
if (direction == Direction::BACKWARD && steps > dev.head_pos(ScanHeadId::PRIMARY)) {
|
|
throw SaneException("Trying to feed behind the home position %d %d",
|
|
steps, dev.head_pos(ScanHeadId::PRIMARY));
|
|
}
|
|
if (uses_secondary_pos && direction == Direction::BACKWARD &&
|
|
steps > dev.head_pos(ScanHeadId::SECONDARY))
|
|
{
|
|
throw SaneException("Trying to feed behind the home position %d %d",
|
|
steps, dev.head_pos(ScanHeadId::SECONDARY));
|
|
}
|
|
|
|
ScanSession session;
|
|
session.params.xres = resolution;
|
|
session.params.yres = resolution;
|
|
session.params.startx = 0;
|
|
session.params.starty = steps;
|
|
session.params.pixels = 50;
|
|
session.params.lines = 3;
|
|
session.params.depth = 8;
|
|
session.params.channels = 1;
|
|
session.params.scan_method = scan_method;
|
|
session.params.scan_mode = ScanColorMode::GRAY;
|
|
session.params.color_filter = ColorFilter::GREEN;
|
|
session.params.contrast_adjustment = dev.settings.contrast;
|
|
session.params.brightness_adjustment = dev.settings.brightness;
|
|
|
|
session.params.flags = ScanFlag::DISABLE_SHADING |
|
|
ScanFlag::DISABLE_GAMMA |
|
|
ScanFlag::FEEDING |
|
|
ScanFlag::IGNORE_STAGGER_OFFSET |
|
|
ScanFlag::IGNORE_COLOR_OFFSET;
|
|
|
|
if (dev.model->asic_type == AsicType::GL124) {
|
|
session.params.flags |= ScanFlag::DISABLE_BUFFER_FULL_MOVE;
|
|
}
|
|
|
|
if (direction == Direction::BACKWARD) {
|
|
session.params.flags |= ScanFlag::REVERSE;
|
|
}
|
|
|
|
compute_session(&dev, session, sensor);
|
|
|
|
dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session);
|
|
|
|
if (dev.model->asic_type != AsicType::GL843) {
|
|
regs_set_exposure(dev.model->asic_type, local_reg,
|
|
sanei_genesys_fixup_exposure({0, 0, 0}));
|
|
}
|
|
scanner_clear_scan_and_feed_counts(dev);
|
|
|
|
dev.interface->write_registers(local_reg);
|
|
if (uses_secondary_head) {
|
|
dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY_AND_SECONDARY);
|
|
}
|
|
|
|
try {
|
|
scanner_start_action(dev, true);
|
|
} catch (...) {
|
|
catch_all_exceptions(__func__, [&]() {
|
|
dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY);
|
|
});
|
|
catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); });
|
|
// restore original registers
|
|
catch_all_exceptions(__func__, [&]() { dev.interface->write_registers(dev.reg); });
|
|
throw;
|
|
}
|
|
|
|
if (is_testing_mode()) {
|
|
dev.interface->test_checkpoint("feed");
|
|
|
|
dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, direction, steps);
|
|
if (uses_secondary_pos) {
|
|
dev.advance_head_pos_by_steps(ScanHeadId::SECONDARY, direction, steps);
|
|
}
|
|
|
|
scanner_stop_action(dev);
|
|
if (uses_secondary_head) {
|
|
dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// wait until feed count reaches the required value
|
|
if (dev.model->model_id == ModelId::CANON_LIDE_700F) {
|
|
dev.cmd_set->update_home_sensor_gpio(dev);
|
|
}
|
|
|
|
// FIXME: should porbably wait for some timeout
|
|
Status status;
|
|
for (unsigned i = 0;; ++i) {
|
|
status = scanner_read_status(dev);
|
|
if (status.is_feeding_finished || (
|
|
direction == Direction::BACKWARD && status.is_at_home))
|
|
{
|
|
break;
|
|
}
|
|
dev.interface->sleep_ms(10);
|
|
}
|
|
|
|
scanner_stop_action(dev);
|
|
if (uses_secondary_head) {
|
|
dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY);
|
|
}
|
|
|
|
dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, direction, steps);
|
|
if (uses_secondary_pos) {
|
|
dev.advance_head_pos_by_steps(ScanHeadId::SECONDARY, direction, steps);
|
|
}
|
|
|
|
// looks like certain scanners lock up if we scan immediately after feeding
|
|
dev.interface->sleep_ms(100);
|
|
}
|
|
|
|
void scanner_move_to_ta(Genesys_Device& dev)
|
|
{
|
|
DBG_HELPER(dbg);
|
|
|
|
unsigned feed = static_cast<unsigned>((dev.model->y_offset_sensor_to_ta * dev.motor.base_ydpi) /
|
|
MM_PER_INCH);
|
|
scanner_move(dev, dev.model->default_method, feed, Direction::FORWARD);
|
|
}
|
|
|
|
void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home)
|
|
{
|
|
DBG_HELPER_ARGS(dbg, "wait_until_home = %d", wait_until_home);
|
|
|
|
switch (dev.model->asic_type) {
|
|
case AsicType::GL841:
|
|
case AsicType::GL842:
|
|
case AsicType::GL843:
|
|
case AsicType::GL845:
|
|
case AsicType::GL846:
|
|
case AsicType::GL847:
|
|
case AsicType::GL124:
|
|
break;
|
|
default:
|
|
throw SaneException("Unsupported asic type");
|
|
}
|
|
|
|
if (dev.model->is_sheetfed) {
|
|
dbg.vlog(DBG_proc, "sheetfed scanner, skipping going back home");
|
|
return;
|
|
}
|
|
|
|
// FIXME: also check whether the scanner actually has a secondary head
|
|
if ((!dev.is_head_pos_known(ScanHeadId::SECONDARY) ||
|
|
dev.head_pos(ScanHeadId::SECONDARY) > 0 ||
|
|
dev.settings.scan_method == ScanMethod::TRANSPARENCY ||
|
|
dev.settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) &&
|
|
(!has_flag(dev.model->flags, ModelFlag::UTA_NO_SECONDARY_MOTOR)))
|
|
{
|
|
scanner_move_back_home_ta(dev);
|
|
}
|
|
|
|
if (dev.is_head_pos_known(ScanHeadId::PRIMARY) &&
|
|
dev.head_pos(ScanHeadId::PRIMARY) > 1000)
|
|
{
|
|
// leave 500 steps for regular slow back home
|
|
scanner_move(dev, dev.model->default_method, dev.head_pos(ScanHeadId::PRIMARY) - 500,
|
|
Direction::BACKWARD);
|
|
}
|
|
|
|
dev.cmd_set->update_home_sensor_gpio(dev);
|
|
|
|
auto status = scanner_read_reliable_status(dev);
|
|
|
|
if (status.is_at_home) {
|
|
dbg.log(DBG_info, "already at home");
|
|
dev.set_head_pos_zero(ScanHeadId::PRIMARY);
|
|
return;
|
|
}
|
|
|
|
Genesys_Register_Set local_reg = dev.reg;
|
|
unsigned resolution = sanei_genesys_get_lowest_ydpi(&dev);
|
|
|
|
const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 1, dev.model->default_method);
|
|
|
|
ScanSession session;
|
|
session.params.xres = resolution;
|
|
session.params.yres = resolution;
|
|
session.params.startx = 0;
|
|
session.params.starty = 40000;
|
|
session.params.pixels = 50;
|
|
session.params.lines = 3;
|
|
session.params.depth = 8;
|
|
session.params.channels = 1;
|
|
session.params.scan_method = dev.settings.scan_method;
|
|
session.params.scan_mode = ScanColorMode::GRAY;
|
|
session.params.color_filter = ColorFilter::GREEN;
|
|
session.params.contrast_adjustment = dev.settings.contrast;
|
|
session.params.brightness_adjustment = dev.settings.brightness;
|
|
|
|
session.params.flags = ScanFlag::DISABLE_SHADING |
|
|
ScanFlag::DISABLE_GAMMA |
|
|
ScanFlag::IGNORE_STAGGER_OFFSET |
|
|
ScanFlag::IGNORE_COLOR_OFFSET |
|
|
ScanFlag::REVERSE;
|
|
|
|
if (dev.model->asic_type == AsicType::GL843) {
|
|
session.params.flags |= ScanFlag::DISABLE_BUFFER_FULL_MOVE;
|
|
}
|
|
|
|
compute_session(&dev, session, sensor);
|
|
|
|
dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session);
|
|
|
|
scanner_clear_scan_and_feed_counts(dev);
|
|
|
|
dev.interface->write_registers(local_reg);
|
|
|
|
if (dev.model->asic_type == AsicType::GL124) {
|
|
gl124::gl124_setup_scan_gpio(&dev, resolution);
|
|
}
|
|
|
|
try {
|
|
scanner_start_action(dev, true);
|
|
} catch (...) {
|
|
catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); });
|
|
// restore original registers
|
|
catch_all_exceptions(__func__, [&]()
|
|
{
|
|
dev.interface->write_registers(dev.reg);
|
|
});
|
|
throw;
|
|
}
|
|
|
|
dev.cmd_set->update_home_sensor_gpio(dev);
|
|
|
|
if (is_testing_mode()) {
|
|
dev.interface->test_checkpoint("move_back_home");
|
|
dev.set_head_pos_zero(ScanHeadId::PRIMARY);
|
|
return;
|
|
}
|
|
|
|
if (wait_until_home) {
|
|
for (unsigned i = 0; i < 300; ++i) {
|
|
auto status = scanner_read_status(dev);
|
|
|
|
if (status.is_at_home) {
|
|
dbg.log(DBG_info, "reached home position");
|
|
if (dev.model->asic_type == AsicType::GL846 ||
|
|
dev.model->asic_type == AsicType::GL847)
|
|
{
|
|
scanner_stop_action(dev);
|
|
}
|
|
dev.set_head_pos_zero(ScanHeadId::PRIMARY);
|
|
return;
|
|
}
|
|
|
|
dev.interface->sleep_ms(100);
|
|
}
|
|
|
|
// when we come here then the scanner needed too much time for this, so we better stop
|
|
// the motor
|
|
catch_all_exceptions(__func__, [&](){ scanner_stop_action(dev); });
|
|
dev.set_head_pos_unknown(ScanHeadId::PRIMARY | ScanHeadId::SECONDARY);
|
|
throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for scanhead to go home");
|
|
}
|
|
dbg.log(DBG_info, "scanhead is still moving");
|
|
}
|
|
|
|
namespace {
|
|
bool should_use_secondary_motor_mode(Genesys_Device& dev)
|
|
{
|
|
bool should_use = !dev.is_head_pos_known(ScanHeadId::SECONDARY) ||
|
|
!dev.is_head_pos_known(ScanHeadId::PRIMARY) ||
|
|
dev.head_pos(ScanHeadId::SECONDARY) > dev.head_pos(ScanHeadId::PRIMARY);
|
|
bool supports = dev.model->model_id == ModelId::CANON_8600F;
|
|
return should_use && supports;
|
|
}
|
|
|
|
void handle_motor_position_after_move_back_home_ta(Genesys_Device& dev, MotorMode motor_mode)
|
|
{
|
|
if (motor_mode == MotorMode::SECONDARY) {
|
|
dev.set_head_pos_zero(ScanHeadId::SECONDARY);
|
|
return;
|
|
}
|
|
|
|
if (dev.is_head_pos_known(ScanHeadId::PRIMARY)) {
|
|
if (dev.head_pos(ScanHeadId::PRIMARY) > dev.head_pos(ScanHeadId::SECONDARY)) {
|
|
dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, Direction::BACKWARD,
|
|
dev.head_pos(ScanHeadId::SECONDARY));
|
|
} else {
|
|
dev.set_head_pos_zero(ScanHeadId::PRIMARY);
|
|
}
|
|
dev.set_head_pos_zero(ScanHeadId::SECONDARY);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
void scanner_move_back_home_ta(Genesys_Device& dev)
|
|
{
|
|
DBG_HELPER(dbg);
|
|
|
|
switch (dev.model->asic_type) {
|
|
case AsicType::GL842:
|
|
case AsicType::GL843:
|
|
case AsicType::GL845:
|
|
break;
|
|
default:
|
|
throw SaneException("Unsupported asic type");
|
|
}
|
|
|
|
Genesys_Register_Set local_reg = dev.reg;
|
|
|
|
auto scan_method = ScanMethod::TRANSPARENCY;
|
|
unsigned resolution = dev.model->get_resolution_settings(scan_method).get_min_resolution_y();
|
|
|
|
const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 1, scan_method);
|
|
|
|
if (dev.is_head_pos_known(ScanHeadId::SECONDARY) &&
|
|
dev.is_head_pos_known(ScanHeadId::PRIMARY) &&
|
|
dev.head_pos(ScanHeadId::SECONDARY) > 1000 &&
|
|
dev.head_pos(ScanHeadId::SECONDARY) <= dev.head_pos(ScanHeadId::PRIMARY))
|
|
{
|
|
// leave 500 steps for regular slow back home
|
|
scanner_move(dev, scan_method, dev.head_pos(ScanHeadId::SECONDARY) - 500,
|
|
Direction::BACKWARD);
|
|
}
|
|
|
|
ScanSession session;
|
|
session.params.xres = resolution;
|
|
session.params.yres = resolution;
|
|
session.params.startx = 0;
|
|
session.params.starty = 40000;
|
|
session.params.pixels = 50;
|
|
session.params.lines = 3;
|
|
session.params.depth = 8;
|
|
session.params.channels = 1;
|
|
session.params.scan_method = scan_method;
|
|
session.params.scan_mode = ScanColorMode::GRAY;
|
|
session.params.color_filter = ColorFilter::GREEN;
|
|
session.params.contrast_adjustment = dev.settings.contrast;
|
|
session.params.brightness_adjustment = dev.settings.brightness;
|
|
|
|
session.params.flags = ScanFlag::DISABLE_SHADING |
|
|
ScanFlag::DISABLE_GAMMA |
|
|
ScanFlag::IGNORE_STAGGER_OFFSET |
|
|
ScanFlag::IGNORE_COLOR_OFFSET |
|
|
ScanFlag::REVERSE;
|
|
|
|
compute_session(&dev, session, sensor);
|
|
|
|
dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session);
|
|
|
|
scanner_clear_scan_and_feed_counts(dev);
|
|
|
|
dev.interface->write_registers(local_reg);
|
|
|
|
auto motor_mode = should_use_secondary_motor_mode(dev) ? MotorMode::SECONDARY
|
|
: MotorMode::PRIMARY_AND_SECONDARY;
|
|
|
|
dev.cmd_set->set_motor_mode(dev, local_reg, motor_mode);
|
|
|
|
try {
|
|
scanner_start_action(dev, true);
|
|
} catch (...) {
|
|
catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); });
|
|
// restore original registers
|
|
catch_all_exceptions(__func__, [&]() { dev.interface->write_registers(dev.reg); });
|
|
throw;
|
|
}
|
|
|
|
if (is_testing_mode()) {
|
|
dev.interface->test_checkpoint("move_back_home_ta");
|
|
|
|
handle_motor_position_after_move_back_home_ta(dev, motor_mode);
|
|
|
|
scanner_stop_action(dev);
|
|
dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY);
|
|
return;
|
|
}
|
|
|
|
for (unsigned i = 0; i < 1200; ++i) {
|
|
|
|
auto status = scanner_read_status(dev);
|
|
|
|
if (status.is_at_home) {
|
|
dbg.log(DBG_info, "TA reached home position");
|
|
|
|
handle_motor_position_after_move_back_home_ta(dev, motor_mode);
|
|
|
|
scanner_stop_action(dev);
|
|
dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY);
|
|
return;
|
|
}
|
|
|
|
dev.interface->sleep_ms(100);
|
|
}
|
|
|
|
throw SaneException("Timeout waiting for XPA lamp to park");
|
|
}
|
|
|
|
void scanner_search_strip(Genesys_Device& dev, bool forward, bool black)
|
|
{
|
|
DBG_HELPER_ARGS(dbg, "%s %s", black ? "black" : "white", forward ? "forward" : "reverse");
|
|
|
|
if (dev.model->asic_type == AsicType::GL841 && !black && forward) {
|
|
dev.frontend.set_gain(0, 0xff);
|
|
dev.frontend.set_gain(1, 0xff);
|
|
dev.frontend.set_gain(2, 0xff);
|
|
}
|
|
|
|
// set up for a gray scan at lowest dpi
|
|
const auto& resolution_settings = dev.model->get_resolution_settings(dev.settings.scan_method);
|
|
unsigned dpi = resolution_settings.get_min_resolution_x();
|
|
unsigned channels = 1;
|
|
|
|
auto& sensor = sanei_genesys_find_sensor(&dev, dpi, channels, dev.settings.scan_method);
|
|
dev.cmd_set->set_fe(&dev, sensor, AFE_SET);
|
|
scanner_stop_action(dev);
|
|
|
|
|
|
// shading calibration is done with dev.motor.base_ydpi
|
|
unsigned lines = static_cast<unsigned>(dev.model->y_size_calib_mm * dpi / MM_PER_INCH);
|
|
if (dev.model->asic_type == AsicType::GL841) {
|
|
lines = 10; // TODO: use dev.model->search_lines
|
|
lines = static_cast<unsigned>((lines * dpi) / MM_PER_INCH);
|
|
}
|
|
|
|
unsigned pixels = dev.model->x_size_calib_mm * dpi / MM_PER_INCH;
|
|
|
|
dev.set_head_pos_zero(ScanHeadId::PRIMARY);
|
|
|
|
unsigned length = 20;
|
|
if (dev.model->asic_type == AsicType::GL841) {
|
|
// 20 cm max length for calibration sheet
|
|
length = static_cast<unsigned>(((200 * dpi) / MM_PER_INCH) / lines);
|
|
}
|
|
|
|
auto local_reg = dev.reg;
|
|
|
|
ScanSession session;
|
|
session.params.xres = dpi;
|
|
session.params.yres = dpi;
|
|
session.params.startx = 0;
|
|
session.params.starty = 0;
|
|
session.params.pixels = pixels;
|
|
session.params.lines = lines;
|
|
session.params.depth = 8;
|
|
session.params.channels = channels;
|
|
session.params.scan_method = dev.settings.scan_method;
|
|
session.params.scan_mode = ScanColorMode::GRAY;
|
|
session.params.color_filter = ColorFilter::RED;
|
|
session.params.contrast_adjustment = dev.settings.contrast;
|
|
session.params.brightness_adjustment = dev.settings.brightness;
|
|
session.params.flags = ScanFlag::DISABLE_SHADING |
|
|
ScanFlag::DISABLE_GAMMA;
|
|
if (dev.model->asic_type != AsicType::GL841 && !forward) {
|
|
session.params.flags |= ScanFlag::REVERSE;
|
|
}
|
|
compute_session(&dev, session, sensor);
|
|
|
|
dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session);
|
|
|
|
dev.interface->write_registers(local_reg);
|
|
|
|
dev.cmd_set->begin_scan(&dev, sensor, &local_reg, true);
|
|
|
|
if (is_testing_mode()) {
|
|
dev.interface->test_checkpoint("search_strip");
|
|
scanner_stop_action(dev);
|
|
return;
|
|
}
|
|
|
|
wait_until_buffer_non_empty(&dev);
|
|
|
|
// now we're on target, we can read data
|
|
auto image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
|
|
|
|
scanner_stop_action(dev);
|
|
|
|
unsigned pass = 0;
|
|
if (dbg_log_image_data()) {
|
|
char title[80];
|
|
std::sprintf(title, "gl_search_strip_%s_%s%02d.tiff",
|
|
black ? "black" : "white", forward ? "fwd" : "bwd", pass);
|
|
write_tiff_file(title, image);
|
|
}
|
|
|
|
// loop until strip is found or maximum pass number done
|
|
bool found = false;
|
|
while (pass < length && !found) {
|
|
dev.interface->write_registers(local_reg);
|
|
|
|
// now start scan
|
|
dev.cmd_set->begin_scan(&dev, sensor, &local_reg, true);
|
|
|
|
wait_until_buffer_non_empty(&dev);
|
|
|
|
// now we're on target, we can read data
|
|
image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
|
|
|
|
scanner_stop_action(dev);
|
|
|
|
if (dbg_log_image_data()) {
|
|
char title[80];
|
|
std::sprintf(title, "gl_search_strip_%s_%s%02d.tiff",
|
|
black ? "black" : "white",
|
|
forward ? "fwd" : "bwd", static_cast<int>(pass));
|
|
write_tiff_file(title, image);
|
|
}
|
|
|
|
unsigned white_level = 90;
|
|
unsigned black_level = 60;
|
|
|
|
std::size_t count = 0;
|
|
// Search data to find black strip
|
|
// When searching forward, we only need one line of the searched color since we
|
|
// will scan forward. But when doing backward search, we need all the area of the ame color
|
|
if (forward) {
|
|
|
|
for (std::size_t y = 0; y < image.get_height() && !found; y++) {
|
|
count = 0;
|
|
|
|
// count of white/black pixels depending on the color searched
|
|
for (std::size_t x = 0; x < image.get_width(); x++) {
|
|
|
|
// when searching for black, detect white pixels
|
|
if (black && image.get_raw_channel(x, y, 0) > white_level) {
|
|
count++;
|
|
}
|
|
|
|
// when searching for white, detect black pixels
|
|
if (!black && image.get_raw_channel(x, y, 0) < black_level) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
// at end of line, if count >= 3%, line is not fully of the desired color
|
|
// so we must go to next line of the buffer */
|
|
// count*100/pixels < 3
|
|
|
|
auto found_percentage = (count * 100 / image.get_width());
|
|
if (found_percentage < 3) {
|
|
found = 1;
|
|
DBG(DBG_data, "%s: strip found forward during pass %d at line %zu\n", __func__,
|
|
pass, y);
|
|
} else {
|
|
DBG(DBG_data, "%s: pixels=%zu, count=%zu (%zu%%)\n", __func__,
|
|
image.get_width(), count, found_percentage);
|
|
}
|
|
}
|
|
} else {
|
|
/* since calibration scans are done forward, we need the whole area
|
|
to be of the required color when searching backward
|
|
*/
|
|
count = 0;
|
|
for (std::size_t y = 0; y < image.get_height(); y++) {
|
|
// count of white/black pixels depending on the color searched
|
|
for (std::size_t x = 0; x < image.get_width(); x++) {
|
|
// when searching for black, detect white pixels
|
|
if (black && image.get_raw_channel(x, y, 0) > white_level) {
|
|
count++;
|
|
}
|
|
// when searching for white, detect black pixels
|
|
if (!black && image.get_raw_channel(x, y, 0) < black_level) {
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// at end of area, if count >= 3%, area is not fully of the desired color
|
|
// so we must go to next buffer
|
|
auto found_percentage = count * 100 / (image.get_width() * image.get_height());
|
|
if (found_percentage < 3) {
|
|
found = 1;
|
|
DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass);
|
|
} else {
|
|
DBG(DBG_data, "%s: pixels=%zu, count=%zu (%zu%%)\n", __func__, image.get_width(),
|
|
count, found_percentage);
|
|
}
|
|
}
|
|
pass++;
|
|
}
|
|
|
|
if (found) {
|
|
DBG(DBG_info, "%s: %s strip found\n", __func__, black ? "black" : "white");
|
|
} else {
|
|
throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found",
|
|
black ? "black" : "white");
|
|
}
|
|
}
|
|
|
|
static int dark_average_channel(const Image& image, unsigned black, unsigned channel)
|
|
{
|
|
auto channels = get_pixel_channels(image.get_format());
|
|
|
|
unsigned avg[3];
|
|
|
|
// computes average values on black margin
|
|
for (unsigned ch = 0; ch < channels; ch++) {
|
|
avg[ch] = 0;
|
|
unsigned count = 0;
|
|
// FIXME: start with the second line because the black pixels often have noise on the first
|
|
// line; the cause is probably incorrectly cleaned up previous scan
|
|
for (std::size_t y = 1; y < image.get_height(); y++) {
|
|
for (unsigned j = 0; j < black; j++) {
|
|
avg[ch] += image.get_raw_channel(j, y, ch);
|
|
count++;
|
|
}
|
|
}
|
|
if (count > 0) {
|
|
avg[ch] /= count;
|
|
}
|
|
DBG(DBG_info, "%s: avg[%d] = %d\n", __func__, ch, avg[ch]);
|
|
}
|
|
DBG(DBG_info, "%s: average = %d\n", __func__, avg[channel]);
|
|
return avg[channel];
|
|
}
|
|
|
|
bool should_calibrate_only_active_area(const Genesys_Device& dev,
|
|
const Genesys_Settings& settings)
|
|
{
|
|
if (settings.scan_method == ScanMethod::TRANSPARENCY ||
|
|
settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
|
|
{
|
|
if (dev.model->model_id == ModelId::CANON_4400F && settings.xres >= 4800) {
|
|
return true;
|
|
}
|
|
if (dev.model->model_id == ModelId::CANON_8600F && settings.xres == 4800) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void scanner_offset_calibration(Genesys_Device& dev, const Genesys_Sensor& sensor,
|
|
Genesys_Register_Set& regs)
|
|
{
|
|
DBG_HELPER(dbg);
|
|
|
|
if (dev.model->asic_type == AsicType::GL842 &&
|
|
dev.frontend.layout.type != FrontendType::WOLFSON)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL843 &&
|
|
dev.frontend.layout.type != FrontendType::WOLFSON)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL845 ||
|
|
dev.model->asic_type == AsicType::GL846)
|
|
{
|
|
// no gain nor offset for AKM AFE
|
|
std::uint8_t reg04 = dev.interface->read_register(gl846::REG_0x04);
|
|
if ((reg04 & gl846::REG_0x04_FESET) == 0x02) {
|
|
return;
|
|
}
|
|
}
|
|
if (dev.model->asic_type == AsicType::GL847) {
|
|
// no gain nor offset for AKM AFE
|
|
std::uint8_t reg04 = dev.interface->read_register(gl847::REG_0x04);
|
|
if ((reg04 & gl847::REG_0x04_FESET) == 0x02) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL124) {
|
|
std::uint8_t reg0a = dev.interface->read_register(gl124::REG_0x0A);
|
|
if (((reg0a & gl124::REG_0x0A_SIFSEL) >> gl124::REG_0x0AS_SIFSEL) == 3) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
unsigned target_pixels = dev.model->x_size_calib_mm * sensor.full_resolution / MM_PER_INCH;
|
|
unsigned start_pixel = 0;
|
|
unsigned black_pixels = (sensor.black_pixels * sensor.full_resolution) / sensor.full_resolution;
|
|
|
|
unsigned channels = 3;
|
|
unsigned lines = 1;
|
|
unsigned resolution = sensor.full_resolution;
|
|
|
|
const Genesys_Sensor* calib_sensor = &sensor;
|
|
if (dev.model->asic_type == AsicType::GL843) {
|
|
lines = 8;
|
|
|
|
// compute divider factor to compute final pixels number
|
|
const auto& dpihw_sensor = sanei_genesys_find_sensor(&dev, dev.settings.xres, channels,
|
|
dev.settings.scan_method);
|
|
resolution = dpihw_sensor.shading_resolution;
|
|
unsigned factor = sensor.full_resolution / resolution;
|
|
|
|
calib_sensor = &sanei_genesys_find_sensor(&dev, resolution, channels,
|
|
dev.settings.scan_method);
|
|
|
|
target_pixels = dev.model->x_size_calib_mm * resolution / MM_PER_INCH;
|
|
black_pixels = calib_sensor->black_pixels / factor;
|
|
|
|
if (should_calibrate_only_active_area(dev, dev.settings)) {
|
|
float offset = dev.model->x_offset_ta;
|
|
start_pixel = static_cast<int>((offset * calib_sensor->get_optical_resolution()) / MM_PER_INCH);
|
|
|
|
float size = dev.model->x_size_ta;
|
|
target_pixels = static_cast<int>((size * calib_sensor->get_optical_resolution()) / MM_PER_INCH);
|
|
}
|
|
|
|
if (dev.model->model_id == ModelId::CANON_4400F &&
|
|
dev.settings.scan_method == ScanMethod::FLATBED)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dev.model->model_id == ModelId::CANON_5600F) {
|
|
// FIXME: use same approach as for GL843 scanners
|
|
lines = 8;
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL847) {
|
|
calib_sensor = &sanei_genesys_find_sensor(&dev, resolution, channels,
|
|
dev.settings.scan_method);
|
|
}
|
|
|
|
ScanFlag flags = ScanFlag::DISABLE_SHADING |
|
|
ScanFlag::DISABLE_GAMMA |
|
|
ScanFlag::SINGLE_LINE |
|
|
ScanFlag::IGNORE_STAGGER_OFFSET |
|
|
ScanFlag::IGNORE_COLOR_OFFSET;
|
|
|
|
if (dev.settings.scan_method == ScanMethod::TRANSPARENCY ||
|
|
dev.settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
|
|
{
|
|
flags |= ScanFlag::USE_XPA;
|
|
}
|
|
|
|
ScanSession session;
|
|
session.params.xres = resolution;
|
|
session.params.yres = resolution;
|
|
session.params.startx = start_pixel;
|
|
session.params.starty = 0;
|
|
session.params.pixels = target_pixels;
|
|
session.params.lines = lines;
|
|
session.params.depth = 8;
|
|
session.params.channels = channels;
|
|
session.params.scan_method = dev.settings.scan_method;
|
|
session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
|
|
session.params.color_filter = dev.model->asic_type == AsicType::GL843 ? ColorFilter::RED
|
|
: dev.settings.color_filter;
|
|
session.params.contrast_adjustment = dev.settings.contrast;
|
|
session.params.brightness_adjustment = dev.settings.brightness;
|
|
session.params.flags = flags;
|
|
compute_session(&dev, session, *calib_sensor);
|
|
|
|
dev.cmd_set->init_regs_for_scan_session(&dev, *calib_sensor, ®s, session);
|
|
|
|
unsigned output_pixels = session.output_pixels;
|
|
|
|
sanei_genesys_set_motor_power(regs, false);
|
|
|
|
int top[3], bottom[3];
|
|
int topavg[3], bottomavg[3], avg[3];
|
|
|
|
// init gain and offset
|
|
for (unsigned ch = 0; ch < 3; ch++)
|
|
{
|
|
bottom[ch] = 10;
|
|
dev.frontend.set_offset(ch, bottom[ch]);
|
|
dev.frontend.set_gain(ch, 0);
|
|
}
|
|
dev.cmd_set->set_fe(&dev, *calib_sensor, AFE_SET);
|
|
|
|
// scan with bottom AFE settings
|
|
dev.interface->write_registers(regs);
|
|
DBG(DBG_info, "%s: starting first line reading\n", __func__);
|
|
|
|
dev.cmd_set->begin_scan(&dev, *calib_sensor, ®s, true);
|
|
|
|
if (is_testing_mode()) {
|
|
dev.interface->test_checkpoint("offset_calibration");
|
|
if (dev.model->asic_type == AsicType::GL842 ||
|
|
dev.model->asic_type == AsicType::GL843)
|
|
{
|
|
scanner_stop_action_no_move(dev, regs);
|
|
}
|
|
return;
|
|
}
|
|
|
|
Image first_line;
|
|
if (dev.model->asic_type == AsicType::GL842 ||
|
|
dev.model->asic_type == AsicType::GL843)
|
|
{
|
|
first_line = read_unshuffled_image_from_scanner(&dev, session,
|
|
session.output_total_bytes_raw);
|
|
scanner_stop_action_no_move(dev, regs);
|
|
} else {
|
|
first_line = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
|
|
|
|
if (dev.model->model_id == ModelId::CANON_5600F) {
|
|
scanner_stop_action_no_move(dev, regs);
|
|
}
|
|
}
|
|
|
|
if (dbg_log_image_data()) {
|
|
char fn[40];
|
|
std::snprintf(fn, 40, "gl843_bottom_offset_%03d_%03d_%03d.tiff",
|
|
bottom[0], bottom[1], bottom[2]);
|
|
write_tiff_file(fn, first_line);
|
|
}
|
|
|
|
for (unsigned ch = 0; ch < 3; ch++) {
|
|
bottomavg[ch] = dark_average_channel(first_line, black_pixels, ch);
|
|
DBG(DBG_info, "%s: bottom avg %d=%d\n", __func__, ch, bottomavg[ch]);
|
|
}
|
|
|
|
// now top value
|
|
for (unsigned ch = 0; ch < 3; ch++) {
|
|
top[ch] = 255;
|
|
dev.frontend.set_offset(ch, top[ch]);
|
|
}
|
|
dev.cmd_set->set_fe(&dev, *calib_sensor, AFE_SET);
|
|
|
|
// scan with top AFE values
|
|
dev.interface->write_registers(regs);
|
|
DBG(DBG_info, "%s: starting second line reading\n", __func__);
|
|
|
|
dev.cmd_set->begin_scan(&dev, *calib_sensor, ®s, true);
|
|
|
|
Image second_line;
|
|
if (dev.model->asic_type == AsicType::GL842 ||
|
|
dev.model->asic_type == AsicType::GL843)
|
|
{
|
|
second_line = read_unshuffled_image_from_scanner(&dev, session,
|
|
session.output_total_bytes_raw);
|
|
scanner_stop_action_no_move(dev, regs);
|
|
} else {
|
|
second_line = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
|
|
|
|
if (dev.model->model_id == ModelId::CANON_5600F) {
|
|
scanner_stop_action_no_move(dev, regs);
|
|
}
|
|
}
|
|
|
|
for (unsigned ch = 0; ch < 3; ch++){
|
|
topavg[ch] = dark_average_channel(second_line, black_pixels, ch);
|
|
DBG(DBG_info, "%s: top avg %d=%d\n", __func__, ch, topavg[ch]);
|
|
}
|
|
|
|
unsigned pass = 0;
|
|
|
|
std::vector<std::uint8_t> debug_image;
|
|
std::size_t debug_image_lines = 0;
|
|
std::string debug_image_info;
|
|
|
|
// loop until acceptable level
|
|
while ((pass < 32) && ((top[0] - bottom[0] > 1) ||
|
|
(top[1] - bottom[1] > 1) ||
|
|
(top[2] - bottom[2] > 1)))
|
|
{
|
|
pass++;
|
|
|
|
for (unsigned ch = 0; ch < 3; ch++) {
|
|
if (top[ch] - bottom[ch] > 1) {
|
|
dev.frontend.set_offset(ch, (top[ch] + bottom[ch]) / 2);
|
|
}
|
|
}
|
|
dev.cmd_set->set_fe(&dev, *calib_sensor, AFE_SET);
|
|
|
|
// scan with no move
|
|
dev.interface->write_registers(regs);
|
|
DBG(DBG_info, "%s: starting second line reading\n", __func__);
|
|
dev.cmd_set->begin_scan(&dev, *calib_sensor, ®s, true);
|
|
|
|
if (dev.model->asic_type == AsicType::GL842 ||
|
|
dev.model->asic_type == AsicType::GL843)
|
|
{
|
|
second_line = read_unshuffled_image_from_scanner(&dev, session,
|
|
session.output_total_bytes_raw);
|
|
scanner_stop_action_no_move(dev, regs);
|
|
} else {
|
|
second_line = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
|
|
|
|
if (dev.model->model_id == ModelId::CANON_5600F) {
|
|
scanner_stop_action_no_move(dev, regs);
|
|
}
|
|
}
|
|
|
|
if (dbg_log_image_data()) {
|
|
char title[100];
|
|
std::snprintf(title, 100, "lines: %d pixels_per_line: %d offsets[0..2]: %d %d %d\n",
|
|
lines, output_pixels,
|
|
dev.frontend.get_offset(0),
|
|
dev.frontend.get_offset(1),
|
|
dev.frontend.get_offset(2));
|
|
debug_image_info += title;
|
|
std::copy(second_line.get_row_ptr(0),
|
|
second_line.get_row_ptr(0) + second_line.get_row_bytes() * second_line.get_height(),
|
|
std::back_inserter(debug_image));
|
|
debug_image_lines += lines;
|
|
}
|
|
|
|
for (unsigned ch = 0; ch < 3; ch++) {
|
|
avg[ch] = dark_average_channel(second_line, black_pixels, ch);
|
|
DBG(DBG_info, "%s: avg[%d]=%d offset=%d\n", __func__, ch, avg[ch],
|
|
dev.frontend.get_offset(ch));
|
|
}
|
|
|
|
// compute new boundaries
|
|
for (unsigned ch = 0; ch < 3; ch++) {
|
|
if (topavg[ch] >= avg[ch]) {
|
|
topavg[ch] = avg[ch];
|
|
top[ch] = dev.frontend.get_offset(ch);
|
|
} else {
|
|
bottomavg[ch] = avg[ch];
|
|
bottom[ch] = dev.frontend.get_offset(ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dbg_log_image_data()) {
|
|
sanei_genesys_write_file("gl_offset_all_desc.txt",
|
|
reinterpret_cast<const std::uint8_t*>(debug_image_info.data()),
|
|
debug_image_info.size());
|
|
write_tiff_file("gl_offset_all.tiff", debug_image.data(), session.params.depth, channels,
|
|
output_pixels, debug_image_lines);
|
|
}
|
|
|
|
DBG(DBG_info, "%s: offset=(%d,%d,%d)\n", __func__,
|
|
dev.frontend.get_offset(0),
|
|
dev.frontend.get_offset(1),
|
|
dev.frontend.get_offset(2));
|
|
}
|
|
|
|
/* With offset and coarse calibration we only want to get our input range into
|
|
a reasonable shape. the fine calibration of the upper and lower bounds will
|
|
be done with shading.
|
|
*/
|
|
void scanner_coarse_gain_calibration(Genesys_Device& dev, const Genesys_Sensor& sensor,
|
|
Genesys_Register_Set& regs, unsigned dpi)
|
|
{
|
|
DBG_HELPER_ARGS(dbg, "dpi = %d", dpi);
|
|
|
|
if (dev.model->asic_type == AsicType::GL842 &&
|
|
dev.frontend.layout.type != FrontendType::WOLFSON)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL843 &&
|
|
dev.frontend.layout.type != FrontendType::WOLFSON)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL845 ||
|
|
dev.model->asic_type == AsicType::GL846)
|
|
{
|
|
// no gain nor offset for AKM AFE
|
|
std::uint8_t reg04 = dev.interface->read_register(gl846::REG_0x04);
|
|
if ((reg04 & gl846::REG_0x04_FESET) == 0x02) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL847) {
|
|
// no gain nor offset for AKM AFE
|
|
std::uint8_t reg04 = dev.interface->read_register(gl847::REG_0x04);
|
|
if ((reg04 & gl847::REG_0x04_FESET) == 0x02) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL124) {
|
|
// no gain nor offset for TI AFE
|
|
std::uint8_t reg0a = dev.interface->read_register(gl124::REG_0x0A);
|
|
if (((reg0a & gl124::REG_0x0A_SIFSEL) >> gl124::REG_0x0AS_SIFSEL) == 3) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL841) {
|
|
// feed to white strip if needed
|
|
if (dev.model->y_offset_calib_white > 0) {
|
|
unsigned move = static_cast<unsigned>(
|
|
(dev.model->y_offset_calib_white * (dev.motor.base_ydpi)) / MM_PER_INCH);
|
|
scanner_move(dev, dev.model->default_method, move, Direction::FORWARD);
|
|
}
|
|
}
|
|
|
|
// coarse gain calibration is always done in color mode
|
|
unsigned channels = 3;
|
|
|
|
unsigned resolution = sensor.full_resolution;
|
|
if (dev.model->asic_type == AsicType::GL841) {
|
|
const auto& dpihw_sensor = sanei_genesys_find_sensor(&dev, dev.settings.xres, channels,
|
|
dev.settings.scan_method);
|
|
resolution = dpihw_sensor.shading_resolution;
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL842 ||
|
|
dev.model->asic_type == AsicType::GL843)
|
|
{
|
|
const auto& dpihw_sensor = sanei_genesys_find_sensor(&dev, dpi, channels,
|
|
dev.settings.scan_method);
|
|
resolution = dpihw_sensor.shading_resolution;
|
|
}
|
|
|
|
float coeff = 1;
|
|
|
|
// Follow CKSEL
|
|
if (dev.model->sensor_id == SensorId::CCD_KVSS080 ||
|
|
dev.model->asic_type == AsicType::GL845 ||
|
|
dev.model->asic_type == AsicType::GL846 ||
|
|
dev.model->asic_type == AsicType::GL847 ||
|
|
dev.model->asic_type == AsicType::GL124)
|
|
{
|
|
if (dev.settings.xres < sensor.full_resolution) {
|
|
coeff = 0.9f;
|
|
}
|
|
}
|
|
|
|
unsigned lines = 10;
|
|
if (dev.model->asic_type == AsicType::GL841) {
|
|
lines = 1;
|
|
}
|
|
|
|
const Genesys_Sensor* calib_sensor = &sensor;
|
|
if (dev.model->asic_type == AsicType::GL841 ||
|
|
dev.model->asic_type == AsicType::GL842 ||
|
|
dev.model->asic_type == AsicType::GL843 ||
|
|
dev.model->asic_type == AsicType::GL847)
|
|
{
|
|
calib_sensor = &sanei_genesys_find_sensor(&dev, resolution, channels,
|
|
dev.settings.scan_method);
|
|
}
|
|
|
|
ScanFlag flags = ScanFlag::DISABLE_SHADING |
|
|
ScanFlag::DISABLE_GAMMA |
|
|
ScanFlag::SINGLE_LINE |
|
|
ScanFlag::IGNORE_STAGGER_OFFSET |
|
|
ScanFlag::IGNORE_COLOR_OFFSET;
|
|
|
|
if (dev.settings.scan_method == ScanMethod::TRANSPARENCY ||
|
|
dev.settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
|
|
{
|
|
flags |= ScanFlag::USE_XPA;
|
|
}
|
|
|
|
ScanSession session;
|
|
session.params.xres = resolution;
|
|
session.params.yres = dev.model->asic_type == AsicType::GL841 ? dev.settings.yres : resolution;
|
|
session.params.startx = 0;
|
|
session.params.starty = 0;
|
|
session.params.pixels = dev.model->x_size_calib_mm * resolution / MM_PER_INCH;
|
|
session.params.lines = lines;
|
|
session.params.depth = dev.model->asic_type == AsicType::GL841 ? 16 : 8;
|
|
session.params.channels = channels;
|
|
session.params.scan_method = dev.settings.scan_method;
|
|
session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
|
|
session.params.color_filter = dev.settings.color_filter;
|
|
session.params.contrast_adjustment = dev.settings.contrast;
|
|
session.params.brightness_adjustment = dev.settings.brightness;
|
|
session.params.flags = flags;
|
|
compute_session(&dev, session, *calib_sensor);
|
|
|
|
std::size_t pixels = session.output_pixels;
|
|
|
|
try {
|
|
dev.cmd_set->init_regs_for_scan_session(&dev, *calib_sensor, ®s, session);
|
|
} catch (...) {
|
|
if (dev.model->asic_type != AsicType::GL841) {
|
|
catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); });
|
|
}
|
|
throw;
|
|
}
|
|
|
|
if (dev.model->asic_type != AsicType::GL841) {
|
|
sanei_genesys_set_motor_power(regs, false);
|
|
}
|
|
|
|
dev.interface->write_registers(regs);
|
|
|
|
if (dev.model->asic_type != AsicType::GL841) {
|
|
dev.cmd_set->set_fe(&dev, *calib_sensor, AFE_SET);
|
|
}
|
|
dev.cmd_set->begin_scan(&dev, *calib_sensor, ®s, true);
|
|
|
|
if (is_testing_mode()) {
|
|
dev.interface->test_checkpoint("coarse_gain_calibration");
|
|
scanner_stop_action(dev);
|
|
dev.cmd_set->move_back_home(&dev, true);
|
|
return;
|
|
}
|
|
|
|
Image image;
|
|
if (dev.model->asic_type == AsicType::GL842 ||
|
|
dev.model->asic_type == AsicType::GL843)
|
|
{
|
|
image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes_raw);
|
|
} else if (dev.model->asic_type == AsicType::GL124) {
|
|
// BUG: we probably want to read whole image, not just first line
|
|
image = read_unshuffled_image_from_scanner(&dev, session, session.output_line_bytes);
|
|
} else {
|
|
image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes);
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL842 ||
|
|
dev.model->asic_type == AsicType::GL843)
|
|
{
|
|
scanner_stop_action_no_move(dev, regs);
|
|
}
|
|
|
|
if (dbg_log_image_data()) {
|
|
write_tiff_file("gl_coarse_gain.tiff", image);
|
|
}
|
|
|
|
for (unsigned ch = 0; ch < channels; ch++) {
|
|
float curr_output = 0;
|
|
float target_value = 0;
|
|
|
|
if (dev.model->asic_type == AsicType::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::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, ®s, session);
|
|
|
|
if (dev.model->asic_type == AsicType::GL841) {
|
|
dev.interface->write_registers(regs); // FIXME: remove this
|
|
}
|
|
|
|
std::uint16_t exp[3];
|
|
|
|
if (dev.model->asic_type == AsicType::GL841) {
|
|
exp[0] = sensor.exposure.red;
|
|
exp[1] = sensor.exposure.green;
|
|
exp[2] = sensor.exposure.blue;
|
|
} else {
|
|
exp[0] = calib_sensor.exposure.red;
|
|
exp[1] = calib_sensor.exposure.green;
|
|
exp[2] = calib_sensor.exposure.blue;
|
|
}
|
|
|
|
std::uint16_t target = sensor.gain_white_ref * 256;
|
|
|
|
std::uint16_t min_exposure = 500; // only gl841
|
|
std::uint16_t max_exposure = ((exp[0] + exp[1] + exp[2]) / 3) * 2; // only gl841
|
|
|
|
std::uint16_t top[3] = {};
|
|
std::uint16_t bottom[3] = {};
|
|
|
|
if (dev.model->asic_type == AsicType::GL845 ||
|
|
dev.model->asic_type == AsicType::GL846)
|
|
{
|
|
bottom[0] = 29000;
|
|
bottom[1] = 29000;
|
|
bottom[2] = 29000;
|
|
|
|
top[0] = 41000;
|
|
top[1] = 51000;
|
|
top[2] = 51000;
|
|
} else if (dev.model->asic_type == AsicType::GL847) {
|
|
bottom[0] = 28000;
|
|
bottom[1] = 28000;
|
|
bottom[2] = 28000;
|
|
|
|
top[0] = 32000;
|
|
top[1] = 32000;
|
|
top[2] = 32000;
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL845 ||
|
|
dev.model->asic_type == AsicType::GL846 ||
|
|
dev.model->asic_type == AsicType::GL847 ||
|
|
dev.model->asic_type == AsicType::GL124)
|
|
{
|
|
sanei_genesys_set_motor_power(regs, false);
|
|
}
|
|
|
|
bool acceptable = false;
|
|
for (unsigned i_test = 0; i_test < 100 && !acceptable; ++i_test) {
|
|
regs_set_exposure(dev.model->asic_type, regs, { exp[0], exp[1], exp[2] });
|
|
|
|
dev.interface->write_registers(regs);
|
|
|
|
dbg.log(DBG_info, "starting line reading");
|
|
dev.cmd_set->begin_scan(&dev, calib_sensor, ®s, true);
|
|
|
|
if (is_testing_mode()) {
|
|
dev.interface->test_checkpoint("led_calibration");
|
|
if (dev.model->asic_type == AsicType::GL841) {
|
|
scanner_stop_action(dev);
|
|
dev.cmd_set->move_back_home(&dev, true);
|
|
return { exp[0], exp[1], exp[2] };
|
|
} else if (dev.model->asic_type == AsicType::GL124) {
|
|
scanner_stop_action(dev);
|
|
return calib_sensor.exposure;
|
|
} else {
|
|
scanner_stop_action(dev);
|
|
dev.cmd_set->move_back_home(&dev, true);
|
|
return calib_sensor.exposure;
|
|
}
|
|
}
|
|
|
|
auto image = read_unshuffled_image_from_scanner(&dev, session, session.output_line_bytes);
|
|
|
|
scanner_stop_action(dev);
|
|
|
|
if (dbg_log_image_data()) {
|
|
char fn[30];
|
|
std::snprintf(fn, 30, "gl_led_%02d.tiff", i_test);
|
|
write_tiff_file(fn, image);
|
|
}
|
|
|
|
int avg[3];
|
|
for (unsigned ch = 0; ch < channels; ch++) {
|
|
avg[ch] = 0;
|
|
for (std::size_t x = 0; x < image.get_width(); x++) {
|
|
avg[ch] += image.get_raw_channel(x, 0, ch);
|
|
}
|
|
avg[ch] /= image.get_width();
|
|
}
|
|
|
|
dbg.vlog(DBG_info, "average: %d, %d, %d", avg[0], avg[1], avg[2]);
|
|
|
|
acceptable = true;
|
|
|
|
if (dev.model->asic_type == AsicType::GL841) {
|
|
if (avg[0] < avg[1] * 0.95 || avg[1] < avg[0] * 0.95 ||
|
|
avg[0] < avg[2] * 0.95 || avg[2] < avg[0] * 0.95 ||
|
|
avg[1] < avg[2] * 0.95 || avg[2] < avg[1] * 0.95)
|
|
{
|
|
acceptable = false;
|
|
}
|
|
|
|
// led exposure is not acceptable if white level is too low.
|
|
// ~80 hardcoded value for white level
|
|
if (avg[0] < 20000 || avg[1] < 20000 || avg[2] < 20000) {
|
|
acceptable = false;
|
|
}
|
|
|
|
// for scanners using target value
|
|
if (target > 0) {
|
|
acceptable = true;
|
|
for (unsigned i = 0; i < 3; i++) {
|
|
// we accept +- 2% delta from target
|
|
if (std::abs(avg[i] - target) > target / 50) {
|
|
exp[i] = (exp[i] * target) / avg[i];
|
|
acceptable = false;
|
|
}
|
|
}
|
|
} else {
|
|
if (!acceptable) {
|
|
unsigned avga = (avg[0] + avg[1] + avg[2]) / 3;
|
|
exp[0] = (exp[0] * avga) / avg[0];
|
|
exp[1] = (exp[1] * avga) / avg[1];
|
|
exp[2] = (exp[2] * avga) / avg[2];
|
|
/* Keep the resulting exposures below this value. Too long exposure drives
|
|
the ccd into saturation. We may fix this by relying on the fact that
|
|
we get a striped scan without shading, by means of statistical calculation
|
|
*/
|
|
unsigned avge = (exp[0] + exp[1] + exp[2]) / 3;
|
|
|
|
if (avge > max_exposure) {
|
|
exp[0] = (exp[0] * max_exposure) / avge;
|
|
exp[1] = (exp[1] * max_exposure) / avge;
|
|
exp[2] = (exp[2] * max_exposure) / avge;
|
|
}
|
|
if (avge < min_exposure) {
|
|
exp[0] = (exp[0] * min_exposure) / avge;
|
|
exp[1] = (exp[1] * min_exposure) / avge;
|
|
exp[2] = (exp[2] * min_exposure) / avge;
|
|
}
|
|
|
|
}
|
|
}
|
|
} else if (dev.model->asic_type == AsicType::GL845 ||
|
|
dev.model->asic_type == AsicType::GL846)
|
|
{
|
|
for (unsigned i = 0; i < 3; i++) {
|
|
if (avg[i] < bottom[i]) {
|
|
if (avg[i] != 0) {
|
|
exp[i] = (exp[i] * bottom[i]) / avg[i];
|
|
} else {
|
|
exp[i] *= 10;
|
|
}
|
|
acceptable = false;
|
|
}
|
|
if (avg[i] > top[i]) {
|
|
if (avg[i] != 0) {
|
|
exp[i] = (exp[i] * top[i]) / avg[i];
|
|
} else {
|
|
exp[i] *= 10;
|
|
}
|
|
acceptable = false;
|
|
}
|
|
}
|
|
} else if (dev.model->asic_type == AsicType::GL847) {
|
|
for (unsigned i = 0; i < 3; i++) {
|
|
if (avg[i] < bottom[i] || avg[i] > top[i]) {
|
|
auto target = (bottom[i] + top[i]) / 2;
|
|
if (avg[i] != 0) {
|
|
exp[i] = (exp[i] * target) / avg[i];
|
|
} else {
|
|
exp[i] *= 10;
|
|
}
|
|
|
|
acceptable = false;
|
|
}
|
|
}
|
|
} else if (dev.model->asic_type == AsicType::GL124) {
|
|
for (unsigned i = 0; i < 3; i++) {
|
|
// we accept +- 2% delta from target
|
|
if (std::abs(avg[i] - target) > target / 50) {
|
|
float prev_weight = 0.5;
|
|
if (avg[i] != 0) {
|
|
exp[i] = exp[i] * prev_weight + ((exp[i] * target) / avg[i]) * (1 - prev_weight);
|
|
} else {
|
|
exp[i] = exp[i] * prev_weight + (exp[i] * 10) * (1 - prev_weight);
|
|
}
|
|
acceptable = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL845 ||
|
|
dev.model->asic_type == AsicType::GL846 ||
|
|
dev.model->asic_type == AsicType::GL847 ||
|
|
dev.model->asic_type == AsicType::GL124)
|
|
{
|
|
// set these values as final ones for scan
|
|
regs_set_exposure(dev.model->asic_type, dev.reg, { exp[0], exp[1], exp[2] });
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL841 ||
|
|
dev.model->asic_type == AsicType::GL842 ||
|
|
dev.model->asic_type == AsicType::GL843)
|
|
{
|
|
dev.cmd_set->move_back_home(&dev, true);
|
|
}
|
|
|
|
if (dev.model->asic_type == AsicType::GL845 ||
|
|
dev.model->asic_type == AsicType::GL846 ||
|
|
dev.model->asic_type == AsicType::GL847)
|
|
{
|
|
if (move > 20) {
|
|
dev.cmd_set->move_back_home(&dev, true);
|
|
}
|
|
}
|
|
|
|
dbg.vlog(DBG_info,"acceptable exposure: %d, %d, %d\n", exp[0], exp[1], exp[2]);
|
|
|
|
return { exp[0], exp[1], exp[2] };
|
|
}
|
|
|
|
void sanei_genesys_calculate_zmod(bool two_table,
|
|
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, ®s, session);
|
|
}
|
|
|
|
// High-level start of scanning
|
|
static void genesys_start_scan(Genesys_Device* dev, bool lamp_off)
|
|
{
|
|
DBG_HELPER(dbg);
|
|
unsigned int steps, expected;
|
|
|
|
|
|
/* since not all scanners are set to wait for head to park
|
|
* we check we are not still parking before starting a new scan */
|
|
if (dev->parking) {
|
|
sanei_genesys_wait_for_home(dev);
|
|
}
|
|
|
|
// disable power saving
|
|
dev->cmd_set->save_power(dev, false);
|
|
|
|
/* wait for lamp warmup : until a warmup for TRANSPARENCY is designed, skip
|
|
* it when scanning from XPA. */
|
|
if (has_flag(dev->model->flags, ModelFlag::WARMUP) &&
|
|
(dev->settings.scan_method != ScanMethod::TRANSPARENCY_INFRARED))
|
|
{
|
|
if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
|
|
dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
|
|
{
|
|
scanner_move_to_ta(*dev);
|
|
}
|
|
|
|
genesys_warmup_lamp(dev);
|
|
}
|
|
|
|
/* set top left x and y values by scanning the internals if flatbed scanners */
|
|
if (!dev->model->is_sheetfed) {
|
|
// TODO: check we can drop this since we cannot have the scanner's head wandering here
|
|
dev->parking = false;
|
|
dev->cmd_set->move_back_home(dev, true);
|
|
}
|
|
|
|
/* move to calibration area for transparency adapter */
|
|
if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
|
|
dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
|
|
{
|
|
scanner_move_to_ta(*dev);
|
|
}
|
|
|
|
/* load document if needed (for sheetfed scanner for instance) */
|
|
if (dev->model->is_sheetfed) {
|
|
dev->cmd_set->load_document(dev);
|
|
}
|
|
|
|
auto& sensor = sanei_genesys_find_sensor_for_write(dev, dev->settings.xres,
|
|
dev->settings.get_channels(),
|
|
dev->settings.scan_method);
|
|
|
|
// send gamma tables. They have been set to device or user value
|
|
// when setting option value */
|
|
dev->cmd_set->send_gamma_table(dev, sensor);
|
|
|
|
/* try to use cached calibration first */
|
|
if (!genesys_restore_calibration (dev, sensor))
|
|
{
|
|
// calibration : sheetfed scanners can't calibrate before each scan.
|
|
// also don't run calibration for those scanners where all passes are disabled
|
|
bool shading_disabled =
|
|
has_flag(dev->model->flags, ModelFlag::DISABLE_ADC_CALIBRATION) &&
|
|
has_flag(dev->model->flags, ModelFlag::DISABLE_EXPOSURE_CALIBRATION) &&
|
|
has_flag(dev->model->flags, ModelFlag::DISABLE_SHADING_CALIBRATION);
|
|
if (!shading_disabled && !dev->model->is_sheetfed) {
|
|
genesys_scanner_calibration(dev, sensor);
|
|
genesys_save_calibration(dev, sensor);
|
|
} else {
|
|
DBG(DBG_warn, "%s: no calibration done\n", __func__);
|
|
}
|
|
}
|
|
|
|
dev->cmd_set->wait_for_motor_stop(dev);
|
|
|
|
if (dev->cmd_set->needs_home_before_init_regs_for_scan(dev)) {
|
|
dev->cmd_set->move_back_home(dev, true);
|
|
}
|
|
|
|
if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
|
|
dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
|
|
{
|
|
scanner_move_to_ta(*dev);
|
|
}
|
|
|
|
init_regs_for_scan(*dev, sensor, dev->reg);
|
|
|
|
/* no lamp during scan */
|
|
if (lamp_off) {
|
|
sanei_genesys_set_lamp_power(dev, sensor, dev->reg, false);
|
|
}
|
|
|
|
/* GL124 is using SHDAREA, so we have to wait for scan to be set up before
|
|
* sending shading data */
|
|
if (dev->cmd_set->has_send_shading_data() &&
|
|
!has_flag(dev->model->flags, ModelFlag::DISABLE_SHADING_CALIBRATION))
|
|
{
|
|
genesys_send_shading_coefficient(dev, sensor);
|
|
}
|
|
|
|
// now send registers for scan
|
|
dev->interface->write_registers(dev->reg);
|
|
|
|
// start effective scan
|
|
dev->cmd_set->begin_scan(dev, sensor, &dev->reg, true);
|
|
|
|
if (is_testing_mode()) {
|
|
dev->interface->test_checkpoint("start_scan");
|
|
return;
|
|
}
|
|
|
|
/*do we really need this? the valid data check should be sufficient -- pierre*/
|
|
/* waits for head to reach scanning position */
|
|
expected = dev->reg.get8(0x3d) * 65536
|
|
+ dev->reg.get8(0x3e) * 256
|
|
+ dev->reg.get8(0x3f);
|
|
do
|
|
{
|
|
// wait some time between each test to avoid overloading USB and CPU
|
|
dev->interface->sleep_ms(100);
|
|
sanei_genesys_read_feed_steps (dev, &steps);
|
|
}
|
|
while (steps < expected);
|
|
|
|
wait_until_buffer_non_empty(dev);
|
|
|
|
// we wait for at least one word of valid scan data
|
|
// this is also done in sanei_genesys_read_data_from_scanner -- pierre
|
|
if (!dev->model->is_sheetfed) {
|
|
do {
|
|
dev->interface->sleep_ms(100);
|
|
sanei_genesys_read_valid_words(dev, &steps);
|
|
}
|
|
while (steps < 1);
|
|
}
|
|
}
|
|
|
|
/* this function does the effective data read in a manner that suits
|
|
the scanner. It does data reordering and resizing if need.
|
|
It also manages EOF and I/O errors, and line distance correction.
|
|
Returns true on success, false on end-of-file.
|
|
*/
|
|
static void genesys_read_ordered_data(Genesys_Device* dev, SANE_Byte* destination, size_t* len)
|
|
{
|
|
DBG_HELPER(dbg);
|
|
size_t bytes = 0;
|
|
|
|
if (!dev->read_active) {
|
|
*len = 0;
|
|
throw SaneException("read is not active");
|
|
}
|
|
|
|
DBG(DBG_info, "%s: frontend requested %zu bytes\n", __func__, *len);
|
|
DBG(DBG_info, "%s: bytes_to_read=%zu, total_bytes_read=%zu\n", __func__,
|
|
dev->total_bytes_to_read, dev->total_bytes_read);
|
|
|
|
/* is there data left to scan */
|
|
if (dev->total_bytes_read >= dev->total_bytes_to_read)
|
|
{
|
|
/* issue park command immediately in case scanner can handle it
|
|
* so we save time */
|
|
if (!dev->model->is_sheetfed && !has_flag(dev->model->flags, ModelFlag::MUST_WAIT) &&
|
|
!dev->parking)
|
|
{
|
|
dev->cmd_set->move_back_home(dev, false);
|
|
dev->parking = true;
|
|
}
|
|
throw SaneException(SANE_STATUS_EOF, "nothing more to scan: EOF");
|
|
}
|
|
|
|
if (is_testing_mode()) {
|
|
if (dev->total_bytes_read + *len > dev->total_bytes_to_read) {
|
|
*len = dev->total_bytes_to_read - dev->total_bytes_read;
|
|
}
|
|
dev->total_bytes_read += *len;
|
|
} else {
|
|
if (dev->model->is_sheetfed) {
|
|
dev->cmd_set->detect_document_end(dev);
|
|
}
|
|
|
|
if (dev->total_bytes_read + *len > dev->total_bytes_to_read) {
|
|
*len = dev->total_bytes_to_read - dev->total_bytes_read;
|
|
}
|
|
|
|
dev->pipeline_buffer.get_data(*len, destination);
|
|
dev->total_bytes_read += *len;
|
|
}
|
|
|
|
/* end scan if all needed data have been read */
|
|
if(dev->total_bytes_read >= dev->total_bytes_to_read)
|
|
{
|
|
dev->cmd_set->end_scan(dev, &dev->reg, true);
|
|
if (dev->model->is_sheetfed) {
|
|
dev->cmd_set->eject_document (dev);
|
|
}
|
|
}
|
|
|
|
DBG(DBG_proc, "%s: completed, %zu bytes read\n", __func__, bytes);
|
|
}
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/* Start of higher level functions */
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
static size_t
|
|
max_string_size (const SANE_String_Const strings[])
|
|
{
|
|
size_t size, max_size = 0;
|
|
SANE_Int i;
|
|
|
|
for (i = 0; strings[i]; ++i)
|
|
{
|
|
size = strlen (strings[i]) + 1;
|
|
if (size > max_size)
|
|
max_size = size;
|
|
}
|
|
return max_size;
|
|
}
|
|
|
|
static std::size_t max_string_size(const std::vector<const char*>& strings)
|
|
{
|
|
std::size_t max_size = 0;
|
|
for (const auto& s : strings) {
|
|
if (!s) {
|
|
continue;
|
|
}
|
|
max_size = std::max(max_size, std::strlen(s));
|
|
}
|
|
return max_size;
|
|
}
|
|
|
|
static unsigned pick_resolution(const std::vector<unsigned>& resolutions, unsigned resolution,
|
|
const char* direction)
|
|
{
|
|
DBG_HELPER(dbg);
|
|
|
|
if (resolutions.empty())
|
|
throw SaneException("Empty resolution list");
|
|
|
|
unsigned best_res = resolutions.front();
|
|
unsigned min_diff = abs_diff(best_res, resolution);
|
|
|
|
for (auto it = std::next(resolutions.begin()); it != resolutions.end(); ++it) {
|
|
unsigned curr_diff = abs_diff(*it, resolution);
|
|
if (curr_diff < min_diff) {
|
|
min_diff = curr_diff;
|
|
best_res = *it;
|
|
}
|
|
}
|
|
|
|
if (best_res != resolution) {
|
|
DBG(DBG_warn, "%s: using resolution %d that is nearest to %d for direction %s\n",
|
|
__func__, best_res, resolution, direction);
|
|
}
|
|
return best_res;
|
|
}
|
|
|
|
static Genesys_Settings calculate_scan_settings(Genesys_Scanner* s)
|
|
{
|
|
DBG_HELPER(dbg);
|
|
|
|
const auto* dev = s->dev;
|
|
Genesys_Settings settings;
|
|
settings.scan_method = s->scan_method;
|
|
settings.scan_mode = option_string_to_scan_color_mode(s->mode);
|
|
|
|
settings.depth = s->bit_depth;
|
|
|
|
if (settings.depth > 8) {
|
|
settings.depth = 16;
|
|
} else if (settings.depth < 8) {
|
|
settings.depth = 1;
|
|
}
|
|
|
|
const auto& resolutions = dev->model->get_resolution_settings(settings.scan_method);
|
|
|
|
settings.xres = pick_resolution(resolutions.resolutions_x, s->resolution, "X");
|
|
settings.yres = pick_resolution(resolutions.resolutions_y, s->resolution, "Y");
|
|
|
|
settings.tl_x = fixed_to_float(s->pos_top_left_x);
|
|
settings.tl_y = fixed_to_float(s->pos_top_left_y);
|
|
float br_x = fixed_to_float(s->pos_bottom_right_x);
|
|
float br_y = fixed_to_float(s->pos_bottom_right_y);
|
|
|
|
settings.lines = static_cast<unsigned>(((br_y - settings.tl_y) * settings.yres) /
|
|
MM_PER_INCH);
|
|
|
|
|
|
unsigned pixels_per_line = static_cast<unsigned>(((br_x - settings.tl_x) * settings.xres) /
|
|
MM_PER_INCH);
|
|
|
|
const auto& sensor = sanei_genesys_find_sensor(dev, settings.xres, settings.get_channels(),
|
|
settings.scan_method);
|
|
|
|
pixels_per_line = session_adjust_output_pixels(pixels_per_line, *dev, sensor,
|
|
settings.xres, settings.yres, true);
|
|
|
|
unsigned xres_factor = s->resolution / settings.xres;
|
|
settings.pixels = pixels_per_line;
|
|
settings.requested_pixels = pixels_per_line * xres_factor;
|
|
|
|
if (s->color_filter == "Red") {
|
|
settings.color_filter = ColorFilter::RED;
|
|
} else if (s->color_filter == "Green") {
|
|
settings.color_filter = ColorFilter::GREEN;
|
|
} else if (s->color_filter == "Blue") {
|
|
settings.color_filter = ColorFilter::BLUE;
|
|
} else {
|
|
settings.color_filter = ColorFilter::NONE;
|
|
}
|
|
|
|
// 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
|