sane-project-backends/backend/genesys.cc

6733 wiersze
212 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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA.
As a special exception, the authors of SANE give permission for
additional uses of the libraries contained in this release of SANE.
The exception is that, if you link a SANE library with other files
to produce an executable, this does not by itself cause the
resulting executable to be covered by the GNU General Public
License. Your use of that executable is in no way restricted on
account of linking the SANE library code into it.
This exception does not, however, invalidate any other reasons why
the executable file might be covered by the GNU General Public
License.
If you submit changes to SANE to the maintainers to be included in
a subsequent release, you agree by submitting the changes that
those changes may be distributed with this exception intact.
If you write modifications of your own for SANE, it is your choice
whether to permit this exception to apply to your modifications.
If you do not wish that, delete this exception notice.
*/
/*
* SANE backend for Genesys Logic GL646/GL841/GL842/GL843/GL846/GL847/GL124 based scanners
*/
#define DEBUG_NOT_STATIC
#include "genesys.h"
#include "genesys_sanei.h"
#include "../include/sane/sanei_config.h"
#include "../include/sane/sanei_magic.h"
#include <array>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <list>
#include <numeric>
#include <exception>
#include <vector>
// Data that we allocate to back SANE_Device objects in s_sane_devices
struct SANE_Device_Data
{
std::string name;
};
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;
static SANE_String_Const mode_list[] = {
SANE_VALUE_SCAN_MODE_COLOR,
SANE_VALUE_SCAN_MODE_GRAY,
/* SANE_TITLE_HALFTONE, currently unused */
SANE_VALUE_SCAN_MODE_LINEART,
0
};
static SANE_String_Const color_filter_list[] = {
SANE_I18N ("Red"),
SANE_I18N ("Green"),
SANE_I18N ("Blue"),
0
};
static SANE_String_Const cis_color_filter_list[] = {
SANE_I18N ("Red"),
SANE_I18N ("Green"),
SANE_I18N ("Blue"),
SANE_I18N ("None"),
0
};
static SANE_String_Const source_list[] = {
SANE_I18N (STR_FLATBED),
SANE_I18N (STR_TRANSPARENCY_ADAPTER),
0
};
static const char* source_list_infrared[] = {
SANE_I18N(STR_FLATBED),
SANE_I18N(STR_TRANSPARENCY_ADAPTER),
SANE_I18N(STR_TRANSPARENCY_ADAPTER_INFRARED),
0
};
static SANE_Range swdespeck_range = {
1,
9,
1
};
static SANE_Range time_range = {
0, /* minimum */
60, /* maximum */
0 /* quantization */
};
static const SANE_Range u12_range = {
0, /* minimum */
4095, /* maximum */
0 /* quantization */
};
static const SANE_Range u14_range = {
0, /* minimum */
16383, /* maximum */
0 /* quantization */
};
static const SANE_Range u16_range = {
0, /* minimum */
65535, /* maximum */
0 /* quantization */
};
static const SANE_Range percentage_range = {
SANE_FIX (0), /* minimum */
SANE_FIX (100), /* maximum */
SANE_FIX (1) /* quantization */
};
static const SANE_Range threshold_curve_range = {
0, /* minimum */
127, /* maximum */
1 /* quantization */
};
/**
* range for brightness and contrast
*/
static const SANE_Range enhance_range = {
-100, /* minimum */
100, /* maximum */
1 /* quantization */
};
/**
* range for expiration time
*/
static const SANE_Range expiration_range = {
-1, /* minimum */
30000, /* maximum */
1 /* quantization */
};
const Genesys_Sensor& sanei_genesys_find_sensor_any(Genesys_Device* dev)
{
for (const auto& sensor : *s_sensors) {
if (dev->model->ccd_type == sensor.sensor_id) {
return sensor;
}
}
throw std::runtime_error("Given device does not have sensor defined");
}
const Genesys_Sensor& sanei_genesys_find_sensor(Genesys_Device* dev, int dpi, unsigned channels,
ScanMethod scan_method)
{
for (const auto& sensor : *s_sensors) {
if (dev->model->ccd_type == sensor.sensor_id && sensor.resolutions.matches(dpi) &&
sensor.matches_channel_count(channels) && sensor.method == scan_method)
{
return sensor;
}
}
throw std::runtime_error("Given device does not have sensor defined");
}
Genesys_Sensor& sanei_genesys_find_sensor_for_write(Genesys_Device* dev, int dpi,
unsigned channels,
ScanMethod scan_method)
{
for (auto& sensor : *s_sensors) {
if (dev->model->ccd_type == sensor.sensor_id && sensor.resolutions.matches(dpi) &&
sensor.matches_channel_count(channels) && sensor.method == scan_method)
{
return sensor;
}
}
throw std::runtime_error("Given device does not have sensor defined");
}
std::vector<std::reference_wrapper<const Genesys_Sensor>>
sanei_genesys_find_sensors_all(Genesys_Device* dev, ScanMethod scan_method)
{
std::vector<std::reference_wrapper<const Genesys_Sensor>> ret;
for (const Genesys_Sensor& sensor : sanei_genesys_find_sensors_all_for_write(dev, scan_method)) {
ret.push_back(sensor);
}
return ret;
}
std::vector<std::reference_wrapper<Genesys_Sensor>>
sanei_genesys_find_sensors_all_for_write(Genesys_Device* dev, ScanMethod scan_method)
{
std::vector<std::reference_wrapper<Genesys_Sensor>> ret;
for (auto& sensor : *s_sensors) {
if (dev->model->ccd_type == sensor.sensor_id && sensor.method == scan_method) {
ret.push_back(sensor);
}
}
return ret;
}
void
sanei_genesys_init_structs (Genesys_Device * dev)
{
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->gpo_type == gpo.gpo_id) {
dev->gpo = gpo;
gpo_ok = true;
break;
}
}
// initialize the motor data stuff
for (const auto& motor : *s_motors) {
if (dev->model->motor_type == motor.motor_id) {
dev->motor = motor;
motor_ok = true;
break;
}
}
for (const auto& frontend : *s_frontends) {
if (dev->model->dac_type == frontend.fe_id) {
dev->frontend_initial = frontend;
fe_ok = true;
break;
}
}
if (!motor_ok || !gpo_ok || !fe_ok) {
DBG(DBG_error0, "%s: bad description(s) for fe/gpo/motor=%d/%d/%d\n", __func__,
dev->model->ccd_type, dev->model->gpo_type, dev->model->motor_type);
}
/* set up initial line distance shift */
dev->ld_shift_r = dev->model->ld_shift_r;
dev->ld_shift_g = dev->model->ld_shift_g;
dev->ld_shift_b = dev->model->ld_shift_b;
}
/* main function for slope creation */
/**
* This function generates a slope table using the given slope
* truncated at the given exposure time or step count, whichever comes first.
* The reached step time is then stored in final_exposure and used for the rest
* of the table. The summed time of the acceleration steps is returned, and the
* number of accerelation steps is put into used_steps.
*
* @param slope_table Table to write to
* @param max_steps Size of slope_table in steps
* @param use_steps Maximum number of steps to use for acceleration
* @param stop_at Minimum step time to use
* @param vstart Start step time of default slope
* @param vend End step time of default slope
* @param steps Step count of default slope
* @param g Power for default slope
* @param used_steps Final number of steps is stored here
* @param vfinal Final step time is stored here
* @return Time for acceleration
* @note All times in pixel time. Correction for other motor timings is not
* done.
*/
SANE_Int sanei_genesys_generate_slope_table(std::vector<uint16_t>& slope_table,
unsigned int max_steps,
unsigned int use_steps,
uint16_t stop_at,
uint16_t vstart,
uint16_t vend,
unsigned int steps,
double g,
unsigned int *used_steps,
unsigned int *vfinal)
{
double t;
SANE_Int sum = 0;
unsigned int i;
unsigned int c = 0;
uint16_t t2;
unsigned int dummy;
unsigned int _vfinal;
if (!used_steps)
used_steps = &dummy;
if (!vfinal)
vfinal = &_vfinal;
DBG(DBG_proc, "%s: table size: %d\n", __func__, max_steps);
DBG(DBG_proc, "%s: stop at time: %d, use %d steps max\n", __func__, stop_at, use_steps);
DBG(DBG_proc, "%s: target slope: vstart: %d, vend: %d, steps: %d, g: %g\n", __func__, vstart,
vend, steps, g);
sum = 0;
c = 0;
*used_steps = 0;
slope_table.clear();
slope_table.reserve(max_steps);
if (use_steps < 1)
use_steps = 1;
if (stop_at < vstart)
{
t2 = vstart;
for (i = 0; i < steps && i < use_steps - 1 && i < max_steps; i++, c++)
{
t = pow (((double) i) / ((double) (steps - 1)), g);
t2 = vstart * (1 - t) + t * vend;
if (t2 < stop_at) {
break;
}
slope_table.push_back(t2);
/* DBG (DBG_io, "slope_table[%3d] = %5d\n", c, t2); */
sum += t2;
}
if (t2 > stop_at)
{
DBG(DBG_warn, "Can not reach target speed(%d) in %d steps.\n", stop_at, use_steps);
DBG(DBG_warn, "Expect image to be distorted. Ignore this if only feeding.\n");
}
*vfinal = t2;
*used_steps += i;
max_steps -= i;
}
else
*vfinal = stop_at;
for (i = 0; i < max_steps; i++, c++)
{
slope_table.push_back(*vfinal);
/* DBG (DBG_io, "slope_table[%3d] = %5d\n", c, *vfinal); */
}
(*used_steps)++;
sum += *vfinal;
DBG(DBG_proc, "%s: returns sum=%d, used %d steps, completed\n", __func__, sum, *used_steps);
return sum;
}
/* Generate slope table for motor movement */
/**
* This function generates a slope table using the slope from the motor struct
* truncated at the given exposure time or step count, whichever comes first.
* The reached step time is then stored in final_exposure and used for the rest
* of the table. The summed time of the acceleration steps is returned, and the
* number of accerelation steps is put into used_steps.
*
* @param dev Device struct
* @param slope_table Table to write to
* @param max_step Size of slope_table in steps
* @param use_steps Maximum number of steps to use for acceleration
* @param step_type Generate table for this step_type. 0=>full, 1=>half,
* 2=>quarter
* @param exposure_time Minimum exposure time of a scan line
* @param yres Resolution of a scan line
* @param used_steps Final number of steps is stored here
* @param final_exposure Final step time is stored here
* @return Time for acceleration
* @note all times in pixel time
*/
SANE_Int sanei_genesys_create_slope_table3(Genesys_Device * dev,
std::vector<uint16_t>& slope_table,
int max_step,
unsigned int use_steps,
int step_type,
int exposure_time,
double yres,
unsigned int *used_steps,
unsigned int *final_exposure)
{
unsigned int sum_time = 0;
unsigned int vtarget;
unsigned int vend;
unsigned int vstart;
unsigned int vfinal;
DBG(DBG_proc, "%s: step_type = %d, exposure_time = %d, yres = %g\n", __func__,
step_type, exposure_time, yres);
/* final speed */
vtarget = (exposure_time * yres) / dev->motor.base_ydpi;
vstart = dev->motor.slopes[step_type].maximum_start_speed;
vend = dev->motor.slopes[step_type].maximum_speed;
vtarget >>= step_type;
if (vtarget > 65535)
vtarget = 65535;
vstart >>= step_type;
if (vstart > 65535)
vstart = 65535;
vend >>= step_type;
if (vend > 65535)
vend = 65535;
sum_time = sanei_genesys_generate_slope_table (slope_table,
max_step,
use_steps,
vtarget,
vstart,
vend,
dev->motor.slopes[step_type].minimum_steps << step_type,
dev->motor.slopes[step_type].g,
used_steps,
&vfinal);
if (final_exposure)
*final_exposure = (vfinal * dev->motor.base_ydpi) / yres;
DBG(DBG_proc, "%s: returns sum_time=%d, completed\n", __func__, sum_time);
return sum_time;
}
/* alternate slope table creation function */
/* the hardcoded values (g and vstart) will go in a motor struct */
SANE_Int genesys_create_slope_table2(Genesys_Device* dev, std::vector<uint16_t>& slope_table,
int steps,
int step_type, int exposure_time,
SANE_Bool same_speed, double yres)
{
double t, g;
SANE_Int sum = 0;
int vstart, vend;
int i;
DBG(DBG_proc, "%s: %d steps, step_type = %d, "
"exposure_time = %d, same_speed = %d, yres = %.2f\n", __func__, steps,
step_type, exposure_time, same_speed, yres);
/* start speed */
if (dev->model->motor_type == MOTOR_5345)
{
if (yres < dev->motor.base_ydpi / 6)
vstart = 2500;
else
vstart = 2000;
}
else
{
if (steps == 2)
vstart = exposure_time;
else if (steps == 3)
vstart = 2 * exposure_time;
else if (steps == 4)
vstart = 1.5 * exposure_time;
else if (steps == 120)
vstart = 1.81674 * exposure_time;
else
vstart = exposure_time;
}
/* final speed */
vend = (exposure_time * yres) / (dev->motor.base_ydpi * (1 << step_type));
/*
type=1 : full
type=2 : half
type=4 : quarter
vend * type * base_ydpi / exposure = yres
*/
/* acceleration */
switch (steps)
{
case 255:
/* test for special case: fast moving slope */
/* todo: a 'fast' boolean parameter should be better */
if (vstart == 2000)
g = 0.2013;
else
g = 0.1677;
break;
case 120:
g = 0.5;
break;
case 67:
g = 0.5;
break;
case 64:
g = 0.2555;
break;
case 44:
g = 0.5;
break;
case 4:
g = 0.5;
break;
case 3:
g = 1;
break;
case 2:
vstart = vend;
g = 1;
break;
default:
g = 0.2635;
}
/* if same speed, no 'g' */
sum = 0;
if (same_speed)
{
for (i = 0; i < 255; i++)
{
slope_table[i] = vend;
sum += slope_table[i];
DBG (DBG_io, "slope_table[%3d] = %5d\n", i, slope_table[i]);
}
}
else
{
for (i = 0; i < steps; i++)
{
t = pow (((double) i) / ((double) (steps - 1)), g);
slope_table[i] = vstart * (1 - t) + t * vend;
DBG (DBG_io, "slope_table[%3d] = %5d\n", i, slope_table[i]);
sum += slope_table[i];
}
for (i = steps; i < 255; i++)
{
slope_table[i] = vend;
DBG (DBG_io, "slope_table[%3d] = %5d\n", i, slope_table[i]);
sum += slope_table[i];
}
}
DBG(DBG_proc, "%s: returns sum=%d, completed\n", __func__, sum);
return sum;
}
/* Generate slope table for motor movement */
/* todo: check details */
SANE_Int sanei_genesys_create_slope_table(Genesys_Device * dev, std::vector<uint16_t>& slope_table,
int steps, int step_type, int exposure_time,
SANE_Bool same_speed, double yres)
{
double t;
double start_speed;
double g;
uint32_t time_period;
int sum_time = 0;
int i, divider;
int same_step;
if (dev->model->motor_type == MOTOR_5345
|| dev->model->motor_type == MOTOR_HP2300
|| dev->model->motor_type == MOTOR_HP2400)
return genesys_create_slope_table2 (dev, slope_table, steps,
step_type, exposure_time,
same_speed, yres);
DBG(DBG_proc, "%s: %d steps, step_type = %d, exposure_time = %d, same_speed =%d\n", __func__,
steps, step_type, exposure_time, same_speed);
DBG(DBG_proc, "%s: yres = %.2f\n", __func__, yres);
g = 0.6;
start_speed = 0.01;
same_step = 4;
divider = 1 << step_type;
time_period =
(uint32_t) (yres * exposure_time / dev->motor.base_ydpi /*MOTOR_GEAR */ );
if ((time_period < 2000) && (same_speed))
same_speed = SANE_FALSE;
time_period = time_period / divider;
if (same_speed)
{
for (i = 0; i < steps; i++)
{
slope_table[i] = (uint16_t) time_period;
sum_time += time_period;
DBG (DBG_io, "slope_table[%d] = %d\n", i, time_period);
}
DBG(DBG_info, "%s: returns sum_time=%d, completed\n", __func__, sum_time);
return sum_time;
}
if (time_period > MOTOR_SPEED_MAX * 5)
{
g = 1.0;
start_speed = 0.05;
same_step = 2;
}
else if (time_period > MOTOR_SPEED_MAX * 4)
{
g = 0.8;
start_speed = 0.04;
same_step = 2;
}
else if (time_period > MOTOR_SPEED_MAX * 3)
{
g = 0.7;
start_speed = 0.03;
same_step = 2;
}
else if (time_period > MOTOR_SPEED_MAX * 2)
{
g = 0.6;
start_speed = 0.02;
same_step = 3;
}
if (dev->model->motor_type == MOTOR_ST24)
{
steps = 255;
switch ((int) yres)
{
case 2400:
g = 0.1672;
start_speed = 1.09;
break;
case 1200:
g = 1;
start_speed = 6.4;
break;
case 600:
g = 0.1672;
start_speed = 1.09;
break;
case 400:
g = 0.2005;
start_speed = 20.0 / 3.0 /*7.5 */ ;
break;
case 300:
g = 0.253;
start_speed = 2.182;
break;
case 150:
g = 0.253;
start_speed = 4.367;
break;
default:
g = 0.262;
start_speed = 7.29;
}
same_step = 1;
}
if (steps <= same_step)
{
time_period =
(uint32_t) (yres * exposure_time /
dev->motor.base_ydpi /*MOTOR_GEAR */ );
time_period = time_period / divider;
if (time_period > 65535)
time_period = 65535;
for (i = 0; i < same_step; i++)
{
slope_table[i] = (uint16_t) time_period;
sum_time += time_period;
DBG (DBG_io, "slope_table[%d] = %d\n", i, time_period);
}
DBG(DBG_proc, "%s: returns sum_time=%d, completed\n", __func__, sum_time);
return sum_time;
}
for (i = 0; i < steps; i++)
{
double j = ((double) i) - same_step + 1; /* start from 1/16 speed */
if (j <= 0)
t = 0;
else
t = pow (j / (steps - same_step), g);
time_period = /* time required for full steps */
(uint32_t) (yres * exposure_time /
dev->motor.base_ydpi /*MOTOR_GEAR */ *
(start_speed + (1 - start_speed) * t));
time_period = time_period / divider;
if (time_period > 65535)
time_period = 65535;
slope_table[i] = (uint16_t) time_period;
sum_time += time_period;
DBG (DBG_io, "slope_table[%d] = %d\n", i, slope_table[i]);
}
DBG(DBG_proc, "%s: returns sum_time=%d, completed\n", __func__, sum_time);
return sum_time;
}
/** @brief computes gamma table
* Generates a gamma table of the given length within 0 and the given
* maximum value
* @param gamma_table gamma table to fill
* @param size size of the table
* @param maximum value allowed for gamma
* @param gamma_max maximum gamma value
* @param gamma gamma to compute values
* @return a gamma table filled with the computed values
* */
void
sanei_genesys_create_gamma_table (std::vector<uint16_t>& gamma_table, int size,
float maximum, float gamma_max, float gamma)
{
gamma_table.clear();
gamma_table.resize(size, 0);
int i;
float value;
DBG(DBG_proc, "%s: size = %d, ""maximum = %g, gamma_max = %g, gamma = %g\n", __func__, size,
maximum, gamma_max, gamma);
for (i = 0; i < size; i++)
{
value = gamma_max * pow ((float) i / size, 1.0 / gamma);
if (value > maximum)
value = maximum;
gamma_table[i] = value;
}
DBG(DBG_proc, "%s: completed\n", __func__);
}
void sanei_genesys_create_default_gamma_table(Genesys_Device* dev,
std::vector<uint16_t>& gamma_table, float gamma)
{
int size = 0;
int max = 0;
if (dev->model->asic_type == AsicType::GL646) {
if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) {
size = 16384;
} else {
size = 4096;
}
max = size - 1;
} else {
size = 256;
max = 65535;
}
sanei_genesys_create_gamma_table(gamma_table, size, max, max, gamma);
}
/* computes the exposure_time on the basis of the given vertical dpi,
the number of pixels the ccd needs to send,
the step_type and the corresponding maximum speed from the motor struct */
/*
Currently considers maximum motor speed at given step_type, minimum
line exposure needed for conversion and led exposure time.
TODO: Should also consider maximum transfer rate: ~6.5MB/s.
Note: The enhance option of the scanners does _not_ help. It only halves
the amount of pixels transfered.
*/
SANE_Int
sanei_genesys_exposure_time2 (Genesys_Device * dev, float ydpi,
int step_type, int endpixel, int exposure_by_led)
{
int exposure_by_ccd = endpixel + 32;
int exposure_by_motor = (dev->motor.slopes[step_type].maximum_speed * dev->motor.base_ydpi) / ydpi;
int exposure = exposure_by_ccd;
if (exposure < exposure_by_motor)
exposure = exposure_by_motor;
if (exposure < exposure_by_led && dev->model->is_cis)
exposure = exposure_by_led;
DBG(DBG_info, "%s: ydpi=%d, step=%d, endpixel=%d led=%d => exposure=%d\n", __func__,
(int)ydpi, step_type, endpixel, exposure_by_led, exposure);
return exposure;
}
/* computes the exposure_time on the basis of the given horizontal dpi */
/* we will clean/simplify it by using constants from a future motor struct */
SANE_Int
sanei_genesys_exposure_time (Genesys_Device * dev, Genesys_Register_Set * reg,
int xdpi)
{
if (dev->model->motor_type == MOTOR_5345)
{
if (dev->cmd_set->get_filter_bit(reg)) {
/* monochrome */
switch (xdpi)
{
case 600:
return 8500;
case 500:
case 400:
case 300:
case 250:
case 200:
case 150:
return 5500;
case 100:
return 6500;
case 50:
return 12000;
default:
return 11000;
}
}
else
{
/* color scan */
switch (xdpi)
{
case 300:
case 250:
case 200:
return 5500;
case 50:
return 12000;
default:
return 11000;
}
}
}
else if (dev->model->motor_type == MOTOR_HP2400)
{
if (dev->cmd_set->get_filter_bit(reg)) {
/* monochrome */
switch (xdpi)
{
case 200:
return 7210;
default:
return 11111;
}
}
else
{
/* color scan */
switch (xdpi)
{
case 600:
return 8751; /*11902; 19200 */
default:
return 11111;
}
}
}
else if (dev->model->motor_type == MOTOR_HP2300)
{
if (dev->cmd_set->get_filter_bit(reg)) {
/* monochrome */
switch (xdpi)
{
case 600:
return 8699; /* 3200; */
case 300:
return 3200; /*10000;, 3200 -> too dark */
case 150:
return 4480; /* 3200 ???, warmup needs 4480 */
case 75:
return 5500;
default:
return 11111;
}
}
else
{
/* color scan */
switch (xdpi)
{
case 600:
return 8699;
case 300:
return 4349;
case 150:
case 75:
return 4480;
default:
return 11111;
}
}
}
return 11000;
}
/* Sends a block of shading information to the scanner.
The data is placed at address 0x0000 for color mode, gray mode and
unconditionally for the following CCD chips: HP2300, HP2400 and HP5345
In the other cases (lineart, halftone on ccd chips not mentioned) the
addresses are 0x2a00 for dpihw==0, 0x5500 for dpihw==1 and 0xa800 for
dpihw==2. //Note: why this?
The data needs to be of size "size", and in little endian byte order.
*/
static void genesys_send_offset_and_shading(Genesys_Device* dev, const Genesys_Sensor& sensor,
uint8_t* data, int size)
{
DBG_HELPER_ARGS(dbg, "(size = %d)", size);
int dpihw;
int start_address;
/* ASIC higher than gl843 doesn't have register 2A/2B, so we route to
* a per ASIC shading data loading function if available.
* It is also used for scanners using SHDAREA */
if (dev->cmd_set->send_shading_data != nullptr) {
dev->cmd_set->send_shading_data(dev, sensor, data, size);
return;
}
/* gl646, gl84[123] case */
dpihw = dev->reg.get8(0x05) >> 6;
/* TODO invert the test so only the 2 models behaving like that are
* tested instead of adding all the others */
/* many scanners send coefficient for lineart/gray like in color mode */
if ((dev->settings.scan_mode == ScanColorMode::LINEART ||
dev->settings.scan_mode == ScanColorMode::HALFTONE)
&& dev->model->ccd_type != CCD_PLUSTEK3800
&& dev->model->ccd_type != CCD_KVSS080
&& dev->model->ccd_type != CCD_G4050
&& dev->model->ccd_type != CCD_CS4400F
&& dev->model->ccd_type != CCD_CS8400F
&& dev->model->ccd_type != CCD_CS8600F
&& dev->model->ccd_type != CCD_DSMOBILE600
&& dev->model->ccd_type != CCD_XP300
&& dev->model->ccd_type != CCD_DP665
&& dev->model->ccd_type != CCD_DP685
&& dev->model->ccd_type != CIS_CANONLIDE80
&& dev->model->ccd_type != CCD_ROADWARRIOR
&& dev->model->ccd_type != CCD_HP2300
&& dev->model->ccd_type != CCD_HP2400
&& dev->model->ccd_type != CCD_HP3670
&& dev->model->ccd_type != CCD_5345) /* lineart, halftone */
{
if (dpihw == 0) { /* 600 dpi */
start_address = 0x02a00;
} else if (dpihw == 1) { /* 1200 dpi */
start_address = 0x05500;
} else if (dpihw == 2) { /* 2400 dpi */
start_address = 0x0a800;
} else { /* reserved */
throw SaneException("unknown dpihw");
}
}
else { // color
start_address = 0x00;
}
sanei_genesys_set_buffer_address(dev, start_address);
dev->cmd_set->bulk_write_data(dev, 0x3c, data, size);
}
// ?
void sanei_genesys_init_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor,
int pixels_per_line)
{
DBG_HELPER(dbg);
int channels;
int i;
/* these models don't need to init shading data due to the use of specific send shading data
function */
if (dev->model->ccd_type==CCD_KVSS080
|| dev->model->ccd_type==CCD_G4050
|| dev->model->ccd_type==CCD_CS4400F
|| dev->model->ccd_type==CCD_CS8400F
|| dev->cmd_set->send_shading_data != NULL)
return;
DBG(DBG_proc, "%s (pixels_per_line = %d)\n", __func__, pixels_per_line);
// BUG: GRAY shouldn't probably be in the if condition below. Discovered when refactoring
if (dev->settings.scan_mode == ScanColorMode::GRAY ||
dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS)
{
channels = 3;
} else {
channels = 1;
}
// 16 bit black, 16 bit white
std::vector<uint8_t> shading_data(pixels_per_line * 4 * channels, 0);
uint8_t* shading_data_ptr = shading_data.data();
for (i = 0; i < pixels_per_line * channels; i++)
{
*shading_data_ptr++ = 0x00; /* dark lo */
*shading_data_ptr++ = 0x00; /* dark hi */
*shading_data_ptr++ = 0x00; /* white lo */
*shading_data_ptr++ = 0x40; /* white hi -> 0x4000 */
}
genesys_send_offset_and_shading(dev, sensor, shading_data.data(),
pixels_per_line * 4 * channels);
}
// Find the position of the reference point: takes gray level 8 bits data and find
// first CCD usable pixel and top of scanning area
void sanei_genesys_search_reference_point(Genesys_Device* dev, Genesys_Sensor& sensor,
const uint8_t* src_data, int start_pixel, int dpi,
int width, int height)
{
DBG_HELPER(dbg);
int x, y;
int current, left, top = 0;
int size, count;
int level = 80; /* edge threshold level */
// sanity check
if ((width < 3) || (height < 3)) {
throw SaneException("invalid width or height");
}
/* transformed image data */
size = width * height;
std::vector<uint8_t> image2(size, 0);
std::vector<uint8_t> image(size, 0);
/* laplace filter to denoise picture */
std::memcpy(image2.data(), src_data, size);
std::memcpy(image.data(), src_data, size); // to initialize unprocessed part of the image buffer
for (y = 1; y < height - 1; y++) {
for (x = 1; x < width - 1; x++) {
image[y * width + x] =
(image2[(y - 1) * width + x + 1] + 2 * image2[(y - 1) * width + x] +
image2[(y - 1) * width + x - 1] + 2 * image2[y * width + x + 1] +
4 * image2[y * width + x] + 2 * image2[y * width + x - 1] +
image2[(y + 1) * width + x + 1] + 2 * image2[(y + 1) * width + x] +
image2[(y + 1) * width + x - 1]) / 16;
}
}
image2 = image;
if (DBG_LEVEL >= DBG_data)
sanei_genesys_write_pnm_file("gl_laplace.pnm", image.data(), 8, 1, width, height);
/* apply X direction sobel filter
-1 0 1
-2 0 2
-1 0 1
and finds threshold level
*/
level = 0;
for (y = 2; y < height - 2; y++) {
for (x = 2; x < width - 2; x++) {
current = image2[(y - 1) * width + x + 1] - image2[(y - 1) * width + x - 1] +
2 * image2[y * width + x + 1] - 2 * image2[y * width + x - 1] +
image2[(y + 1) * width + x + 1] - image2[(y + 1) * width + x - 1];
if (current < 0)
current = -current;
if (current > 255)
current = 255;
image[y * width + x] = current;
if (current > level)
level = current;
}
}
if (DBG_LEVEL >= DBG_data)
sanei_genesys_write_pnm_file("gl_xsobel.pnm", image.data(), 8, 1, width, height);
/* set up detection level */
level = level / 3;
/* find left black margin first
todo: search top before left
we average the result of N searches */
left = 0;
count = 0;
for (y = 2; y < 11; y++)
{
x = 8;
while ((x < width / 2) && (image[y * width + x] < level))
{
image[y * width + x] = 255;
x++;
}
count++;
left += x;
}
if (DBG_LEVEL >= DBG_data)
sanei_genesys_write_pnm_file("gl_detected-xsobel.pnm", image.data(), 8, 1, width, height);
left = left / count;
/* turn it in CCD pixel at full sensor optical resolution */
sensor.CCD_start_xoffset = start_pixel + (left * sensor.optical_res) / dpi;
/* find top edge by detecting black strip */
/* apply Y direction sobel filter
-1 -2 -1
0 0 0
1 2 1
*/
level = 0;
for (y = 2; y < height - 2; y++) {
for (x = 2; x < width - 2; x++) {
current = -image2[(y - 1) * width + x + 1] - 2 * image2[(y - 1) * width + x] -
image2[(y - 1) * width + x - 1] + image2[(y + 1) * width + x + 1] +
2 * image2[(y + 1) * width + x] + image2[(y + 1) * width + x - 1];
if (current < 0)
current = -current;
if (current > 255)
current = 255;
image[y * width + x] = current;
if (current > level)
level = current;
}
}
if (DBG_LEVEL >= DBG_data)
sanei_genesys_write_pnm_file("gl_ysobel.pnm", image.data(), 8, 1, width, height);
/* set up detection level */
level = level / 3;
/* search top of horizontal black stripe : TODO yet another flag */
if (dev->model->ccd_type == CCD_5345
&& dev->model->motor_type == MOTOR_5345)
{
top = 0;
count = 0;
for (x = width / 2; x < width - 1; x++)
{
y = 2;
while ((y < height) && (image[x + y * width] < level))
{
image[y * width + x] = 255;
y++;
}
count++;
top += y;
}
if (DBG_LEVEL >= DBG_data)
sanei_genesys_write_pnm_file("gl_detected-ysobel.pnm", image.data(), 8, 1, width, height);
top = top / count;
/* bottom of black stripe is of fixed witdh, this hardcoded value
* will be moved into device struct if more such values are needed */
top += 10;
dev->model->y_offset_calib = SANE_FIX ((top * MM_PER_INCH) / dpi);
DBG(DBG_info, "%s: black stripe y_offset = %f mm \n", __func__,
SANE_UNFIX (dev->model->y_offset_calib));
}
/* find white corner in dark area : TODO yet another flag */
if ((dev->model->ccd_type == CCD_HP2300
&& dev->model->motor_type == MOTOR_HP2300)
|| (dev->model->ccd_type == CCD_HP2400
&& dev->model->motor_type == MOTOR_HP2400)
|| (dev->model->ccd_type == CCD_HP3670
&& dev->model->motor_type == MOTOR_HP3670))
{
top = 0;
count = 0;
for (x = 10; x < 60; x++)
{
y = 2;
while ((y < height) && (image[x + y * width] < level))
y++;
top += y;
count++;
}
top = top / count;
dev->model->y_offset_calib = SANE_FIX ((top * MM_PER_INCH) / dpi);
DBG(DBG_info, "%s: white corner y_offset = %f mm\n", __func__,
SANE_UNFIX (dev->model->y_offset_calib));
}
DBG(DBG_proc, "%s: CCD_start_xoffset = %d, left = %d, top = %d\n", __func__,
sensor.CCD_start_xoffset, left, top);
}
void sanei_genesys_calculate_zmod(SANE_Bool two_table,
uint32_t exposure_time,
const std::vector<uint16_t>& slope_table,
unsigned acceleration_steps,
unsigned move_steps,
unsigned buffer_acceleration_steps,
uint32_t* out_z1, uint32_t* out_z2)
{
DBG(DBG_info, "%s: two_table=%d\n", __func__, two_table);
// acceleration total time
unsigned sum = std::accumulate(slope_table.begin(), slope_table.begin() + acceleration_steps,
0, std::plus<unsigned>());
/* Z1MOD:
c = sum(slope_table; reg_stepno)
d = reg_fwdstep * <cruising speed>
Z1MOD = (c+d) % exposure_time
*/
*out_z1 = (sum + buffer_acceleration_steps * slope_table[acceleration_steps - 1]) % exposure_time;
/* Z2MOD:
a = sum(slope_table; reg_stepno)
b = move_steps or 1 if 2 tables
Z1MOD = (a+b) % exposure_time
*/
if (!two_table) {
sum = sum + (move_steps * slope_table[acceleration_steps - 1]);
} else {
sum = sum + slope_table[acceleration_steps - 1];
}
*out_z2 = sum % exposure_time;
}
static uint8_t genesys_adjust_gain(double* applied_multi, double multi, uint8_t gain)
{
double voltage, original_voltage;
uint8_t new_gain = 0;
DBG(DBG_proc, "%s: multi=%f, gain=%d\n", __func__, multi, gain);
voltage = 0.5 + gain * 0.25;
original_voltage = voltage;
voltage *= multi;
new_gain = (uint8_t) ((voltage - 0.5) * 4);
if (new_gain > 0x0e)
new_gain = 0x0e;
voltage = 0.5 + (new_gain) * 0.25;
*applied_multi = voltage / original_voltage;
DBG(DBG_proc, "%s: orig voltage=%.2f, new voltage=%.2f, *applied_multi=%f, new_gain=%d\n",
__func__, original_voltage, voltage, *applied_multi, new_gain);
return new_gain;
}
// todo: is return status necessary (unchecked?)
static void genesys_average_white(Genesys_Device* dev, Genesys_Sensor& sensor, int channels,
int channel, uint8_t* data, int size, int *max_average)
{
DBG_HELPER_ARGS(dbg, "channels=%d, channel=%d, size=%d", channels, channel, size);
int gain_white_ref, sum, range;
int average;
int i;
range = size / 50;
if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
{
gain_white_ref = sensor.fau_gain_white_ref * 256;
} else {
gain_white_ref = sensor.gain_white_ref * 256;
}
if (range < 1)
range = 1;
size = size / (2 * range * channels);
data += (channel * 2);
*max_average = 0;
while (size--)
{
sum = 0;
for (i = 0; i < range; i++)
{
sum += (*data);
sum += *(data + 1) * 256;
data += (2 * channels); /* byte based */
}
average = (sum / range);
if (average > *max_average)
*max_average = average;
}
DBG(DBG_proc, "%s: max_average=%d, gain_white_ref = %d, finished\n", __func__, *max_average,
gain_white_ref);
if (*max_average >= gain_white_ref)
throw SaneException(SANE_STATUS_INVAL);
}
/* todo: understand, values are too high */
static int
genesys_average_black (Genesys_Device * dev, int channel,
uint8_t * data, int pixels)
{
int i;
int sum;
int pixel_step;
DBG(DBG_proc, "%s: channel=%d, pixels=%d\n", __func__, channel, pixels);
sum = 0;
if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS)
{
data += (channel * 2);
pixel_step = 3 * 2;
}
else
{
pixel_step = 2;
}
for (i = 0; i < pixels; i++)
{
sum += *data;
sum += *(data + 1) * 256;
data += pixel_step;
}
DBG(DBG_proc, "%s = %d\n", __func__, sum / pixels);
return (int) (sum / pixels);
}
// todo: check; it works but the lines 1, 2, and 3 are too dark even with the
// same offset and gain settings?
static void genesys_coarse_calibration(Genesys_Device* dev, Genesys_Sensor& sensor)
{
DBG_HELPER_ARGS(dbg, "scan_mode = %d", static_cast<unsigned>(dev->settings.scan_mode));
int size;
int black_pixels;
int white_average;
uint8_t offset[4] = { 0xa0, 0x00, 0xa0, 0x40 }; /* first value isn't used */
uint16_t white[12], dark[12];
int i, j;
black_pixels = sensor.black_pixels
* dev->settings.xres / sensor.optical_res;
unsigned channels = dev->settings.get_channels();
DBG(DBG_info, "channels %d y_size %d xres %d\n", channels, dev->model->y_size,
dev->settings.xres);
size =
channels * 2 * SANE_UNFIX (dev->model->y_size) * dev->settings.xres /
25.4;
/* 1 1 mm 1/inch inch/mm */
std::vector<uint8_t> calibration_data(size);
std::vector<uint8_t> all_data(size * 4, 1);
dev->cmd_set->set_fe(dev, sensor, AFE_INIT);
dev->frontend.set_gain(0, 2);
dev->frontend.set_gain(1, 2);
dev->frontend.set_gain(2, 2); // TODO: ? was 2
dev->frontend.set_offset(0, offset[0]);
dev->frontend.set_offset(1, offset[0]);
dev->frontend.set_offset(2, offset[0]);
for (i = 0; i < 4; i++) /* read 4 lines */
{
if (i < 3) /* first 3 lines */
{
dev->frontend.set_offset(0, offset[i]);
dev->frontend.set_offset(1, offset[i]);
dev->frontend.set_offset(2, offset[i]);
}
if (i == 1) /* second line */
{
double applied_multi;
double gain_white_ref;
if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
{
gain_white_ref = sensor.fau_gain_white_ref * 256;
} else {
gain_white_ref = sensor.gain_white_ref * 256;
}
// white and black are defined downwards
uint8_t gain0 = genesys_adjust_gain(&applied_multi,
gain_white_ref / (white[0] - dark[0]),
dev->frontend.get_gain(0));
uint8_t gain1 = genesys_adjust_gain(&applied_multi,
gain_white_ref / (white[1] - dark[1]),
dev->frontend.get_gain(1));
uint8_t gain2 = genesys_adjust_gain(&applied_multi,
gain_white_ref / (white[2] - dark[2]),
dev->frontend.get_gain(2));
// FIXME: looks like overwritten data. Are the above calculations doing
// anything at all?
dev->frontend.set_gain(0, gain0);
dev->frontend.set_gain(1, gain1);
dev->frontend.set_gain(2, gain2);
dev->frontend.set_gain(0, 2);
dev->frontend.set_gain(1, 2);
dev->frontend.set_gain(2, 2);
sanei_genesys_fe_write_data(dev, 0x28, dev->frontend.get_gain(0));
sanei_genesys_fe_write_data(dev, 0x29, dev->frontend.get_gain(1));
sanei_genesys_fe_write_data(dev, 0x2a, dev->frontend.get_gain(2));
}
if (i == 3) /* last line */
{
double x, y, rate;
for (j = 0; j < 3; j++)
{
x =
(double) (dark[(i - 2) * 3 + j] -
dark[(i - 1) * 3 + j]) * 254 / (offset[i - 1] / 2 -
offset[i - 2] / 2);
y = x - x * (offset[i - 1] / 2) / 254 - dark[(i - 1) * 3 + j];
rate = (x - DARK_VALUE - y) * 254 / x + 0.5;
uint8_t curr_offset = static_cast<uint8_t>(rate);
if (curr_offset > 0x7f) {
curr_offset = 0x7f;
}
curr_offset <<= 1;
dev->frontend.set_offset(j, curr_offset);
}
}
sanei_genesys_fe_write_data(dev, 0x20, dev->frontend.get_offset(0));
sanei_genesys_fe_write_data(dev, 0x21, dev->frontend.get_offset(1));
sanei_genesys_fe_write_data(dev, 0x22, dev->frontend.get_offset(2));
DBG(DBG_info,
"%s: doing scan: gain: %d/%d/%d, offset: %d/%d/%d\n", __func__,
dev->frontend.get_gain(0),
dev->frontend.get_gain(1),
dev->frontend.get_gain(2),
dev->frontend.get_offset(0),
dev->frontend.get_offset(1),
dev->frontend.get_offset(2));
dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, SANE_FALSE);
sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size);
std::memcpy(all_data.data() + i * size, calibration_data.data(), size);
if (i == 3) /* last line */
{
std::vector<uint8_t> all_data_8(size * 4 / 2);
unsigned int count;
for (count = 0; count < (unsigned int) (size * 4 / 2); count++)
all_data_8[count] = all_data[count * 2 + 1];
sanei_genesys_write_pnm_file("gl_coarse.pnm", all_data_8.data(), 8, channels, size / 6, 4);
}
dev->cmd_set->end_scan(dev, &dev->calib_reg, SANE_TRUE);
if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS)
{
for (j = 0; j < 3; j++)
{
genesys_average_white(dev, sensor, 3, j, calibration_data.data(), size, &white_average);
white[i * 3 + j] = white_average;
dark[i * 3 + j] =
genesys_average_black (dev, j, calibration_data.data(),
black_pixels);
DBG(DBG_info, "%s: white[%d]=%d, black[%d]=%d\n", __func__,
i * 3 + j, white[i * 3 + j], i * 3 + j, dark[i * 3 + j]);
}
}
else /* one color-component modes */
{
genesys_average_white(dev, sensor, 1, 0, calibration_data.data(), size, &white_average);
white[i * 3 + 0] = white[i * 3 + 1] = white[i * 3 + 2] =
white_average;
dark[i * 3 + 0] = dark[i * 3 + 1] = dark[i * 3 + 2] =
genesys_average_black (dev, 0, calibration_data.data(), black_pixels);
}
} /* for (i = 0; i < 4; i++) */
DBG(DBG_info, "%s: final: gain: %d/%d/%d, offset: %d/%d/%d\n", __func__,
dev->frontend.get_gain(0),
dev->frontend.get_gain(1),
dev->frontend.get_gain(2),
dev->frontend.get_offset(0),
dev->frontend.get_offset(1),
dev->frontend.get_offset(2));
}
/* Averages image data.
average_data and calibration_data are little endian 16 bit words.
*/
static void genesys_average_data(uint16_t* average_data,
uint8_t * calibration_data,
uint32_t lines,
uint32_t pixel_components_per_line)
{
uint32_t x, y;
uint32_t sum;
for (x = 0; x < pixel_components_per_line; x++)
{
sum = 0;
for (y = 0; y < lines; y++)
{
sum += calibration_data[(x + y * pixel_components_per_line) * 2];
sum +=
calibration_data[(x + y * pixel_components_per_line) * 2 +
1] * 256;
}
sum /= lines;
*average_data++ = sum;
}
}
/**
* 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_dark_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor)
{
DBG_HELPER(dbg);
size_t size;
uint32_t pixels_per_line;
uint8_t channels;
SANE_Bool motor;
/* end pixel - start pixel */
pixels_per_line = dev->calib_pixels;
channels = dev->calib_channels;
uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset;
dev->average_size = channels * out_pixels_per_line;
dev->dark_average_data.clear();
dev->dark_average_data.resize(dev->average_size);
if (dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) {
// FIXME: dark shading currently not supported on infrared transparency scans
return;
}
// FIXME: the current calculation is likely incorrect on non-GL843 implementations,
// but this needs checking
if (dev->calib_total_bytes_to_read > 0) {
size = dev->calib_total_bytes_to_read;
} else if (dev->model->asic_type == AsicType::GL843) {
size = channels * 2 * pixels_per_line * dev->calib_lines;
} else {
size = channels * 2 * pixels_per_line * (dev->calib_lines + 1);
}
std::vector<uint8_t> calibration_data(size);
motor=SANE_TRUE;
if (dev->model->flags & GENESYS_FLAG_SHADING_NO_MOVE)
{
motor=SANE_FALSE;
}
/* turn off motor and lamp power for flatbed scanners, but not for sheetfed scanners
* because they have a calibration sheet with a sufficient black strip */
if (dev->model->is_sheetfed == SANE_FALSE)
{
sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, false);
sanei_genesys_set_motor_power(dev->calib_reg, motor);
}
else
{
sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, true);
sanei_genesys_set_motor_power(dev->calib_reg, motor);
}
dev->write_registers(dev->calib_reg);
// wait some time to let lamp to get dark
sanei_genesys_sleep_ms(200);
dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, SANE_FALSE);
sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size);
dev->cmd_set->end_scan(dev, &dev->calib_reg, SANE_TRUE);
std::fill(dev->dark_average_data.begin(),
dev->dark_average_data.begin() + dev->calib_pixels_offset * channels, 0);
genesys_average_data(dev->dark_average_data.data() + dev->calib_pixels_offset * channels,
calibration_data.data(), dev->calib_lines, pixels_per_line * channels);
if (DBG_LEVEL >= DBG_data)
{
sanei_genesys_write_pnm_file("gl_black_shading.pnm", calibration_data.data(), 16,
channels, pixels_per_line, dev->calib_lines);
sanei_genesys_write_pnm_file16("gl_black_average.pnm", dev->dark_average_data.data(),
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_dummy_dark_shading(Genesys_Device* dev, const Genesys_Sensor& sensor)
{
DBG_HELPER(dbg);
uint32_t pixels_per_line;
uint8_t channels;
uint32_t skip, xend;
int dummy1, dummy2, dummy3; /* dummy black average per channel */
pixels_per_line = dev->calib_pixels;
channels = dev->calib_channels;
uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset;
dev->average_size = channels * out_pixels_per_line;
dev->dark_average_data.clear();
dev->dark_average_data.resize(dev->average_size, 0);
/* we average values on 'the left' where CCD pixels are under casing and
give darkest values. We then use these as dummy dark calibration */
if (dev->settings.xres <= sensor.optical_res / 2)
{
skip = 4;
xend = 36;
}
else
{
skip = 4;
xend = 68;
}
if (dev->model->ccd_type==CCD_G4050
|| dev->model->ccd_type==CCD_CS4400F
|| dev->model->ccd_type==CCD_CS8400F
|| dev->model->ccd_type==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 * 2 * x] = dummy1;
if (channels > 1) {
dev->dark_average_data[channels * 2 * x + 1] = dummy2;
dev->dark_average_data[channels * 2 * x + 2] = dummy3;
}
}
}
static void genesys_repark_sensor_before_shading(Genesys_Device* dev)
{
if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK) {
// rewind keeps registers and slopes table intact from previous scan but is not
// available on all supported chipsets (or may cause scan artifacts, see #7)
if (dev->cmd_set->rewind) {
dev->cmd_set->rewind(dev);
} else {
dev->cmd_set->slow_back_home(dev, SANE_TRUE);
}
if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
{
dev->cmd_set->move_to_ta(dev);
}
}
}
static void genesys_white_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor)
{
DBG_HELPER_ARGS(dbg, "lines = %d", (unsigned int)dev->calib_lines);
size_t size;
uint32_t pixels_per_line;
uint8_t channels;
SANE_Bool motor;
pixels_per_line = dev->calib_pixels;
channels = dev->calib_channels;
uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset;
dev->white_average_data.clear();
dev->white_average_data.resize(channels * out_pixels_per_line);
// FIXME: the current calculation is likely incorrect on non-GL843 implementations,
// but this needs checking
if (dev->calib_total_bytes_to_read > 0) {
size = dev->calib_total_bytes_to_read;
} else if (dev->model->asic_type == AsicType::GL843) {
size = channels * 2 * pixels_per_line * dev->calib_lines;
} else {
size = channels * 2 * pixels_per_line * (dev->calib_lines + 1);
}
std::vector<uint8_t> calibration_data(size);
motor=SANE_TRUE;
if (dev->model->flags & GENESYS_FLAG_SHADING_NO_MOVE)
{
motor=SANE_FALSE;
}
// turn on motor and lamp power
sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, true);
sanei_genesys_set_motor_power(dev->calib_reg, motor);
dev->write_registers(dev->calib_reg);
if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION) {
sanei_genesys_sleep_ms(500); // make sure lamp is bright again
}
dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, SANE_TRUE);
sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size);
dev->cmd_set->end_scan(dev, &dev->calib_reg, SANE_TRUE);
if (DBG_LEVEL >= DBG_data) {
sanei_genesys_write_pnm_file("gl_white_shading.pnm", calibration_data.data(), 16,
channels, pixels_per_line, dev->calib_lines);
}
std::fill(dev->dark_average_data.begin(),
dev->dark_average_data.begin() + dev->calib_pixels_offset * channels, 0);
genesys_average_data(dev->white_average_data.data() + dev->calib_pixels_offset * channels,
calibration_data.data(), dev->calib_lines, pixels_per_line * channels);
if (DBG_LEVEL >= DBG_data) {
sanei_genesys_write_pnm_file16("gl_white_average.pnm", dev->white_average_data.data(),
channels, out_pixels_per_line, 1);
}
/* in case we haven't done dark calibration, build dummy data from white_average */
if (!(dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION))
{
genesys_dummy_dark_shading(dev, sensor);
}
if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK)
{
dev->cmd_set->slow_back_home(dev, SANE_TRUE);
}
}
// This calibration uses a scan over the calibration target, comprising a black and a white strip.
// (So the motor must be on.)
static void genesys_dark_white_shading_calibration(Genesys_Device* dev,
const Genesys_Sensor& sensor)
{
DBG_HELPER_ARGS(dbg, "lines = %d", (unsigned int)dev->calib_lines);
size_t size;
uint32_t pixels_per_line;
uint8_t channels;
unsigned int x;
int y;
uint32_t dark, white, dark_sum, white_sum, dark_count, white_count, col,
dif;
SANE_Bool motor;
pixels_per_line = dev->calib_pixels;
channels = dev->calib_channels;
uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset;
dev->average_size = channels * out_pixels_per_line;
dev->white_average_data.clear();
dev->white_average_data.resize(dev->average_size);
dev->dark_average_data.clear();
dev->dark_average_data.resize(dev->average_size);
if (dev->calib_total_bytes_to_read > 0)
size = dev->calib_total_bytes_to_read;
else
size = channels * 2 * pixels_per_line * dev->calib_lines;
std::vector<uint8_t> calibration_data(size);
motor=SANE_TRUE;
if (dev->model->flags & GENESYS_FLAG_SHADING_NO_MOVE)
{
motor=SANE_FALSE;
}
// turn on motor and lamp power
sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, true);
sanei_genesys_set_motor_power(dev->calib_reg, motor);
dev->write_registers(dev->calib_reg);
dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, SANE_FALSE);
sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size);
dev->cmd_set->end_scan(dev, &dev->calib_reg, SANE_TRUE);
if (DBG_LEVEL >= DBG_data)
{
if (dev->model->is_cis)
{
sanei_genesys_write_pnm_file("gl_black_white_shading.pnm", calibration_data.data(),
16, 1, pixels_per_line*channels,
dev->calib_lines);
}
else
{
sanei_genesys_write_pnm_file("gl_black_white_shading.pnm", calibration_data.data(),
16, channels, pixels_per_line,
dev->calib_lines);
}
}
std::fill(dev->dark_average_data.begin(),
dev->dark_average_data.begin() + dev->calib_pixels_offset * channels, 0);
std::fill(dev->white_average_data.begin(),
dev->white_average_data.begin() + dev->calib_pixels_offset * channels, 0);
uint16_t* average_white = dev->white_average_data.data() + dev->calib_pixels_offset * channels;
uint16_t* average_dark = dev->dark_average_data.data() + dev->calib_pixels_offset * channels;
for (x = 0; x < pixels_per_line * channels; x++)
{
dark = 0xffff;
white = 0;
for (y = 0; y < (int)dev->calib_lines; y++)
{
col = calibration_data[(x + y * pixels_per_line * channels) * 2];
col |=
calibration_data[(x + y * pixels_per_line * channels) * 2 +
1] << 8;
if (col > white)
white = col;
if (col < dark)
dark = col;
}
dif = white - dark;
dark = dark + dif / 8;
white = white - dif / 8;
dark_count = 0;
dark_sum = 0;
white_count = 0;
white_sum = 0;
for (y = 0; y < (int)dev->calib_lines; y++)
{
col = calibration_data[(x + y * pixels_per_line * channels) * 2];
col |=
calibration_data[(x + y * pixels_per_line * channels) * 2 +
1] << 8;
if (col >= white)
{
white_sum += col;
white_count++;
}
if (col <= dark)
{
dark_sum += col;
dark_count++;
}
}
dark_sum /= dark_count;
white_sum /= white_count;
*average_dark++ = dark_sum;
*average_white++ = white_sum;
}
if (DBG_LEVEL >= DBG_data) {
sanei_genesys_write_pnm_file16("gl_white_average.pnm", dev->white_average_data.data(),
channels, out_pixels_per_line, 1);
sanei_genesys_write_pnm_file16("gl_dark_average.pnm", dev->dark_average_data.data(),
channels, out_pixels_per_line, 1);
}
}
/* computes one coefficient given bright-dark value
* @param coeff factor giving 1.00 gain
* @param target desired target code
* @param value brght-dark value
* */
static unsigned int
compute_coefficient (unsigned int coeff, unsigned int target, unsigned int value)
{
int result;
if (value > 0)
{
result = (coeff * target) / value;
if (result >= 65535)
{
result = 65535;
}
}
else
{
result = coeff;
}
return result;
}
/** @brief compute shading coefficients for LiDE scanners
* The dark/white shading is actually performed _after_ reducing
* resolution via averaging. only dark/white shading data for what would be
* first pixel at full resolution is used.
*
* scanner raw input to output value calculation:
* o=(i-off)*(gain/coeff)
*
* from datasheet:
* off=dark_average
* gain=coeff*bright_target/(bright_average-dark_average)
* works for dark_target==0
*
* what we want is these:
* bright_target=(bright_average-off)*(gain/coeff)
* dark_target=(dark_average-off)*(gain/coeff)
* leading to
* off = (dark_average*bright_target - bright_average*dark_target)/(bright_target - dark_target)
* gain = (bright_target - dark_target)/(bright_average - dark_average)*coeff
*
* @param dev scanner's device
* @param shading_data memory area where to store the computed shading coefficients
* @param pixels_per_line number of pixels per line
* @param words_per_color memory words per color channel
* @param channels number of color channels (actually 1 or 3)
* @param o shading coefficients left offset
* @param coeff 4000h or 2000h depending on fast scan mode or not (GAIN4 bit)
* @param target_bright value of the white target code
* @param target_dark value of the black target code
*/
static void
compute_averaged_planar (Genesys_Device * dev, const Genesys_Sensor& sensor,
uint8_t * shading_data,
unsigned int pixels_per_line,
unsigned int words_per_color,
unsigned int channels,
unsigned int o,
unsigned int coeff,
unsigned int target_bright,
unsigned int target_dark)
{
unsigned int x, i, j, br, dk, res, avgpixels, basepixels, val;
unsigned int fill,factor;
DBG(DBG_info, "%s: pixels=%d, offset=%d\n", __func__, pixels_per_line, o);
/* initialize result */
memset (shading_data, 0xff, words_per_color * 3 * 2);
/*
strangely i can write 0x20000 bytes beginning at 0x00000 without overwriting
slope tables - which begin at address 0x10000(for 1200dpi hw mode):
memory is organized in words(2 bytes) instead of single bytes. explains
quite some things
*/
/*
another one: the dark/white shading is actually performed _after_ reducing
resolution via averaging. only dark/white shading data for what would be
first pixel at full resolution is used.
*/
/*
scanner raw input to output value calculation:
o=(i-off)*(gain/coeff)
from datasheet:
off=dark_average
gain=coeff*bright_target/(bright_average-dark_average)
works for dark_target==0
what we want is these:
bright_target=(bright_average-off)*(gain/coeff)
dark_target=(dark_average-off)*(gain/coeff)
leading to
off = (dark_average*bright_target - bright_average*dark_target)/(bright_target - dark_target)
gain = (bright_target - dark_target)/(bright_average - dark_average)*coeff
*/
res = dev->settings.xres;
if (sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres) > 1)
{
res *= 2;
}
/* this should be evenly dividable */
basepixels = sensor.optical_res / res;
/* gl841 supports 1/1 1/2 1/3 1/4 1/5 1/6 1/8 1/10 1/12 1/15 averaging */
if (basepixels < 1)
avgpixels = 1;
else if (basepixels < 6)
avgpixels = basepixels;
else if (basepixels < 8)
avgpixels = 6;
else if (basepixels < 10)
avgpixels = 8;
else if (basepixels < 12)
avgpixels = 10;
else if (basepixels < 15)
avgpixels = 12;
else
avgpixels = 15;
/* LiDE80 packs shading data */
if(dev->model->ccd_type != CIS_CANONLIDE80)
{
factor=1;
fill=avgpixels;
}
else
{
factor=avgpixels;
fill=1;
}
DBG(DBG_info, "%s: averaging over %d pixels\n", __func__, avgpixels);
DBG(DBG_info, "%s: packing factor is %d\n", __func__, factor);
DBG(DBG_info, "%s: fill length is %d\n", __func__, fill);
for (x = 0; x <= pixels_per_line - avgpixels; x += avgpixels)
{
if ((x + o) * 2 * 2 + 3 > words_per_color * 2)
break;
for (j = 0; j < channels; j++)
{
dk = 0;
br = 0;
for (i = 0; i < avgpixels; i++)
{
// dark data
dk += dev->dark_average_data[(x + i + pixels_per_line * j)];
// white data
br += dev->white_average_data[(x + i + pixels_per_line * j)];
}
br /= avgpixels;
dk /= avgpixels;
if (br * target_dark > dk * target_bright)
val = 0;
else if (dk * target_bright - br * target_dark >
65535 * (target_bright - target_dark))
val = 65535;
else
{
val = (dk * target_bright - br * target_dark) / (target_bright - target_dark);
}
/*fill all pixels, even if only the last one is relevant*/
for (i = 0; i < fill; i++)
{
shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j] = val & 0xff;
shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 1] = val >> 8;
}
val = br - dk;
if (65535 * val > (target_bright - target_dark) * coeff)
{
val = (coeff * (target_bright - target_dark)) / val;
}
else
{
val = 65535;
}
/*fill all pixels, even if only the last one is relevant*/
for (i = 0; i < fill; i++)
{
shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 2] = val & 0xff;
shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 3] = val >> 8;
}
}
/* fill remaining channels */
for (j = channels; j < 3; j++)
{
for (i = 0; i < fill; i++)
{
shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j ] = shading_data[(x/factor + o + i) * 2 * 2 ];
shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 1] = shading_data[(x/factor + o + i) * 2 * 2 + 1];
shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 2] = shading_data[(x/factor + o + i) * 2 * 2 + 2];
shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 3] = shading_data[(x/factor + o + i) * 2 * 2 + 3];
}
}
}
}
static std::array<unsigned, 3> color_order_to_cmat(ColorOrder color_order)
{
switch (color_order) {
case ColorOrder::RGB: return {0, 1, 2};
case ColorOrder::GBR: return {2, 0, 1};
default:
throw std::logic_error("Unknown color order");
}
}
/**
* Computes shading coefficient using formula in data sheet. 16bit data values
* manipulated here are little endian. For now we assume deletion scanning type
* and that there is always 3 channels.
* @param dev scanner's device
* @param shading_data memory area where to store the computed shading coefficients
* @param pixels_per_line number of pixels per line
* @param channels number of color channels (actually 1 or 3)
* @param cmat color transposition matrix
* @param offset shading coefficients left offset
* @param coeff 4000h or 2000h depending on fast scan mode or not
* @param target value of the target code
*/
static void compute_coefficients(Genesys_Device * dev,
uint8_t * shading_data,
unsigned int pixels_per_line,
unsigned int channels,
ColorOrder color_order,
int offset,
unsigned int coeff,
unsigned int target)
{
uint8_t *ptr; /* contain 16bit words in little endian */
unsigned int x, c;
unsigned int val, br, dk;
unsigned int start, end;
DBG(DBG_io, "%s: pixels_per_line=%d, coeff=0x%04x\n", __func__, pixels_per_line, coeff);
auto cmat = color_order_to_cmat(color_order);
/* compute start & end values depending of the offset */
if (offset < 0)
{
start = -1 * offset;
end = pixels_per_line;
}
else
{
start = 0;
end = pixels_per_line - offset;
}
for (c = 0; c < channels; c++)
{
for (x = start; x < end; x++)
{
/* TODO if channels=1 , use filter to know the base addr */
ptr = shading_data + 4 * ((x + offset) * channels + cmat[c]);
// dark data
dk = dev->dark_average_data[x * channels + c];
// white data
br = dev->white_average_data[x * channels + c];
/* compute coeff */
val=compute_coefficient(coeff,target,br-dk);
/* assign it */
ptr[0] = dk & 255;
ptr[1] = dk / 256;
ptr[2] = val & 0xff;
ptr[3] = val / 256;
}
}
}
/**
* Computes shading coefficient using formula in data sheet. 16bit data values
* manipulated here are little endian. Data is in planar form, ie grouped by
* lines of the same color component.
* @param dev scanner's device
* @param shading_data memory area where to store the computed shading coefficients
* @param factor averaging factor when the calibration scan is done at a higher resolution
* than the final scan
* @param pixels_per_line number of pixels per line
* @param words_per_color total number of shading data words for one color element
* @param channels number of color channels (actually 1 or 3)
* @param cmat transcoding matrix for color channel order
* @param offset shading coefficients left offset
* @param coeff 4000h or 2000h depending on fast scan mode or not
* @param target white target value
*/
static void compute_planar_coefficients(Genesys_Device * dev,
uint8_t * shading_data,
unsigned int factor,
unsigned int pixels_per_line,
unsigned int words_per_color,
unsigned int channels,
ColorOrder color_order,
unsigned int offset,
unsigned int coeff,
unsigned int target)
{
uint8_t *ptr; /* contains 16bit words in little endian */
uint32_t x, c, i;
uint32_t val, dk, br;
auto cmat = color_order_to_cmat(color_order);
DBG(DBG_io, "%s: factor=%d, pixels_per_line=%d, words=0x%X, coeff=0x%04x\n", __func__, factor,
pixels_per_line, words_per_color, coeff);
for (c = 0; c < channels; c++)
{
/* shading data is larger than pixels_per_line so offset can be neglected */
for (x = 0; x < pixels_per_line; x+=factor)
{
/* x2 because of 16 bit values, and x2 since one coeff for dark
* and another for white */
ptr = shading_data + words_per_color * cmat[c] * 2 + (x + offset) * 4;
dk = 0;
br = 0;
/* average case */
for(i=0;i<factor;i++)
{
dk += dev->dark_average_data[((x+i) + pixels_per_line * c)];
br += dev->white_average_data[((x+i) + pixels_per_line * c)];
}
dk /= factor;
br /= factor;
val = compute_coefficient (coeff, target, br - dk);
/* we duplicate the information to have calibration data at optical resolution */
for (i = 0; i < factor; i++)
{
ptr[0 + 4 * i] = dk & 255;
ptr[1 + 4 * i] = dk / 256;
ptr[2 + 4 * i] = val & 0xff;
ptr[3 + 4 * i] = val / 256;
}
}
}
/* in case of gray level scan, we duplicate shading information on all
* three color channels */
if(channels==1)
{
memcpy(shading_data+cmat[1]*2*words_per_color,
shading_data+cmat[0]*2*words_per_color,
words_per_color*2);
memcpy(shading_data+cmat[2]*2*words_per_color,
shading_data+cmat[0]*2*words_per_color,
words_per_color*2);
}
}
static void
compute_shifted_coefficients (Genesys_Device * dev,
const Genesys_Sensor& sensor,
uint8_t * shading_data,
unsigned int pixels_per_line,
unsigned int channels,
ColorOrder color_order,
int offset,
unsigned int coeff,
unsigned int target_dark,
unsigned int target_bright,
unsigned int patch_size) /* contigous extent */
{
unsigned int x, avgpixels, basepixels, i, j, val1, val2;
unsigned int br_tmp [3], dk_tmp [3];
uint8_t *ptr = shading_data + offset * 3 * 4; /* contain 16bit words in little endian */
unsigned int patch_cnt = offset * 3; /* at start, offset of first patch */
auto cmat = color_order_to_cmat(color_order);
x = dev->settings.xres;
if (sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres) > 1)
x *= 2; /* scanner is using half-ccd mode */
basepixels = sensor.optical_res / x; /*this should be evenly dividable */
/* gl841 supports 1/1 1/2 1/3 1/4 1/5 1/6 1/8 1/10 1/12 1/15 averaging */
if (basepixels < 1)
avgpixels = 1;
else if (basepixels < 6)
avgpixels = basepixels;
else if (basepixels < 8)
avgpixels = 6;
else if (basepixels < 10)
avgpixels = 8;
else if (basepixels < 12)
avgpixels = 10;
else if (basepixels < 15)
avgpixels = 12;
else
avgpixels = 15;
DBG(DBG_info, "%s: pixels_per_line=%d, coeff=0x%04x, averaging over %d pixels\n", __func__,
pixels_per_line, coeff, avgpixels);
for (x = 0; x <= pixels_per_line - avgpixels; x += avgpixels) {
memset (&br_tmp, 0, sizeof(br_tmp));
memset (&dk_tmp, 0, sizeof(dk_tmp));
for (i = 0; i < avgpixels; i++) {
for (j = 0; j < channels; j++) {
br_tmp[j] += dev->white_average_data[((x + i) * channels + j)];
dk_tmp[i] += dev->dark_average_data[((x + i) * channels + j)];
}
}
for (j = 0; j < channels; j++) {
br_tmp[j] /= avgpixels;
dk_tmp[j] /= avgpixels;
if (br_tmp[j] * target_dark > dk_tmp[j] * target_bright)
val1 = 0;
else if (dk_tmp[j] * target_bright - br_tmp[j] * target_dark > 65535 * (target_bright - target_dark))
val1 = 65535;
else
val1 = (dk_tmp[j] * target_bright - br_tmp[j] * target_dark) / (target_bright - target_dark);
val2 = br_tmp[j] - dk_tmp[j];
if (65535 * val2 > (target_bright - target_dark) * coeff)
val2 = (coeff * (target_bright - target_dark)) / val2;
else
val2 = 65535;
br_tmp[j] = val1;
dk_tmp[j] = val2;
}
for (i = 0; i < avgpixels; i++) {
for (j = 0; j < channels; j++) {
* ptr++ = br_tmp[ cmat[j] ] & 0xff;
* ptr++ = br_tmp[ cmat[j] ] >> 8;
* ptr++ = dk_tmp[ cmat[j] ] & 0xff;
* ptr++ = dk_tmp[ cmat[j] ] >> 8;
patch_cnt++;
if (patch_cnt == patch_size) {
patch_cnt = 0;
val1 = cmat[2];
cmat[2] = cmat[1];
cmat[1] = cmat[0];
cmat[0] = val1;
}
}
}
}
}
static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_Sensor& sensor)
{
DBG_HELPER(dbg);
uint32_t pixels_per_line;
uint8_t channels;
int o;
unsigned int length; /**> number of shading calibration data words */
unsigned int factor;
unsigned int coeff, target_code, words_per_color = 0;
pixels_per_line = dev->calib_pixels + dev->calib_pixels_offset;
channels = dev->calib_channels;
/* we always build data for three channels, even for gray
* we make the shading data such that each color channel data line is contiguous
* to the next one, which allow to write the 3 channels in 1 write
* during genesys_send_shading_coefficient, some values are words, other bytes
* hence the x2 factor */
switch (dev->reg.get8(0x05) >> 6)
{
/* 600 dpi */
case 0:
words_per_color = 0x2a00;
break;
/* 1200 dpi */
case 1:
words_per_color = 0x5500;
break;
/* 2400 dpi */
case 2:
words_per_color = 0xa800;
break;
/* 4800 dpi */
case 3:
words_per_color = 0x15000;
break;
}
/* special case, memory is aligned on 0x5400, this has yet to be explained */
/* could be 0xa800 because sensor is truly 2400 dpi, then halved because
* we only set 1200 dpi */
if(dev->model->ccd_type==CIS_CANONLIDE80)
{
words_per_color = 0x5400;
}
length = words_per_color * 3 * 2;
/* allocate computed size */
// contains 16bit words in little endian
std::vector<uint8_t> shading_data(length, 0);
/* TARGET/(Wn-Dn) = white gain -> ~1.xxx then it is multiplied by 0x2000
or 0x4000 to give an integer
Wn = white average for column n
Dn = dark average for column n
*/
if (dev->cmd_set->get_gain4_bit(&dev->calib_reg)) {
coeff = 0x4000;
} else {
coeff = 0x2000;
}
/* compute avg factor */
if(dev->settings.xres>sensor.optical_res)
{
factor=1;
}
else
{
factor=sensor.optical_res/dev->settings.xres;
}
/* for GL646, shading data is planar if REG01_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 REG01_FASTMOD.
*/
/* TODO setup a struct in genesys_devices that
* will handle these settings instead of having this switch growing up */
switch (dev->model->ccd_type)
{
case CCD_XP300:
case CCD_ROADWARRIOR:
case CCD_DP665:
case CCD_DP685:
case 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 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 CCD_HP2300:
target_code = 0xdc00;
o = 2;
if(dev->settings.xres<=sensor.optical_res/2)
{
o = o - sensor.dummy_pixel / 2;
}
compute_coefficients (dev,
shading_data.data(),
pixels_per_line,
3,
ColorOrder::RGB,
o,
coeff,
target_code);
break;
case CCD_5345:
target_code = 0xe000;
o = 4;
if(dev->settings.xres<=sensor.optical_res/2)
{
o = o - sensor.dummy_pixel;
}
compute_coefficients (dev,
shading_data.data(),
pixels_per_line,
3,
ColorOrder::RGB,
o,
coeff,
target_code);
break;
case CCD_HP3670:
case 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 CCD_KVSS080:
case CCD_PLUSTEK3800:
case CCD_G4050:
case CCD_CS4400F:
case CCD_CS8400F:
case CCD_CS8600F:
target_code = 0xe000;
o = 0;
compute_coefficients (dev,
shading_data.data(),
pixels_per_line,
3,
ColorOrder::RGB,
o,
coeff,
target_code);
break;
case CIS_CANONLIDE700:
case CIS_CANONLIDE100:
case CIS_CANONLIDE200:
case CIS_CANONLIDE110:
case CIS_CANONLIDE120:
case CIS_CANONLIDE210:
case CIS_CANONLIDE220:
/* TODO store this in a data struct so we avoid
* growing this switch */
switch(dev->model->ccd_type)
{
case CIS_CANONLIDE110:
case CIS_CANONLIDE120:
case CIS_CANONLIDE210:
case CIS_CANONLIDE220:
target_code = 0xf000;
break;
case CIS_CANONLIDE700:
target_code = 0xc000; /* from experimentation */
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 CCD_CANONLIDE35:
compute_averaged_planar (dev, sensor,
shading_data.data(),
pixels_per_line,
words_per_color,
channels,
4,
coeff,
0xe000,
0x0a00);
break;
case CIS_CANONLIDE80:
compute_averaged_planar (dev, sensor,
shading_data.data(),
pixels_per_line,
words_per_color,
channels,
0,
coeff,
0xe000,
0x0800);
break;
case CCD_PLUSTEK_3600:
compute_shifted_coefficients (dev, sensor,
shading_data.data(),
pixels_per_line,
channels,
ColorOrder::RGB,
12, /* offset */
coeff,
0x0001, /* target_dark */
0xf900, /* target_bright */
256); /* patch_size: contigous extent */
break;
default:
throw SaneException(SANE_STATUS_UNSUPPORTED, "sensor %d not supported",
dev->model->ccd_type);
break;
}
// do the actual write of shading calibration data to the scanner
genesys_send_offset_and_shading(dev, sensor, shading_data.data(), length);
}
/**
* search calibration cache list for an entry matching required scan.
* If one is found, set device calibration with it
* @param dev scanner's device
* @return false if no matching cache entry has been
* found, true if one has been found and used.
*/
static bool
genesys_restore_calibration(Genesys_Device * dev, Genesys_Sensor& sensor)
{
DBG_HELPER(dbg);
// if no cache or no function to evaluate cache entry ther can be no match/
if (dev->cmd_set->is_compatible_calibration == nullptr || dev->calibration_cache.empty()) {
return false;
}
/* we walk the link list of calibration cache in search for a
* matching one */
for (auto& cache : dev->calibration_cache)
{
if (dev->cmd_set->is_compatible_calibration(dev, sensor, &cache, SANE_FALSE)) {
dev->frontend = cache.frontend;
/* we don't restore the gamma fields */
sensor.exposure = cache.sensor.exposure;
dev->average_size = cache.average_size;
dev->calib_pixels = cache.calib_pixels;
dev->calib_channels = cache.calib_channels;
dev->dark_average_data = cache.dark_average_data;
dev->white_average_data = cache.white_average_data;
if(dev->cmd_set->send_shading_data == nullptr) {
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
if (dev->cmd_set->is_compatible_calibration == nullptr)
return;
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 (dev->cmd_set->is_compatible_calibration(dev, sensor, &*cache_it, SANE_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->used_setup = dev->current_setup;
found_cache_it->params = dev->session.params;
found_cache_it->frontend = dev->frontend;
found_cache_it->sensor = sensor;
found_cache_it->calib_pixels = dev->calib_pixels;
found_cache_it->calib_channels = dev->calib_channels;
#ifdef HAVE_SYS_TIME_H
gettimeofday(&time,NULL);
found_cache_it->last_calibration = time.tv_sec;
#endif
}
/**
* does the calibration process for a flatbed scanner
* - offset calibration
* - gain calibration
* - shading calibration
* @param dev device to calibrate
*/
static void genesys_flatbed_calibration(Genesys_Device* dev, Genesys_Sensor& sensor)
{
DBG_HELPER(dbg);
uint32_t pixels_per_line;
unsigned coarse_res = sensor.optical_res;
if (dev->settings.yres <= sensor.optical_res / 2) {
coarse_res /= 2;
}
if (dev->model->model_id == MODEL_CANON_CANOSCAN_8400F ||
dev->model->model_id == MODEL_CANON_CANOSCAN_8600F)
{
coarse_res = 1200;
}
/* do offset calibration if needed */
if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION)
{
sanei_usb_testing_record_message("offset_calibration");
dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg);
/* since all the registers are set up correctly, just use them */
sanei_usb_testing_record_message("coarse_gain_calibration");
dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, coarse_res);
}
else
/* since we have 2 gain calibration proc, skip second if first one was
used. */
{
sanei_usb_testing_record_message("init_regs_for_coarse_calibration");
dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg);
sanei_usb_testing_record_message("genesys_coarse_calibration");
genesys_coarse_calibration(dev, sensor);
}
if (dev->model->is_cis)
{
/* the afe now sends valid data for doing led calibration */
sanei_usb_testing_record_message("led_calibration");
sensor.exposure = dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg);
/* calibrate afe again to match new exposure */
if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION)
{
sanei_usb_testing_record_message("offset_calibration");
dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg);
/* since all the registers are set up correctly, just use them */
sanei_usb_testing_record_message("coarse_gain_calibration");
dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, coarse_res);
}
else
/* since we have 2 gain calibration proc, skip second if first one was
used. */
{
sanei_usb_testing_record_message("init_regs_for_coarse_calibration");
dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg);
sanei_usb_testing_record_message("genesys_coarse_calibration");
genesys_coarse_calibration(dev, sensor);
}
}
/* we always use sensor pixel number when the ASIC can't handle multi-segments sensor */
if (!(dev->model->flags & GENESYS_FLAG_SIS_SENSOR))
{
pixels_per_line = (SANE_UNFIX (dev->model->x_size) * dev->settings.xres) / MM_PER_INCH;
}
else
{
pixels_per_line = sensor.sensor_pixels;
}
/* send default shading data */
sanei_usb_testing_record_message("sanei_genesys_init_shading_data");
sanei_genesys_init_shading_data(dev, sensor, pixels_per_line);
if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
{
dev->cmd_set->move_to_ta(dev);
}
/* shading calibration */
sanei_usb_testing_record_message("init_regs_for_shading");
dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg);
if (dev->model->flags & GENESYS_FLAG_DARK_WHITE_CALIBRATION)
{
sanei_usb_testing_record_message("genesys_dark_white_shading_calibration");
genesys_dark_white_shading_calibration(dev, sensor);
}
else
{
DBG(DBG_proc, "%s : genesys_dark_shading_calibration dev->calib_reg ", __func__);
debug_dump(DBG_proc, dev->calib_reg);
if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)
{
sanei_usb_testing_record_message("genesys_dark_shading_calibration");
genesys_dark_shading_calibration(dev, sensor);
}
genesys_repark_sensor_before_shading(dev);
sanei_usb_testing_record_message("init_regs_for_shading2");
dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg);
sanei_usb_testing_record_message("genesys_white_shading_calibration");
genesys_white_shading_calibration(dev, sensor);
}
if (dev->cmd_set->send_shading_data == nullptr) {
sanei_usb_testing_record_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);
SANE_Bool forward = SANE_TRUE;
if (dev->cmd_set->search_strip == nullptr) {
throw SaneException(SANE_STATUS_UNSUPPORTED, "no strip searching function available");
}
// first step, load document
dev->cmd_set->load_document(dev);
/* led, offset and gain calibration are influenced by scan
* settings. So we set it to sensor resolution */
dev->settings.xres = sensor.optical_res;
/* XP200 needs to calibrate a full and half sensor's resolution */
if (dev->model->ccd_type == CIS_XP200
&& dev->settings.xres <= sensor.optical_res / 2)
dev->settings.xres /= 2;
/* the afe needs to sends valid data even before calibration */
/* go to a white area */
try {
dev->cmd_set->search_strip(dev, sensor, forward, SANE_FALSE);
} catch (...) {
catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
throw;
}
if (dev->model->is_cis)
{
dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg);
}
/* calibrate afe */
if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION)
{
dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg);
/* since all the registers are set up correctly, just use them */
dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, sensor.optical_res);
}
else
/* since we have 2 gain calibration proc, skip second if first one was
used. */
{
dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg);
genesys_coarse_calibration(dev, sensor);
}
/* search for a full width black strip and then do a 16 bit scan to
* gather black shading data */
if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)
{
/* seek black/white reverse/forward */
try {
dev->cmd_set->search_strip(dev, sensor, forward, SANE_TRUE);
} catch (...) {
catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
throw;
}
dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg);
try {
genesys_dark_shading_calibration(dev, sensor);
} catch (...) {
catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
throw;
}
forward = SANE_FALSE;
}
/* go to a white area */
try {
dev->cmd_set->search_strip(dev, sensor, forward, SANE_FALSE);
} catch (...) {
catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); });
throw;
}
genesys_repark_sensor_before_shading(dev);
dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg);
try {
genesys_white_shading_calibration(dev, sensor);
} 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 */
if (!(dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION))
{
dev->dark_average_data.clear();
dev->dark_average_data.resize(dev->average_size, 0x0f0f);
/* XXX STEF XXX
* with black point in white shading, build an average black
* pixel and use it to fill the dark_average
* dev->calib_pixels
(sensor.sensor_pixels * dev->settings.xres) / sensor.optical_res,
dev->calib_lines,
*/
}
/* send the shading coefficient when doing whole line shading
* but not when using SHDAREA like GL124 */
if (dev->cmd_set->send_shading_data == nullptr) {
genesys_send_shading_coefficient(dev, sensor);
}
// save the calibration data
genesys_save_calibration(dev, sensor);
// and finally eject calibration sheet
dev->cmd_set->eject_document(dev);
// restore settings
dev->settings.xres = sensor.optical_res;
}
/**
* does the calibration process for a device
* @param dev device to calibrate
*/
static void genesys_scanner_calibration(Genesys_Device* dev, Genesys_Sensor& sensor)
{
DBG_HELPER(dbg);
if (dev->model->is_sheetfed == SANE_FALSE)
{
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);
int seconds = 0;
int pixel;
int channels, total_size;
double first_average = 0;
double second_average = 0;
int difference = 255;
int empty, lines = 3;
/* check if the current chipset implements warmup */
if (dev->cmd_set->init_regs_for_warmup == NULL) {
throw SaneException("init_regs_for_warmup not implemented");
}
const auto& sensor = sanei_genesys_find_sensor_any(dev);
dev->cmd_set->init_regs_for_warmup(dev, sensor, &dev->reg, &channels, &total_size);
std::vector<uint8_t> first_line(total_size);
std::vector<uint8_t> second_line(total_size);
do
{
DBG(DBG_info, "%s: one more loop\n", __func__);
dev->cmd_set->begin_scan(dev, sensor, &dev->reg, SANE_FALSE);
do
{
sanei_genesys_test_buffer_empty(dev, &empty);
}
while (empty);
try {
sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size);
} catch (...) {
// FIXME: document why this retry is here
sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size);
}
dev->cmd_set->end_scan(dev, &dev->reg, SANE_TRUE);
sanei_genesys_sleep_ms(1000);
seconds++;
dev->cmd_set->begin_scan(dev, sensor, &dev->reg, SANE_FALSE);
do
{
sanei_genesys_test_buffer_empty(dev, &empty);
sanei_genesys_sleep_ms(100);
}
while (empty);
sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size);
dev->cmd_set->end_scan(dev, &dev->reg, SANE_TRUE);
/* compute difference between the two scans */
for (pixel = 0; pixel < total_size; pixel++)
{
// 16 bit data
if (dev->cmd_set->get_bitset_bit(&dev->reg)) {
first_average += (first_line[pixel] + first_line[pixel + 1] * 256);
second_average += (second_line[pixel] + second_line[pixel + 1] * 256);
pixel++;
}
else
{
first_average += first_line[pixel];
second_average += second_line[pixel];
}
}
if (dev->cmd_set->get_bitset_bit(&dev->reg)) {
first_average /= pixel;
second_average /= pixel;
difference = fabs (first_average - second_average);
DBG(DBG_info, "%s: average = %.2f, diff = %.3f\n", __func__,
100 * ((second_average) / (256 * 256)),
100 * (difference / second_average));
if (second_average > (100 * 256)
&& (difference / second_average) < 0.002)
break;
}
else
{
first_average /= pixel;
second_average /= pixel;
if (DBG_LEVEL >= DBG_data)
{
sanei_genesys_write_pnm_file("gl_warmup1.pnm", first_line.data(), 8, channels,
total_size / (lines * channels), lines);
sanei_genesys_write_pnm_file("gl_warmup2.pnm", second_line.data(), 8, channels,
total_size / (lines * channels), lines);
}
DBG(DBG_info, "%s: average 1 = %.2f, average 2 = %.2f\n", __func__, first_average,
second_average);
/* if delta below 15/255 ~= 5.8%, lamp is considred warm enough */
if (fabs (first_average - second_average) < 15
&& second_average > 55)
break;
}
/* sleep another second before next loop */
sanei_genesys_sleep_ms(1000);
seconds++;
}
while (seconds < WARMUP_TIME);
if (seconds >= WARMUP_TIME)
{
throw SaneException(SANE_STATUS_IO_ERROR,
"warmup timed out after %d seconds. Lamp defective?", seconds);
}
else
{
DBG(DBG_info, "%s: warmup succeeded after %d seconds\n", __func__, seconds);
}
}
// High-level start of scanning
static void genesys_start_scan(Genesys_Device* dev, SANE_Bool lamp_off)
{
DBG_HELPER(dbg);
unsigned int steps, expected;
SANE_Bool empty;
/* since not all scanners are set ot wait for head to park
* we check we are not still parking before starting a new scan */
if (dev->parking == SANE_TRUE)
{
sanei_genesys_wait_for_home(dev);
}
// disable power saving
dev->cmd_set->save_power(dev, SANE_FALSE);
/* wait for lamp warmup : until a warmup for TRANSPARENCY is designed, skip
* it when scanning from XPA. */
if (!(dev->model->flags & GENESYS_FLAG_SKIP_WARMUP)
&& (dev->settings.scan_method == ScanMethod::FLATBED))
{
genesys_warmup_lamp(dev);
}
/* set top left x and y values by scanning the internals if flatbed scanners */
if (dev->model->is_sheetfed == SANE_FALSE)
{
/* do the geometry detection only once */
if ((dev->model->flags & GENESYS_FLAG_SEARCH_START)
&& (dev->model->y_offset_calib == 0))
{
dev->cmd_set->search_start_position (dev);
dev->parking = SANE_FALSE;
dev->cmd_set->slow_back_home (dev, SANE_TRUE);
dev->scanhead_position_in_steps = 0;
}
else
{
/* Go home */
/* TODO: check we can drop this since we cannot have the
scanner's head wandering here */
dev->parking = SANE_FALSE;
dev->cmd_set->slow_back_home (dev, SANE_TRUE);
dev->scanhead_position_in_steps = 0;
}
}
/* move to calibration area for transparency adapter */
if ((dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) &&
dev->cmd_set->move_to_ta != nullptr)
{
dev->cmd_set->move_to_ta(dev);
}
/* load document if needed (for sheetfed scanner for instance) */
if (dev->model->is_sheetfed == SANE_TRUE
&& dev->cmd_set->load_document != nullptr)
{
dev->cmd_set->load_document(dev);
}
auto& sensor = sanei_genesys_find_sensor_for_write(dev, dev->settings.xres,
dev->settings.get_channels(),
dev->settings.scan_method);
// send gamma tables. They have been set to device or user value
// when setting option value */
dev->cmd_set->send_gamma_table(dev, sensor);
/* try to use cached calibration first */
if (!genesys_restore_calibration (dev, sensor))
{
/* calibration : sheetfed scanners can't calibrate before each scan */
/* and also those who have the NO_CALIBRATION flag */
if (!(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)
&&dev->model->is_sheetfed == SANE_FALSE)
{
genesys_scanner_calibration(dev, sensor);
genesys_save_calibration (dev, sensor);
}
else
{
DBG(DBG_warn, "%s: no calibration done\n", __func__);
}
}
/* build look up table for dynamic lineart */
if(dev->settings.dynamic_lineart==SANE_TRUE)
{
sanei_genesys_load_lut(dev->lineart_lut, 8, 8, 50, 205, dev->settings.threshold_curve,
dev->settings.threshold-127);
}
if (dev->cmd_set->wait_for_motor_stop) {
dev->cmd_set->wait_for_motor_stop(dev);
}
if (dev->cmd_set->needs_home_before_init_regs_for_scan &&
dev->cmd_set->needs_home_before_init_regs_for_scan(dev) &&
dev->cmd_set->slow_back_home)
{
dev->cmd_set->slow_back_home(dev, SANE_TRUE);
}
if (dev->settings.scan_method == ScanMethod::TRANSPARENCY ||
dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED)
{
dev->cmd_set->move_to_ta(dev);
}
dev->cmd_set->init_regs_for_scan(dev, sensor);
/* no lamp during scan */
if(lamp_off == SANE_TRUE)
{
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->send_shading_data != nullptr &&
!(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION))
{
genesys_send_shading_coefficient(dev, sensor);
}
// now send registers for scan
dev->write_registers(dev->reg);
// start effective scan
dev->cmd_set->begin_scan(dev, sensor, &dev->reg, SANE_TRUE);
/*do we really need this? the valid data check should be sufficent -- pierre*/
/* waits for head to reach scanning position */
expected = dev->reg.get8(0x3d) * 65536
+ dev->reg.get8(0x3e) * 256
+ dev->reg.get8(0x3f);
do
{
// wait some time between each test to avoid overloading USB and CPU
sanei_genesys_sleep_ms(100);
sanei_genesys_read_feed_steps (dev, &steps);
}
while (steps < expected);
// wait for buffers to be filled
do {
sanei_genesys_test_buffer_empty(dev, &empty);
} while (empty);
/* when doing one or two-table movement, let the motor settle to scanning speed */
/* and scanning start before reading data */
/* the valid data check already waits until the scanner delivers data. this here leads to unnecessary buffer full conditions in the scanner.
if (dev->cmd_set->get_fast_feed_bit (dev->reg)) {
sanei_genesys_sleep_ms(1000);
} else {
sanei_genesys_sleep_ms(500);
}
*/
/* then 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 == SANE_FALSE)
{
do
{
sanei_genesys_sleep_ms(100);
sanei_genesys_read_valid_words(dev, &steps);
}
while (steps < 1);
}
}
#include "genesys_conv.cc"
static void accurate_line_read(Genesys_Device* dev, Genesys_Buffer& buffer)
{
DBG_HELPER(dbg);
buffer.reset();
dev->cmd_set->bulk_read_data(dev, 0x45, buffer.get_write_pos(buffer.size()),
buffer.size());
buffer.produce(buffer.size());
}
/** @brief fill buffer while reducing vertical resolution
* This function fills a read buffer with scanned data from a sensor
* which puts odd and even pixels in 2 different data segment. So a complete
* must be read and bytes interleaved to get usable by the other stages
* of the backend
*/
static void genesys_fill_line_interp_buffer(Genesys_Device* dev, uint8_t* work_buffer_dst,
size_t size)
{
DBG_HELPER(dbg);
size_t count;
/* fill buffer if needed */
if (dev->oe_buffer.avail() == 0)
{
accurate_line_read(dev, dev->oe_buffer);
}
/* copy size bytes of data, copying from a line when line count matches */
count = 0;
while (count < size)
{
/* line counter */
// dev->line_interp holds the number of lines scanned for one line of data sent
if (((dev->line_count / dev->session.params.channels) % dev->line_interp) == 0) {
/* copy pixel when line matches */
work_buffer_dst[count] = dev->oe_buffer.get_read_pos()[dev->cur];
count++;
}
/* always update pointer so we skip uncopied data */
dev->cur++;
/* go to next line if needed */
if (dev->cur == dev->deseg.pixel_groups) {
dev->oe_buffer.set_pos(dev->oe_buffer.pos() + dev->deseg.raw_channel_bytes);
dev->cur = 0;
dev->line_count++;
}
/* read a new buffer if needed */
if (dev->oe_buffer.pos() >= dev->oe_buffer.avail())
{
accurate_line_read(dev, dev->oe_buffer);
}
}
}
/** @brief fill buffer for segmented sensors
* This function fills a read buffer with scanned data from a sensor segmented
* in several parts (multi-lines sensors). Data of the same valid area is read
* back to back and must be interleaved to get usable by the other stages
* of the backend
*/
static void genesys_fill_segmented_buffer(Genesys_Device* dev, uint8_t* work_buffer_dst,
size_t size)
{
DBG_HELPER(dbg);
size_t count;
int depth,i,n,k;
depth = dev->settings.depth;
if (dev->settings.scan_mode == ScanColorMode::LINEART && dev->settings.dynamic_lineart==SANE_FALSE)
depth = 1;
/* fill buffer if needed */
if (dev->oe_buffer.avail() == 0)
{
accurate_line_read(dev, dev->oe_buffer);
}
/* copy size bytes of data, copying from a subwindow of each line
* when last line of buffer is exhausted, read another one */
count = 0;
while (count < size)
{
if (depth==1) {
while (dev->cur < dev->deseg.pixel_groups && count < size) {
for (n=0; n<dev->segnb; n++) {
work_buffer_dst[count+n] = 0;
}
/* interleaving is at bit level */
for (i=0;i<8;i++) {
k=count+(i*dev->segnb)/8;
for (n=0;n<dev->segnb;n++) {
work_buffer_dst[k] = work_buffer_dst[k] << 1;
if ((dev->oe_buffer.get_read_pos()[dev->cur + dev->skip + dev->deseg.conseq_pixel_dist_bytes * dev->segment_order[n]])&(128>>i)) {
work_buffer_dst[k] |= 1;
}
}
}
/* update counter and pointer */
count += dev->segnb;
dev->cur++;
}
}
if (depth==8) {
while (dev->cur < dev->deseg.pixel_groups && count < size) {
for (n=0;n<dev->segnb;n++) {
work_buffer_dst[count+n] = dev->oe_buffer.get_read_pos()[dev->cur + dev->skip + dev->deseg.conseq_pixel_dist_bytes *dev->segment_order[n]];
}
/* update counter and pointer */
count += dev->segnb;
dev->cur++;
}
}
if (depth==16) {
while (dev->cur < dev->deseg.pixel_groups && count < size) {
for (n=0;n<dev->segnb;n++) {
work_buffer_dst[count+n*2] = dev->oe_buffer.get_read_pos()[dev->cur + dev->skip + dev->deseg.conseq_pixel_dist_bytes * dev->segment_order[n]];
work_buffer_dst[count+n*2+1] = dev->oe_buffer.get_read_pos()[dev->cur + dev->skip + dev->deseg.conseq_pixel_dist_bytes * dev->segment_order[n] + 1];
}
/* update counter and pointer */
count += dev->segnb*2;
dev->cur+=2;
}
}
/* go to next line if needed */
if (dev->cur == dev->deseg.pixel_groups) {
dev->oe_buffer.set_pos(dev->oe_buffer.pos() + dev->deseg.raw_channel_bytes);
dev->cur = 0;
}
/* read a new buffer if needed */
if (dev->oe_buffer.pos() >= dev->oe_buffer.avail())
{
accurate_line_read(dev, dev->oe_buffer);
}
}
}
/**
*
*/
static void genesys_fill_read_buffer(Genesys_Device* dev)
{
DBG_HELPER(dbg);
size_t size;
size_t space;
uint8_t *work_buffer_dst;
/* for sheetfed scanner, we must check is document is shorter than
* the requested scan */
if (dev->model->is_sheetfed == SANE_TRUE)
{
dev->cmd_set->detect_document_end(dev);
}
space = dev->read_buffer.size() - dev->read_buffer.avail();
work_buffer_dst = dev->read_buffer.get_write_pos(space);
size = space;
/* never read an odd number. exception: last read
the chip internal counter does not count half words. */
size &= ~1;
/* Some setups need the reads to be multiples of 256 bytes */
size &= ~0xff;
if (dev->read_bytes_left_after_deseg < size) {
size = dev->read_bytes_left_after_deseg;
/*round up to a multiple of 256 bytes */
size += (size & 0xff) ? 0x100 : 0x00;
size &= ~0xff;
}
/* early out if our remaining buffer capacity is too low */
if (size == 0)
return;
DBG(DBG_io, "%s: reading %lu bytes\n", __func__, (u_long) size);
/* size is already maxed to our needs. for most models bulk_read_data
will read as much data as requested. */
/* due to sensors and motors, not all data can be directly used. It
* may have to be read from another intermediate buffer and then processed.
* There are currently 3 intermediate stages:
* - handling of odd/even sensors
* - handling of line interpolation for motors that can't have low
* enough dpi
* - handling of multi-segments sensors
*
* This is also the place where full duplex data will be handled.
*/
if (dev->line_interp>0)
{
// line interpolation
genesys_fill_line_interp_buffer(dev, work_buffer_dst, size);
}
else if (dev->segnb>1)
{
// multi-segment sensors processing
genesys_fill_segmented_buffer(dev, work_buffer_dst, size);
}
else /* regular case with no extra copy */
{
dev->cmd_set->bulk_read_data(dev, 0x45, work_buffer_dst, size);
}
if (size > dev->read_bytes_left_after_deseg) {
size = dev->read_bytes_left_after_deseg;
}
dev->read_bytes_left_after_deseg -= size;
dev->read_buffer.produce(size);
}
/* this function does the effective data read in a manner that suits
the scanner. It does data reordering and resizing if need.
It also manages EOF and I/O errors, and line distance correction.
Returns true on success, false on end-of-file.
*/
static void genesys_read_ordered_data(Genesys_Device* dev, SANE_Byte* destination, size_t* len)
{
DBG_HELPER(dbg);
size_t bytes, extra;
unsigned int channels, depth, src_pixels;
unsigned int ccd_shift[12], shift_count;
uint8_t *work_buffer_src;
uint8_t *work_buffer_dst;
unsigned int dst_lines;
unsigned int step_1_mode;
unsigned int needs_reorder;
unsigned int needs_ccd;
unsigned int needs_shrink;
unsigned int needs_reverse;
Genesys_Buffer *src_buffer;
Genesys_Buffer *dst_buffer;
if (dev->read_active != SANE_TRUE)
{
*len = 0;
throw SaneException("read is not active");
}
debug_dump(DBG_info, dev->current_setup);
debug_dump(DBG_info, dev->session.params);
/* prepare conversion */
channels = dev->session.params.channels;
depth = dev->session.params.depth;
src_pixels = dev->current_setup.pixels;
needs_reorder = 1;
if (channels != 3 && depth != 16)
needs_reorder = 0;
#ifndef WORDS_BIGENDIAN
if (channels != 3 && depth == 16)
needs_reorder = 0;
if (channels == 3 && depth == 16 && !dev->model->is_cis &&
dev->model->line_mode_color_order == COLOR_ORDER_RGB)
needs_reorder = 0;
#endif
if (channels == 3 && depth == 8 && !dev->model->is_cis &&
dev->model->line_mode_color_order == COLOR_ORDER_RGB)
needs_reorder = 0;
needs_ccd = dev->current_setup.max_shift > 0;
needs_shrink = dev->settings.requested_pixels != src_pixels;
needs_reverse = depth == 1;
DBG(DBG_info, "%s: using filters:%s%s%s%s\n", __func__,
needs_reorder ? " reorder" : "",
needs_ccd ? " ccd" : "",
needs_shrink ? " shrink" : "",
needs_reverse ? " reverse" : "");
DBG(DBG_info, "%s: frontend requested %lu bytes\n", __func__, (u_long) * len);
DBG(DBG_info, "%s: bytes_to_read=%lu, total_bytes_read=%lu\n", __func__,
(u_long) dev->total_bytes_to_read, (u_long) dev->total_bytes_read);
/* is there data left to scan */
if (dev->total_bytes_read >= dev->total_bytes_to_read)
{
/* issue park command immediatly in case scanner can handle it
* so we save time */
if (dev->model->is_sheetfed == SANE_FALSE
&& !(dev->model->flags & GENESYS_FLAG_MUST_WAIT)
&& dev->parking == SANE_FALSE)
{
dev->cmd_set->slow_back_home(dev, SANE_FALSE);
dev->parking = SANE_TRUE;
}
throw SaneException(SANE_STATUS_EOF, "nothing more to scan: EOF");
}
DBG(DBG_info, "%s: %lu lines left by output\n", __func__,
((dev->total_bytes_to_read - dev->total_bytes_read) * 8UL) /
(dev->settings.requested_pixels * channels * depth));
DBG(DBG_info, "%s: %lu lines left by input\n", __func__,
((dev->read_bytes_left_after_deseg + dev->read_buffer.avail()) * 8UL) /
(src_pixels * channels * depth));
if (channels == 1)
{
ccd_shift[0] = 0;
ccd_shift[1] = dev->current_setup.stagger;
shift_count = 2;
}
else
{
ccd_shift[0] =
((dev->ld_shift_r * dev->settings.yres) /
dev->motor.base_ydpi);
ccd_shift[1] =
((dev->ld_shift_g * dev->settings.yres) /
dev->motor.base_ydpi);
ccd_shift[2] =
((dev->ld_shift_b * dev->settings.yres) /
dev->motor.base_ydpi);
ccd_shift[3] = ccd_shift[0] + dev->current_setup.stagger;
ccd_shift[4] = ccd_shift[1] + dev->current_setup.stagger;
ccd_shift[5] = ccd_shift[2] + dev->current_setup.stagger;
shift_count = 6;
}
/* convert data */
/*
0. fill_read_buffer
-------------- read_buffer ----------------------
1a). (opt)uncis (assumes color components to be laid out
planar)
1b). (opt)reverse_RGB (assumes pixels to be BGR or BBGGRR))
-------------- lines_buffer ----------------------
2a). (opt)line_distance_correction (assumes RGB or RRGGBB)
2b). (opt)unstagger (assumes pixels to be depth*channels/8
bytes long, unshrinked)
------------- shrink_buffer ---------------------
3. (opt)shrink_lines (assumes component separation in pixels)
-------------- out_buffer -----------------------
4. memcpy to destination (for lineart with bit reversal)
*/
/*FIXME: for lineart we need sub byte addressing in buffers, or conversion to
bytes at 0. and back to bits at 4.
Problems with the first approach:
- its not clear how to check if we need to output an incomplete byte
because it is the last one.
*/
/*FIXME: add lineart support for gl646. in the meantime add logic to convert
from gray to lineart at the end? would suffer the above problem,
total_bytes_to_read and total_bytes_read help in that case.
*/
genesys_fill_read_buffer(dev);
src_buffer = &(dev->read_buffer);
/* maybe reorder components/bytes */
if (needs_reorder)
{
if (depth == 1) {
throw SaneException("Can't reorder single bit data\n");
}
dst_buffer = &(dev->lines_buffer);
work_buffer_src = src_buffer->get_read_pos();
bytes = src_buffer->avail();
/*how many bytes can be processed here?*/
/*we are greedy. we work as much as possible*/
if (bytes > dst_buffer->size() - dst_buffer->avail())
bytes = dst_buffer->size() - dst_buffer->avail();
dst_lines = (bytes * 8) / (src_pixels * channels * depth);
bytes = (dst_lines * src_pixels * channels * depth) / 8;
work_buffer_dst = dst_buffer->get_write_pos(bytes);
DBG(DBG_info, "%s: reordering %d lines\n", __func__, dst_lines);
if (dst_lines != 0)
{
if (channels == 3)
{
step_1_mode = 0;
if (depth == 16)
step_1_mode |= 1;
if (dev->model->is_cis)
step_1_mode |= 2;
if (dev->model->line_mode_color_order == COLOR_ORDER_BGR)
step_1_mode |= 4;
switch (step_1_mode)
{
case 1: /* RGB, chunky, 16 bit */
#ifdef WORDS_BIGENDIAN
genesys_reorder_components_endian_16(work_buffer_src, work_buffer_dst, dst_lines,
src_pixels, 3);
break;
#endif /*WORDS_BIGENDIAN */
case 0: /* RGB, chunky, 8 bit */
break;
case 2: /* RGB, cis, 8 bit */
genesys_reorder_components_cis_8(work_buffer_src, work_buffer_dst, dst_lines,
src_pixels);
break;
case 3: /* RGB, cis, 16 bit */
genesys_reorder_components_cis_16(work_buffer_src, work_buffer_dst, dst_lines,
src_pixels);
break;
case 4: /* BGR, chunky, 8 bit */
genesys_reorder_components_bgr_8(work_buffer_src, work_buffer_dst, dst_lines,
src_pixels);
break;
case 5: /* BGR, chunky, 16 bit */
genesys_reorder_components_bgr_16(work_buffer_src, work_buffer_dst, dst_lines,
src_pixels);
break;
case 6: /* BGR, cis, 8 bit */
genesys_reorder_components_cis_bgr_8(work_buffer_src, work_buffer_dst, dst_lines,
src_pixels);
break;
case 7: /* BGR, cis, 16 bit */
genesys_reorder_components_cis_bgr_16(work_buffer_src, work_buffer_dst, dst_lines,
src_pixels);
break;
}
}
else
{
#ifdef WORDS_BIGENDIAN
if (depth == 16)
{
genesys_reorder_components_endian_16(work_buffer_src, work_buffer_dst, dst_lines,
src_pixels, 1);
}
#endif /*WORDS_BIGENDIAN */
}
dst_buffer->produce(bytes);
src_buffer->consume(bytes);
}
src_buffer = dst_buffer;
}
/* maybe reverse effects of ccd layout */
if (needs_ccd)
{
// should not happen with depth == 1.
if (depth == 1) {
throw SaneException("Can't reverse ccd single bit data\n");
}
dst_buffer = &(dev->shrink_buffer);
work_buffer_src = src_buffer->get_read_pos();
bytes = src_buffer->avail();
extra =
(dev->current_setup.max_shift * src_pixels * channels * depth) / 8;
/*extra bytes are reserved, and should not be consumed*/
if (bytes < extra)
bytes = 0;
else
bytes -= extra;
/*how many bytes can be processed here?*/
/*we are greedy. we work as much as possible*/
if (bytes > dst_buffer->size() - dst_buffer->avail())
bytes = dst_buffer->size() - dst_buffer->avail();
dst_lines = (bytes * 8) / (src_pixels * channels * depth);
bytes = (dst_lines * src_pixels * channels * depth) / 8;
work_buffer_dst = dst_buffer->get_write_pos(bytes);
DBG(DBG_info, "%s: un-ccd-ing %d lines\n", __func__, dst_lines);
if (dst_lines != 0)
{
if (depth == 8) {
genesys_reverse_ccd_8(work_buffer_src, work_buffer_dst, dst_lines,
src_pixels * channels, ccd_shift, shift_count);
} else {
genesys_reverse_ccd_16(work_buffer_src, work_buffer_dst, dst_lines,
src_pixels * channels, ccd_shift, shift_count);
}
dst_buffer->produce(bytes);
src_buffer->consume(bytes);
}
src_buffer = dst_buffer;
}
/* maybe shrink(or enlarge) lines */
if (needs_shrink)
{
dst_buffer = &(dev->out_buffer);
work_buffer_src = src_buffer->get_read_pos();
bytes = src_buffer->avail();
/*lines in input*/
dst_lines = (bytes * 8) / (src_pixels * channels * depth);
/* how many lines can be processed here? */
/* we are greedy. we work as much as possible */
bytes = dst_buffer->size() - dst_buffer->avail();
if (dst_lines > (bytes * 8) / (dev->settings.requested_pixels * channels * depth)) {
dst_lines = (bytes * 8) / (dev->settings.requested_pixels * channels * depth);
}
bytes = (dst_lines * dev->settings.requested_pixels * channels * depth) / 8;
work_buffer_dst = dst_buffer->get_write_pos(bytes);
DBG(DBG_info, "%s: shrinking %d lines\n", __func__, dst_lines);
if (dst_lines != 0)
{
if (depth == 1)
genesys_shrink_lines_1(work_buffer_src, work_buffer_dst, dst_lines, src_pixels,
dev->settings.requested_pixels, channels);
else if (depth == 8)
genesys_shrink_lines_8(work_buffer_src, work_buffer_dst, dst_lines, src_pixels,
dev->settings.requested_pixels, channels);
else
genesys_shrink_lines_16(work_buffer_src, work_buffer_dst, dst_lines, src_pixels,
dev->settings.requested_pixels, channels);
/* we just consumed this many bytes*/
bytes = (dst_lines * src_pixels * channels * depth) / 8;
src_buffer->consume(bytes);
/* we just created this many bytes*/
bytes = (dst_lines * dev->settings.requested_pixels * channels * depth) / 8;
dst_buffer->produce(bytes);
}
src_buffer = dst_buffer;
}
/* move data to destination */
bytes = src_buffer->avail();
if (bytes > *len)
bytes = *len;
work_buffer_src = src_buffer->get_read_pos();
if (needs_reverse)
{
genesys_reverse_bits(work_buffer_src, destination, bytes);
*len = bytes;
}
else
{
memcpy (destination, work_buffer_src, bytes);
*len = bytes;
}
/* avoid signaling some extra data because we have treated a full block
* on the last block */
if (dev->total_bytes_read + *len > dev->total_bytes_to_read)
*len = dev->total_bytes_to_read - dev->total_bytes_read;
/* count bytes sent to frontend */
dev->total_bytes_read += *len;
src_buffer->consume(bytes);
/* end scan if all needed data have been read */
if(dev->total_bytes_read >= dev->total_bytes_to_read)
{
dev->cmd_set->end_scan(dev, &dev->reg, SANE_TRUE);
if (dev->model->is_sheetfed == SANE_TRUE) {
dev->cmd_set->eject_document (dev);
}
}
DBG(DBG_proc, "%s: completed, %lu bytes read\n", __func__, (u_long) 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 unsigned pick_resolution(const std::vector<unsigned>& resolutions, unsigned resolution,
const char* direction)
{
DBG_HELPER(dbg);
if (resolutions.empty())
throw SaneException("Empty resolution list");
unsigned best_res = resolutions.front();
unsigned min_diff = abs_diff(best_res, resolution);
for (auto it = std::next(resolutions.begin()); it != resolutions.end(); ++it) {
unsigned curr_diff = abs_diff(*it, resolution);
if (curr_diff < min_diff) {
min_diff = curr_diff;
best_res = *it;
}
}
if (best_res != resolution) {
DBG(DBG_warn, "%s: using resolution %d that is nearest to %d for direction %s\n",
__func__, best_res, resolution, direction);
}
return best_res;
}
static void calc_parameters(Genesys_Scanner* s)
{
DBG_HELPER(dbg);
double tl_x = 0, tl_y = 0, br_x = 0, br_y = 0;
tl_x = SANE_UNFIX(s->pos_top_left_x);
tl_y = SANE_UNFIX(s->pos_top_left_y);
br_x = SANE_UNFIX(s->pos_bottom_right_x);
br_y = SANE_UNFIX(s->pos_bottom_right_y);
s->params.last_frame = SANE_TRUE; /* only single pass scanning supported */
if (s->mode == SANE_VALUE_SCAN_MODE_GRAY || s->mode == SANE_VALUE_SCAN_MODE_LINEART) {
s->params.format = SANE_FRAME_GRAY;
} else {
s->params.format = SANE_FRAME_RGB;
}
if (s->mode == SANE_VALUE_SCAN_MODE_LINEART) {
s->params.depth = 1;
} else {
s->params.depth = s->bit_depth;
}
s->dev->settings.depth = s->bit_depth;
/* interpolation */
s->dev->settings.disable_interpolation = s->disable_interpolation;
// FIXME: use correct sensor
const auto& sensor = sanei_genesys_find_sensor_any(s->dev);
// hardware settings
if (s->resolution > sensor.optical_res && s->dev->settings.disable_interpolation) {
s->dev->settings.xres = sensor.optical_res;
} else {
s->dev->settings.xres = s->resolution;
}
s->dev->settings.yres = s->resolution;
s->dev->settings.xres = pick_resolution(s->dev->model->xdpi_values, s->dev->settings.xres, "X");
s->dev->settings.yres = pick_resolution(s->dev->model->ydpi_values, s->dev->settings.yres, "Y");
s->params.lines = ((br_y - tl_y) * s->dev->settings.yres) / MM_PER_INCH;
unsigned pixels_per_line = ((br_x - tl_x) * s->dev->settings.xres) / MM_PER_INCH;
/* we need an even pixels number
* TODO invert test logic or generalize behaviour across all ASICs */
if ((s->dev->model->flags & GENESYS_FLAG_SIS_SENSOR) ||
s->dev->model->asic_type == AsicType::GL847 ||
s->dev->model->asic_type == AsicType::GL124 ||
s->dev->model->asic_type == AsicType::GL845 ||
s->dev->model->asic_type == AsicType::GL846 ||
s->dev->model->asic_type == AsicType::GL843)
{
if (s->dev->settings.xres <= 1200) {
pixels_per_line = (pixels_per_line / 4) * 4;
} else if (s->dev->settings.xres < s->dev->settings.yres) {
// BUG: this is an artifact of the fact that the resolution was twice as large than
// the actual resolution when scanning above the supported scanner X resolution
pixels_per_line = (pixels_per_line / 8) * 8;
} else {
pixels_per_line = (pixels_per_line / 16) * 16;
}
}
/* corner case for true lineart for sensor with several segments
* or when xres is doubled to match yres */
if (s->dev->settings.xres >= 1200 && (
s->dev->model->asic_type == AsicType::GL124 ||
s->dev->model->asic_type == AsicType::GL847 ||
s->dev->current_setup.xres < s->dev->session.params.yres))
{
if (s->dev->settings.xres < s->dev->settings.yres) {
// FIXME: this is an artifact of the fact that the resolution was twice as large than
// the actual resolution when scanning above the supported scanner X resolution
pixels_per_line = (pixels_per_line / 8) * 8;
} else {
pixels_per_line = (pixels_per_line / 16) * 16;
}
}
unsigned xres_factor = s->resolution / s->dev->settings.xres;
unsigned bytes_per_line = 0;
if (s->params.depth > 8)
{
s->params.depth = 16;
bytes_per_line = 2 * pixels_per_line;
}
else if (s->params.depth == 1)
{
// round down pixel number. This will is lossy operation, at most 7 pixels will be lost
pixels_per_line = (pixels_per_line / 8) * 8;
bytes_per_line = pixels_per_line / 8;
} else {
bytes_per_line = pixels_per_line;
}
if (s->params.format == SANE_FRAME_RGB) {
bytes_per_line *= 3;
}
if (s->mode == SANE_VALUE_SCAN_MODE_COLOR) {
s->dev->settings.scan_mode = ScanColorMode::COLOR_SINGLE_PASS;
} else if (s->mode == SANE_VALUE_SCAN_MODE_GRAY) {
s->dev->settings.scan_mode = ScanColorMode::GRAY;
} else if (s->mode == SANE_TITLE_HALFTONE) {
s->dev->settings.scan_mode = ScanColorMode::HALFTONE;
} else { /* Lineart */
s->dev->settings.scan_mode = ScanColorMode::LINEART;
}
if (s->source == STR_FLATBED) {
s->dev->settings.scan_method = ScanMethod::FLATBED;
} else if (s->source == STR_TRANSPARENCY_ADAPTER) {
s->dev->settings.scan_method = ScanMethod::TRANSPARENCY;
} else if (s->source == STR_TRANSPARENCY_ADAPTER_INFRARED) {
s->dev->settings.scan_method = ScanMethod::TRANSPARENCY_INFRARED;
}
s->dev->settings.lines = s->params.lines;
s->dev->settings.pixels = pixels_per_line;
s->dev->settings.requested_pixels = pixels_per_line * xres_factor;
s->params.pixels_per_line = pixels_per_line * xres_factor;
s->params.bytes_per_line = bytes_per_line * xres_factor;
s->dev->settings.tl_x = tl_x;
s->dev->settings.tl_y = tl_y;
// threshold setting
s->dev->settings.threshold = 2.55 * (SANE_UNFIX(s->threshold));
// color filter
if (s->color_filter == "Red") {
s->dev->settings.color_filter = ColorFilter::RED;
} else if (s->color_filter == "Green") {
s->dev->settings.color_filter = ColorFilter::GREEN;
} else if (s->color_filter == "Blue") {
s->dev->settings.color_filter = ColorFilter::BLUE;
} else {
s->dev->settings.color_filter = ColorFilter::NONE;
}
// true gray
if (s->color_filter == "None") {
s->dev->settings.true_gray = 1;
} else {
s->dev->settings.true_gray = 0;
}
/* dynamic lineart */
s->dev->settings.dynamic_lineart = SANE_FALSE;
s->dev->settings.threshold_curve=0;
if (!s->disable_dynamic_lineart && s->dev->settings.scan_mode == ScanColorMode::LINEART) {
s->dev->settings.dynamic_lineart = SANE_TRUE;
}
/* hardware lineart works only when we don't have interleave data
* for GL847 scanners, ie up to 600 DPI, then we have to rely on
* dynamic_lineart */
if (s->dev->settings.xres > 600 &&
s->dev->model->asic_type==AsicType::GL847 &&
s->dev->settings.scan_mode == ScanColorMode::LINEART)
{
s->dev->settings.dynamic_lineart = SANE_TRUE;
}
// threshold curve for dynamic rasterization
s->dev->settings.threshold_curve = s->threshold_curve;
/* some digital processing requires the whole picture to be buffered */
/* no digital processing takes place when doing preview, or when bit depth is
* higher than 8 bits */
if ((s->swdespeck || s->swcrop || s->swdeskew || s->swderotate ||(SANE_UNFIX(s->swskip)>0))
&& (!s->preview)
&& (s->bit_depth <= 8))
{
s->dev->buffer_image=SANE_TRUE;
}
else
{
s->dev->buffer_image=SANE_FALSE;
}
/* brigthness and contrast only for for 8 bit scans */
if(s->bit_depth <= 8)
{
s->dev->settings.contrast = (s->contrast * 127) / 100;
s->dev->settings.brightness = (s->brightness * 127) / 100;
}
else
{
s->dev->settings.contrast=0;
s->dev->settings.brightness=0;
}
/* cache expiration time */
s->dev->settings.expiration_time = s->expiration_time;
}
static void create_bpp_list (Genesys_Scanner * s, const std::vector<unsigned>& bpp)
{
s->bpp_list[0] = bpp.size();
std::reverse_copy(bpp.begin(), bpp.end(), s->bpp_list + 1);
}
/** @brief this function initialize a gamma vector based on the ASIC:
* Set up a default gamma table vector based on device description
* gl646: 12 or 14 bits gamma table depending on GENESYS_FLAG_14BIT_GAMMA
* gl84x: 16 bits
* gl12x: 16 bits
* @param scanner pointer to scanner session to get options
* @param option option number of the gamma table to set
*/
static void
init_gamma_vector_option (Genesys_Scanner * scanner, int option)
{
/* the option is inactive until the custom gamma control
* is enabled */
scanner->opt[option].type = SANE_TYPE_INT;
scanner->opt[option].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED;
scanner->opt[option].unit = SANE_UNIT_NONE;
scanner->opt[option].constraint_type = SANE_CONSTRAINT_RANGE;
if (scanner->dev->model->asic_type == AsicType::GL646) {
if ((scanner->dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) != 0)
{
scanner->opt[option].size = 16384 * sizeof (SANE_Word);
scanner->opt[option].constraint.range = &u14_range;
}
else
{ /* 12 bits gamma tables */
scanner->opt[option].size = 4096 * sizeof (SANE_Word);
scanner->opt[option].constraint.range = &u12_range;
}
}
else
{ /* other asics have 16 bits words gamma table */
scanner->opt[option].size = 256 * sizeof (SANE_Word);
scanner->opt[option].constraint.range = &u16_range;
}
}
/**
* allocate a geometry range
* @param size maximum size of the range
* @return a pointer to a valid range or NULL
*/
static SANE_Range *create_range(SANE_Fixed size)
{
SANE_Range *range=NULL;
range=(SANE_Range *)malloc(sizeof(SANE_Range));
if(range!=NULL)
{
range->min = SANE_FIX (0.0);
range->max = size;
range->quant = SANE_FIX (0.0);
}
return range;
}
/** @brief generate calibration cache file nam
* Generates the calibration cache file name to use.
* Tries to store the chache in $HOME/.sane or
* then fallbacks to $TMPDIR or TMP. The filename
* uses the model name if only one scanner is plugged
* else is uses the device name when several identical
* scanners are in use.
* @param currdev current scanner device
* @return an allocated string containing a file name
*/
static std::string calibration_filename(Genesys_Device *currdev)
{
std::string ret;
ret.resize(PATH_MAX);
char *ptr;
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
*/
ptr = getenv ("HOME");
if(ptr==NULL)
{
ptr = getenv ("USERPROFILE");
}
if(ptr==NULL)
{
ptr = getenv ("TMPDIR");
}
if(ptr==NULL)
{
ptr = getenv ("TMP");
}
/* now choose filename:
* 1 - if only one scanner, name of the model
* 2 - if several scanners of the same model, use device name,
* replacing special chars
*/
count=0;
/* count models of the same names if several scanners attached */
if(s_devices->size() > 1) {
for (const auto& dev : *s_devices) {
if (dev.model->model_id == currdev->model->model_id) {
count++;
}
}
}
if(count>1)
{
std::snprintf(filename, sizeof(filename), "%s.cal", currdev->file_name.c_str());
for(i=0;i<strlen(filename);i++)
{
if(filename[i]==':'||filename[i]==PATH_SEP)
{
filename[i]='_';
}
}
}
else
{
snprintf(filename,sizeof(filename),"%s.cal",currdev->model->name);
}
/* build final final name : store dir + filename */
if (ptr == nullptr) {
int size = std::snprintf(&ret.front(), ret.size(), "%s", filename);
ret.resize(size);
}
else
{
int size = 0;
#ifdef HAVE_MKDIR
/* make sure .sane directory exists in existing store dir */
size = std::snprintf(&ret.front(), ret.size(), "%s%c.sane", ptr, PATH_SEP);
ret.resize(size);
mkdir(ret.c_str(), 0700);
ret.resize(PATH_MAX);
#endif
size = std::snprintf(&ret.front(), ret.size(), "%s%c.sane%c%s",
ptr, PATH_SEP, PATH_SEP, filename);
ret.resize(size);
}
DBG(DBG_info, "%s: calibration filename >%s<\n", __func__, ret.c_str());
return ret;
}
static void init_options(Genesys_Scanner* s)
{
DBG_HELPER(dbg);
SANE_Int option;
SANE_Word *dpi_list;
Genesys_Model *model = s->dev->model;
SANE_Range *x_range, *y_range;
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].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[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 (source_list);
s->opt[OPT_SOURCE].constraint.string_list = source_list;
s->source = STR_FLATBED;
if (model->flags & GENESYS_FLAG_HAS_UTA)
{
ENABLE (OPT_SOURCE);
if (model->flags & GENESYS_FLAG_HAS_UTA_INFRARED) {
s->opt[OPT_SOURCE].size = max_string_size(source_list_infrared);
s->opt[OPT_SOURCE].constraint.string_list = source_list_infrared;
}
}
else
{
DISABLE (OPT_SOURCE);
}
/* 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 = 0;
s->opt[OPT_BIT_DEPTH].constraint.word_list = s->bpp_list;
create_bpp_list (s, model->bpp_gray_values);
s->bit_depth = 8;
if (s->opt[OPT_BIT_DEPTH].constraint.word_list[0] < 2) {
DISABLE (OPT_BIT_DEPTH);
}
// resolution
auto resolutions = model->get_resolutions();
unsigned min_dpi = *std::min_element(resolutions.begin(), resolutions.end());
dpi_list = (SANE_Word*) malloc((resolutions.size() + 1) * sizeof(SANE_Word));
if (!dpi_list) {
throw SaneException(SANE_STATUS_NO_MEM);
}
dpi_list[0] = resolutions.size();
std::copy(resolutions.begin(), resolutions.end(), dpi_list + 1);
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;
s->opt[OPT_RESOLUTION].constraint.word_list = dpi_list;
s->resolution = min_dpi;
/* "Geometry" group: */
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;
x_range=create_range(model->x_size);
if(x_range==NULL)
{
throw SaneException(SANE_STATUS_NO_MEM);
}
y_range=create_range(model->y_size);
if(y_range==NULL)
{
throw SaneException(SANE_STATUS_NO_MEM);
}
/* top-left x */
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_X].constraint.range = x_range;
s->pos_top_left_x = 0;
/* top-left y */
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_TL_Y].constraint.range = y_range;
s->pos_top_left_y = 0;
/* bottom-right x */
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_X].constraint.range = x_range;
s->pos_bottom_right_x = x_range->max;
/* bottom-right y */
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;
s->opt[OPT_BR_Y].constraint.range = y_range;
s->pos_bottom_right_y = y_range->max;
/* "Enhancement" group: */
s->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N ("Enhancement");
s->opt[OPT_ENHANCEMENT_GROUP].desc = "";
s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
s->opt[OPT_ENHANCEMENT_GROUP].cap = SANE_CAP_ADVANCED;
s->opt[OPT_ENHANCEMENT_GROUP].size = 0;
s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
/* custom-gamma table */
s->opt[OPT_CUSTOM_GAMMA].name = SANE_NAME_CUSTOM_GAMMA;
s->opt[OPT_CUSTOM_GAMMA].title = SANE_TITLE_CUSTOM_GAMMA;
s->opt[OPT_CUSTOM_GAMMA].desc = SANE_DESC_CUSTOM_GAMMA;
s->opt[OPT_CUSTOM_GAMMA].type = SANE_TYPE_BOOL;
s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_ADVANCED;
s->custom_gamma = false;
/* grayscale gamma vector */
s->opt[OPT_GAMMA_VECTOR].name = SANE_NAME_GAMMA_VECTOR;
s->opt[OPT_GAMMA_VECTOR].title = SANE_TITLE_GAMMA_VECTOR;
s->opt[OPT_GAMMA_VECTOR].desc = SANE_DESC_GAMMA_VECTOR;
init_gamma_vector_option (s, OPT_GAMMA_VECTOR);
/* red gamma vector */
s->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R;
s->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R;
s->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R;
init_gamma_vector_option (s, OPT_GAMMA_VECTOR_R);
/* green gamma vector */
s->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G;
s->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G;
s->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G;
init_gamma_vector_option (s, OPT_GAMMA_VECTOR_G);
/* blue gamma vector */
s->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B;
s->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B;
s->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B;
init_gamma_vector_option (s, OPT_GAMMA_VECTOR_B);
/* currently, there are only gamma table options in this group,
* so if the scanner doesn't support gamma table, disable the
* whole group */
if (!(model->flags & GENESYS_FLAG_CUSTOM_GAMMA))
{
s->opt[OPT_ENHANCEMENT_GROUP].cap |= SANE_CAP_INACTIVE;
s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;
DBG(DBG_info, "%s: custom gamma disabled\n", __func__);
}
/* software base image enhancements, these are consuming as many
* memory than used by the full scanned image and may fail at high
* resolution
*/
/* software deskew */
s->opt[OPT_SWDESKEW].name = "swdeskew";
s->opt[OPT_SWDESKEW].title = "Software deskew";
s->opt[OPT_SWDESKEW].desc = "Request backend to rotate skewed pages digitally";
s->opt[OPT_SWDESKEW].type = SANE_TYPE_BOOL;
s->opt[OPT_SWDESKEW].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
s->swdeskew = false;
/* software deskew */
s->opt[OPT_SWDESPECK].name = "swdespeck";
s->opt[OPT_SWDESPECK].title = "Software despeck";
s->opt[OPT_SWDESPECK].desc = "Request backend to remove lone dots digitally";
s->opt[OPT_SWDESPECK].type = SANE_TYPE_BOOL;
s->opt[OPT_SWDESPECK].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
s->swdespeck = false;
/* software despeckle radius */
s->opt[OPT_DESPECK].name = "despeck";
s->opt[OPT_DESPECK].title = "Software despeckle diameter";
s->opt[OPT_DESPECK].desc = "Maximum diameter of lone dots to remove from scan";
s->opt[OPT_DESPECK].type = SANE_TYPE_INT;
s->opt[OPT_DESPECK].unit = SANE_UNIT_NONE;
s->opt[OPT_DESPECK].constraint_type = SANE_CONSTRAINT_RANGE;
s->opt[OPT_DESPECK].constraint.range = &swdespeck_range;
s->opt[OPT_DESPECK].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED | SANE_CAP_INACTIVE;
s->despeck = 1;
/* crop by software */
s->opt[OPT_SWCROP].name = "swcrop";
s->opt[OPT_SWCROP].title = SANE_I18N ("Software crop");
s->opt[OPT_SWCROP].desc = SANE_I18N ("Request backend to remove border from pages digitally");
s->opt[OPT_SWCROP].type = SANE_TYPE_BOOL;
s->opt[OPT_SWCROP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
s->opt[OPT_SWCROP].unit = SANE_UNIT_NONE;
s->swcrop = false;
/* Software blank page skip */
s->opt[OPT_SWSKIP].name = "swskip";
s->opt[OPT_SWSKIP].title = SANE_I18N ("Software blank skip percentage");
s->opt[OPT_SWSKIP].desc = SANE_I18N("Request driver to discard pages with low numbers of dark pixels");
s->opt[OPT_SWSKIP].type = SANE_TYPE_FIXED;
s->opt[OPT_SWSKIP].unit = SANE_UNIT_PERCENT;
s->opt[OPT_SWSKIP].constraint_type = SANE_CONSTRAINT_RANGE;
s->opt[OPT_SWSKIP].constraint.range = &(percentage_range);
s->opt[OPT_SWSKIP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
s->swskip = 0; // disable by default
/* Software Derotate */
s->opt[OPT_SWDEROTATE].name = "swderotate";
s->opt[OPT_SWDEROTATE].title = SANE_I18N ("Software derotate");
s->opt[OPT_SWDEROTATE].desc = SANE_I18N("Request driver to detect and correct 90 degree image rotation");
s->opt[OPT_SWDEROTATE].type = SANE_TYPE_BOOL;
s->opt[OPT_SWDEROTATE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
s->opt[OPT_SWDEROTATE].unit = SANE_UNIT_NONE;
s->swderotate = false;
/* Software brightness */
s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
s->opt[OPT_BRIGHTNESS].constraint.range = &(enhance_range);
s->opt[OPT_BRIGHTNESS].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
s->brightness = 0; // disable by default
/* Sowftware contrast */
s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
s->opt[OPT_CONTRAST].type = SANE_TYPE_INT;
s->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE;
s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
s->opt[OPT_CONTRAST].constraint.range = &(enhance_range);
s->opt[OPT_CONTRAST].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
s->contrast = 0; // disable by default
/* "Extras" group: */
s->opt[OPT_EXTRAS_GROUP].title = SANE_I18N ("Extras");
s->opt[OPT_EXTRAS_GROUP].desc = "";
s->opt[OPT_EXTRAS_GROUP].type = SANE_TYPE_GROUP;
s->opt[OPT_EXTRAS_GROUP].cap = SANE_CAP_ADVANCED;
s->opt[OPT_EXTRAS_GROUP].size = 0;
s->opt[OPT_EXTRAS_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
/* BW threshold */
s->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD;
s->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD;
s->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD;
s->opt[OPT_THRESHOLD].type = SANE_TYPE_FIXED;
s->opt[OPT_THRESHOLD].unit = SANE_UNIT_PERCENT;
s->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE;
s->opt[OPT_THRESHOLD].constraint.range = &percentage_range;
s->threshold = SANE_FIX(50);
/* BW threshold curve */
s->opt[OPT_THRESHOLD_CURVE].name = "threshold-curve";
s->opt[OPT_THRESHOLD_CURVE].title = SANE_I18N ("Threshold curve");
s->opt[OPT_THRESHOLD_CURVE].desc = SANE_I18N ("Dynamic threshold curve, from light to dark, normally 50-65");
s->opt[OPT_THRESHOLD_CURVE].type = SANE_TYPE_INT;
s->opt[OPT_THRESHOLD_CURVE].unit = SANE_UNIT_NONE;
s->opt[OPT_THRESHOLD_CURVE].constraint_type = SANE_CONSTRAINT_RANGE;
s->opt[OPT_THRESHOLD_CURVE].constraint.range = &threshold_curve_range;
s->threshold_curve = 50;
/* dynamic linart */
s->opt[OPT_DISABLE_DYNAMIC_LINEART].name = "disable-dynamic-lineart";
s->opt[OPT_DISABLE_DYNAMIC_LINEART].title = SANE_I18N ("Disable dynamic lineart");
s->opt[OPT_DISABLE_DYNAMIC_LINEART].desc =
SANE_I18N ("Disable use of a software adaptive algorithm to generate lineart relying instead on hardware lineart.");
s->opt[OPT_DISABLE_DYNAMIC_LINEART].type = SANE_TYPE_BOOL;
s->opt[OPT_DISABLE_DYNAMIC_LINEART].unit = SANE_UNIT_NONE;
s->opt[OPT_DISABLE_DYNAMIC_LINEART].constraint_type = SANE_CONSTRAINT_NONE;
s->disable_dynamic_lineart = false;
/* fastmod is required for hw lineart to work */
if ((s->dev->model->asic_type == AsicType::GL646) &&
(s->dev->model->motor_type != MOTOR_XP200))
{
s->opt[OPT_DISABLE_DYNAMIC_LINEART].cap = SANE_CAP_INACTIVE;
}
/* disable_interpolation */
s->opt[OPT_DISABLE_INTERPOLATION].name = "disable-interpolation";
s->opt[OPT_DISABLE_INTERPOLATION].title =
SANE_I18N ("Disable interpolation");
s->opt[OPT_DISABLE_INTERPOLATION].desc =
SANE_I18N
("When using high resolutions where the horizontal resolution is smaller "
"than the vertical resolution this disables horizontal interpolation.");
s->opt[OPT_DISABLE_INTERPOLATION].type = SANE_TYPE_BOOL;
s->opt[OPT_DISABLE_INTERPOLATION].unit = SANE_UNIT_NONE;
s->opt[OPT_DISABLE_INTERPOLATION].constraint_type = SANE_CONSTRAINT_NONE;
s->disable_interpolation = false;
/* color filter */
s->opt[OPT_COLOR_FILTER].name = "color-filter";
s->opt[OPT_COLOR_FILTER].title = SANE_I18N ("Color filter");
s->opt[OPT_COLOR_FILTER].desc =
SANE_I18N
("When using gray or lineart this option selects the used color.");
s->opt[OPT_COLOR_FILTER].type = SANE_TYPE_STRING;
s->opt[OPT_COLOR_FILTER].constraint_type = SANE_CONSTRAINT_STRING_LIST;
/* true gray not yet supported for GL847 and GL124 scanners */
if (!model->is_cis || model->asic_type==AsicType::GL847 || model->asic_type==AsicType::GL124) {
s->opt[OPT_COLOR_FILTER].size = max_string_size (color_filter_list);
s->opt[OPT_COLOR_FILTER].constraint.string_list = color_filter_list;
s->color_filter = s->opt[OPT_COLOR_FILTER].constraint.string_list[1];
}
else
{
s->opt[OPT_COLOR_FILTER].size = max_string_size (cis_color_filter_list);
s->opt[OPT_COLOR_FILTER].constraint.string_list = cis_color_filter_list;
/* default to "None" ie true gray */
s->color_filter = s->opt[OPT_COLOR_FILTER].constraint.string_list[3];
}
// no support for color filter for cis+gl646 scanners
if (model->asic_type == AsicType::GL646 && model->is_cis) {
DISABLE (OPT_COLOR_FILTER);
}
/* calibration store file name */
s->opt[OPT_CALIBRATION_FILE].name = "calibration-file";
s->opt[OPT_CALIBRATION_FILE].title = SANE_I18N ("Calibration file");
s->opt[OPT_CALIBRATION_FILE].desc = SANE_I18N ("Specify the calibration file to use");
s->opt[OPT_CALIBRATION_FILE].type = SANE_TYPE_STRING;
s->opt[OPT_CALIBRATION_FILE].unit = SANE_UNIT_NONE;
s->opt[OPT_CALIBRATION_FILE].size = PATH_MAX;
s->opt[OPT_CALIBRATION_FILE].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
s->opt[OPT_CALIBRATION_FILE].constraint_type = SANE_CONSTRAINT_NONE;
s->calibration_file.clear();
/* disable option if ran as root */
#ifdef HAVE_GETUID
if(geteuid()==0)
{
DISABLE (OPT_CALIBRATION_FILE);
}
#endif
/* expiration time for calibration cache entries */
s->opt[OPT_EXPIRATION_TIME].name = "expiration-time";
s->opt[OPT_EXPIRATION_TIME].title = SANE_I18N ("Calibration cache expiration time");
s->opt[OPT_EXPIRATION_TIME].desc = SANE_I18N ("Time (in minutes) before a cached calibration expires. "
"A value of 0 means cache is not used. A negative value means cache never expires.");
s->opt[OPT_EXPIRATION_TIME].type = SANE_TYPE_INT;
s->opt[OPT_EXPIRATION_TIME].unit = SANE_UNIT_NONE;
s->opt[OPT_EXPIRATION_TIME].constraint_type = SANE_CONSTRAINT_RANGE;
s->opt[OPT_EXPIRATION_TIME].constraint.range = &expiration_range;
s->expiration_time = 60; // 60 minutes by default
/* Powersave time (turn lamp off) */
s->opt[OPT_LAMP_OFF_TIME].name = "lamp-off-time";
s->opt[OPT_LAMP_OFF_TIME].title = SANE_I18N ("Lamp off time");
s->opt[OPT_LAMP_OFF_TIME].desc =
SANE_I18N
("The lamp will be turned off after the given time (in minutes). "
"A value of 0 means, that the lamp won't be turned off.");
s->opt[OPT_LAMP_OFF_TIME].type = SANE_TYPE_INT;
s->opt[OPT_LAMP_OFF_TIME].unit = SANE_UNIT_NONE;
s->opt[OPT_LAMP_OFF_TIME].constraint_type = SANE_CONSTRAINT_RANGE;
s->opt[OPT_LAMP_OFF_TIME].constraint.range = &time_range;
s->lamp_off_time = 15; // 15 minutes
/* turn lamp off during scan */
s->opt[OPT_LAMP_OFF].name = "lamp-off-scan";
s->opt[OPT_LAMP_OFF].title = SANE_I18N ("Lamp off during scan");
s->opt[OPT_LAMP_OFF].desc = SANE_I18N ("The lamp will be turned off during scan. ");
s->opt[OPT_LAMP_OFF].type = SANE_TYPE_BOOL;
s->opt[OPT_LAMP_OFF].unit = SANE_UNIT_NONE;
s->opt[OPT_LAMP_OFF].constraint_type = SANE_CONSTRAINT_NONE;
s->lamp_off = false;
s->opt[OPT_SENSOR_GROUP].title = SANE_TITLE_SENSORS;
s->opt[OPT_SENSOR_GROUP].desc = SANE_DESC_SENSORS;
s->opt[OPT_SENSOR_GROUP].type = SANE_TYPE_GROUP;
s->opt[OPT_SENSOR_GROUP].cap = SANE_CAP_ADVANCED;
s->opt[OPT_SENSOR_GROUP].size = 0;
s->opt[OPT_SENSOR_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
s->opt[OPT_SCAN_SW].name = SANE_NAME_SCAN;
s->opt[OPT_SCAN_SW].title = SANE_TITLE_SCAN;
s->opt[OPT_SCAN_SW].desc = SANE_DESC_SCAN;
s->opt[OPT_SCAN_SW].type = SANE_TYPE_BOOL;
s->opt[OPT_SCAN_SW].unit = SANE_UNIT_NONE;
if (model->buttons & GENESYS_HAS_SCAN_SW)
s->opt[OPT_SCAN_SW].cap =
SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
s->opt[OPT_SCAN_SW].cap = SANE_CAP_INACTIVE;
/* SANE_NAME_FILE is not for buttons */
s->opt[OPT_FILE_SW].name = "file";
s->opt[OPT_FILE_SW].title = SANE_I18N ("File button");
s->opt[OPT_FILE_SW].desc = SANE_I18N ("File button");
s->opt[OPT_FILE_SW].type = SANE_TYPE_BOOL;
s->opt[OPT_FILE_SW].unit = SANE_UNIT_NONE;
if (model->buttons & GENESYS_HAS_FILE_SW)
s->opt[OPT_FILE_SW].cap =
SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
s->opt[OPT_FILE_SW].cap = SANE_CAP_INACTIVE;
s->opt[OPT_EMAIL_SW].name = SANE_NAME_EMAIL;
s->opt[OPT_EMAIL_SW].title = SANE_TITLE_EMAIL;
s->opt[OPT_EMAIL_SW].desc = SANE_DESC_EMAIL;
s->opt[OPT_EMAIL_SW].type = SANE_TYPE_BOOL;
s->opt[OPT_EMAIL_SW].unit = SANE_UNIT_NONE;
if (model->buttons & GENESYS_HAS_EMAIL_SW)
s->opt[OPT_EMAIL_SW].cap =
SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
s->opt[OPT_EMAIL_SW].cap = SANE_CAP_INACTIVE;
s->opt[OPT_COPY_SW].name = SANE_NAME_COPY;
s->opt[OPT_COPY_SW].title = SANE_TITLE_COPY;
s->opt[OPT_COPY_SW].desc = SANE_DESC_COPY;
s->opt[OPT_COPY_SW].type = SANE_TYPE_BOOL;
s->opt[OPT_COPY_SW].unit = SANE_UNIT_NONE;
if (model->buttons & GENESYS_HAS_COPY_SW)
s->opt[OPT_COPY_SW].cap =
SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
s->opt[OPT_COPY_SW].cap = SANE_CAP_INACTIVE;
s->opt[OPT_PAGE_LOADED_SW].name = SANE_NAME_PAGE_LOADED;
s->opt[OPT_PAGE_LOADED_SW].title = SANE_TITLE_PAGE_LOADED;
s->opt[OPT_PAGE_LOADED_SW].desc = SANE_DESC_PAGE_LOADED;
s->opt[OPT_PAGE_LOADED_SW].type = SANE_TYPE_BOOL;
s->opt[OPT_PAGE_LOADED_SW].unit = SANE_UNIT_NONE;
if (model->buttons & GENESYS_HAS_PAGE_LOADED_SW)
s->opt[OPT_PAGE_LOADED_SW].cap =
SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
s->opt[OPT_PAGE_LOADED_SW].cap = SANE_CAP_INACTIVE;
/* OCR button */
s->opt[OPT_OCR_SW].name = "ocr";
s->opt[OPT_OCR_SW].title = SANE_I18N ("OCR button");
s->opt[OPT_OCR_SW].desc = SANE_I18N ("OCR button");
s->opt[OPT_OCR_SW].type = SANE_TYPE_BOOL;
s->opt[OPT_OCR_SW].unit = SANE_UNIT_NONE;
if (model->buttons & GENESYS_HAS_OCR_SW)
s->opt[OPT_OCR_SW].cap =
SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
s->opt[OPT_OCR_SW].cap = SANE_CAP_INACTIVE;
/* power button */
s->opt[OPT_POWER_SW].name = "power";
s->opt[OPT_POWER_SW].title = SANE_I18N ("Power button");
s->opt[OPT_POWER_SW].desc = SANE_I18N ("Power button");
s->opt[OPT_POWER_SW].type = SANE_TYPE_BOOL;
s->opt[OPT_POWER_SW].unit = SANE_UNIT_NONE;
if (model->buttons & GENESYS_HAS_POWER_SW)
s->opt[OPT_POWER_SW].cap =
SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
s->opt[OPT_POWER_SW].cap = SANE_CAP_INACTIVE;
/* extra button */
s->opt[OPT_EXTRA_SW].name = "extra";
s->opt[OPT_EXTRA_SW].title = SANE_I18N ("Extra button");
s->opt[OPT_EXTRA_SW].desc = SANE_I18N ("Extra button");
s->opt[OPT_EXTRA_SW].type = SANE_TYPE_BOOL;
s->opt[OPT_EXTRA_SW].unit = SANE_UNIT_NONE;
if (model->buttons & GENESYS_HAS_EXTRA_SW)
s->opt[OPT_EXTRA_SW].cap =
SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
s->opt[OPT_EXTRA_SW].cap = SANE_CAP_INACTIVE;
/* calibration needed */
s->opt[OPT_NEED_CALIBRATION_SW].name = "need-calibration";
s->opt[OPT_NEED_CALIBRATION_SW].title = SANE_I18N ("Needs calibration");
s->opt[OPT_NEED_CALIBRATION_SW].desc = SANE_I18N ("The scanner needs calibration for the current settings");
s->opt[OPT_NEED_CALIBRATION_SW].type = SANE_TYPE_BOOL;
s->opt[OPT_NEED_CALIBRATION_SW].unit = SANE_UNIT_NONE;
if (model->buttons & GENESYS_HAS_CALIBRATE)
s->opt[OPT_NEED_CALIBRATION_SW].cap =
SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
s->opt[OPT_NEED_CALIBRATION_SW].cap = SANE_CAP_INACTIVE;
/* button group */
s->opt[OPT_BUTTON_GROUP].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 SANE_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=SANE_TRUE;
return SANE_STATUS_GOOD;
}
static SANE_Status
attach (SANE_String_Const devname, Genesys_Device ** devp, SANE_Bool may_wait)
{
DBG_HELPER_ARGS(dbg, "devp %s NULL, may_wait = %d", devp ? "!=" : "==", may_wait);
Genesys_Device *dev = 0;
if (devp)
*devp = 0;
if (!devname)
{
DBG(DBG_error, "%s: devname == NULL\n", __func__);
return SANE_STATUS_INVAL;
}
for (auto& dev : *s_devices) {
if (dev.file_name == devname) {
if (devp)
*devp = &dev;
DBG(DBG_info, "%s: device `%s' was already in device list\n", __func__, devname);
return SANE_STATUS_GOOD;
}
}
DBG(DBG_info, "%s: trying to open device `%s'\n", __func__, devname);
UsbDevice usb_dev;
usb_dev.open(devname);
DBG(DBG_info, "%s: device `%s' successfully opened\n", __func__, devname);
int vendor, product;
usb_dev.get_vendor_product(vendor, product);
/* KV-SS080 is an auxiliary device which requires a master device to be here */
if(vendor == 0x04da && product == 0x100f)
{
present=SANE_FALSE;
sanei_usb_find_devices (vendor, 0x1006, check_present);
sanei_usb_find_devices (vendor, 0x1007, check_present);
sanei_usb_find_devices (vendor, 0x1010, check_present);
if (present == SANE_FALSE) {
throw SaneException("master device not present");
}
}
Genesys_USB_Device_Entry* found_usb_dev = nullptr;
for (auto& usb_dev : *s_usb_devices) {
if (usb_dev.vendor == static_cast<unsigned>(vendor) &&
usb_dev.product == static_cast<unsigned>(product))
{
found_usb_dev = &usb_dev;
break;
}
}
if (found_usb_dev == nullptr) {
DBG(DBG_error, "%s: vendor 0x%xd product 0x%xd is not supported by this backend\n", __func__,
vendor, product);
return SANE_STATUS_INVAL;
}
s_devices->emplace_back();
dev = &s_devices->back();
dev->file_name = devname;
dev->model = &found_usb_dev->model;
dev->vendorId = found_usb_dev->vendor;
dev->productId = found_usb_dev->product;
dev->usb_mode = 0; /* i.e. unset */
dev->already_initialized = SANE_FALSE;
DBG(DBG_info, "%s: found %s flatbed scanner %s at %s\n", __func__, dev->model->vendor,
dev->model->model, dev->file_name.c_str());
if (devp) {
*devp = dev;
}
usb_dev.close();
return SANE_STATUS_GOOD;
}
static SANE_Status
attach_one_device_impl(SANE_String_Const devname)
{
Genesys_Device *dev;
SANE_Status status = attach(devname, &dev, SANE_FALSE);
if (status != SANE_STATUS_GOOD) {
DBG(DBG_info, "%s: failed to attach: %s\n", __func__, sane_strstatus(status));
return status;
}
return status;
}
static SANE_Status attach_one_device(SANE_String_Const devname)
{
return wrap_exceptions_to_status_code(__func__, [=]()
{
return attach_one_device_impl(devname);
});
}
/* configuration framework functions */
// this function is passed to C API, it must not throw
static SANE_Status
config_attach_genesys(SANEI_Config __sane_unused__ *config, const char *devname) noexcept
{
/* the devname has been processed and is ready to be used
* directly. Since the backend is an USB only one, we can
* call sanei_usb_attach_matching_devices straight */
sanei_usb_attach_matching_devices (devname, attach_one_device);
return SANE_STATUS_GOOD;
}
/* probes for scanner to attach to the backend */
static SANE_Status
probe_genesys_devices (void)
{
DBG_HELPER(dbg);
SANEI_Config config;
SANE_Status status = SANE_STATUS_GOOD;
/* set configuration options structure : no option for this backend */
config.descriptors = NULL;
config.values = NULL;
config.count = 0;
/* generic configure and attach function */
status = sanei_configure_attach (GENESYS_CONFIG_FILE, &config,
config_attach_genesys);
DBG(DBG_info, "%s: %d devices currently attached\n", __func__, (int) s_devices->size());
return status;
}
/**
* 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 = 8;
bool read_calibration(std::istream& str, Genesys_Device::Calibration& calibration,
const std::string& path)
{
DBG_HELPER(dbg);
std::string ident;
serialize(str, ident);
if (ident != CALIBRATION_IDENT) {
DBG(DBG_info, "%s: Incorrect calibration file '%s' header\n", __func__, path.c_str());
return false;
}
size_t version;
serialize(str, version);
if (version != CALIBRATION_VERSION) {
DBG(DBG_info, "%s: Incorrect calibration file '%s' version\n", __func__, path.c_str());
return false;
}
calibration.clear();
serialize(str, calibration);
return true;
}
/**
* reads previously cached calibration data
* from file defined in dev->calib_file
*/
static bool sanei_genesys_read_calibration(Genesys_Device::Calibration& calibration,
const std::string& path)
{
DBG_HELPER(dbg);
std::ifstream str;
str.open(path);
if (!str.is_open()) {
DBG(DBG_info, "%s: Cannot open %s\n", __func__, path.c_str());
return false;
}
return read_calibration(str, calibration, path);
}
void write_calibration(std::ostream& str, Genesys_Device::Calibration& calibration)
{
std::string ident = CALIBRATION_IDENT;
serialize(str, ident);
size_t version = CALIBRATION_VERSION;
serialize(str, version);
serialize_newline(str);
serialize(str, calibration);
}
static void write_calibration(Genesys_Device::Calibration& calibration, const std::string& path)
{
DBG_HELPER(dbg);
std::ofstream str;
str.open(path);
if (!str.is_open()) {
throw SaneException("Cannot open calibration for writing");
}
write_calibration(str, calibration);
}
/** @brief buffer scanned picture
* In order to allow digital processing, we must be able to put all the
* scanned picture in a buffer.
*/
static void genesys_buffer_image(Genesys_Scanner *s)
{
DBG_HELPER(dbg);
size_t maximum; /**> maximum bytes size of the scan */
size_t len; /**> length of scanned data read */
size_t total; /**> total of butes read */
size_t size; /**> size of image buffer */
size_t read_size; /**> size of reads */
int lines; /** number of lines of the scan */
Genesys_Device *dev = s->dev;
/* compute maximum number of lines for the scan */
if (s->params.lines > 0)
{
lines = s->params.lines;
}
else
{
lines =
(SANE_UNFIX (dev->model->y_size) * dev->settings.yres) / MM_PER_INCH;
}
DBG(DBG_info, "%s: buffering %d lines of %d bytes\n", __func__, lines,
s->params.bytes_per_line);
/* maximum bytes to read */
maximum = s->params.bytes_per_line * lines;
if(s->dev->settings.dynamic_lineart==SANE_TRUE)
{
maximum *= 8;
}
/* initial size of the read buffer */
size =
((2048 * 2048) / s->params.bytes_per_line) * s->params.bytes_per_line;
/* read size */
read_size = size / 2;
dev->img_buffer.resize(size);
/* loop reading data until we reach maximum or EOF */
total = 0;
while (total < maximum) {
len = size - maximum;
if (len > read_size)
{
len = read_size;
}
try {
genesys_read_ordered_data(dev, dev->img_buffer.data() + total, &len);
} catch (const SaneException& e) {
if (e.status() == SANE_STATUS_EOF) {
// ideally we shouldn't end up here, but because computations are duplicated and
// slightly different everywhere in the genesys backend, we have no other choice
break;
}
throw;
}
total += len;
// do we need to enlarge read buffer ?
if (total + read_size > size) {
size += read_size;
dev->img_buffer.resize(size);
}
}
/* since digital processing is going to take place,
* issue head parking command so that the head move while
* computing so we can save time
*/
if (dev->model->is_sheetfed == SANE_FALSE &&
dev->parking == SANE_FALSE)
{
dev->cmd_set->slow_back_home(dev, dev->model->flags & GENESYS_FLAG_MUST_WAIT);
dev->parking = !(s->dev->model->flags & GENESYS_FLAG_MUST_WAIT);
}
/* in case of dynamic lineart, we have buffered gray data which
* must be converted to lineart first */
if(s->dev->settings.dynamic_lineart==SANE_TRUE)
{
total/=8;
std::vector<uint8_t> lineart(total);
genesys_gray_lineart (dev,
dev->img_buffer.data(),
lineart.data(),
dev->settings.pixels,
(total*8)/dev->settings.pixels,
dev->settings.threshold);
dev->img_buffer = lineart;
}
/* update counters */
dev->total_bytes_to_read = total;
dev->total_bytes_read = 0;
/* update params */
s->params.lines = total / s->params.bytes_per_line;
if (DBG_LEVEL >= DBG_io2)
{
sanei_genesys_write_pnm_file("gl_unprocessed.pnm", dev->img_buffer.data(), s->params.depth,
s->params.format==SANE_FRAME_RGB ? 3 : 1,
s->params.pixels_per_line, s->params.lines);
}
}
/* -------------------------- SANE API functions ------------------------- */
SANE_Status
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 version %d.%d from %s\n",
SANE_CURRENT_MAJOR, V_MINOR, PACKAGE_STRING);
#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, V_MINOR, 0);
/* init usb use */
sanei_usb_init ();
/* init sanei_magic */
sanei_magic_init();
s_scanners.init();
s_devices.init();
s_sane_devices.init();
s_sane_devices_data.init();
s_sane_devices_ptrs.init();
genesys_init_sensor_tables();
genesys_init_frontend_tables();
genesys_init_gpo_tables();
genesys_init_motor_tables();
genesys_init_usb_device_tables();
DBG(DBG_info, "%s: %s endian machine\n", __func__,
#ifdef WORDS_BIGENDIAN
"big"
#else
"little"
#endif
);
// cold-plug case :detection of allready connected scanners
return probe_genesys_devices ();
}
extern "C" SANE_Status sane_init(SANE_Int * version_code, SANE_Auth_Callback authorize)
{
return wrap_exceptions_to_status_code(__func__, [=]()
{
return sane_init_impl(version_code, authorize);
});
}
void
sane_exit_impl(void)
{
DBG_HELPER(dbg);
sanei_usb_exit();
run_functions_at_backend_exit();
}
void sane_exit()
{
catch_all_exceptions(__func__, [](){ sane_exit_impl(); });
}
SANE_Status
sane_get_devices_impl(const SANE_Device *** device_list, SANE_Bool local_only)
{
DBG_HELPER_ARGS(dbg, "local_only = %s", local_only == SANE_TRUE ? "true" : "false");
/* hot-plug case : detection of newly connected scanners */
sanei_usb_scan_devices ();
probe_genesys_devices ();
s_sane_devices->clear();
s_sane_devices_data->clear();
s_sane_devices_ptrs->clear();
s_sane_devices->reserve(s_devices->size());
s_sane_devices_data->reserve(s_devices->size());
s_sane_devices_ptrs->reserve(s_devices->size() + 1);
for (auto dev_it = s_devices->begin(); dev_it != s_devices->end();) {
present = SANE_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);
*((SANE_Device ***)device_list) = s_sane_devices_ptrs->data();
return SANE_STATUS_GOOD;
}
SANE_Status sane_get_devices(const SANE_Device *** device_list, SANE_Bool local_only)
{
return wrap_exceptions_to_status_code(__func__, [=]()
{
return sane_get_devices_impl(device_list, local_only);
});
}
SANE_Status
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: couldn't find `%s' in devlist, trying attach\n", __func__, devicename);
SANE_Status status = attach(devicename, &dev, SANE_TRUE);
if (status != SANE_STATUS_GOOD) {
DBG(DBG_info, "%s: failed to attach: %s\n", __func__, sane_strstatus(status));
return status;
}
}
else
DBG(DBG_info, "%s: found `%s' in devlist\n", __func__, dev->model->name);
}
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)
return SANE_STATUS_INVAL;
if (dev->model->flags & GENESYS_FLAG_UNTESTED)
{
DBG(DBG_error0, "WARNING: Your scanner is not fully supported or at least \n");
DBG(DBG_error0, " had only limited testing. Please be careful and \n");
DBG(DBG_error0, " report any failure/success to \n");
DBG(DBG_error0, " sane-devel@alioth-lists.debian.net. Please provide as many\n");
DBG(DBG_error0, " details as possible, e.g. the exact name of your\n");
DBG(DBG_error0, " scanner and what does (not) work.\n");
}
dbg.vstatus("open device '%s'", dev->file_name.c_str());
dev->usb_dev.open(dev->file_name.c_str());
dbg.clear();
s_scanners->push_back(Genesys_Scanner());
auto* s = &s_scanners->back();
s->dev = dev;
s->scanning = SANE_FALSE;
s->dev->parking = SANE_FALSE;
s->dev->read_active = SANE_FALSE;
s->dev->force_calibration = 0;
s->dev->line_interp = 0;
s->dev->line_count = 0;
s->dev->segnb = 0;
s->dev->binary=NULL;
*handle = s;
if (!dev->already_initialized) {
sanei_genesys_init_structs (dev);
}
init_options(s);
sanei_genesys_init_cmd_set(s->dev);
// FIXME: we create sensor tables for the sensor, this should happen when we know which sensor
// we will select
dev->cmd_set->init(dev);
// some hardware capabilities are detected through sensors
s->dev->cmd_set->update_hardware_sensors (s);
/* here is the place to fetch a stored calibration cache */
if (s->dev->force_calibration == 0)
{
auto path = calibration_filename(s->dev);
s->calibration_file = path;
s->dev->calib_file = path;
DBG(DBG_info, "%s: Calibration filename set to:\n", __func__);
DBG(DBG_info, "%s: >%s<\n", __func__, s->dev->calib_file.c_str());
catch_all_exceptions(__func__, [&]()
{
sanei_genesys_read_calibration(s->dev->calibration_cache, s->dev->calib_file);
});
}
return SANE_STATUS_GOOD;
}
SANE_Status sane_open(SANE_String_Const devicename, SANE_Handle* handle)
{
return wrap_exceptions_to_status_code(__func__, [=]()
{
return sane_open_impl(devicename, handle);
});
}
void
sane_close_impl(SANE_Handle handle)
{
DBG_HELPER(dbg);
/* remove handle from list of open handles: */
auto it = s_scanners->end();
for (auto it2 = s_scanners->begin(); it2 != s_scanners->end(); it2++)
{
if (&*it2 == handle) {
it = it2;
break;
}
}
if (it == s_scanners->end())
{
DBG(DBG_error, "%s: invalid handle %p\n", __func__, handle);
return; /* oops, not a handle we know about */
}
Genesys_Scanner* s = &*it;
/* eject document for sheetfed scanners */
if (s->dev->model->is_sheetfed == SANE_TRUE)
{
catch_all_exceptions(__func__, [&](){ s->dev->cmd_set->eject_document(s->dev); });
}
else
{
/* in case scanner is parking, wait for the head
* to reach home position */
if(s->dev->parking==SANE_TRUE)
{
sanei_genesys_wait_for_home(s->dev);
}
}
// enable power saving before leaving
s->dev->cmd_set->save_power(s->dev, SANE_TRUE);
// here is the place to store calibration cache
if (s->dev->force_calibration == 0) {
catch_all_exceptions(__func__, [&](){ write_calibration(s->dev->calibration_cache,
s->dev->calib_file); });
}
s->dev->already_initialized = SANE_FALSE;
/* for an handful of bytes .. */
free ((void *)(size_t)s->opt[OPT_RESOLUTION].constraint.word_list);
free ((void *)(size_t)s->opt[OPT_TL_X].constraint.range);
free ((void *)(size_t)s->opt[OPT_TL_Y].constraint.range);
s->dev->clear();
// LAMP OFF : same register across all the ASICs */
s->dev->write_register(0x03, 0x00);
catch_all_exceptions(__func__, [&](){ s->dev->usb_dev.clear_halt(); });
// we need this to avoid these ASIC getting stuck in bulk writes
catch_all_exceptions(__func__, [&](){ s->dev->usb_dev.reset(); });
// not freeing s->dev because it's in the dev list
catch_all_exceptions(__func__, [&](){ s->dev->usb_dev.close(); });
s_scanners->erase(it);
}
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)
{
Genesys_Scanner *s = (Genesys_Scanner*) handle;
if ((unsigned) option >= NUM_OPTIONS)
return 0;
DBG(DBG_io2, "%s: option = %s (%d)\n", __func__, s->opt[option].name, option);
return s->opt + option;
}
const SANE_Option_Descriptor *
sane_get_option_descriptor(SANE_Handle handle, SANE_Int option)
{
const SANE_Option_Descriptor* ret = NULL;
catch_all_exceptions(__func__, [&]()
{
ret = sane_get_option_descriptor_impl(handle, option);
});
return ret;
}
/* gets an option , called by sane_control_option */
static SANE_Status
get_option_value (Genesys_Scanner * s, int option, void *val)
{
DBG_HELPER(dbg);
unsigned int i;
SANE_Word* table = nullptr;
std::vector<uint16_t> gamma_table;
unsigned option_size = 0;
SANE_Status status = SANE_STATUS_GOOD;
const Genesys_Sensor& sensor = sanei_genesys_find_sensor(s->dev, s->dev->settings.xres,
s->dev->settings.get_channels(),
s->dev->settings.scan_method);
switch (option)
{
/* geometry */
case OPT_TL_X:
*reinterpret_cast<SANE_Word*>(val) = s->pos_top_left_x;
break;
case OPT_TL_Y:
*reinterpret_cast<SANE_Word*>(val) = s->pos_top_left_y;
break;
case OPT_BR_X:
*reinterpret_cast<SANE_Word*>(val) = s->pos_bottom_right_x;
break;
case OPT_BR_Y:
*reinterpret_cast<SANE_Word*>(val) = s->pos_bottom_right_y;
break;
/* word options: */
case OPT_NUM_OPTS:
*reinterpret_cast<SANE_Word*>(val) = NUM_OPTIONS;
break;
case OPT_RESOLUTION:
*reinterpret_cast<SANE_Word*>(val) = s->resolution;
break;
case OPT_BIT_DEPTH:
*reinterpret_cast<SANE_Word*>(val) = s->bit_depth;
break;
case OPT_PREVIEW:
*reinterpret_cast<SANE_Word*>(val) = s->preview;
break;
case OPT_THRESHOLD:
*reinterpret_cast<SANE_Word*>(val) = s->threshold;
break;
case OPT_THRESHOLD_CURVE:
*reinterpret_cast<SANE_Word*>(val) = s->threshold_curve;
break;
case OPT_DISABLE_DYNAMIC_LINEART:
*reinterpret_cast<SANE_Word*>(val) = s->disable_dynamic_lineart;
break;
case OPT_DISABLE_INTERPOLATION:
*reinterpret_cast<SANE_Word*>(val) = s->disable_interpolation;
break;
case OPT_LAMP_OFF:
*reinterpret_cast<SANE_Word*>(val) = s->lamp_off;
break;
case OPT_LAMP_OFF_TIME:
*reinterpret_cast<SANE_Word*>(val) = s->lamp_off_time;
break;
case OPT_SWDESKEW:
*reinterpret_cast<SANE_Word*>(val) = s->swdeskew;
break;
case OPT_SWCROP:
*reinterpret_cast<SANE_Word*>(val) = s->swcrop;
break;
case OPT_SWDESPECK:
*reinterpret_cast<SANE_Word*>(val) = s->swdespeck;
break;
case OPT_SWDEROTATE:
*reinterpret_cast<SANE_Word*>(val) = s->swderotate;
break;
case OPT_SWSKIP:
*reinterpret_cast<SANE_Word*>(val) = s->swskip;
break;
case OPT_DESPECK:
*reinterpret_cast<SANE_Word*>(val) = s->despeck;
break;
case OPT_CONTRAST:
*reinterpret_cast<SANE_Word*>(val) = s->contrast;
break;
case OPT_BRIGHTNESS:
*reinterpret_cast<SANE_Word*>(val) = s->brightness;
break;
case OPT_EXPIRATION_TIME:
*reinterpret_cast<SANE_Word*>(val) = s->expiration_time;
break;
case OPT_CUSTOM_GAMMA:
*reinterpret_cast<SANE_Word*>(val) = s->custom_gamma;
break;
/* string options: */
case OPT_MODE:
std::strcpy(reinterpret_cast<char*>(val), s->mode.c_str());
break;
case OPT_COLOR_FILTER:
std::strcpy(reinterpret_cast<char*>(val), s->color_filter.c_str());
break;
case OPT_CALIBRATION_FILE:
std::strcpy(reinterpret_cast<char*>(val), s->calibration_file.c_str());
break;
case OPT_SOURCE:
std::strcpy(reinterpret_cast<char*>(val), s->source.c_str());
break;
/* word array options */
case OPT_GAMMA_VECTOR:
table = (SANE_Word *) val;
if (s->color_filter == "Red") {
gamma_table = get_gamma_table(s->dev, sensor, GENESYS_RED);
} else if (s->color_filter == "Blue") {
gamma_table = get_gamma_table(s->dev, sensor, GENESYS_BLUE);
} else {
gamma_table = get_gamma_table(s->dev, sensor, GENESYS_GREEN);
}
option_size = s->opt[option].size / sizeof (SANE_Word);
if (gamma_table.size() != option_size) {
throw std::runtime_error("The size of the gamma tables does not match");
}
for (i = 0; i < option_size; i++) {
table[i] = gamma_table[i];
}
break;
case OPT_GAMMA_VECTOR_R:
table = (SANE_Word *) val;
gamma_table = get_gamma_table(s->dev, sensor, GENESYS_RED);
option_size = s->opt[option].size / sizeof (SANE_Word);
if (gamma_table.size() != option_size) {
throw std::runtime_error("The size of the gamma tables does not match");
}
for (i = 0; i < option_size; i++) {
table[i] = gamma_table[i];
}
break;
case OPT_GAMMA_VECTOR_G:
table = (SANE_Word *) val;
gamma_table = get_gamma_table(s->dev, sensor, GENESYS_GREEN);
option_size = s->opt[option].size / sizeof (SANE_Word);
if (gamma_table.size() != option_size) {
throw std::runtime_error("The size of the gamma tables does not match");
}
for (i = 0; i < option_size; i++) {
table[i] = gamma_table[i];
}
break;
case OPT_GAMMA_VECTOR_B:
table = (SANE_Word *) val;
gamma_table = get_gamma_table(s->dev, sensor, GENESYS_BLUE);
option_size = s->opt[option].size / sizeof (SANE_Word);
if (gamma_table.size() != option_size) {
throw std::runtime_error("The size of the gamma tables does not match");
}
for (i = 0; i < option_size; i++) {
table[i] = gamma_table[i];
}
break;
/* sensors */
case OPT_SCAN_SW:
case OPT_FILE_SW:
case OPT_EMAIL_SW:
case OPT_COPY_SW:
case OPT_PAGE_LOADED_SW:
case OPT_OCR_SW:
case OPT_POWER_SW:
case OPT_EXTRA_SW:
s->dev->cmd_set->update_hardware_sensors(s);
*(SANE_Bool *) val = s->buttons[genesys_option_to_button(option)].read();
break;
case OPT_NEED_CALIBRATION_SW:
/* scanner needs calibration for current mode unless a matching
* calibration cache is found */
*(SANE_Bool *) val = SANE_TRUE;
for (auto& cache : s->dev->calibration_cache)
{
if (s->dev->cmd_set->is_compatible_calibration(s->dev, sensor, &cache, SANE_FALSE)) {
*(SANE_Bool *) val = SANE_FALSE;
}
}
break;
default:
DBG(DBG_warn, "%s: can't get unknown option %d\n", __func__, option);
}
return status;
}
/** @brief set calibration file value
* Set calibration file value. Load new cache values from file if it exists,
* else creates the file*/
static void set_calibration_value(Genesys_Scanner* s, const char* val)
{
DBG_HELPER(dbg);
std::string new_calib_path = val;
Genesys_Device::Calibration new_calibration;
bool is_calib_success = false;
catch_all_exceptions(__func__, [&]()
{
is_calib_success = sanei_genesys_read_calibration(new_calibration, new_calib_path);
});
if (!is_calib_success) {
return;
}
s->dev->calibration_cache = std::move(new_calibration);
s->dev->calib_file = new_calib_path;
s->calibration_file = new_calib_path;
DBG(DBG_info, "%s: Calibration filename set to '%s':\n", __func__, new_calib_path.c_str());
}
/* sets an option , called by sane_control_option */
static SANE_Status
set_option_value (Genesys_Scanner * s, int option, void *val,
SANE_Int * myinfo)
{
DBG_HELPER(dbg);
SANE_Status status = SANE_STATUS_GOOD;
SANE_Word *table;
unsigned int i;
SANE_Range *x_range, *y_range;
unsigned option_size = 0;
switch (option)
{
case OPT_TL_X:
s->pos_top_left_x = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_TL_Y:
s->pos_top_left_y = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_BR_X:
s->pos_bottom_right_x = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_BR_Y:
s->pos_bottom_right_y = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_RESOLUTION:
s->resolution = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_THRESHOLD:
s->threshold = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_THRESHOLD_CURVE:
s->threshold_curve = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_DISABLE_DYNAMIC_LINEART:
s->disable_dynamic_lineart = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_SWCROP:
s->swcrop = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_SWDESKEW:
s->swdeskew = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_DESPECK:
s->despeck = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_SWDEROTATE:
s->swderotate = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_SWSKIP:
s->swskip = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_DISABLE_INTERPOLATION:
s->disable_interpolation = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_LAMP_OFF:
s->lamp_off = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_PREVIEW:
s->preview = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_BRIGHTNESS:
s->brightness = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_CONTRAST:
s->contrast = *reinterpret_cast<SANE_Word*>(val);
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS;
break;
case OPT_SWDESPECK:
s->swdespeck = *reinterpret_cast<SANE_Word*>(val);
if (s->swdespeck) {
ENABLE(OPT_DESPECK);
} else {
DISABLE(OPT_DESPECK);
}
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
break;
/* software enhancement functions only apply to 8 or 1 bits data */
case OPT_BIT_DEPTH:
s->bit_depth = *reinterpret_cast<SANE_Word*>(val);
if(s->bit_depth>8)
{
DISABLE(OPT_SWDESKEW);
DISABLE(OPT_SWDESPECK);
DISABLE(OPT_SWCROP);
DISABLE(OPT_DESPECK);
DISABLE(OPT_SWDEROTATE);
DISABLE(OPT_SWSKIP);
DISABLE(OPT_CONTRAST);
DISABLE(OPT_BRIGHTNESS);
}
else
{
ENABLE(OPT_SWDESKEW);
ENABLE(OPT_SWDESPECK);
ENABLE(OPT_SWCROP);
ENABLE(OPT_DESPECK);
ENABLE(OPT_SWDEROTATE);
ENABLE(OPT_SWSKIP);
ENABLE(OPT_CONTRAST);
ENABLE(OPT_BRIGHTNESS);
}
calc_parameters(s);
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
break;
case OPT_SOURCE:
if (s->source != reinterpret_cast<const char*>(val)) {
s->source = reinterpret_cast<const char*>(val);
// change geometry constraint to the new source value
if (s->source == STR_FLATBED)
{
x_range=create_range(s->dev->model->x_size);
y_range=create_range(s->dev->model->y_size);
}
else
{
x_range=create_range(s->dev->model->x_size_ta);
y_range=create_range(s->dev->model->y_size_ta);
}
if(x_range==NULL || y_range==NULL)
{
return SANE_STATUS_NO_MEM;
}
/* assign new values */
free((void *)(size_t)s->opt[OPT_TL_X].constraint.range);
free((void *)(size_t)s->opt[OPT_TL_Y].constraint.range);
s->opt[OPT_TL_X].constraint.range = x_range;
s->pos_top_left_x = 0;
s->opt[OPT_TL_Y].constraint.range = y_range;
s->pos_top_left_y = 0;
s->opt[OPT_BR_X].constraint.range = x_range;
s->pos_bottom_right_x = x_range->max;
s->opt[OPT_BR_Y].constraint.range = y_range;
s->pos_bottom_right_y = y_range->max;
/* signals reload */
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
}
break;
case OPT_MODE:
s->mode = reinterpret_cast<const char*>(val);
if (s->mode == SANE_VALUE_SCAN_MODE_LINEART)
{
ENABLE (OPT_THRESHOLD);
ENABLE (OPT_THRESHOLD_CURVE);
DISABLE (OPT_BIT_DEPTH);
if (s->dev->model->asic_type != AsicType::GL646 || !s->dev->model->is_cis) {
ENABLE(OPT_COLOR_FILTER);
}
ENABLE (OPT_DISABLE_DYNAMIC_LINEART);
}
else
{
DISABLE (OPT_THRESHOLD);
DISABLE (OPT_THRESHOLD_CURVE);
DISABLE (OPT_DISABLE_DYNAMIC_LINEART);
if (s->mode == SANE_VALUE_SCAN_MODE_GRAY)
{
if (s->dev->model->asic_type != AsicType::GL646 || !s->dev->model->is_cis) {
ENABLE(OPT_COLOR_FILTER);
}
create_bpp_list (s, s->dev->model->bpp_gray_values);
}
else
{
DISABLE (OPT_COLOR_FILTER);
create_bpp_list (s, s->dev->model->bpp_color_values);
}
if (s->bpp_list[0] < 2)
DISABLE (OPT_BIT_DEPTH);
else
ENABLE (OPT_BIT_DEPTH);
}
calc_parameters(s);
/* if custom gamma, toggle gamma table options according to the mode */
if (s->custom_gamma)
{
if (s->mode == SANE_VALUE_SCAN_MODE_COLOR)
{
DISABLE (OPT_GAMMA_VECTOR);
ENABLE (OPT_GAMMA_VECTOR_R);
ENABLE (OPT_GAMMA_VECTOR_G);
ENABLE (OPT_GAMMA_VECTOR_B);
}
else
{
ENABLE (OPT_GAMMA_VECTOR);
DISABLE (OPT_GAMMA_VECTOR_R);
DISABLE (OPT_GAMMA_VECTOR_G);
DISABLE (OPT_GAMMA_VECTOR_B);
}
}
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
break;
case OPT_COLOR_FILTER:
s->color_filter = reinterpret_cast<const char*>(val);
calc_parameters(s);
break;
case OPT_CALIBRATION_FILE:
if (s->dev->force_calibration == 0) {
set_calibration_value(s, reinterpret_cast<const char*>(val));
}
break;
case OPT_LAMP_OFF_TIME:
if (*reinterpret_cast<SANE_Word*>(val) != s->lamp_off_time) {
s->lamp_off_time = *reinterpret_cast<SANE_Word*>(val);
s->dev->cmd_set->set_powersaving(s->dev, s->lamp_off_time);
}
break;
case OPT_EXPIRATION_TIME:
if (*reinterpret_cast<SANE_Word*>(val) != s->expiration_time) {
s->expiration_time = *reinterpret_cast<SANE_Word*>(val);
// BUG: this is most likely not intended behavior, found out during refactor
s->dev->cmd_set->set_powersaving(s->dev, s->expiration_time);
}
break;
case OPT_CUSTOM_GAMMA:
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
s->custom_gamma = *reinterpret_cast<SANE_Bool*>(val);
if (s->custom_gamma) {
if (s->mode == SANE_VALUE_SCAN_MODE_COLOR)
{
DISABLE (OPT_GAMMA_VECTOR);
ENABLE (OPT_GAMMA_VECTOR_R);
ENABLE (OPT_GAMMA_VECTOR_G);
ENABLE (OPT_GAMMA_VECTOR_B);
}
else
{
ENABLE (OPT_GAMMA_VECTOR);
DISABLE (OPT_GAMMA_VECTOR_R);
DISABLE (OPT_GAMMA_VECTOR_G);
DISABLE (OPT_GAMMA_VECTOR_B);
}
}
else
{
DISABLE (OPT_GAMMA_VECTOR);
DISABLE (OPT_GAMMA_VECTOR_R);
DISABLE (OPT_GAMMA_VECTOR_G);
DISABLE (OPT_GAMMA_VECTOR_B);
for (auto& table : s->dev->gamma_override_tables) {
table.clear();
}
}
break;
case OPT_GAMMA_VECTOR:
table = (SANE_Word *) val;
option_size = s->opt[option].size / sizeof (SANE_Word);
s->dev->gamma_override_tables[GENESYS_RED].resize(option_size);
s->dev->gamma_override_tables[GENESYS_GREEN].resize(option_size);
s->dev->gamma_override_tables[GENESYS_BLUE].resize(option_size);
for (i = 0; i < option_size; i++) {
s->dev->gamma_override_tables[GENESYS_RED][i] = table[i];
s->dev->gamma_override_tables[GENESYS_GREEN][i] = table[i];
s->dev->gamma_override_tables[GENESYS_BLUE][i] = table[i];
}
break;
case OPT_GAMMA_VECTOR_R:
table = (SANE_Word *) val;
option_size = s->opt[option].size / sizeof (SANE_Word);
s->dev->gamma_override_tables[GENESYS_RED].resize(option_size);
for (i = 0; i < option_size; i++) {
s->dev->gamma_override_tables[GENESYS_RED][i] = table[i];
}
break;
case OPT_GAMMA_VECTOR_G:
table = (SANE_Word *) val;
option_size = s->opt[option].size / sizeof (SANE_Word);
s->dev->gamma_override_tables[GENESYS_GREEN].resize(option_size);
for (i = 0; i < option_size; i++) {
s->dev->gamma_override_tables[GENESYS_GREEN][i] = table[i];
}
break;
case OPT_GAMMA_VECTOR_B:
table = (SANE_Word *) val;
option_size = s->opt[option].size / sizeof (SANE_Word);
s->dev->gamma_override_tables[GENESYS_BLUE].resize(option_size);
for (i = 0; i < option_size; i++) {
s->dev->gamma_override_tables[GENESYS_BLUE][i] = table[i];
}
break;
case OPT_CALIBRATE: {
auto& sensor = sanei_genesys_find_sensor_for_write(s->dev, s->dev->settings.xres,
s->dev->settings.get_channels(),
s->dev->settings.scan_method);
catch_all_exceptions(__func__, [&]()
{
s->dev->cmd_set->save_power(s->dev, SANE_FALSE);
genesys_scanner_calibration(s->dev, sensor);
});
catch_all_exceptions(__func__, [&]()
{
s->dev->cmd_set->save_power(s->dev, SANE_TRUE);
});
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
break;
}
case OPT_CLEAR_CALIBRATION:
s->dev->calibration_cache.clear();
/* remove file */
unlink(s->dev->calib_file.c_str());
/* signals that sensors will have to be read again */
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
break;
case OPT_FORCE_CALIBRATION:
s->dev->force_calibration = 1;
s->dev->calibration_cache.clear();
s->dev->calib_file.clear();
/* signals that sensors will have to be read again */
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
break;
case OPT_IGNORE_OFFSETS: {
s->dev->ignore_offsets = true;
break;
}
default:
DBG(DBG_warn, "%s: can't set unknown option %d\n", __func__, option);
}
return status;
}
/* sets and gets scanner option values */
SANE_Status
sane_control_option_impl(SANE_Handle handle, SANE_Int option,
SANE_Action action, void *val, SANE_Int * info)
{
Genesys_Scanner *s = (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_Status status = SANE_STATUS_GOOD;
SANE_Word cap;
SANE_Int myinfo = 0;
if (info)
*info = 0;
if (s->scanning)
{
DBG(DBG_warn, "%s: don't call this function while scanning (option = %s (%d))\n", __func__,
s->opt[option].name, option);
return SANE_STATUS_DEVICE_BUSY;
}
if (option >= NUM_OPTIONS || option < 0)
{
DBG(DBG_warn, "%s: option %d >= NUM_OPTIONS || option < 0\n", __func__, option);
return SANE_STATUS_INVAL;
}
cap = s->opt[option].cap;
if (!SANE_OPTION_IS_ACTIVE (cap))
{
DBG(DBG_warn, "%s: option %d is inactive\n", __func__, option);
return SANE_STATUS_INVAL;
}
switch (action)
{
case SANE_ACTION_GET_VALUE:
status = get_option_value (s, option, val);
break;
case SANE_ACTION_SET_VALUE:
if (!SANE_OPTION_IS_SETTABLE (cap))
{
DBG(DBG_warn, "%s: option %d is not settable\n", __func__, option);
return SANE_STATUS_INVAL;
}
status = sanei_constrain_value (s->opt + option, val, &myinfo);
if (status != SANE_STATUS_GOOD)
{
DBG(DBG_warn, "%s: sanei_constrain_value returned %s\n", __func__,
sane_strstatus(status));
return status;
}
status = set_option_value (s, option, val, &myinfo);
break;
case SANE_ACTION_SET_AUTO:
DBG(DBG_error,
"%s: SANE_ACTION_SET_AUTO unsupported since no option has SANE_CAP_AUTOMATIC\n",
__func__);
status = SANE_STATUS_INVAL;
break;
default:
DBG(DBG_warn, "%s: unknown action %d for option %d\n", __func__, action, option);
status = SANE_STATUS_INVAL;
break;
}
if (info)
*info = myinfo;
return status;
}
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__, [=]()
{
return sane_control_option_impl(handle, option, action, val, info);
});
}
SANE_Status sane_get_parameters_impl(SANE_Handle handle, SANE_Parameters* params)
{
DBG_HELPER(dbg);
Genesys_Scanner *s = (Genesys_Scanner*) handle;
/* don't recompute parameters once data reading is active, ie during scan */
if(s->dev->read_active == SANE_FALSE)
{
calc_parameters(s);
}
if (params)
{
*params = s->params;
/* in the case of a sheetfed scanner, when full height is specified
* we override the computed line number with -1 to signal that we
* don't know the real document height.
* We don't do that doing buffering image for digital processing
*/
if (s->dev->model->is_sheetfed == SANE_TRUE
&& s->dev->buffer_image == SANE_FALSE
&& s->pos_bottom_right_y == s->opt[OPT_BR_Y].constraint.range->max)
{
params->lines = -1;
}
}
return SANE_STATUS_GOOD;
}
SANE_Status sane_get_parameters(SANE_Handle handle, SANE_Parameters* params)
{
return wrap_exceptions_to_status_code(__func__, [=]()
{
return sane_get_parameters_impl(handle, params);
});
}
SANE_Status sane_start_impl(SANE_Handle handle)
{
DBG_HELPER(dbg);
Genesys_Scanner *s = (Genesys_Scanner*) handle;
SANE_Status status=SANE_STATUS_GOOD;
if (s->pos_top_left_x >= s->pos_bottom_right_x)
{
DBG(DBG_error0, "%s: top left x >= bottom right x --- exiting\n", __func__);
return SANE_STATUS_INVAL;
}
if (s->pos_top_left_y >= s->pos_bottom_right_y)
{
DBG(DBG_error0, "%s: top left y >= bottom right y --- exiting\n", __func__);
return SANE_STATUS_INVAL;
}
/* First make sure we have a current parameter set. Some of the
parameters will be overwritten below, but that's OK. */
calc_parameters(s);
genesys_start_scan(s->dev, s->lamp_off);
s->scanning = SANE_TRUE;
/* allocate intermediate buffer when doing dynamic lineart */
if(s->dev->settings.dynamic_lineart==SANE_TRUE)
{
s->dev->binarize_buffer.clear();
s->dev->binarize_buffer.alloc(s->dev->settings.pixels);
s->dev->local_buffer.clear();
s->dev->local_buffer.alloc(s->dev->binarize_buffer.size() * 8);
}
/* if one of the software enhancement option is selected,
* we do the scan internally, process picture then put it an internal
* buffer. Since cropping may change scan parameters, we recompute them
* at the end */
if (s->dev->buffer_image)
{
genesys_buffer_image(s);
/* check if we need to skip this page, sheetfed scanners
* can go to next doc while flatbed ones can't */
if (s->swskip > 0 && IS_ACTIVE(OPT_SWSKIP)) {
status = sanei_magic_isBlank(&s->params,
s->dev->img_buffer.data(),
SANE_UNFIX(s->swskip));
if(status == SANE_STATUS_NO_DOCS)
{
if (s->dev->model->is_sheetfed == SANE_TRUE)
{
DBG(DBG_info, "%s: blank page, recurse\n", __func__);
return sane_start(handle);
}
return status;
}
}
if (s->swdeskew) {
const auto& sensor = sanei_genesys_find_sensor(s->dev, s->dev->settings.xres,
s->dev->settings.get_channels(),
s->dev->settings.scan_method);
catch_all_exceptions(__func__, [&](){ genesys_deskew(s, sensor); });
}
if (s->swdespeck) {
catch_all_exceptions(__func__, [&](){ genesys_despeck(s); });
}
if(s->swcrop) {
catch_all_exceptions(__func__, [&](){ genesys_crop(s); });
}
if(s->swderotate) {
catch_all_exceptions(__func__, [&](){ genesys_derotate(s); });
}
}
return status;
}
SANE_Status sane_start(SANE_Handle handle)
{
return wrap_exceptions_to_status_code(__func__, [=]()
{
return sane_start_impl(handle);
});
}
SANE_Status
sane_read_impl(SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int* len)
{
DBG_HELPER(dbg);
Genesys_Scanner *s = (Genesys_Scanner*) handle;
Genesys_Device *dev;
size_t local_len;
if (!s)
{
DBG(DBG_error, "%s: handle is null!\n", __func__);
return SANE_STATUS_INVAL;
}
dev=s->dev;
if (!dev)
{
DBG(DBG_error, "%s: dev is null!\n", __func__);
return SANE_STATUS_INVAL;
}
if (!buf)
{
DBG(DBG_error, "%s: buf is null!\n", __func__);
return SANE_STATUS_INVAL;
}
if (!len)
{
DBG(DBG_error, "%s: len is null!\n", __func__);
return SANE_STATUS_INVAL;
}
*len = 0;
if (!s->scanning)
{
DBG(DBG_warn, "%s: scan was cancelled, is over or has not been initiated yet\n", __func__);
return SANE_STATUS_CANCELLED;
}
DBG(DBG_proc, "%s: start, %d maximum bytes required\n", __func__, max_len);
DBG(DBG_io2, "%s: bytes_to_read=%lu, total_bytes_read=%lu\n", __func__,
(u_long) dev->total_bytes_to_read, (u_long) dev->total_bytes_read);
DBG(DBG_io2, "%s: desegmented bytes to read = %lu\n", __func__,
(u_long) dev->read_bytes_left_after_deseg);
if(dev->total_bytes_read>=dev->total_bytes_to_read)
{
DBG(DBG_proc, "%s: nothing more to scan: EOF\n", __func__);
/* issue park command immediatly in case scanner can handle it
* so we save time */
if (dev->model->is_sheetfed == SANE_FALSE
&& !(dev->model->flags & GENESYS_FLAG_MUST_WAIT)
&& dev->parking == SANE_FALSE)
{
dev->cmd_set->slow_back_home(dev, SANE_FALSE);
dev->parking = SANE_TRUE;
}
return SANE_STATUS_EOF;
}
local_len = max_len;
/* in case of image processing, all data has been stored in
* buffer_image. So read data from it if it exists, else from scanner */
if(!dev->buffer_image)
{
/* dynamic lineart is another kind of digital processing that needs
* another layer of buffering on top of genesys_read_ordered_data */
if(dev->settings.dynamic_lineart==SANE_TRUE)
{
/* if buffer is empty, fill it with genesys_read_ordered_data */
if(dev->binarize_buffer.avail() == 0)
{
/* store gray data */
local_len=dev->local_buffer.size();
dev->local_buffer.reset();
genesys_read_ordered_data(dev, dev->local_buffer.get_write_pos(local_len),
&local_len);
dev->local_buffer.produce(local_len);
dev->binarize_buffer.reset();
genesys_gray_lineart(dev, dev->local_buffer.get_read_pos(),
dev->binarize_buffer.get_write_pos(local_len / 8),
dev->settings.pixels,
local_len/dev->settings.pixels,
dev->settings.threshold);
dev->binarize_buffer.produce(local_len / 8);
}
/* return data from lineart buffer if any, up to the available amount */
local_len = max_len;
if((size_t)max_len>dev->binarize_buffer.avail())
{
local_len=dev->binarize_buffer.avail();
}
if(local_len)
{
memcpy(buf, dev->binarize_buffer.get_read_pos(), local_len);
dev->binarize_buffer.consume(local_len);
}
}
else
{
// most usual case, direct read of data from scanner */
genesys_read_ordered_data(dev, buf, &local_len);
}
}
else /* read data from buffer */
{
if(dev->total_bytes_read+local_len>dev->total_bytes_to_read)
{
local_len=dev->total_bytes_to_read-dev->total_bytes_read;
}
memcpy(buf, dev->img_buffer.data() + dev->total_bytes_read, local_len);
dev->total_bytes_read+=local_len;
}
*len = local_len;
if(local_len>(size_t)max_len)
{
fprintf (stderr, "[genesys] sane_read: returning incorrect length!!\n");
}
DBG(DBG_proc, "%s: %d bytes returned\n", __func__, *len);
return SANE_STATUS_GOOD;
}
SANE_Status sane_read(SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int* len)
{
return wrap_exceptions_to_status_code(__func__, [=]()
{
return sane_read_impl(handle, buf, max_len, len);
});
}
void sane_cancel_impl(SANE_Handle handle)
{
DBG_HELPER(dbg);
Genesys_Scanner *s = (Genesys_Scanner*) handle;
/* end binary logging if needed */
if (s->dev->binary!=NULL)
{
fclose(s->dev->binary);
s->dev->binary=NULL;
}
s->scanning = SANE_FALSE;
s->dev->read_active = SANE_FALSE;
s->dev->img_buffer.clear();
/* no need to end scan if we are parking the head */
if(s->dev->parking==SANE_FALSE)
{
s->dev->cmd_set->end_scan(s->dev, &s->dev->reg, SANE_TRUE);
}
/* park head if flatbed scanner */
if (s->dev->model->is_sheetfed == SANE_FALSE)
{
if(s->dev->parking==SANE_FALSE)
{
s->dev->cmd_set->slow_back_home (s->dev, s->dev->model->flags &
GENESYS_FLAG_MUST_WAIT);
s->dev->parking = !(s->dev->model->flags & GENESYS_FLAG_MUST_WAIT);
}
}
else
{ /* in case of sheetfed scanners, we have to eject the document if still present */
s->dev->cmd_set->eject_document(s->dev);
}
/* enable power saving mode unless we are parking .... */
if(s->dev->parking==SANE_FALSE)
{
s->dev->cmd_set->save_power(s->dev, SANE_TRUE);
}
return;
}
void sane_cancel(SANE_Handle handle)
{
catch_all_exceptions(__func__, [=]() { sane_cancel_impl(handle); });
}
SANE_Status
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 = (Genesys_Scanner*) handle;
if (!s->scanning)
{
DBG(DBG_error, "%s: not scanning\n", __func__);
return SANE_STATUS_INVAL;
}
if (non_blocking)
return SANE_STATUS_UNSUPPORTED;
return SANE_STATUS_GOOD;
}
SANE_Status
sane_set_io_mode(SANE_Handle handle, SANE_Bool non_blocking)
{
return wrap_exceptions_to_status_code(__func__, [=]()
{
return sane_set_io_mode_impl(handle, non_blocking);
});
}
SANE_Status
sane_get_select_fd_impl(SANE_Handle handle, SANE_Int * fd)
{
DBG_HELPER_ARGS(dbg, "handle = %p, fd = %p", handle, (void *) fd);
Genesys_Scanner *s = (Genesys_Scanner*) handle;
if (!s->scanning)
{
DBG(DBG_error, "%s: not scanning\n", __func__);
return SANE_STATUS_INVAL;
}
return SANE_STATUS_UNSUPPORTED;
}
SANE_Status
sane_get_select_fd(SANE_Handle handle, SANE_Int * fd)
{
return wrap_exceptions_to_status_code(__func__, [=]()
{
return sane_get_select_fd_impl(handle, fd);
});
}
GenesysButtonName genesys_option_to_button(int option)
{
switch (option) {
case OPT_SCAN_SW: return BUTTON_SCAN_SW;
case OPT_FILE_SW: return BUTTON_FILE_SW;
case OPT_EMAIL_SW: return BUTTON_EMAIL_SW;
case OPT_COPY_SW: return BUTTON_COPY_SW;
case OPT_PAGE_LOADED_SW: return BUTTON_PAGE_LOADED_SW;
case OPT_OCR_SW: return BUTTON_OCR_SW;
case OPT_POWER_SW: return BUTTON_POWER_SW;
case OPT_EXTRA_SW: return BUTTON_EXTRA_SW;
default: throw std::runtime_error("Unknown option to convert to button index");
}
}