kopia lustrzana https://gitlab.com/sane-project/backends
7786 wiersze
221 KiB
C
7786 wiersze
221 KiB
C
/* sane - Scanner Access Now Easy.
|
|
|
|
Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
|
|
Copyright (C) 2004, 2005 Gerhard Jaeger <gerhard@gjaeger.de>
|
|
Copyright (C) 2004-2012 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/GL847/GL124 based scanners
|
|
*/
|
|
|
|
#define BUILD 2301
|
|
#define BACKEND_NAME genesys
|
|
|
|
#include "genesys.h"
|
|
#include "../include/sane/sanei_config.h"
|
|
#include "../include/sane/sanei_magic.h"
|
|
#include "genesys_devices.c"
|
|
|
|
static SANE_Int num_devices = 0;
|
|
static Genesys_Device *first_dev = 0;
|
|
static Genesys_Scanner *first_handle = 0;
|
|
static const SANE_Device **devlist = 0;
|
|
/* Array of newly attached devices */
|
|
static Genesys_Device **new_dev = 0;
|
|
/* Length of new_dev array */
|
|
static SANE_Int new_dev_len = 0;
|
|
/* Number of entries alloced for new_dev */
|
|
static SANE_Int new_dev_alloced = 0;
|
|
|
|
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 (FLATBED),
|
|
SANE_I18N (TRANSPARENCY_ADAPTER),
|
|
0
|
|
};
|
|
|
|
static SANE_Range swdespeck_range = {
|
|
1,
|
|
9,
|
|
1
|
|
};
|
|
|
|
static SANE_Range time_range = {
|
|
0, /* minimum */
|
|
60, /* maximum */
|
|
0 /* quantization */
|
|
};
|
|
|
|
static const SANE_Range u8_range = {
|
|
0, /* minimum */
|
|
255, /* 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 */
|
|
};
|
|
|
|
void
|
|
sanei_genesys_init_structs (Genesys_Device * dev)
|
|
{
|
|
unsigned int i, sensor_ok = 0, gpo_ok = 0, motor_ok = 0;
|
|
|
|
/* initialize the sensor data stuff */
|
|
for (i = 0; i < sizeof (Sensor) / sizeof (Genesys_Sensor); i++)
|
|
{
|
|
if (dev->model->ccd_type == Sensor[i].sensor_id)
|
|
{
|
|
memcpy (&dev->sensor, &Sensor[i], sizeof (Genesys_Sensor));
|
|
sensor_ok = 1;
|
|
}
|
|
}
|
|
|
|
/* initialize the GPO data stuff */
|
|
for (i = 0; i < sizeof (Gpo) / sizeof (Genesys_Gpo); i++)
|
|
{
|
|
if (dev->model->gpo_type == Gpo[i].gpo_id)
|
|
{
|
|
memcpy (&dev->gpo, &Gpo[i], sizeof (Genesys_Gpo));
|
|
gpo_ok = 1;
|
|
}
|
|
}
|
|
|
|
/* initialize the motor data stuff */
|
|
for (i = 0; i < sizeof (Motor) / sizeof (Genesys_Motor); i++)
|
|
{
|
|
if (dev->model->motor_type == Motor[i].motor_id)
|
|
{
|
|
memcpy (&dev->motor, &Motor[i], sizeof (Genesys_Motor));
|
|
motor_ok = 1;
|
|
}
|
|
}
|
|
|
|
/* sanity check */
|
|
if (sensor_ok == 0 || motor_ok == 0 || gpo_ok == 0)
|
|
{
|
|
DBG (DBG_error0,
|
|
"sanei_genesys_init_structs: bad description(s) for ccd/gpo/motor=%d/%d/%d\n",
|
|
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;
|
|
}
|
|
|
|
void
|
|
sanei_genesys_init_fe (Genesys_Device * dev)
|
|
{
|
|
unsigned int i;
|
|
|
|
DBGSTART;
|
|
for (i = 0; i < sizeof (Wolfson) / sizeof (Genesys_Frontend); i++)
|
|
{
|
|
if (dev->model->dac_type == Wolfson[i].fe_id)
|
|
{
|
|
memcpy (&dev->frontend, &Wolfson[i], sizeof (Genesys_Frontend));
|
|
return;
|
|
}
|
|
}
|
|
DBG (DBG_error0,
|
|
"sanei_genesys_init_fe: failed to find description for dac_type %d\n",
|
|
dev->model->dac_type);
|
|
DBG (DBG_info, "sanei_genesys_init_fe: dac_type %d set up\n",
|
|
dev->model->dac_type);
|
|
DBGCOMPLETED;
|
|
}
|
|
|
|
/* 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_step 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 (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, "sanei_genesys_generate_slope_table: table size: %d\n",
|
|
max_steps);
|
|
|
|
DBG (DBG_proc,
|
|
"sanei_genesys_generate_slope_table: stop at time: %d, use %d steps max\n",
|
|
stop_at, use_steps);
|
|
|
|
DBG (DBG_proc,
|
|
"sanei_genesys_generate_slope_table: target slope: "
|
|
"vstart: %d, vend: %d, steps: %d, g: %g\n", vstart, vend, steps, g);
|
|
|
|
sum = 0;
|
|
c = 0;
|
|
*used_steps = 0;
|
|
|
|
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++ = 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++ = *vfinal;
|
|
/* DBG (DBG_io, "slope_table[%3d] = %5d\n", c, *vfinal); */
|
|
}
|
|
|
|
(*used_steps)++;
|
|
sum += *vfinal;
|
|
|
|
DBG (DBG_proc,
|
|
"sanei_genesys_generate_slope_table: returns sum=%d, used %d steps, completed\n",
|
|
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,
|
|
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,
|
|
int power_mode)
|
|
{
|
|
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, power_mode = %d\n", __FUNCTION__, step_type,
|
|
exposure_time, yres, power_mode);
|
|
|
|
/* final speed */
|
|
vtarget = (exposure_time * yres) / dev->motor.base_ydpi;
|
|
|
|
vstart = dev->motor.slopes[power_mode][step_type].maximum_start_speed;
|
|
vend = dev->motor.slopes[power_mode][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[power_mode][step_type].minimum_steps << step_type,
|
|
dev->motor.slopes[power_mode][step_type].g,
|
|
used_steps,
|
|
&vfinal);
|
|
|
|
if (final_exposure)
|
|
*final_exposure = (vfinal * dev->motor.base_ydpi) / yres;
|
|
|
|
DBG (DBG_proc,
|
|
"sanei_genesys_create_slope_table: returns sum_time=%d, completed\n",
|
|
sum_time);
|
|
|
|
return sum_time;
|
|
}
|
|
|
|
|
|
/* alternate slope table creation function */
|
|
/* the hardcoded values (g and vstart) will go in a motor struct */
|
|
static SANE_Int
|
|
genesys_create_slope_table2 (Genesys_Device * dev,
|
|
uint16_t * slope_table, int steps,
|
|
int step_type, int exposure_time,
|
|
SANE_Bool same_speed, double yres,
|
|
int power_mode)
|
|
{
|
|
double t, g;
|
|
SANE_Int sum = 0;
|
|
int vstart, vend;
|
|
int i;
|
|
|
|
DBG (DBG_proc,
|
|
"sanei_genesys_create_slope_table2: %d steps, step_type = %d, "
|
|
"exposure_time = %d, same_speed = %d, yres = %.2f, power_mode = %d\n",
|
|
steps, step_type, exposure_time, same_speed, yres, power_mode);
|
|
|
|
/* 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,
|
|
"sanei_genesys_create_slope_table2: returns sum=%d, completed\n", sum);
|
|
|
|
return sum;
|
|
}
|
|
|
|
/* Generate slope table for motor movement */
|
|
/* todo: check details */
|
|
SANE_Int
|
|
sanei_genesys_create_slope_table (Genesys_Device * dev,
|
|
uint16_t * slope_table, int steps,
|
|
int step_type, int exposure_time,
|
|
SANE_Bool same_speed, double yres,
|
|
int power_mode)
|
|
{
|
|
double t;
|
|
double start_speed;
|
|
double g;
|
|
uint32_t time_period;
|
|
int sum_time = 0;
|
|
int i, divider;
|
|
int same_step;
|
|
|
|
dev = dev;
|
|
|
|
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, power_mode);
|
|
|
|
DBG (DBG_proc,
|
|
"sanei_genesys_create_slope_table: %d steps, step_type = %d, "
|
|
"exposure_time = %d, same_speed =%d\n", steps, step_type,
|
|
exposure_time, same_speed);
|
|
DBG (DBG_proc, "sanei_genesys_create_slope_table: yres = %.2f\n", 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,
|
|
"sanei_genesys_create_slope_table: returns sum_time=%d, completed\n",
|
|
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,
|
|
"sanei_genesys_create_slope_table: returns sum_time=%d, completed\n",
|
|
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,
|
|
"sanei_genesys_create_slope_table: returns sum_time=%d, completed\n",
|
|
sum_time);
|
|
|
|
return sum_time;
|
|
}
|
|
|
|
/* computes gamma table */
|
|
void
|
|
sanei_genesys_create_gamma_table (uint16_t * gamma_table, int size,
|
|
float maximum, float gamma_max, float gamma)
|
|
{
|
|
int i;
|
|
float value;
|
|
|
|
DBG (DBG_proc,
|
|
"sanei_genesys_create_gamma_table: size = %d, "
|
|
"maximum = %g, gamma_max = %g, gamma = %g\n",
|
|
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_data,
|
|
"sanei_genesys_create_gamma_table: gamma_table[%.3d] = %.5d\n",
|
|
i, (int) value); */
|
|
}
|
|
DBG (DBG_proc, "sanei_genesys_create_gamma_table: completed\n");
|
|
}
|
|
|
|
|
|
/* 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 led_exposure, int power_mode)
|
|
{
|
|
int exposure_by_ccd = endpixel + 32;
|
|
int exposure_by_motor =
|
|
(dev->motor.slopes[power_mode][step_type].maximum_speed
|
|
* dev->motor.base_ydpi) / ydpi;
|
|
int exposure_by_led = led_exposure;
|
|
|
|
int exposure = exposure_by_ccd;
|
|
|
|
if (exposure < exposure_by_motor)
|
|
exposure = exposure_by_motor;
|
|
|
|
if (exposure < exposure_by_led && dev->model->is_cis)
|
|
exposure = exposure_by_led;
|
|
|
|
return exposure;
|
|
}
|
|
|
|
/* 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->model->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->model->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->model->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 dev->settings.exposure_time;
|
|
}
|
|
|
|
|
|
|
|
/* 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.
|
|
*/
|
|
#ifndef UNIT_TESTING
|
|
static
|
|
#endif
|
|
SANE_Status
|
|
genesys_send_offset_and_shading (Genesys_Device * dev, uint8_t * data,
|
|
int size)
|
|
{
|
|
int dpihw;
|
|
int start_address;
|
|
SANE_Status status;
|
|
|
|
DBG (DBG_proc, "genesys_send_offset_and_shading (size = %d)\n", size);
|
|
|
|
/* ASIC higher than gl843 doesn't have register 2A/2B, so we route to
|
|
* a per ASIC shading data loading function if available */
|
|
if(dev->model->cmd_set->send_shading_data!=NULL)
|
|
{
|
|
status=dev->model->cmd_set->send_shading_data(dev, data, size);
|
|
DBGCOMPLETED;
|
|
return status;
|
|
}
|
|
|
|
/* gl646, gl84[123] case */
|
|
dpihw = sanei_genesys_read_reg_from_set (dev->reg, 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 < 2
|
|
&& dev->model->ccd_type != CCD_KVSS080
|
|
&& dev->model->ccd_type != CCD_G4050
|
|
&& 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 != 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 */
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
else /* color */
|
|
start_address = 0x00;
|
|
|
|
status = sanei_genesys_set_buffer_address (dev, start_address);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_send_offset_and_shading: failed to set buffer address: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = dev->model->cmd_set->bulk_write_data (dev, 0x3c, data, size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_send_offset_and_shading: failed to send shading table: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
DBGCOMPLETED;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* ? */
|
|
SANE_Status
|
|
sanei_genesys_init_shading_data (Genesys_Device * dev, int pixels_per_line)
|
|
{
|
|
SANE_Status status;
|
|
uint8_t *shading_data, *shading_data_ptr;
|
|
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->cmd_set->send_shading_data!=NULL)
|
|
return SANE_STATUS_GOOD;
|
|
|
|
DBG (DBG_proc, "sanei_genesys_init_shading_data (pixels_per_line = %d)\n",
|
|
pixels_per_line);
|
|
|
|
if (dev->settings.scan_mode >= 2) /* 3 pass or single pass color */
|
|
channels = 3;
|
|
else
|
|
channels = 1;
|
|
|
|
shading_data = malloc (pixels_per_line * 4 * channels); /* 16 bit black, 16 bit white */
|
|
if (!shading_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"sanei_genesys_init_shading_data: failed to allocate memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
shading_data_ptr = shading_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 */
|
|
}
|
|
|
|
status =
|
|
genesys_send_offset_and_shading (dev, shading_data,
|
|
pixels_per_line * 4 * channels);
|
|
if (status != SANE_STATUS_GOOD)
|
|
DBG (DBG_error,
|
|
"sanei_genesys_init_shading_data: failed to send shading data: %s\n",
|
|
sane_strstatus (status));
|
|
|
|
free (shading_data);
|
|
|
|
DBGCOMPLETED;
|
|
return status;
|
|
}
|
|
|
|
|
|
/* Find the position of the reference point:
|
|
takes gray level 8 bits data and find
|
|
first CCD usable pixel and top of scanning area */
|
|
SANE_Status
|
|
sanei_genesys_search_reference_point (Genesys_Device * dev, uint8_t * data,
|
|
int start_pixel, int dpi, int width,
|
|
int height)
|
|
{
|
|
int x, y;
|
|
int current, left, top = 0;
|
|
uint8_t *image;
|
|
int size, count;
|
|
int level = 80; /* edge threshold level */
|
|
|
|
/*sanity check */
|
|
if ((width < 3) || (height < 3))
|
|
return SANE_STATUS_INVAL;
|
|
|
|
/* transformed image data */
|
|
size = width * height;
|
|
image = malloc (size);
|
|
if (!image)
|
|
{
|
|
DBG (DBG_error,
|
|
"sanei_genesys_search_reference_point: failed to allocate memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
/* laplace filter to denoise picture */
|
|
memcpy (image, 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] =
|
|
(data[(y - 1) * width + x + 1] + 2 * data[(y - 1) * width + x] +
|
|
data[(y - 1) * width + x - 1] + 2 * data[y * width + x + 1] +
|
|
4 * data[y * width + x] + 2 * data[y * width + x - 1] +
|
|
data[(y + 1) * width + x + 1] + 2 * data[(y + 1) * width + x] +
|
|
data[(y + 1) * width + x - 1]) / 16;
|
|
}
|
|
|
|
memcpy (data, image, size);
|
|
if (DBG_LEVEL >= DBG_data)
|
|
sanei_genesys_write_pnm_file ("laplace.pnm", image, 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 =
|
|
data[(y - 1) * width + x + 1] - data[(y - 1) * width + x - 1] +
|
|
2 * data[y * width + x + 1] - 2 * data[y * width + x - 1] +
|
|
data[(y + 1) * width + x + 1] - data[(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 ("xsobel.pnm", image, 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 ("detected-xsobel.pnm", image, 8, 1, width,
|
|
height);
|
|
left = left / count;
|
|
|
|
/* turn it in CCD pixel at full sensor optical resolution */
|
|
dev->sensor.CCD_start_xoffset =
|
|
start_pixel + (left * dev->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 =
|
|
-data[(y - 1) * width + x + 1] - 2 * data[(y - 1) * width + x] -
|
|
data[(y - 1) * width + x - 1] + data[(y + 1) * width + x + 1] +
|
|
2 * data[(y + 1) * width + x] + data[(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 ("ysobel.pnm", image, 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 ("detected-ysobel.pnm", image, 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,
|
|
"sanei_genesys_search_reference_point: black stripe y_offset = %f mm \n",
|
|
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,
|
|
"sanei_genesys_search_reference_point: white corner y_offset = %f mm\n",
|
|
SANE_UNFIX (dev->model->y_offset_calib));
|
|
}
|
|
|
|
free (image);
|
|
DBG (DBG_proc,
|
|
"sanei_genesys_search_reference_point: CCD_start_xoffset = %d, left = %d, top = %d\n",
|
|
dev->sensor.CCD_start_xoffset, left, top);
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
void
|
|
sanei_genesys_calculate_zmode2 (SANE_Bool two_table,
|
|
uint32_t exposure_time,
|
|
uint16_t * slope_table,
|
|
int reg21,
|
|
int move, int reg22, uint32_t * z1,
|
|
uint32_t * z2)
|
|
{
|
|
int i;
|
|
int sum;
|
|
DBG (DBG_info, "sanei_genesys_calculate_zmode2: two_table=%d\n", two_table);
|
|
|
|
/* acceleration total time */
|
|
sum = 0;
|
|
for (i = 0; i < reg21; i++)
|
|
sum += slope_table[i];
|
|
|
|
/* compute Z1MOD */
|
|
/* c=sum(slope_table;reg21)
|
|
d=reg22*cruising speed
|
|
Z1MOD=(c+d) % exposure_time */
|
|
*z1 = (sum + reg22 * slope_table[reg21 - 1]) % exposure_time;
|
|
|
|
/* compute Z2MOD */
|
|
/* a=sum(slope_table;reg21), b=move or 1 if 2 tables */
|
|
/* Z2MOD=(a+b) % exposure_time */
|
|
if (!two_table)
|
|
sum = sum + (move * slope_table[reg21 - 1]);
|
|
else
|
|
sum = sum + slope_table[reg21 - 1];
|
|
*z2 = sum % exposure_time;
|
|
}
|
|
|
|
|
|
/* huh? */
|
|
/* todo: double check */
|
|
/* Z1 and Z2 seem to be a time to synchronize with clock or a phase correction */
|
|
/* steps_sum is the result of create_slope_table */
|
|
/* last_speed is the last entry of the slope_table */
|
|
/* feedl is registers 3d,3e,3f */
|
|
/* fastfed is register 02 bit 3 */
|
|
/* scanfed is register 1f */
|
|
/* fwdstep is register 22 */
|
|
/* tgtime is register 6c bit 6+7 >> 6 */
|
|
|
|
void
|
|
sanei_genesys_calculate_zmode (Genesys_Device * dev, uint32_t exposure_time,
|
|
uint32_t steps_sum, uint16_t last_speed,
|
|
uint32_t feedl, uint8_t fastfed,
|
|
uint8_t scanfed, uint8_t fwdstep,
|
|
uint8_t tgtime, uint32_t * z1, uint32_t * z2)
|
|
{
|
|
uint8_t exposure_factor;
|
|
|
|
dev = dev;
|
|
|
|
exposure_factor = pow (2, tgtime); /* todo: originally, this is always 2^0 ! */
|
|
|
|
/* Z1 is for buffer-full backward forward moving */
|
|
*z1 =
|
|
exposure_factor * ((steps_sum + fwdstep * last_speed) % exposure_time);
|
|
|
|
/* Z2 is for acceleration before scan */
|
|
if (fastfed) /* two curve mode */
|
|
{
|
|
*z2 =
|
|
exposure_factor * ((steps_sum + scanfed * last_speed) %
|
|
exposure_time);
|
|
}
|
|
else /* one curve mode */
|
|
{
|
|
*z2 =
|
|
exposure_factor * ((steps_sum + feedl * last_speed) % exposure_time);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
genesys_adjust_gain (Genesys_Device * dev, double *applied_multi,
|
|
uint8_t * new_gain, double multi, uint8_t gain)
|
|
{
|
|
double voltage, original_voltage;
|
|
|
|
DBG (DBG_proc, "genesys_adjust_gain: multi=%f, gain=%d\n", multi, gain);
|
|
|
|
dev = dev;
|
|
|
|
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,
|
|
"genesys_adjust_gain: orig voltage=%.2f, new voltage=%.2f, "
|
|
"*applied_multi=%f, *new_gain=%d\n", original_voltage, voltage,
|
|
*applied_multi, *new_gain);
|
|
return;
|
|
}
|
|
|
|
|
|
/* todo: is return status necessary (unchecked?) */
|
|
static SANE_Status
|
|
genesys_average_white (Genesys_Device * dev, int channels, int channel,
|
|
uint8_t * data, int size, int *max_average)
|
|
{
|
|
int gain_white_ref, sum, range;
|
|
int average;
|
|
int i;
|
|
|
|
DBG (DBG_proc,
|
|
"genesys_average_white: channels=%d, channel=%d, size=%d\n",
|
|
channels, channel, size);
|
|
|
|
range = size / 50;
|
|
|
|
if (dev->settings.scan_method == SCAN_METHOD_TRANSPARENCY) /* transparency mode */
|
|
gain_white_ref = dev->sensor.fau_gain_white_ref * 256;
|
|
else
|
|
gain_white_ref = dev->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,
|
|
"genesys_average_white: max_average=%d, gain_white_ref = %d, finished\n",
|
|
*max_average, gain_white_ref);
|
|
|
|
if (*max_average >= gain_white_ref)
|
|
return SANE_STATUS_INVAL;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* 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, "genesys_average_black: channel=%d, pixels=%d\n",
|
|
channel, pixels);
|
|
|
|
sum = 0;
|
|
|
|
if (dev->settings.scan_mode == SCAN_MODE_COLOR) /* single pass color */
|
|
{
|
|
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, "genesys_average_black = %d\n", 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 SANE_Status
|
|
genesys_coarse_calibration (Genesys_Device * dev)
|
|
{
|
|
int size;
|
|
int black_pixels;
|
|
int white_average;
|
|
int channels;
|
|
SANE_Status status;
|
|
uint8_t offset[4] = { 0xa0, 0x00, 0xa0, 0x40 }; /* first value isn't used */
|
|
uint16_t white[12], dark[12];
|
|
int i, j;
|
|
uint8_t *calibration_data, *all_data;
|
|
|
|
DBG (DBG_info, "genesys_coarse_calibration (scan_mode = %d)\n",
|
|
dev->settings.scan_mode);
|
|
|
|
black_pixels = dev->sensor.black_pixels
|
|
* dev->settings.xres / dev->sensor.optical_res;
|
|
|
|
if (dev->settings.scan_mode == SCAN_MODE_COLOR) /* single pass color */
|
|
channels = 3;
|
|
else
|
|
channels = 1;
|
|
|
|
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 */
|
|
|
|
calibration_data = malloc (size);
|
|
if (!calibration_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: failed to allocate memory(%d bytes)\n",
|
|
size);
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
all_data = calloc (1, size * 4);
|
|
|
|
status = dev->model->cmd_set->set_fe (dev, AFE_INIT);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: failed to set frontend: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
dev->frontend.sign[0] = dev->frontend.sign[1] = dev->frontend.sign[2] = 0;
|
|
dev->frontend.gain[0] = dev->frontend.gain[1] = dev->frontend.gain[2] = 2; /* todo: ? was 2 */
|
|
dev->frontend.offset[0] = dev->frontend.offset[1] =
|
|
dev->frontend.offset[2] = offset[0];
|
|
|
|
for (i = 0; i < 4; i++) /* read 4 lines */
|
|
{
|
|
if (i < 3) /* first 3 lines */
|
|
dev->frontend.offset[0] = dev->frontend.offset[1] =
|
|
dev->frontend.offset[2] = offset[i];
|
|
|
|
if (i == 1) /* second line */
|
|
{
|
|
double applied_multi;
|
|
double gain_white_ref;
|
|
|
|
if (dev->settings.scan_method == SCAN_METHOD_TRANSPARENCY) /* Transparency */
|
|
gain_white_ref = dev->sensor.fau_gain_white_ref * 256;
|
|
else
|
|
gain_white_ref = dev->sensor.gain_white_ref * 256;
|
|
/* white and black are defined downwards */
|
|
|
|
genesys_adjust_gain (dev, &applied_multi,
|
|
&dev->frontend.gain[0],
|
|
gain_white_ref / (white[0] - dark[0]),
|
|
dev->frontend.gain[0]);
|
|
genesys_adjust_gain (dev, &applied_multi,
|
|
&dev->frontend.gain[1],
|
|
gain_white_ref / (white[1] - dark[1]),
|
|
dev->frontend.gain[1]);
|
|
genesys_adjust_gain (dev, &applied_multi,
|
|
&dev->frontend.gain[2],
|
|
gain_white_ref / (white[2] - dark[2]),
|
|
dev->frontend.gain[2]);
|
|
|
|
dev->frontend.gain[0] = dev->frontend.gain[1] =
|
|
dev->frontend.gain[2] = 2;
|
|
|
|
status =
|
|
sanei_genesys_fe_write_data (dev, 0x28, dev->frontend.gain[0]);
|
|
if (status != SANE_STATUS_GOOD) /* todo: this was 0x28 + 3 ? */
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: Failed to write gain[0]: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status =
|
|
sanei_genesys_fe_write_data (dev, 0x29, dev->frontend.gain[1]);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: Failed to write gain[1]: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status =
|
|
sanei_genesys_fe_write_data (dev, 0x2a, dev->frontend.gain[2]);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: Failed to write gain[2]: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
dev->frontend.offset[j] = (uint8_t) (rate);
|
|
|
|
if (dev->frontend.offset[j] > 0x7f)
|
|
dev->frontend.offset[j] = 0x7f;
|
|
dev->frontend.offset[j] <<= 1;
|
|
}
|
|
}
|
|
status =
|
|
sanei_genesys_fe_write_data (dev, 0x20, dev->frontend.offset[0]);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: Failed to write offset[0]: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status =
|
|
sanei_genesys_fe_write_data (dev, 0x21, dev->frontend.offset[1]);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: Failed to write offset[1]: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status =
|
|
sanei_genesys_fe_write_data (dev, 0x22, dev->frontend.offset[2]);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: Failed to write offset[2]: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
DBG (DBG_info,
|
|
"genesys_coarse_calibration: doing scan: sign: %d/%d/%d, gain: %d/%d/%d, offset: %d/%d/%d\n",
|
|
dev->frontend.sign[0], dev->frontend.sign[1],
|
|
dev->frontend.sign[2], dev->frontend.gain[0],
|
|
dev->frontend.gain[1], dev->frontend.gain[2],
|
|
dev->frontend.offset[0], dev->frontend.offset[1],
|
|
dev->frontend.offset[2]);
|
|
|
|
status =
|
|
dev->model->cmd_set->begin_scan (dev, dev->calib_reg, SANE_FALSE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: Failed to begin scan: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status =
|
|
sanei_genesys_read_data_from_scanner (dev, calibration_data, size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: Failed to read data: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
memcpy (all_data + i * size, calibration_data, size);
|
|
if (i == 3) /* last line */
|
|
{
|
|
SANE_Byte *all_data_8 = malloc (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];
|
|
status =
|
|
sanei_genesys_write_pnm_file ("coarse.pnm", all_data_8, 8,
|
|
channels, size / 6, 4);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: sanei_genesys_write_pnm_file failed: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
status = dev->model->cmd_set->end_scan (dev, dev->calib_reg, SANE_TRUE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_coarse_calibration: Failed to end scan: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
if (dev->settings.scan_mode == SCAN_MODE_COLOR) /* single pass color */
|
|
{
|
|
for (j = 0; j < 3; j++)
|
|
{
|
|
genesys_average_white (dev, 3, j, calibration_data, size,
|
|
&white_average);
|
|
white[i * 3 + j] = white_average;
|
|
dark[i * 3 + j] =
|
|
genesys_average_black (dev, j, calibration_data,
|
|
black_pixels);
|
|
DBG (DBG_info,
|
|
"genesys_coarse_calibration: white[%d]=%d, black[%d]=%d\n",
|
|
i * 3 + j, white[i * 3 + j], i * 3 + j, dark[i * 3 + j]);
|
|
}
|
|
}
|
|
else /* one color-component modes */
|
|
{
|
|
genesys_average_white (dev, 1, 0, calibration_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, black_pixels);
|
|
}
|
|
|
|
if (i == 3)
|
|
{
|
|
if (dev->settings.scan_mode == SCAN_MODE_COLOR) /* single pass color */
|
|
{
|
|
/* todo: huh? */
|
|
dev->dark[0] =
|
|
(uint16_t) (1.6925 * dark[i * 3 + 0] + 0.1895 * 256);
|
|
dev->dark[1] =
|
|
(uint16_t) (1.4013 * dark[i * 3 + 1] + 0.3147 * 256);
|
|
dev->dark[2] =
|
|
(uint16_t) (1.2931 * dark[i * 3 + 2] + 0.1558 * 256);
|
|
}
|
|
else /* one color-component modes */
|
|
{
|
|
switch (dev->settings.color_filter)
|
|
{
|
|
case 0:
|
|
default:
|
|
dev->dark[0] =
|
|
(uint16_t) (1.6925 * dark[i * 3 + 0] +
|
|
(1.1895 - 1.0) * 256);
|
|
dev->dark[1] = dev->dark[2] = dev->dark[0];
|
|
break;
|
|
|
|
case 1:
|
|
dev->dark[1] =
|
|
(uint16_t) (1.4013 * dark[i * 3 + 1] +
|
|
(1.3147 - 1.0) * 256);
|
|
dev->dark[0] = dev->dark[2] = dev->dark[1];
|
|
break;
|
|
|
|
case 2:
|
|
dev->dark[2] =
|
|
(uint16_t) (1.2931 * dark[i * 3 + 2] +
|
|
(1.1558 - 1.0) * 256);
|
|
dev->dark[0] = dev->dark[1] = dev->dark[2];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} /* for (i = 0; i < 4; i++) */
|
|
|
|
DBG (DBG_info,
|
|
"genesys_coarse_calibration: final: sign: %d/%d/%d, gain: %d/%d/%d, offset: %d/%d/%d\n",
|
|
dev->frontend.sign[0], dev->frontend.sign[1], dev->frontend.sign[2],
|
|
dev->frontend.gain[0], dev->frontend.gain[1], dev->frontend.gain[2],
|
|
dev->frontend.offset[0], dev->frontend.offset[1],
|
|
dev->frontend.offset[2]);
|
|
DBGCOMPLETED;
|
|
|
|
return status;
|
|
}
|
|
|
|
/* Averages image data.
|
|
average_data and calibration_data are little endian 16 bit words.
|
|
*/
|
|
#ifndef UNIT_TESTING
|
|
static
|
|
#endif
|
|
void
|
|
genesys_average_data (uint8_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 & 255;
|
|
*average_data++ = sum / 256;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @return SANE_STATUS_GOOD if OK, else an error
|
|
*/
|
|
static SANE_Status
|
|
genesys_dark_shading_calibration (Genesys_Device * dev)
|
|
{
|
|
SANE_Status status;
|
|
size_t size;
|
|
uint32_t pixels_per_line;
|
|
uint8_t channels;
|
|
uint8_t *calibration_data;
|
|
SANE_Bool motor;
|
|
|
|
DBGSTART;
|
|
|
|
/* end pixel - start pixel */
|
|
pixels_per_line = dev->calib_pixels;
|
|
channels = dev->calib_channels;
|
|
|
|
FREE_IFNOT_NULL (dev->dark_average_data);
|
|
|
|
dev->average_size = channels * 2 * pixels_per_line;
|
|
|
|
dev->dark_average_data = malloc (dev->average_size);
|
|
if (!dev->dark_average_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_dark_shading_calibration: failed to allocate average memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
/* size is size in bytes for scanarea: bytes_per_line * lines */
|
|
size = channels * 2 * pixels_per_line * (dev->calib_lines + 1);
|
|
|
|
calibration_data = malloc (size);
|
|
if (!calibration_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_dark_shading_calibration: failed to allocate calibration data memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
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)
|
|
{
|
|
dev->model->cmd_set->set_lamp_power (dev, dev->calib_reg, SANE_FALSE);
|
|
dev->model->cmd_set->set_motor_power (dev->calib_reg, motor);
|
|
}
|
|
else
|
|
{
|
|
dev->model->cmd_set->set_lamp_power (dev, dev->calib_reg, SANE_TRUE);
|
|
dev->model->cmd_set->set_motor_power (dev->calib_reg, motor);
|
|
}
|
|
|
|
status =
|
|
dev->model->cmd_set->bulk_write_register (dev, dev->calib_reg,
|
|
dev->model->
|
|
cmd_set->bulk_full_size ());
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_dark_shading_calibration: failed to bulk write registers: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
usleep (200 * 1000); /* wait 200 ms: lamp needs some time to get dark */
|
|
|
|
status = dev->model->cmd_set->begin_scan (dev, dev->calib_reg, SANE_FALSE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_dark_shading_calibration: Failed to begin scan: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = sanei_genesys_read_data_from_scanner (dev, calibration_data, size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_dark_shading_calibration: failed to read data: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = dev->model->cmd_set->end_scan (dev, dev->calib_reg, SANE_TRUE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_dark_shading_calibration: failed to end scan: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
genesys_average_data (dev->dark_average_data, calibration_data,
|
|
dev->calib_lines,
|
|
pixels_per_line * channels);
|
|
|
|
if (DBG_LEVEL >= DBG_data)
|
|
{
|
|
sanei_genesys_write_pnm_file ("black_shading.pnm", calibration_data, 16,
|
|
channels, pixels_per_line,
|
|
dev->calib_lines);
|
|
sanei_genesys_write_pnm_file ("black_average.pnm",
|
|
dev->dark_average_data, 16, channels,
|
|
pixels_per_line, 1);
|
|
}
|
|
|
|
free (calibration_data);
|
|
|
|
DBGCOMPLETED;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/*
|
|
* 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 SANE_Status
|
|
genesys_dummy_dark_shading (Genesys_Device * dev)
|
|
{
|
|
uint32_t pixels_per_line;
|
|
uint8_t channels;
|
|
uint32_t x, skip, xend;
|
|
int dummy1, dummy2, dummy3; /* dummy black average per channel */
|
|
|
|
DBGSTART;
|
|
|
|
pixels_per_line = dev->calib_pixels;
|
|
channels = dev->calib_channels;
|
|
|
|
FREE_IFNOT_NULL (dev->dark_average_data);
|
|
|
|
dev->average_size = channels * 2 * pixels_per_line;
|
|
dev->dark_average_data = malloc (dev->average_size);
|
|
if (!dev->dark_average_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_dummy_dark_shading: failed to allocate average memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
memset (dev->dark_average_data, 0x00, channels * 2 * pixels_per_line);
|
|
|
|
/* 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 <= dev->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_KVSS080)
|
|
{
|
|
skip = 2;
|
|
xend = dev->sensor.black_pixels;
|
|
}
|
|
|
|
/* average each channels on half left margin */
|
|
dummy1 = 0;
|
|
dummy2 = 0;
|
|
dummy3 = 0;
|
|
|
|
for (x = skip + 1; x <= xend; x++)
|
|
{
|
|
dummy1 +=
|
|
dev->white_average_data[channels * 2 * x] +
|
|
256 * dev->white_average_data[channels * 2 * x + 1];
|
|
if (channels > 1)
|
|
{
|
|
dummy2 +=
|
|
(dev->white_average_data[channels * 2 * x + 2] +
|
|
256 * dev->white_average_data[channels * 2 * x + 3]);
|
|
dummy3 +=
|
|
(dev->white_average_data[channels * 2 * x + 4] +
|
|
256 * dev->white_average_data[channels * 2 * x + 5]);
|
|
}
|
|
}
|
|
|
|
dummy1 /= (xend - skip);
|
|
if (channels > 1)
|
|
{
|
|
dummy2 /= (xend - skip);
|
|
dummy3 /= (xend - skip);
|
|
}
|
|
DBG (DBG_proc,
|
|
"genesys_dummy_dark_shading: dummy1=%d, dummy2=%d, dummy3=%d \n",
|
|
dummy1, dummy2, dummy3);
|
|
|
|
/* fill dark_average */
|
|
for (x = 0; x < pixels_per_line; x++)
|
|
{
|
|
dev->dark_average_data[channels * 2 * x] = dummy1 & 0xff;
|
|
dev->dark_average_data[channels * 2 * x + 1] = dummy1 >> 8;
|
|
if (channels > 1)
|
|
{
|
|
dev->dark_average_data[channels * 2 * x + 2] = dummy2 & 0xff;
|
|
dev->dark_average_data[channels * 2 * x + 3] = dummy2 >> 8;
|
|
dev->dark_average_data[channels * 2 * x + 4] = dummy3 & 0xff;
|
|
dev->dark_average_data[channels * 2 * x + 5] = dummy3 >> 8;
|
|
}
|
|
}
|
|
|
|
DBGCOMPLETED;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
genesys_white_shading_calibration (Genesys_Device * dev)
|
|
{
|
|
SANE_Status status;
|
|
size_t size;
|
|
uint32_t pixels_per_line;
|
|
uint8_t *calibration_data;
|
|
uint8_t channels;
|
|
SANE_Bool motor;
|
|
|
|
DBG (DBG_proc, "genesys_white_shading_calibration (lines = %d)\n",
|
|
dev->calib_lines);
|
|
|
|
pixels_per_line = dev->calib_pixels;
|
|
channels = dev->calib_channels;
|
|
|
|
if (dev->white_average_data)
|
|
free (dev->white_average_data);
|
|
|
|
dev->white_average_data = malloc (channels * 2 * pixels_per_line);
|
|
if (!dev->white_average_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_white_shading_calibration: failed to allocate average memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
size = channels * 2 * pixels_per_line * (dev->calib_lines + 1);
|
|
|
|
calibration_data = malloc (size);
|
|
if (!calibration_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_white_shading_calibration: failed to allocate calibration memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
motor=SANE_TRUE;
|
|
if (dev->model->flags & GENESYS_FLAG_SHADING_NO_MOVE)
|
|
{
|
|
motor=SANE_FALSE;
|
|
}
|
|
|
|
/* turn on motor and lamp power */
|
|
dev->model->cmd_set->set_lamp_power (dev, dev->calib_reg, SANE_TRUE);
|
|
dev->model->cmd_set->set_motor_power (dev->calib_reg, motor);
|
|
if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK)
|
|
{
|
|
status = dev->model->cmd_set->slow_back_home (dev, SANE_TRUE);
|
|
}
|
|
|
|
status =
|
|
dev->model->cmd_set->bulk_write_register (dev, dev->calib_reg,
|
|
dev->model->
|
|
cmd_set->bulk_full_size ());
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_white_shading_calibration: failed to bulk write registers: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)
|
|
usleep (500 * 1000); /* wait 500ms to make sure lamp is bright again */
|
|
|
|
status = dev->model->cmd_set->begin_scan (dev, dev->calib_reg, SANE_TRUE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_white_shading_calibration: Failed to begin scan: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = sanei_genesys_read_data_from_scanner (dev, calibration_data, size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_white_shading_calibration: failed to read data: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = dev->model->cmd_set->end_scan (dev, dev->calib_reg, SANE_TRUE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_white_shading_calibration: failed to end scan: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
if (DBG_LEVEL >= DBG_data)
|
|
sanei_genesys_write_pnm_file ("white_shading.pnm", calibration_data, 16,
|
|
channels, pixels_per_line,
|
|
dev->calib_lines);
|
|
|
|
genesys_average_data (dev->white_average_data, calibration_data,
|
|
dev->calib_lines,
|
|
pixels_per_line * channels);
|
|
|
|
if (DBG_LEVEL >= DBG_data)
|
|
sanei_genesys_write_pnm_file ("white_average.pnm",
|
|
dev->white_average_data, 16, channels,
|
|
pixels_per_line, 1);
|
|
|
|
free (calibration_data);
|
|
|
|
/* in case we haven't done dark calibration, build dummy data from white_average */
|
|
if (!(dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION))
|
|
{
|
|
status = genesys_dummy_dark_shading (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_white_shading_calibration: failed to do dummy dark shading calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK)
|
|
{
|
|
status = dev->model->cmd_set->slow_back_home (dev, SANE_TRUE);
|
|
}
|
|
|
|
DBGCOMPLETED;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* This calibration uses a scan over the calibration target, comprising a
|
|
* black and a white strip. (So the motor must be on.)
|
|
*/
|
|
static SANE_Status
|
|
genesys_dark_white_shading_calibration (Genesys_Device * dev)
|
|
{
|
|
SANE_Status status;
|
|
size_t size;
|
|
uint32_t pixels_per_line;
|
|
uint8_t *calibration_data, *average_white, *average_dark;
|
|
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;
|
|
|
|
|
|
DBG (DBG_proc, "genesys_black_white_shading_calibration (lines = %d)\n",
|
|
dev->calib_lines);
|
|
|
|
pixels_per_line = dev->calib_pixels;
|
|
channels = dev->calib_channels;
|
|
|
|
if (dev->white_average_data)
|
|
free (dev->white_average_data);
|
|
|
|
dev->average_size = channels * 2 * pixels_per_line;
|
|
|
|
dev->white_average_data = malloc (dev->average_size);
|
|
if (!dev->white_average_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_dark_white_shading_calibration: failed to allocate average memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
if (dev->dark_average_data)
|
|
free (dev->dark_average_data);
|
|
|
|
dev->dark_average_data = malloc (channels * 2 * pixels_per_line);
|
|
if (!dev->dark_average_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_dark_white_shading_shading_calibration: failed to allocate average memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
size = channels * 2 * pixels_per_line * dev->calib_lines;
|
|
|
|
calibration_data = malloc (size);
|
|
if (!calibration_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_dark_white_shading_calibration: failed to allocate calibration memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
motor=SANE_TRUE;
|
|
if (dev->model->flags & GENESYS_FLAG_SHADING_NO_MOVE)
|
|
{
|
|
motor=SANE_FALSE;
|
|
}
|
|
|
|
/* turn on motor and lamp power */
|
|
dev->model->cmd_set->set_lamp_power (dev, dev->calib_reg, SANE_TRUE);
|
|
dev->model->cmd_set->set_motor_power (dev->calib_reg, motor);
|
|
|
|
status =
|
|
dev->model->cmd_set->bulk_write_register (dev, dev->calib_reg,
|
|
dev->model->
|
|
cmd_set->bulk_full_size ());
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_dark_white_shading_calibration: failed to bulk write registers: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = dev->model->cmd_set->begin_scan (dev, dev->calib_reg, SANE_FALSE);
|
|
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_dark_white_shading_calibration: Failed to begin scan: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = sanei_genesys_read_data_from_scanner (dev, calibration_data, size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_dark_white_shading_calibration: Failed to read data: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = dev->model->cmd_set->end_scan (dev, dev->calib_reg, SANE_TRUE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
free (calibration_data);
|
|
DBG (DBG_error,
|
|
"genesys_dark_white_shading_calibration: Failed to end scan: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
if (DBG_LEVEL >= DBG_data)
|
|
sanei_genesys_write_pnm_file ("black_white_shading.pnm", calibration_data,
|
|
16, channels, pixels_per_line,
|
|
dev->calib_lines);
|
|
|
|
|
|
average_white = dev->white_average_data;
|
|
average_dark = dev->dark_average_data;
|
|
|
|
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 & 255;
|
|
*average_dark++ = dark_sum >> 8;
|
|
|
|
*average_white++ = white_sum & 255;
|
|
*average_white++ = white_sum >> 8;
|
|
}
|
|
|
|
if (DBG_LEVEL >= DBG_data)
|
|
{
|
|
sanei_genesys_write_pnm_file ("white_average.pnm",
|
|
dev->white_average_data, 16, channels,
|
|
pixels_per_line, 1);
|
|
sanei_genesys_write_pnm_file ("dark_average.pnm",
|
|
dev->dark_average_data, 16, channels,
|
|
pixels_per_line, 1);
|
|
}
|
|
|
|
free (calibration_data);
|
|
|
|
DBGCOMPLETED;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* 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
|
|
* @param deletion is true if scan is done with deletion set, else averaging
|
|
* is used
|
|
*/
|
|
#ifndef UNIT_TESTING
|
|
static
|
|
#endif
|
|
void
|
|
compute_averaged_planar (Genesys_Device * dev,
|
|
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,
|
|
SANE_Bool deletion)
|
|
{
|
|
unsigned int x, i, j, br, dk, res, avgpixels, val, loop;
|
|
|
|
DBG (DBG_info, "%s: pixels=%d, offset=%d\n", __FUNCTION__, pixels_per_line, o);
|
|
/* initialize result */
|
|
memset (shading_data, 0xff, words_per_color * 3 * 2);
|
|
|
|
/* duplicate half-ccd logic */
|
|
res = dev->settings.xres;
|
|
if ((dev->model->flags & GENESYS_FLAG_HALF_CCD_MODE)
|
|
&& dev->settings.xres <= dev->sensor.optical_res / 2)
|
|
{
|
|
/* scanner is using half-ccd mode */
|
|
res *= 2;
|
|
}
|
|
if (dev->settings.double_xres == SANE_TRUE)
|
|
{
|
|
/* scanner is using double x resolution */
|
|
res *= 2;
|
|
}
|
|
|
|
/* this should be evenly dividable */
|
|
avgpixels = dev->sensor.optical_res / res;
|
|
|
|
if(deletion==SANE_TRUE)
|
|
{
|
|
loop=1;
|
|
}
|
|
else
|
|
{
|
|
loop=avgpixels;
|
|
}
|
|
|
|
DBG (DBG_info, "%s: averaging over %d pixels\n", __FUNCTION__, avgpixels);
|
|
|
|
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 < loop; i++)
|
|
{
|
|
/* dark data */
|
|
dk += (dev->dark_average_data[(x + i + pixels_per_line * j) * 2]
|
|
| (dev-> dark_average_data[(x + i + pixels_per_line * j) * 2 + 1] << 8));
|
|
|
|
/* white data */
|
|
br += (dev->white_average_data[(x + i + pixels_per_line * j) * 2]
|
|
| (dev-> white_average_data[(x + i + pixels_per_line * j) * 2 + 1] << 8));
|
|
}
|
|
|
|
br /= i;
|
|
dk /= i;
|
|
|
|
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);
|
|
|
|
shading_data[(x/avgpixels) * 2 * 2 + words_per_color * 2 * j] = val & 0xff;
|
|
shading_data[(x/avgpixels) * 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;
|
|
|
|
shading_data[(x/avgpixels) * 2 * 2 + words_per_color * 2 * j + 2] = val & 0xff;
|
|
shading_data[(x/avgpixels) * 2 * 2 + words_per_color * 2 * j + 3] = val >> 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @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 offset shading coefficients left offset
|
|
* @param coeff 4000h or 2000h depending on fast scan mode or not
|
|
* @param target value of the target code
|
|
*/
|
|
#ifndef UNIT_TESTING
|
|
static
|
|
#endif
|
|
void
|
|
compute_coefficients (Genesys_Device * dev,
|
|
uint8_t * shading_data,
|
|
unsigned int pixels_per_line,
|
|
unsigned int channels,
|
|
unsigned int cmat[3],
|
|
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,
|
|
"compute_coefficients: pixels_per_line=%d, coeff=0x%04x\n", pixels_per_line, coeff);
|
|
|
|
/* 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 * 2 * channels + c * 2];
|
|
dk += 256 * dev->dark_average_data[x * 2 * channels + c * 2 + 1];
|
|
|
|
/* white data */
|
|
br = dev->white_average_data[x * 2 * channels + c * 2];
|
|
br += 256 * dev->white_average_data[x * 2 * channels + c * 2 + 1];
|
|
|
|
/* 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
|
|
* @shading_data memory area where to store the computed shading coefficients
|
|
* @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 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
|
|
*/
|
|
#ifndef UNIT_TESTING
|
|
static
|
|
#endif
|
|
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,
|
|
unsigned int cmat[3],
|
|
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;
|
|
|
|
DBG (DBG_io,
|
|
"compute_planar_coefficients: factor=%d, pixels_per_line=%d, words=0x%X, coeff=0x%04x\n", 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 +=
|
|
256 * dev->dark_average_data[((x+i) + pixels_per_line * c) * 2 + 1];
|
|
dk += dev->dark_average_data[((x+i) + pixels_per_line * c) * 2];
|
|
br +=
|
|
256 * dev->white_average_data[((x+i) + pixels_per_line * c) * 2 + 1];
|
|
br += dev->white_average_data[((x+i) + pixels_per_line * c) * 2];
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
#ifndef UNIT_TESTING
|
|
static
|
|
#endif
|
|
void
|
|
compute_shifted_coefficients (Genesys_Device * dev,
|
|
uint8_t * shading_data,
|
|
unsigned int pixels_per_line,
|
|
unsigned int channels,
|
|
unsigned int cmat[3],
|
|
int offset,
|
|
unsigned int coeff,
|
|
unsigned int target_dark,
|
|
unsigned int target_bright,
|
|
unsigned int patch_size) /* contigous extent */
|
|
{
|
|
unsigned int x, avgpixels, 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 */
|
|
|
|
x = dev->settings.xres;
|
|
if ((dev->model->flags & GENESYS_FLAG_HALF_CCD_MODE) &&
|
|
(dev->settings.xres <= dev->sensor.optical_res / 2))
|
|
x *= 2; /* scanner is using half-ccd mode */
|
|
avgpixels = dev->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 (avgpixels < 1)
|
|
avgpixels = 1;
|
|
else if (avgpixels < 6)
|
|
avgpixels = avgpixels;
|
|
else if (avgpixels < 8)
|
|
avgpixels = 6;
|
|
else if (avgpixels < 10)
|
|
avgpixels = 8;
|
|
else if (avgpixels < 12)
|
|
avgpixels = 10;
|
|
else if (avgpixels < 15)
|
|
avgpixels = 12;
|
|
else
|
|
avgpixels = 15;
|
|
DBG (DBG_info, "compute_shifted_coefficients: pixels_per_line=%d, coeff=0x%04x, averaging over %d pixels\n", 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) * 2] |
|
|
(dev->white_average_data[((x + i) * channels + j) * 2 + 1] << 8));
|
|
dk_tmp[i] += (dev->dark_average_data[((x + i) * channels + j) * 2] |
|
|
(dev->dark_average_data[((x + i) * channels + j) * 2 + 1] << 8));
|
|
}
|
|
}
|
|
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 SANE_Status
|
|
genesys_send_shading_coefficient (Genesys_Device * dev)
|
|
{
|
|
SANE_Status status;
|
|
uint32_t pixels_per_line;
|
|
uint8_t *shading_data; /**> contains 16bit words in little endian */
|
|
uint8_t channels;
|
|
int o;
|
|
unsigned int length; /**> number of shading calibration data words */
|
|
unsigned int x, j, i, res, factor;
|
|
unsigned int cmat[3]; /**> matrix of color channels */
|
|
unsigned int coeff, target_code, val, avgpixels, dk, words_per_color = 0;
|
|
unsigned int target_dark, target_bright, br;
|
|
|
|
DBGSTART;
|
|
|
|
|
|
pixels_per_line = dev->calib_pixels;
|
|
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 (sanei_genesys_read_reg_from_set (dev->reg, 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;
|
|
}
|
|
|
|
length = words_per_color * 3 * 2;
|
|
/* allocate computed size */
|
|
shading_data = malloc (length);
|
|
if (!shading_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_send_shading_coefficient: failed to allocate memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
memset (shading_data, 0, length);
|
|
|
|
/* 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->model->cmd_set->get_gain4_bit (dev->calib_reg))
|
|
coeff = 0x4000;
|
|
else
|
|
coeff = 0x2000;
|
|
|
|
/* compute avg factor */
|
|
if(dev->settings.xres>dev->sensor.optical_res)
|
|
{
|
|
factor=1;
|
|
}
|
|
else
|
|
{
|
|
factor=dev->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.
|
|
*/
|
|
|
|
/* at some point me may thought of a setup struct in genesys_devices that
|
|
* will handle these settings instead of having this switch growing up */
|
|
cmat[0] = 0;
|
|
cmat[1] = 1;
|
|
cmat[2] = 2;
|
|
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,
|
|
factor,
|
|
pixels_per_line,
|
|
words_per_color,
|
|
channels,
|
|
cmat,
|
|
o,
|
|
coeff,
|
|
target_code);
|
|
break;
|
|
case CIS_XP200:
|
|
target_code = 0xdc00;
|
|
o = 2;
|
|
cmat[0] = 2; /* red is last */
|
|
cmat[1] = 0; /* green is first */
|
|
cmat[2] = 1; /* blue is second */
|
|
compute_planar_coefficients (dev,
|
|
shading_data,
|
|
1,
|
|
pixels_per_line,
|
|
words_per_color,
|
|
channels,
|
|
cmat,
|
|
o,
|
|
coeff,
|
|
target_code);
|
|
break;
|
|
case CCD_HP2300:
|
|
target_code = 0xdc00;
|
|
o = 2;
|
|
if(dev->settings.xres<=dev->sensor.optical_res/2)
|
|
{
|
|
o = o - dev->sensor.dummy_pixel / 2;
|
|
}
|
|
compute_coefficients (dev,
|
|
shading_data,
|
|
pixels_per_line,
|
|
3,
|
|
cmat,
|
|
o,
|
|
coeff,
|
|
target_code);
|
|
break;
|
|
case CCD_5345:
|
|
target_code = 0xe000;
|
|
o = 4;
|
|
if(dev->settings.xres<=dev->sensor.optical_res/2)
|
|
{
|
|
o = o - dev->sensor.dummy_pixel;
|
|
}
|
|
compute_coefficients (dev,
|
|
shading_data,
|
|
pixels_per_line,
|
|
3,
|
|
cmat,
|
|
o,
|
|
coeff,
|
|
target_code);
|
|
break;
|
|
case CCD_HP3670:
|
|
case CCD_HP2400:
|
|
target_code = 0xe000;
|
|
/* offset is cksel dependent, but we can't use this in common code */
|
|
if(dev->settings.xres<=300)
|
|
{
|
|
o = -10; /* OK for <=300 */
|
|
}
|
|
else if(dev->settings.xres<=600)
|
|
{
|
|
o = -6; /* ok at 600 */
|
|
}
|
|
else
|
|
{
|
|
o = +2;
|
|
}
|
|
compute_coefficients (dev,
|
|
shading_data,
|
|
pixels_per_line,
|
|
3,
|
|
cmat,
|
|
o,
|
|
coeff,
|
|
target_code);
|
|
break;
|
|
case CCD_KVSS080:
|
|
case CCD_G4050:
|
|
target_code = 0xe000;
|
|
o = 0;
|
|
compute_coefficients (dev,
|
|
shading_data,
|
|
pixels_per_line,
|
|
3,
|
|
cmat,
|
|
o,
|
|
coeff,
|
|
target_code);
|
|
break;
|
|
case CIS_CANONLIDE700:
|
|
case CIS_CANONLIDE100:
|
|
case CIS_CANONLIDE200:
|
|
case CIS_CANONLIDE110:
|
|
words_per_color=pixels_per_line*2;
|
|
length = words_per_color * 3 * 2;
|
|
free(shading_data);
|
|
shading_data = malloc (length);
|
|
if (!shading_data)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_send_shading_coefficient: failed to allocate memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
memset (shading_data, 0, length);
|
|
compute_planar_coefficients (dev,
|
|
shading_data,
|
|
1,
|
|
pixels_per_line,
|
|
words_per_color,
|
|
channels,
|
|
cmat,
|
|
0,
|
|
coeff,
|
|
0xdc00);
|
|
break;
|
|
case CCD_CANONLIDE35:
|
|
target_bright = 0xfa00;
|
|
target_dark = 0xa00;
|
|
o = 4; /*first four pixels are ignored */
|
|
memset (shading_data, 0xff, length);
|
|
|
|
/*
|
|
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
|
|
*/
|
|
/* duplicate half-ccd logic */
|
|
res = dev->settings.xres;
|
|
if ((dev->model->flags & GENESYS_FLAG_HALF_CCD_MODE) &&
|
|
dev->settings.xres <= dev->sensor.optical_res / 2)
|
|
res *= 2; /* scanner is using half-ccd mode */
|
|
/*this should be evenly dividable */
|
|
avgpixels = dev->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 (avgpixels < 1)
|
|
avgpixels = 1;
|
|
else if (avgpixels < 6)
|
|
avgpixels = avgpixels;
|
|
else if (avgpixels < 8)
|
|
avgpixels = 6;
|
|
else if (avgpixels < 10)
|
|
avgpixels = 8;
|
|
else if (avgpixels < 12)
|
|
avgpixels = 10;
|
|
else if (avgpixels < 15)
|
|
avgpixels = 12;
|
|
else
|
|
avgpixels = 15;
|
|
|
|
DBG (DBG_info,
|
|
"genesys_send_shading_coefficient: averaging over %d pixels\n",
|
|
avgpixels);
|
|
|
|
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) *
|
|
2] |
|
|
(dev->dark_average_data
|
|
[(x + i + pixels_per_line * j) * 2 + 1] << 8));
|
|
|
|
/* white data */
|
|
br +=
|
|
(dev->white_average_data[(x + i +
|
|
pixels_per_line * j) *
|
|
2] |
|
|
(dev->white_average_data
|
|
[(x + i + pixels_per_line * j) * 2 + 1] << 8));
|
|
}
|
|
|
|
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 < avgpixels; i++)
|
|
{
|
|
shading_data[(x + o + i) * 2 * 2 +
|
|
words_per_color * 2 * j] = val & 0xff;
|
|
shading_data[(x + 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 < avgpixels; i++)
|
|
{
|
|
shading_data[(x + o + i) * 2 * 2 +
|
|
words_per_color * 2 * j + 2] = val & 0xff;
|
|
shading_data[(x + 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 < avgpixels; i++)
|
|
{
|
|
shading_data[(x + o + i) * 2 * 2 +
|
|
words_per_color * 2 * j] =
|
|
shading_data[(x + o + i) * 2 * 2 + words_per_color * 0];
|
|
shading_data[(x + o + i) * 2 * 2 +
|
|
words_per_color * 2 * j + 1] =
|
|
shading_data[(x + o + i) * 2 * 2 +
|
|
words_per_color * 2 * 0 + 1];
|
|
shading_data[(x + o + i) * 2 * 2 +
|
|
words_per_color * 2 * j + 2] =
|
|
shading_data[(x + o + i) * 2 * 2 +
|
|
words_per_color * 2 * 0 + 2];
|
|
shading_data[(x + o + i) * 2 * 2 +
|
|
words_per_color * 2 * j + 3] =
|
|
shading_data[(x + o + i) * 2 * 2 +
|
|
words_per_color * 2 * 0 + 3];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* creates a black line in image
|
|
for ( x = 65; x < 66; x++) {
|
|
for ( j = 0; j < 3; j++) {
|
|
shading_data[(x+o) * 2 * 2 + words_per_color * 2 * j + 0] = 0;
|
|
shading_data[(x+o) * 2 * 2 + words_per_color * 2 * j + 1] = 0;
|
|
shading_data[(x+o) * 2 * 2 + words_per_color * 2 * j + 2] = 0;
|
|
shading_data[(x+o) * 2 * 2 + words_per_color * 2 * j + 3] = 0;
|
|
}
|
|
}
|
|
*/
|
|
break;
|
|
case CCD_PLUSTEK_3600:
|
|
compute_shifted_coefficients (dev,
|
|
shading_data,
|
|
pixels_per_line,
|
|
channels,
|
|
cmat,
|
|
12, /* offset */
|
|
coeff,
|
|
0x0001, /* target_dark */
|
|
0xf900, /* target_bright */
|
|
256); /* patch_size: contigous extent */
|
|
break;
|
|
default:
|
|
DBG (DBG_error,
|
|
"genesys_send_shading_coefficient: sensor %d not supported\n",
|
|
dev->model->ccd_type);
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
break;
|
|
}
|
|
|
|
/* do the actual write of shading calibration data to the scanner */
|
|
status = genesys_send_offset_and_shading (dev, shading_data, length);
|
|
if (status != SANE_STATUS_GOOD)
|
|
DBG (DBG_error,
|
|
"genesys_send_shading_coefficient: failed to send shading data: %s\n",
|
|
sane_strstatus (status));
|
|
|
|
free (shading_data);
|
|
DBGCOMPLETED;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 SANE_STATUS_UNSUPPORTED if no matching cache entry has been
|
|
* found, SANE_STATUS_GOOD if one has been found and used.
|
|
*/
|
|
static SANE_Status
|
|
genesys_restore_calibration (Genesys_Device * dev)
|
|
{
|
|
SANE_Status status;
|
|
Genesys_Calibration_Cache *cache;
|
|
|
|
DBGSTART;
|
|
|
|
/* if no cache or no function to evaluate cache entry ther can be no match */
|
|
if (!dev->model->cmd_set->is_compatible_calibration
|
|
|| dev->calibration_cache == NULL)
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
|
|
/* we walk the link list of calibration cache in search for a
|
|
* matching one */
|
|
for (cache = dev->calibration_cache; cache; cache = cache->next)
|
|
{
|
|
status = dev->model->cmd_set->is_compatible_calibration (dev, cache,
|
|
SANE_FALSE);
|
|
/* SANE_STATUS_GOOD, a matching cache has been found
|
|
* so we use it to populate calibration data
|
|
*/
|
|
if (status == SANE_STATUS_GOOD)
|
|
{
|
|
memcpy (&dev->frontend, &cache->frontend, sizeof (dev->frontend));
|
|
/* we don't restore the gamma fields */
|
|
/* XXX STEF XXX
|
|
memcpy (&dev->sensor, &cache->sensor, offsetof (Genesys_Sensor, red_gamma));
|
|
*/
|
|
memcpy (dev->sensor.regs_0x10_0x1d, cache->sensor.regs_0x10_0x1d, 6);
|
|
free (dev->dark_average_data);
|
|
free (dev->white_average_data);
|
|
|
|
dev->average_size = cache->average_size;
|
|
dev->calib_pixels = cache->calib_pixels;
|
|
dev->calib_channels = cache->calib_channels;
|
|
|
|
dev->dark_average_data = (uint8_t *) malloc (cache->average_size);
|
|
dev->white_average_data = (uint8_t *) malloc (cache->average_size);
|
|
|
|
if (!dev->dark_average_data || !dev->white_average_data)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
memcpy (dev->dark_average_data,
|
|
cache->dark_average_data, dev->average_size);
|
|
memcpy (dev->white_average_data,
|
|
cache->white_average_data, dev->average_size);
|
|
|
|
|
|
if(dev->model->cmd_set->send_shading_data==NULL)
|
|
{
|
|
status = genesys_send_shading_coefficient (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_restore_calibration: failed to send shading calibration coefficients: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
DBG (DBG_proc, "genesys_restore_calibration: restored\n");
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* here status is either SANE_STATUS_UNSUPPORTED which mean tested cache
|
|
* entry doesn't match, or an fatal error */
|
|
if (status != SANE_STATUS_UNSUPPORTED)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_restore_calibration: fail while checking compatibility: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
DBG (DBG_proc, "genesys_restore_calibration: completed(nothing found)\n");
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
genesys_save_calibration (Genesys_Device * dev)
|
|
{
|
|
SANE_Status status = SANE_STATUS_UNSUPPORTED;
|
|
Genesys_Calibration_Cache *cache = NULL;
|
|
#ifdef HAVE_SYS_TIME_H
|
|
struct timeval time;
|
|
#endif
|
|
|
|
DBGSTART;
|
|
|
|
if (!dev->model->cmd_set->is_compatible_calibration)
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
|
|
if (dev->calibration_cache != NULL)
|
|
{
|
|
for (cache = dev->calibration_cache; cache; cache = cache->next)
|
|
{
|
|
status = dev->model->cmd_set->is_compatible_calibration (dev, cache, SANE_TRUE);
|
|
if (status == SANE_STATUS_UNSUPPORTED)
|
|
{
|
|
continue;
|
|
}
|
|
else if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_save_calibration: fail while checking compatibility: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if we found on overridable cache, we reuse it */
|
|
if (cache)
|
|
{
|
|
free(cache->dark_average_data);
|
|
free(cache->white_average_data);
|
|
}
|
|
else
|
|
{
|
|
/* create a new cache entry and insert it in the linked list */
|
|
cache = malloc (sizeof (Genesys_Calibration_Cache));
|
|
if (!cache)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
memset (cache, 0, sizeof (Genesys_Calibration_Cache));
|
|
|
|
cache->next = dev->calibration_cache;
|
|
dev->calibration_cache = cache;
|
|
}
|
|
|
|
cache->average_size = dev->average_size;
|
|
|
|
cache->dark_average_data = (uint8_t *) malloc (cache->average_size);
|
|
if (!cache->dark_average_data)
|
|
return SANE_STATUS_NO_MEM;
|
|
cache->white_average_data = (uint8_t *) malloc (cache->average_size);
|
|
if (!cache->white_average_data)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
memcpy (&cache->used_setup, &dev->current_setup, sizeof (cache->used_setup));
|
|
memcpy (&cache->frontend, &dev->frontend, sizeof (cache->frontend));
|
|
memcpy (&cache->sensor, &dev->sensor, sizeof (cache->sensor));
|
|
|
|
cache->calib_pixels = dev->calib_pixels;
|
|
cache->calib_channels = dev->calib_channels;
|
|
memcpy (cache->dark_average_data, dev->dark_average_data, cache->average_size);
|
|
memcpy (cache->white_average_data, dev->white_average_data, cache->average_size);
|
|
#ifdef HAVE_SYS_TIME_H
|
|
gettimeofday(&time,NULL);
|
|
cache->last_calibration = time.tv_sec;
|
|
#endif
|
|
|
|
DBGCOMPLETED;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/**
|
|
* does the calibration process for a flatbed scanner
|
|
* - offset calibration
|
|
* - gain calibration
|
|
* - shading calibration
|
|
* @param dev device to calibrate
|
|
* @return SANE_STATUS_GOOD if everything when all right, else the error code.
|
|
*/
|
|
static SANE_Status
|
|
genesys_flatbed_calibration (Genesys_Device * dev)
|
|
{
|
|
SANE_Status status;
|
|
uint32_t pixels_per_line;
|
|
int yres;
|
|
|
|
DBG (DBG_info, "genesys_flatbed_calibration\n");
|
|
|
|
yres = dev->sensor.optical_res;
|
|
if (dev->settings.yres <= dev->sensor.optical_res / 2)
|
|
yres /= 2;
|
|
|
|
/* do offset calibration if needed */
|
|
if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION)
|
|
{
|
|
status = dev->model->cmd_set->offset_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: offset calibration failed: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* since all the registers are set up correctly, just use them */
|
|
status = dev->model->cmd_set->coarse_gain_calibration (dev, yres);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: coarse gain calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
}
|
|
else
|
|
/* since we have 2 gain calibration proc, skip second if first one was
|
|
used. */
|
|
{
|
|
status = dev->model->cmd_set->init_regs_for_coarse_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: failed to send calibration registers: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = genesys_coarse_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: failed to do coarse gain calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
}
|
|
|
|
if (dev->model->is_cis)
|
|
{
|
|
/* the afe now sends valid data for doing led calibration */
|
|
status = dev->model->cmd_set->led_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: led calibration failed: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* calibrate afe again to match new exposure */
|
|
if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION)
|
|
{
|
|
status = dev->model->cmd_set->offset_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: offset calibration failed: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* since all the registers are set up correctly, just use them */
|
|
|
|
status = dev->model->cmd_set->coarse_gain_calibration (dev, yres);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: coarse gain calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
else
|
|
/* since we have 2 gain calibration proc, skip second if first one was
|
|
used. */
|
|
{
|
|
status =
|
|
dev->model->cmd_set->init_regs_for_coarse_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: failed to send calibration registers: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = genesys_coarse_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: failed to do static calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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 = dev->sensor.sensor_pixels;
|
|
}
|
|
|
|
/* send default shading data */
|
|
status = sanei_genesys_init_shading_data (dev, pixels_per_line);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: failed to init shading process: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* shading calibration */
|
|
status = dev->model->cmd_set->init_regs_for_shading (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error, "genesys_flatbed_calibration: failed to send shading "
|
|
"registers: %s\n", sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
if (dev->model->flags & GENESYS_FLAG_DARK_WHITE_CALIBRATION)
|
|
{
|
|
status = genesys_dark_white_shading_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: failed to do dark+white shading calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)
|
|
{
|
|
status = genesys_dark_shading_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: failed to do dark shading calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
status = genesys_white_shading_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: failed to do white shading calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
if(dev->model->cmd_set->send_shading_data==NULL)
|
|
{
|
|
status = genesys_send_shading_coefficient (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_flatbed_calibration: failed to send shading calibration coefficients: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
DBG (DBG_info, "genesys_flatbed_calibration: completed\n");
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @return SANE_STATUS_GOOD if everything when all right, else the error code.
|
|
*/
|
|
static SANE_Status
|
|
genesys_sheetfed_calibration (Genesys_Device * dev)
|
|
{
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
SANE_Bool forward = SANE_TRUE;
|
|
int xres;
|
|
|
|
DBGSTART;
|
|
if (dev->model->cmd_set->search_strip == NULL)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: no strip searching function available\n");
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
/* first step, load document */
|
|
status = dev->model->cmd_set->load_document (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed to load document: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
|
|
DBG (DBG_info, "genesys_sheetfed_calibration\n");
|
|
|
|
/* led, offset and gain calibration are influenced by scan
|
|
* settings. So we set it to sensor resolution */
|
|
xres = dev->sensor.optical_res;
|
|
dev->settings.xres = dev->sensor.optical_res;
|
|
/* XP200 needs to calibrate a full and half sensor's resolution */
|
|
if (dev->model->ccd_type == CIS_XP200
|
|
&& dev->settings.xres <= dev->sensor.optical_res / 2)
|
|
dev->settings.xres /= 2;
|
|
|
|
/* the afe needs to sends valid data even before calibration */
|
|
|
|
/* go to a white area */
|
|
status = dev->model->cmd_set->search_strip (dev, forward, SANE_FALSE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed to find white strip: %s\n",
|
|
sane_strstatus (status));
|
|
dev->model->cmd_set->eject_document (dev);
|
|
return status;
|
|
}
|
|
|
|
if (dev->model->is_cis)
|
|
{
|
|
status = dev->model->cmd_set->led_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: led calibration failed: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/* calibrate afe */
|
|
if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION)
|
|
{
|
|
status = dev->model->cmd_set->offset_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: offset calibration failed: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* since all the registers are set up correctly, just use them */
|
|
|
|
status = dev->model->cmd_set->coarse_gain_calibration (dev, xres);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: coarse gain calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
else
|
|
/* since we have 2 gain calibration proc, skip second if first one was
|
|
used. */
|
|
{
|
|
status =
|
|
dev->model->cmd_set->init_regs_for_coarse_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed to send calibration registers: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = genesys_coarse_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed to do static calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/* 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 */
|
|
status = dev->model->cmd_set->search_strip (dev, forward, SANE_TRUE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed to find black strip: %s\n",
|
|
sane_strstatus (status));
|
|
dev->model->cmd_set->eject_document (dev);
|
|
return status;
|
|
}
|
|
|
|
status = dev->model->cmd_set->init_regs_for_shading (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed to do set up registers for shading calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
status = genesys_dark_shading_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
dev->model->cmd_set->eject_document (dev);
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed to do dark shading calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
forward = SANE_FALSE;
|
|
}
|
|
|
|
|
|
/* go to a white area */
|
|
status = dev->model->cmd_set->search_strip (dev, forward, SANE_FALSE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed to find white strip: %s\n",
|
|
sane_strstatus (status));
|
|
dev->model->cmd_set->eject_document (dev);
|
|
return status;
|
|
}
|
|
|
|
status = dev->model->cmd_set->init_regs_for_shading (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed to do set up registers for shading calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
status = genesys_white_shading_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
dev->model->cmd_set->eject_document (dev);
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed eject target: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* in case we haven't black shading data, build it from black pixels
|
|
* of white calibration */
|
|
if (!(dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION))
|
|
{
|
|
FREE_IFNOT_NULL (dev->dark_average_data);
|
|
dev->dark_average_data = malloc (dev->average_size);
|
|
memset (dev->dark_average_data, 0x0f, dev->average_size);
|
|
/* 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
|
|
(dev->sensor.sensor_pixels * dev->settings.xres) / dev->sensor.optical_res,
|
|
dev->calib_lines,
|
|
*/
|
|
}
|
|
|
|
/* send the shading coefficient when doing whole line shading
|
|
* but not when using SHDAREA like GL124 */
|
|
if(dev->model->cmd_set->send_shading_data==NULL)
|
|
{
|
|
status = genesys_send_shading_coefficient (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed to send shading calibration coefficients: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/* save the calibration data */
|
|
genesys_save_calibration (dev);
|
|
|
|
/* and finally eject calibration sheet */
|
|
status = dev->model->cmd_set->eject_document (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_sheetfed_calibration: failed to eject document: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* resotre settings */
|
|
dev->settings.xres = xres;
|
|
DBGCOMPLETED;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/**
|
|
* does the calibration process for a device
|
|
* @param dev device to calibrate
|
|
*/
|
|
static SANE_Status
|
|
genesys_scanner_calibration (Genesys_Device * dev)
|
|
{
|
|
if (dev->model->is_sheetfed == SANE_FALSE)
|
|
{
|
|
return genesys_flatbed_calibration (dev);
|
|
}
|
|
return genesys_sheetfed_calibration (dev);
|
|
}
|
|
|
|
/* unused function kept in case it may be usefull in the futur */
|
|
#if 0
|
|
static SANE_Status
|
|
genesys_wait_not_moving (Genesys_Device * dev, int mseconds)
|
|
{
|
|
uint8_t value;
|
|
SANE_Status status;
|
|
|
|
DBG (DBG_proc,
|
|
"genesys_wait_not_moving: waiting %d mseconds for motor to stop\n",
|
|
mseconds);
|
|
while (mseconds > 0)
|
|
{
|
|
RIE (sanei_genesys_get_status (dev, &value));
|
|
|
|
if (dev->model->cmd_set->test_motor_flag_bit (value))
|
|
{
|
|
usleep (100 * 1000);
|
|
mseconds -= 100;
|
|
DBG (DBG_io,
|
|
"genesys_wait_not_moving: motor is moving, %d mseconds to go\n",
|
|
mseconds);
|
|
}
|
|
else
|
|
{
|
|
DBG (DBG_info,
|
|
"genesys_wait_not_moving: motor is not moving, exiting\n");
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
}
|
|
DBG (DBG_error,
|
|
"genesys_wait_not_moving: motor is still moving, timeout exceeded\n");
|
|
return SANE_STATUS_DEVICE_BUSY;
|
|
}
|
|
#endif
|
|
|
|
/* Function to build a lookup table (LUT), often
|
|
used by scanners to implement brightness/contrast/gamma
|
|
or by backends to speed binarization/thresholding
|
|
|
|
offset and slope inputs are -127 to +127
|
|
|
|
slope rotates line around central input/output val,
|
|
0 makes horizontal line
|
|
|
|
pos zero neg
|
|
. x . . x
|
|
. x . . x
|
|
out . x .xxxxxxxxxxx . x
|
|
. x . . x
|
|
....x....... ............ .......x....
|
|
in in in
|
|
|
|
offset moves line vertically, and clamps to output range
|
|
0 keeps the line crossing the center of the table
|
|
|
|
high low
|
|
. xxxxxxxx .
|
|
. x .
|
|
out x . x
|
|
. . x
|
|
............ xxxxxxxx....
|
|
in in
|
|
|
|
out_min/max provide bounds on output values,
|
|
useful when building thresholding lut.
|
|
0 and 255 are good defaults otherwise.
|
|
*/
|
|
static SANE_Status
|
|
load_lut (unsigned char * lut,
|
|
int in_bits, int out_bits,
|
|
int out_min, int out_max,
|
|
int slope, int offset)
|
|
{
|
|
SANE_Status ret = SANE_STATUS_GOOD;
|
|
int i, j;
|
|
double shift, rise;
|
|
int max_in_val = (1 << in_bits) - 1;
|
|
int max_out_val = (1 << out_bits) - 1;
|
|
unsigned char * lut_p = lut;
|
|
|
|
DBGSTART;
|
|
|
|
/* slope is converted to rise per unit run:
|
|
* first [-127,127] to [-1,1]
|
|
* then multiply by PI/2 to convert to radians
|
|
* then take the tangent (T.O.A)
|
|
* then multiply by the normal linear slope
|
|
* because the table may not be square, i.e. 1024x256*/
|
|
rise = tan((double)slope/127 * M_PI/2) * max_out_val / max_in_val;
|
|
|
|
/* line must stay vertically centered, so figure
|
|
* out vertical offset at central input value */
|
|
shift = (double)max_out_val/2 - (rise*max_in_val/2);
|
|
|
|
/* convert the user offset setting to scale of output
|
|
* first [-127,127] to [-1,1]
|
|
* then to [-max_out_val/2,max_out_val/2]*/
|
|
shift += (double)offset / 127 * max_out_val / 2;
|
|
|
|
for(i=0;i<=max_in_val;i++){
|
|
j = rise*i + shift;
|
|
|
|
if(j<out_min){
|
|
j=out_min;
|
|
}
|
|
else if(j>out_max){
|
|
j=out_max;
|
|
}
|
|
|
|
*lut_p=j;
|
|
lut_p++;
|
|
}
|
|
|
|
DBGCOMPLETED;
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/* 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 SANE_Status
|
|
genesys_warmup_lamp (Genesys_Device * dev)
|
|
{
|
|
uint8_t *first_line, *second_line;
|
|
int seconds = 0;
|
|
int pixel;
|
|
int channels, total_size;
|
|
double first_average = 0;
|
|
double second_average = 0;
|
|
int difference = 255;
|
|
int empty, lines = 3;
|
|
SANE_Status status = SANE_STATUS_IO_ERROR;
|
|
|
|
DBGSTART;
|
|
|
|
/* check if the current chipset implements warmup */
|
|
if(dev->model->cmd_set->init_regs_for_warmup==NULL)
|
|
{
|
|
DBG (DBG_error, "%s: init_regs_for_warmup not implemented\n", __FUNCTION__);
|
|
return status;
|
|
}
|
|
|
|
dev->model->cmd_set->init_regs_for_warmup (dev, dev->reg, &channels, &total_size);
|
|
first_line = malloc (total_size);
|
|
if (!first_line)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
second_line = malloc (total_size);
|
|
if (!second_line)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
do
|
|
{
|
|
DBG (DBG_info, "genesys_warmup_lamp: one more loop\n");
|
|
RIE (dev->model->cmd_set->begin_scan (dev, dev->reg, SANE_FALSE));
|
|
do
|
|
{
|
|
sanei_genesys_test_buffer_empty (dev, &empty);
|
|
}
|
|
while (empty);
|
|
|
|
status = sanei_genesys_read_data_from_scanner (dev, first_line, total_size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
RIE (sanei_genesys_read_data_from_scanner
|
|
(dev, first_line, total_size));
|
|
}
|
|
|
|
RIE (dev->model->cmd_set->end_scan (dev, dev->reg, SANE_TRUE));
|
|
|
|
sleep (1); /* sleep 1 s */
|
|
seconds++;
|
|
|
|
RIE (dev->model->cmd_set->begin_scan (dev, dev->reg, SANE_FALSE));
|
|
do
|
|
{
|
|
sanei_genesys_test_buffer_empty (dev, &empty);
|
|
usleep (100 * 1000);
|
|
}
|
|
while (empty);
|
|
RIE (sanei_genesys_read_data_from_scanner
|
|
(dev, second_line, total_size));
|
|
RIE (dev->model->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->model->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->model->cmd_set->get_bitset_bit (dev->reg))
|
|
{
|
|
first_average /= pixel;
|
|
second_average /= pixel;
|
|
difference = abs (first_average - second_average);
|
|
DBG (DBG_info,
|
|
"genesys_warmup_lamp: average = %.2f, diff = %.3f\n",
|
|
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 ("warmup1.pnm", first_line, 8,
|
|
channels,
|
|
total_size / (lines * channels),
|
|
lines);
|
|
sanei_genesys_write_pnm_file ("warmup2.pnm", second_line, 8,
|
|
channels,
|
|
total_size / (lines * channels),
|
|
lines);
|
|
}
|
|
DBG (DBG_info, "genesys_warmup_lamp: average 1 = %.2f, average 2 = %.2f\n", first_average, second_average);
|
|
/* if delta below 15/255 ~= 5.8%, lamp is considred warm enough */
|
|
if (abs (first_average - second_average) < 15
|
|
&& second_average > 55)
|
|
break;
|
|
}
|
|
|
|
/* sleep another second before next loop */
|
|
sleep (1);
|
|
seconds++;
|
|
}
|
|
while (seconds < WARMUP_TIME);
|
|
|
|
if (seconds >= WARMUP_TIME)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_warmup_lamp: warmup timed out after %d seconds. Lamp defective?\n",
|
|
seconds);
|
|
status = SANE_STATUS_IO_ERROR;
|
|
}
|
|
else
|
|
{
|
|
DBG (DBG_info,
|
|
"genesys_warmup_lamp: warmup succeeded after %d seconds\n",
|
|
seconds);
|
|
}
|
|
|
|
free (first_line);
|
|
free (second_line);
|
|
|
|
DBGCOMPLETED;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/* High-level start of scanning */
|
|
static SANE_Status
|
|
genesys_start_scan (Genesys_Device * dev, SANE_Bool lamp_off)
|
|
{
|
|
SANE_Status status;
|
|
unsigned int steps, expected;
|
|
SANE_Bool empty;
|
|
|
|
DBGSTART;
|
|
|
|
/* 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)
|
|
{
|
|
status = sanei_genesys_wait_for_home (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to wait for head to park: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/* disable power saving*/
|
|
status = dev->model->cmd_set->save_power (dev, SANE_FALSE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to disable power saving mode: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* 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 == SCAN_METHOD_FLATBED))
|
|
{
|
|
RIE (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))
|
|
{
|
|
status = dev->model->cmd_set->search_start_position (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to search start position: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
dev->parking = SANE_FALSE;
|
|
status = dev->model->cmd_set->slow_back_home (dev, SANE_TRUE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to move scanhead to "
|
|
"home position: %s\n", sane_strstatus (status));
|
|
return status;
|
|
}
|
|
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;
|
|
status = dev->model->cmd_set->slow_back_home (dev, SANE_TRUE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to move scanhead to "
|
|
"home position: %s\n", sane_strstatus (status));
|
|
return status;
|
|
}
|
|
dev->scanhead_position_in_steps = 0;
|
|
}
|
|
}
|
|
|
|
/* move to calibration area for transparency adapter */
|
|
if ((dev->settings.scan_method == SCAN_METHOD_TRANSPARENCY)
|
|
&& dev->model->cmd_set->move_to_ta != NULL)
|
|
{
|
|
status=dev->model->cmd_set->move_to_ta(dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to move to start of transparency adapter: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/* load document if needed (for sheetfed scanner for instance) */
|
|
if (dev->model->is_sheetfed == SANE_TRUE
|
|
&& dev->model->cmd_set->load_document != NULL)
|
|
{
|
|
status = dev->model->cmd_set->load_document (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to load document: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/* send custom or generic gamma tables depending on flag */
|
|
if (dev->model->flags & GENESYS_FLAG_CUSTOM_GAMMA)
|
|
{
|
|
/* use custom gamma table */
|
|
status = dev->model->cmd_set->send_gamma_table (dev, 0);
|
|
}
|
|
else
|
|
{
|
|
/* send default gamma table if no custom gamma */
|
|
status = dev->model->cmd_set->send_gamma_table (dev, 1);
|
|
}
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to init gamma table: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* try to use cached calibration first */
|
|
status = genesys_restore_calibration (dev);
|
|
if (status == SANE_STATUS_UNSUPPORTED)
|
|
{
|
|
/* 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)
|
|
{
|
|
status = genesys_scanner_calibration (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to do scanner calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
genesys_save_calibration (dev);
|
|
}
|
|
else
|
|
{
|
|
DBG (DBG_warn, "genesys_start_scan: no calibration done\n");
|
|
}
|
|
}
|
|
else if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to restore calibration: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* build look up table for dynamic lineart */
|
|
if(dev->settings.dynamic_lineart==SANE_TRUE)
|
|
{
|
|
status = load_lut(dev->lineart_lut, 8, 8, 50, 205,
|
|
dev->settings.threshold_curve,
|
|
dev->settings.threshold-127);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error, "genesys_start_scan: failed to build lut\n");
|
|
return status;
|
|
}
|
|
}
|
|
|
|
status = dev->model->cmd_set->init_regs_for_scan (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to do init registers for scan: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* no lamp during scan */
|
|
if(lamp_off == SANE_TRUE)
|
|
{
|
|
dev->model->cmd_set->set_lamp_power (dev, dev->reg, SANE_FALSE);
|
|
}
|
|
|
|
/* GL124 is using SHDAREA, so we have to wait for scan to be set up before
|
|
* sending shading data */
|
|
if( (dev->model->cmd_set->send_shading_data!=NULL)
|
|
&& !(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION))
|
|
{
|
|
status = genesys_send_shading_coefficient (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to send shading calibration coefficients: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/* now send registers for scan */
|
|
status =
|
|
dev->model->cmd_set->bulk_write_register (dev, dev->reg,
|
|
dev->model->
|
|
cmd_set->bulk_full_size ());
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to bulk write registers, status = %d\n",
|
|
status);
|
|
return status;
|
|
}
|
|
|
|
/* start effective scan */
|
|
status = dev->model->cmd_set->begin_scan (dev, dev->reg, SANE_TRUE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to begin scan: %s\n",
|
|
sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
/*do we really need this? the valid data check should be sufficent -- pierre*/
|
|
/* waits for head to reach scanning position */
|
|
expected = sanei_genesys_read_reg_from_set (dev->reg, 0x3d) * 65536
|
|
+ sanei_genesys_read_reg_from_set (dev->reg, 0x3e) * 256
|
|
+ sanei_genesys_read_reg_from_set (dev->reg, 0x3f);
|
|
do
|
|
{
|
|
/* wait 1/10th of second between each test to avoid
|
|
overloading USB and CPU */
|
|
usleep (100 * 1000);
|
|
status = sanei_genesys_read_feed_steps (dev, &steps);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: Failed to read feed steps: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
while (steps < expected);
|
|
|
|
/* wait for buffers to be filled */
|
|
do
|
|
{
|
|
RIE (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->model->cmd_set->get_fast_feed_bit (dev->reg))
|
|
usleep (1000 * 1000);
|
|
else
|
|
usleep (500 * 1000);
|
|
*/
|
|
/* 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
|
|
{
|
|
usleep (100 * 1000);
|
|
status = sanei_genesys_read_valid_words (dev, &steps);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_start_scan: failed to read valid words: %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
}
|
|
while (steps < 1);
|
|
}
|
|
|
|
DBGCOMPLETED;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* this is _not_ a ringbuffer.
|
|
if we need a block which does not fit at the end of our available data,
|
|
we move the available data to the beginning.
|
|
*/
|
|
|
|
SANE_Status
|
|
sanei_genesys_buffer_alloc (Genesys_Buffer * buf, size_t size)
|
|
{
|
|
buf->buffer = (SANE_Byte *) malloc (size);
|
|
if (!buf->buffer)
|
|
return SANE_STATUS_NO_MEM;
|
|
buf->avail = 0;
|
|
buf->pos = 0;
|
|
buf->size = size;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
SANE_Status
|
|
sanei_genesys_buffer_free (Genesys_Buffer * buf)
|
|
{
|
|
SANE_Byte *tmp = buf->buffer;
|
|
buf->avail = 0;
|
|
buf->size = 0;
|
|
buf->pos = 0;
|
|
buf->buffer = NULL;
|
|
if (tmp)
|
|
free (tmp);
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
SANE_Byte *
|
|
sanei_genesys_buffer_get_write_pos (Genesys_Buffer * buf, size_t size)
|
|
{
|
|
if (buf->avail + size > buf->size)
|
|
return NULL;
|
|
if (buf->pos + buf->avail + size > buf->size)
|
|
{
|
|
memmove (buf->buffer, buf->buffer + buf->pos, buf->avail);
|
|
buf->pos = 0;
|
|
}
|
|
return buf->buffer + buf->pos + buf->avail;
|
|
}
|
|
|
|
SANE_Byte *
|
|
sanei_genesys_buffer_get_read_pos (Genesys_Buffer * buf)
|
|
{
|
|
return buf->buffer + buf->pos;
|
|
}
|
|
|
|
SANE_Status
|
|
sanei_genesys_buffer_produce (Genesys_Buffer * buf, size_t size)
|
|
{
|
|
if (size > buf->size - buf->avail)
|
|
return SANE_STATUS_INVAL;
|
|
buf->avail += size;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
SANE_Status
|
|
sanei_genesys_buffer_consume (Genesys_Buffer * buf, size_t size)
|
|
{
|
|
if (size > buf->avail)
|
|
return SANE_STATUS_INVAL;
|
|
buf->avail -= size;
|
|
buf->pos += size;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
#include "genesys_conv.c"
|
|
|
|
static SANE_Status accurate_line_read(Genesys_Device * dev,
|
|
SANE_Byte *buffer,
|
|
size_t size)
|
|
{
|
|
SANE_Status status;
|
|
status = dev->model->cmd_set->bulk_read_data (dev, 0x45, buffer, size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"accurate_line_read: failed to read %lu bytes (%s)\n",
|
|
(u_long) size, sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
/* done reading */
|
|
dev->oe_buffer.avail = size;
|
|
dev->oe_buffer.pos = 0;
|
|
return status;
|
|
}
|
|
|
|
/** @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 SANE_Status
|
|
genesys_fill_line_interp_buffer (Genesys_Device * dev, uint8_t *work_buffer_dst, size_t size)
|
|
{
|
|
size_t count;
|
|
SANE_Status status;
|
|
|
|
/* fill buffer if needed */
|
|
if (dev->oe_buffer.avail == 0)
|
|
{
|
|
status = accurate_line_read(dev,dev->oe_buffer.buffer,dev->oe_buffer.size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"%s: failed to read %lu bytes (%s)\n", __FUNCTION__,
|
|
(u_long) dev->oe_buffer.size, sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
}
|
|
|
|
/* 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->current_setup.channels) % dev->line_interp)==0)
|
|
{
|
|
/* copy pixel when line matches */
|
|
work_buffer_dst[count] = dev->oe_buffer.buffer[dev->cur + dev->oe_buffer.pos];
|
|
count++;
|
|
}
|
|
|
|
/* always update pointer so we skip uncopied data */
|
|
dev->cur++;
|
|
|
|
/* go to next line if needed */
|
|
if (dev->cur == dev->len)
|
|
{
|
|
dev->oe_buffer.pos += dev->bpl;
|
|
dev->cur = 0;
|
|
dev->line_count++;
|
|
}
|
|
|
|
/* read a new buffer if needed */
|
|
if (dev->oe_buffer.pos >= dev->oe_buffer.avail)
|
|
{
|
|
status = accurate_line_read(dev,dev->oe_buffer.buffer,dev->oe_buffer.size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"%s: failed to read %lu bytes (%s)\n", __FUNCTION__,
|
|
(u_long) dev->oe_buffer.size, sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/** @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 SANE_Status
|
|
genesys_fill_segmented_buffer (Genesys_Device * dev, uint8_t *work_buffer_dst, size_t size)
|
|
{
|
|
size_t count;
|
|
SANE_Status status;
|
|
int depth,i,n,k;
|
|
|
|
depth = dev->settings.depth;
|
|
if (dev->settings.scan_mode == SCAN_MODE_LINEART && dev->settings.dynamic_lineart==SANE_FALSE)
|
|
depth = 1;
|
|
|
|
/* fill buffer if needed */
|
|
if (dev->oe_buffer.avail == 0)
|
|
{
|
|
status = accurate_line_read(dev,dev->oe_buffer.buffer,dev->oe_buffer.size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"%s: failed to read %lu bytes (%s)\n", __FUNCTION__,
|
|
(u_long) dev->oe_buffer.size, sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
}
|
|
|
|
/* 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(dev->settings.double_xres==SANE_TRUE)
|
|
{
|
|
/* copy only even pixel */
|
|
work_buffer_dst[count] = dev->oe_buffer.buffer[dev->cur + dev->oe_buffer.pos];
|
|
/* update counter and pointer */
|
|
count++;
|
|
dev->cur++;
|
|
}
|
|
else
|
|
{
|
|
if(depth==1)
|
|
{
|
|
while (dev->cur < dev->len && count < size)
|
|
{
|
|
for(n=0;n<dev->segnb;n++)
|
|
{
|
|
work_buffer_dst[count+n] = 0;
|
|
}
|
|
/* interleaving is a 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.buffer[dev->cur + dev->skip + dev->dist*dev->order[n] + dev->oe_buffer.pos])&(128>>i))
|
|
{
|
|
work_buffer_dst[k] |= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* update counter and pointer */
|
|
count += dev->segnb;
|
|
dev->cur++;
|
|
}
|
|
}
|
|
if(depth==8)
|
|
{
|
|
while (dev->cur < dev->len && count < size)
|
|
{
|
|
for(n=0;n<dev->segnb;n++)
|
|
{
|
|
work_buffer_dst[count+n] = dev->oe_buffer.buffer[dev->cur + dev->skip + dev->dist*dev->order[n] + dev->oe_buffer.pos];
|
|
}
|
|
/* update counter and pointer */
|
|
count += dev->segnb;
|
|
dev->cur++;
|
|
}
|
|
}
|
|
if(depth==16)
|
|
{
|
|
while (dev->cur < dev->len && count < size)
|
|
{
|
|
for(n=0;n<dev->segnb;n++)
|
|
{
|
|
work_buffer_dst[count+n*2] = dev->oe_buffer.buffer[dev->cur + dev->skip + dev->dist*dev->order[n] + dev->oe_buffer.pos];
|
|
work_buffer_dst[count+n*2+1] = dev->oe_buffer.buffer[dev->cur + dev->skip + dev->dist*dev->order[n] + dev->oe_buffer.pos+1];
|
|
}
|
|
/* update counter and pointer */
|
|
count += dev->segnb*2;
|
|
dev->cur+=2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* go to next line if needed */
|
|
if (dev->cur == dev->len)
|
|
{
|
|
dev->oe_buffer.pos += dev->bpl;
|
|
dev->cur = 0;
|
|
}
|
|
|
|
/* read a new buffer if needed */
|
|
if (dev->oe_buffer.pos >= dev->oe_buffer.avail)
|
|
{
|
|
status = accurate_line_read(dev,dev->oe_buffer.buffer,dev->oe_buffer.size);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"%s: failed to read %lu bytes (%s)\n", __FUNCTION__,
|
|
(u_long) dev->oe_buffer.size, sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static SANE_Status
|
|
genesys_fill_read_buffer (Genesys_Device * dev)
|
|
{
|
|
size_t size;
|
|
size_t space;
|
|
SANE_Status status;
|
|
uint8_t *work_buffer_dst;
|
|
|
|
DBGSTART;
|
|
|
|
/* for sheetfed scanner, we must check is document is shorter than
|
|
* the requested scan */
|
|
if (dev->model->is_sheetfed == SANE_TRUE)
|
|
{
|
|
status = dev->model->cmd_set->detect_document_end (dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
return status;
|
|
}
|
|
|
|
space = dev->read_buffer.size - dev->read_buffer.avail;
|
|
|
|
work_buffer_dst = sanei_genesys_buffer_get_write_pos (&(dev->read_buffer),
|
|
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 < size)
|
|
{
|
|
size = dev->read_bytes_left;
|
|
/*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 SANE_STATUS_GOOD;
|
|
|
|
DBG (DBG_io, "genesys_fill_read_buffer: reading %lu bytes\n",
|
|
(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 */
|
|
status = genesys_fill_line_interp_buffer (dev, work_buffer_dst, size);
|
|
}
|
|
else if (dev->segnb>1)
|
|
{
|
|
/* multi-segment sensors processing */
|
|
status = genesys_fill_segmented_buffer (dev, work_buffer_dst, size);
|
|
}
|
|
else /* regular case with no extra copy */
|
|
{
|
|
status = dev->model->cmd_set->bulk_read_data (dev, 0x45, work_buffer_dst, size);
|
|
}
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_fill_read_buffer: failed to read %lu bytes (%s)\n",
|
|
(u_long) size, sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
if (size > dev->read_bytes_left)
|
|
size = dev->read_bytes_left;
|
|
|
|
dev->read_bytes_left -= size;
|
|
|
|
RIE (sanei_genesys_buffer_produce (&(dev->read_buffer), size));
|
|
|
|
DBGCOMPLETED;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* 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.
|
|
*/
|
|
static SANE_Status
|
|
genesys_read_ordered_data (Genesys_Device * dev, SANE_Byte * destination,
|
|
size_t * len)
|
|
{
|
|
SANE_Status status;
|
|
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;
|
|
unsigned int needs_gray_lineart;
|
|
Genesys_Buffer *src_buffer;
|
|
Genesys_Buffer *dst_buffer;
|
|
|
|
DBGSTART;
|
|
if (dev->read_active != SANE_TRUE)
|
|
{
|
|
DBG (DBG_error, "genesys_read_ordered_data: read not active!\n");
|
|
*len = 0;
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
|
|
DBG (DBG_info, "genesys_read_ordered_data: dumping current_setup:\n"
|
|
"\tpixels: %d\n"
|
|
"\tlines: %d\n"
|
|
"\tdepth: %d\n"
|
|
"\tchannels: %d\n"
|
|
"\texposure_time: %d\n"
|
|
"\txres: %g\n"
|
|
"\tyres: %g\n"
|
|
"\thalf_ccd: %s\n"
|
|
"\tstagger: %d\n"
|
|
"\tmax_shift: %d\n",
|
|
dev->current_setup.pixels,
|
|
dev->current_setup.lines,
|
|
dev->current_setup.depth,
|
|
dev->current_setup.channels,
|
|
dev->current_setup.exposure_time,
|
|
dev->current_setup.xres,
|
|
dev->current_setup.yres,
|
|
dev->current_setup.half_ccd ? "yes" : "no",
|
|
dev->current_setup.stagger, dev->current_setup.max_shift);
|
|
|
|
/*prepare conversion*/
|
|
/* current settings */
|
|
channels = dev->current_setup.channels;
|
|
depth = dev->current_setup.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.pixels != src_pixels;
|
|
needs_reverse = depth == 1;
|
|
needs_gray_lineart = depth == 8 && dev->settings.scan_mode == 0;
|
|
|
|
DBG (DBG_info,
|
|
"genesys_read_ordered_data: using filters:%s%s%s%s%s\n",
|
|
needs_reorder ? " reorder" : "",
|
|
needs_ccd ? " ccd" : "",
|
|
needs_shrink ? " shrink" : "",
|
|
needs_reverse ? " reverse" : "",
|
|
needs_gray_lineart ? " gray_lineart" : "");
|
|
|
|
DBG (DBG_info,
|
|
"genesys_read_ordered_data: frontend requested %lu bytes\n",
|
|
(u_long) * len);
|
|
|
|
DBG (DBG_info,
|
|
"genesys_read_ordered_data: bytes_to_read=%lu, total_bytes_read=%lu\n",
|
|
(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)
|
|
{
|
|
DBG (DBG_proc,
|
|
"genesys_read_ordered_data: nothing more to scan: EOF\n");
|
|
*len = 0;
|
|
return SANE_STATUS_EOF;
|
|
}
|
|
|
|
DBG (DBG_info, "genesys_read_ordered_data: %lu lines left by output\n",
|
|
((dev->total_bytes_to_read - dev->total_bytes_read) * 8UL) /
|
|
(dev->settings.pixels * channels * depth));
|
|
DBG (DBG_info, "genesys_read_ordered_data: %lu lines left by input\n",
|
|
((dev->read_bytes_left + 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.
|
|
*/
|
|
|
|
status = genesys_fill_read_buffer (dev);
|
|
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_read_ordered_data: genesys_fill_read_buffer failed\n");
|
|
return status;
|
|
}
|
|
|
|
src_buffer = &(dev->read_buffer);
|
|
|
|
/* maybe reorder components/bytes */
|
|
if (needs_reorder)
|
|
{
|
|
/*not implemented for depth == 1.*/
|
|
if (depth == 1)
|
|
{
|
|
DBG (DBG_error, "Can't reorder single bit data\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
dst_buffer = &(dev->lines_buffer);
|
|
|
|
work_buffer_src = sanei_genesys_buffer_get_read_pos (src_buffer);
|
|
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 = sanei_genesys_buffer_get_write_pos (dst_buffer,
|
|
bytes);
|
|
|
|
DBG (DBG_info, "genesys_read_ordered_data: reordering %d lines\n",
|
|
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
|
|
status =
|
|
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 */
|
|
status = SANE_STATUS_GOOD;
|
|
break;
|
|
case 2: /* RGB, cis, 8 bit */
|
|
status =
|
|
genesys_reorder_components_cis_8 (work_buffer_src,
|
|
work_buffer_dst,
|
|
dst_lines, src_pixels);
|
|
break;
|
|
case 3: /* RGB, cis, 16 bit */
|
|
status =
|
|
genesys_reorder_components_cis_16 (work_buffer_src,
|
|
work_buffer_dst,
|
|
dst_lines, src_pixels);
|
|
break;
|
|
case 4: /* BGR, chunky, 8 bit */
|
|
status =
|
|
genesys_reorder_components_bgr_8 (work_buffer_src,
|
|
work_buffer_dst,
|
|
dst_lines, src_pixels);
|
|
break;
|
|
case 5: /* BGR, chunky, 16 bit */
|
|
status =
|
|
genesys_reorder_components_bgr_16 (work_buffer_src,
|
|
work_buffer_dst,
|
|
dst_lines, src_pixels);
|
|
break;
|
|
case 6: /* BGR, cis, 8 bit */
|
|
status =
|
|
genesys_reorder_components_cis_bgr_8 (work_buffer_src,
|
|
work_buffer_dst,
|
|
dst_lines,
|
|
src_pixels);
|
|
break;
|
|
case 7: /* BGR, cis, 16 bit */
|
|
status =
|
|
genesys_reorder_components_cis_bgr_16 (work_buffer_src,
|
|
work_buffer_dst,
|
|
dst_lines,
|
|
src_pixels);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef WORDS_BIGENDIAN
|
|
if (depth == 16)
|
|
{
|
|
status =
|
|
genesys_reorder_components_endian_16 (work_buffer_src,
|
|
work_buffer_dst,
|
|
dst_lines,
|
|
src_pixels, 1);
|
|
}
|
|
else
|
|
{
|
|
status = SANE_STATUS_GOOD;
|
|
}
|
|
#else /*!WORDS_BIGENDIAN */
|
|
status = SANE_STATUS_GOOD;
|
|
#endif /*WORDS_BIGENDIAN */
|
|
}
|
|
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_read_ordered_data: failed to convert byte ordering(%s)\n",
|
|
sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
RIE (sanei_genesys_buffer_produce (dst_buffer, bytes));
|
|
|
|
RIE (sanei_genesys_buffer_consume (src_buffer, bytes));
|
|
}
|
|
src_buffer = dst_buffer;
|
|
}
|
|
|
|
/* maybe reverse effects of ccd layout */
|
|
if (needs_ccd)
|
|
{
|
|
/*should not happen with depth == 1.*/
|
|
if (depth == 1)
|
|
{
|
|
DBG (DBG_error, "Can't reverse ccd for single bit data\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
dst_buffer = &(dev->shrink_buffer);
|
|
|
|
work_buffer_src = sanei_genesys_buffer_get_read_pos (src_buffer);
|
|
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 =
|
|
sanei_genesys_buffer_get_write_pos (dst_buffer, bytes);
|
|
|
|
DBG (DBG_info, "genesys_read_ordered_data: un-ccd-ing %d lines\n",
|
|
dst_lines);
|
|
|
|
if (dst_lines != 0)
|
|
{
|
|
|
|
if (depth == 8)
|
|
status = genesys_reverse_ccd_8 (work_buffer_src, work_buffer_dst,
|
|
dst_lines,
|
|
src_pixels * channels,
|
|
ccd_shift, shift_count);
|
|
else
|
|
status = genesys_reverse_ccd_16 (work_buffer_src, work_buffer_dst,
|
|
dst_lines,
|
|
src_pixels * channels,
|
|
ccd_shift, shift_count);
|
|
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_read_ordered_data: failed to reverse ccd effects(%s)\n",
|
|
sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
RIE (sanei_genesys_buffer_produce (dst_buffer, bytes));
|
|
|
|
RIE (sanei_genesys_buffer_consume (src_buffer, bytes));
|
|
}
|
|
src_buffer = dst_buffer;
|
|
}
|
|
|
|
/* maybe shrink(or enlarge) lines */
|
|
if (needs_shrink)
|
|
{
|
|
|
|
dst_buffer = &(dev->out_buffer);
|
|
|
|
work_buffer_src = sanei_genesys_buffer_get_read_pos (src_buffer);
|
|
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.pixels * channels * depth))
|
|
dst_lines = (bytes * 8) / (dev->settings.pixels * channels * depth);
|
|
|
|
bytes = (dst_lines * dev->settings.pixels * channels * depth) / 8;
|
|
|
|
work_buffer_dst =
|
|
sanei_genesys_buffer_get_write_pos (dst_buffer, bytes);
|
|
|
|
DBG (DBG_info, "genesys_read_ordered_data: shrinking %d lines\n",
|
|
dst_lines);
|
|
|
|
if (dst_lines != 0)
|
|
{
|
|
if (depth == 1)
|
|
status = genesys_shrink_lines_1 (work_buffer_src,
|
|
work_buffer_dst,
|
|
dst_lines,
|
|
src_pixels,
|
|
dev->settings.pixels,
|
|
channels);
|
|
else if (depth == 8)
|
|
status = genesys_shrink_lines_8 (work_buffer_src,
|
|
work_buffer_dst,
|
|
dst_lines,
|
|
src_pixels,
|
|
dev->settings.pixels, channels);
|
|
else
|
|
status = genesys_shrink_lines_16 (work_buffer_src,
|
|
work_buffer_dst,
|
|
dst_lines,
|
|
src_pixels,
|
|
dev->settings.pixels, channels);
|
|
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_read_ordered_data: failed to shrink lines(%s)\n",
|
|
sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
/*we just consumed this many bytes*/
|
|
bytes = (dst_lines * src_pixels * channels * depth) / 8;
|
|
RIE (sanei_genesys_buffer_consume (src_buffer, bytes));
|
|
|
|
/*we just created this many bytes*/
|
|
bytes = (dst_lines * dev->settings.pixels * channels * depth) / 8;
|
|
RIE (sanei_genesys_buffer_produce (dst_buffer, bytes));
|
|
|
|
}
|
|
src_buffer = dst_buffer;
|
|
}
|
|
|
|
/* move data to destination */
|
|
|
|
bytes = src_buffer->avail;
|
|
if (bytes > *len)
|
|
bytes = *len;
|
|
work_buffer_src = sanei_genesys_buffer_get_read_pos (src_buffer);
|
|
|
|
if (needs_reverse)
|
|
{
|
|
status = genesys_reverse_bits (work_buffer_src, destination, bytes);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_read_ordered_data: failed to reverse bits(%s)\n",
|
|
sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
*len = bytes;
|
|
}
|
|
else if (needs_gray_lineart)
|
|
{
|
|
if (depth != 8)
|
|
{
|
|
DBG (DBG_error, "Cannot convert from 16bit to lineart\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
/* lines in input to process */
|
|
dst_lines = bytes / (dev->settings.pixels * channels);
|
|
if(dst_lines==0)
|
|
{
|
|
/* padd to at least line length */
|
|
dst_lines=1;
|
|
}
|
|
bytes = dst_lines * dev->settings.pixels * channels;
|
|
|
|
status = genesys_gray_lineart (dev,
|
|
work_buffer_src,
|
|
destination,
|
|
dev->settings.pixels,
|
|
dst_lines,
|
|
dev->settings.threshold);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"genesys_read_ordered_data: failed to convert bits(%s)\n",
|
|
sane_strstatus (status));
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
*len = dst_lines * channels *
|
|
(dev->settings.pixels / 8 + ((dev->settings.pixels % 8) ? 1 : 0));
|
|
}
|
|
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;
|
|
|
|
RIE (sanei_genesys_buffer_consume (src_buffer, bytes));
|
|
|
|
/* end scan if all needed data have been read */
|
|
/* TODO extend this to other ASICs */
|
|
if(((dev->model->asic_type == GENESYS_GL847)
|
|
||(dev->model->asic_type == GENESYS_GL124))
|
|
&&(dev->total_bytes_read >= dev->total_bytes_to_read))
|
|
{
|
|
dev->model->cmd_set->end_scan (dev, dev->reg, SANE_TRUE);
|
|
}
|
|
|
|
DBG (DBG_proc, "genesys_read_ordered_data: completed, %lu bytes read\n",
|
|
(u_long) bytes);
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/* 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 SANE_Status
|
|
calc_parameters (Genesys_Scanner * s)
|
|
{
|
|
SANE_String mode, source, color_filter;
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
SANE_Int depth = 0, resolution = 0;
|
|
double tl_x = 0, tl_y = 0, br_x = 0, br_y = 0;
|
|
|
|
mode = s->val[OPT_MODE].s;
|
|
source = s->val[OPT_SOURCE].s;
|
|
color_filter = s->val[OPT_COLOR_FILTER].s;
|
|
depth = s->val[OPT_BIT_DEPTH].w;
|
|
resolution = s->val[OPT_RESOLUTION].w;
|
|
tl_x = SANE_UNFIX (s->val[OPT_TL_X].w);
|
|
tl_y = SANE_UNFIX (s->val[OPT_TL_Y].w);
|
|
br_x = SANE_UNFIX (s->val[OPT_BR_X].w);
|
|
br_y = SANE_UNFIX (s->val[OPT_BR_Y].w);
|
|
|
|
s->params.last_frame = SANE_TRUE; /* only single pass scanning supported */
|
|
|
|
if (strcmp (mode, SANE_VALUE_SCAN_MODE_GRAY) == 0
|
|
|| strcmp (mode, SANE_VALUE_SCAN_MODE_LINEART) == 0)
|
|
s->params.format = SANE_FRAME_GRAY;
|
|
else /* Color */
|
|
s->params.format = SANE_FRAME_RGB;
|
|
|
|
if (strcmp (mode, SANE_VALUE_SCAN_MODE_LINEART) == 0)
|
|
s->params.depth = 1;
|
|
else
|
|
s->params.depth = depth;
|
|
s->dev->settings.depth = depth;
|
|
|
|
/* interpolation */
|
|
s->dev->settings.disable_interpolation =
|
|
s->val[OPT_DISABLE_INTERPOLATION].w == SANE_TRUE;
|
|
|
|
/* hardware settings */
|
|
if (resolution > s->dev->sensor.optical_res &&
|
|
s->dev->settings.disable_interpolation)
|
|
s->dev->settings.xres = s->dev->sensor.optical_res;
|
|
else
|
|
s->dev->settings.xres = resolution;
|
|
s->dev->settings.yres = resolution;
|
|
|
|
s->params.lines = ((br_y - tl_y) * s->dev->settings.yres) / MM_PER_INCH;
|
|
s->params.pixels_per_line =
|
|
((br_x - tl_x) * resolution) / MM_PER_INCH;
|
|
|
|
/* we need an even number of pixels for even/odd handling */
|
|
if (s->dev->model->flags & GENESYS_FLAG_SIS_SENSOR
|
|
|| s->dev->model->asic_type == GENESYS_GL847
|
|
|| s->dev->model->asic_type == GENESYS_GL124
|
|
|| s->dev->model->asic_type == GENESYS_GL843)
|
|
{
|
|
if (s->dev->settings.xres <= 1200)
|
|
s->params.pixels_per_line = (s->params.pixels_per_line/4)*4;
|
|
else
|
|
s->params.pixels_per_line = (s->params.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 == GENESYS_GL124
|
|
|| s->dev->current_setup.xres < s->dev->current_setup.yres
|
|
)
|
|
)
|
|
{
|
|
s->params.pixels_per_line = (s->params.pixels_per_line/16)*16;
|
|
}
|
|
|
|
s->params.bytes_per_line = s->params.pixels_per_line;
|
|
if (s->params.depth > 8)
|
|
{
|
|
s->params.depth = 16;
|
|
s->params.bytes_per_line *= 2;
|
|
}
|
|
else if (s->params.depth == 1)
|
|
{
|
|
s->params.bytes_per_line /= 8;
|
|
/* round down pixel number
|
|
really? rounding down means loss of at most 7 pixels! -- pierre */
|
|
s->params.pixels_per_line = 8 * s->params.bytes_per_line;
|
|
}
|
|
|
|
if (s->params.format == SANE_FRAME_RGB)
|
|
s->params.bytes_per_line *= 3;
|
|
|
|
if (strcmp (mode, SANE_VALUE_SCAN_MODE_COLOR) == 0)
|
|
s->dev->settings.scan_mode = SCAN_MODE_COLOR;
|
|
else if (strcmp (mode, SANE_VALUE_SCAN_MODE_GRAY) == 0)
|
|
s->dev->settings.scan_mode = SCAN_MODE_GRAY;
|
|
else if (strcmp (mode, SANE_TITLE_HALFTONE) == 0)
|
|
s->dev->settings.scan_mode = SCAN_MODE_HALFTONE;
|
|
else /* Lineart */
|
|
s->dev->settings.scan_mode = SCAN_MODE_LINEART;
|
|
|
|
/* TODO: change and check */
|
|
if (strcmp (source, FLATBED) == 0)
|
|
s->dev->settings.scan_method = SCAN_METHOD_FLATBED;
|
|
else /* transparency */
|
|
s->dev->settings.scan_method = SCAN_METHOD_TRANSPARENCY;
|
|
|
|
s->dev->settings.lines = s->params.lines;
|
|
s->dev->settings.pixels = s->params.pixels_per_line;
|
|
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->val[OPT_THRESHOLD].w));
|
|
|
|
/* color filter */
|
|
if (strcmp (color_filter, "Red") == 0)
|
|
s->dev->settings.color_filter = 0;
|
|
else if (strcmp (color_filter, "Green") == 0)
|
|
s->dev->settings.color_filter = 1;
|
|
else if (strcmp (color_filter, "Blue") == 0)
|
|
s->dev->settings.color_filter = 2;
|
|
else
|
|
s->dev->settings.color_filter = 3;
|
|
|
|
/* true gray */
|
|
if (strcmp (color_filter, "None") == 0)
|
|
s->dev->settings.true_gray = 1;
|
|
else
|
|
s->dev->settings.true_gray = 0;
|
|
|
|
/* dynamic lineart */
|
|
s->dev->settings.dynamic_lineart =
|
|
s->val[OPT_DISABLE_DYNAMIC_LINEART].w == SANE_FALSE;
|
|
|
|
/* threshold curve for dynamic ratserization */
|
|
if(s->dev->settings.dynamic_lineart==SANE_TRUE)
|
|
s->dev->settings.threshold_curve=s->val[OPT_THRESHOLD_CURVE].w;
|
|
else
|
|
s->dev->settings.threshold_curve=0;
|
|
|
|
/* 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->val[OPT_SWDESPECK].b
|
|
|| s->val[OPT_SWCROP].b
|
|
|| s->val[OPT_SWDESKEW].b
|
|
|| s->val[OPT_SWDEROTATE].b
|
|
||(SANE_UNFIX(s->val[OPT_SWSKIP].w)>0))
|
|
&& (!s->val[OPT_PREVIEW].b)
|
|
&& (s->val[OPT_BIT_DEPTH].w <= 8))
|
|
{
|
|
s->dev->buffer_image=SANE_TRUE;
|
|
}
|
|
else
|
|
{
|
|
s->dev->buffer_image=SANE_FALSE;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
create_bpp_list (Genesys_Scanner * s, SANE_Int * bpp)
|
|
{
|
|
int count;
|
|
|
|
for (count = 0; bpp[count] != 0; count++)
|
|
;
|
|
s->bpp_list[0] = count;
|
|
for (count = 0; bpp[count] != 0; count++)
|
|
{
|
|
s->bpp_list[s->bpp_list[0] - count] = bpp[count];
|
|
}
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* this function initialize a gamma vector based on the ASIC:
|
|
* gl646: 12 or 14 bits gamma table depending on GENESYS_FLAG_14BIT_GAMMA
|
|
* gl84x: 16 bits
|
|
*/
|
|
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 == GENESYS_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
|
|
{ /* GL841 case 16 bits gamma table */
|
|
scanner->opt[option].size = 256 * sizeof (SANE_Word);
|
|
scanner->opt[option].constraint.range = &u16_range;
|
|
}
|
|
/* default value is NULL */
|
|
scanner->val[option].wa = NULL;
|
|
}
|
|
|
|
/**
|
|
* allocate a geometry range
|
|
* @param size maximum size of the range
|
|
* @return a poiter 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;
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
init_options (Genesys_Scanner * s)
|
|
{
|
|
SANE_Int option, count, min_dpi;
|
|
SANE_Status status;
|
|
SANE_Word *dpi_list;
|
|
Genesys_Model *model = s->dev->model;
|
|
SANE_Bool has_ta;
|
|
SANE_Range *x_range, *y_range;
|
|
|
|
DBGSTART;
|
|
|
|
/* no transparency adaptor support yet */
|
|
has_ta = SANE_FALSE;
|
|
|
|
memset (s->opt, 0, sizeof (s->opt));
|
|
memset (s->val, 0, sizeof (s->val));
|
|
|
|
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;
|
|
s->val[OPT_NUM_OPTS].w = NUM_OPTIONS;
|
|
|
|
/* "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->val[OPT_MODE].s = strdup (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->val[OPT_SOURCE].s = strdup (FLATBED);
|
|
if (!(model->flags & GENESYS_FLAG_HAS_UTA))
|
|
{
|
|
DISABLE (OPT_SOURCE);
|
|
}
|
|
else
|
|
{
|
|
ENABLE (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->val[OPT_PREVIEW].w = SANE_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->val[OPT_BIT_DEPTH].w = 8;
|
|
if (s->opt[OPT_BIT_DEPTH].constraint.word_list[0] < 2)
|
|
DISABLE (OPT_BIT_DEPTH);
|
|
|
|
/* resolution */
|
|
min_dpi=200000;
|
|
for (count = 0; model->xdpi_values[count] != 0; count++)
|
|
{
|
|
if(model->xdpi_values[count]<min_dpi)
|
|
{
|
|
min_dpi=model->xdpi_values[count];
|
|
}
|
|
}
|
|
dpi_list = malloc ((count + 1) * sizeof (SANE_Word));
|
|
if (!dpi_list)
|
|
return SANE_STATUS_NO_MEM;
|
|
dpi_list[0] = count;
|
|
for (count = 0; model->xdpi_values[count] != 0; count++)
|
|
dpi_list[count + 1] = model->xdpi_values[count];
|
|
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->val[OPT_RESOLUTION].w = 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)
|
|
{
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
y_range=create_range(model->y_size);
|
|
if(y_range==NULL)
|
|
{
|
|
return 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->val[OPT_TL_X].w = 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->val[OPT_TL_Y].w = 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->val[OPT_BR_X].w = 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->val[OPT_BR_Y].w = 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->val[OPT_CUSTOM_GAMMA].b = SANE_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, "init_options: custom gamma disabled\n");
|
|
}
|
|
|
|
/* 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->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
|
|
s->val[OPT_SWDESKEW].b = SANE_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->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
|
|
s->val[OPT_SWDESPECK].b = SANE_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;
|
|
s->val[OPT_DESPECK].w = 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->val[OPT_SWCROP].b = SANE_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);
|
|
/* disable by default */
|
|
s->val[OPT_SWSKIP].w = 0;
|
|
|
|
/* 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_SWCROP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
|
|
s->opt[OPT_SWDEROTATE].unit = SANE_UNIT_NONE;
|
|
s->val[OPT_SWDEROTATE].b = SANE_FALSE;
|
|
|
|
/* "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->val[OPT_THRESHOLD].w = 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->val[OPT_THRESHOLD_CURVE].w = 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->val[OPT_DISABLE_DYNAMIC_LINEART].w = SANE_FALSE;
|
|
|
|
/* not working for GL646 scanners yet, and required for GL847 ones */
|
|
if (s->dev->model->asic_type == GENESYS_GL646 || s->dev->model->asic_type == GENESYS_GL847)
|
|
{
|
|
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->val[OPT_DISABLE_INTERPOLATION].w = SANE_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 scanners */
|
|
if(!model->is_cis || model->asic_type==GENESYS_GL847)
|
|
{
|
|
s->opt[OPT_COLOR_FILTER].size = max_string_size (color_filter_list);
|
|
s->opt[OPT_COLOR_FILTER].constraint.string_list = color_filter_list;
|
|
s->val[OPT_COLOR_FILTER].s = strdup (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->val[OPT_COLOR_FILTER].s = strdup (s->opt[OPT_COLOR_FILTER].constraint.string_list[3]);
|
|
}
|
|
|
|
/* no support for color filter for cis+gl646 scanners */
|
|
if (model->asic_type == GENESYS_GL646 && model->is_cis)
|
|
{
|
|
DISABLE (OPT_COLOR_FILTER);
|
|
}
|
|
|
|
/* 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->val[OPT_LAMP_OFF_TIME].w = 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->val[OPT_LAMP_OFF].w = SANE_FALSE;
|
|
|
|
s->opt[OPT_SENSOR_GROUP].name = SANE_NAME_SENSORS;
|
|
s->opt[OPT_SENSOR_GROUP].title = SANE_TITLE_SENSORS;
|
|
s->opt[OPT_SENSOR_GROUP].desc = SANE_DESC_SENSORS;
|
|
s->opt[OPT_SENSOR_GROUP].type = SANE_TYPE_GROUP;
|
|
s->opt[OPT_SENSOR_GROUP].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;
|
|
s->val[OPT_SCAN_SW].b = 0;
|
|
s->last_val[OPT_SCAN_SW].b = 0;
|
|
|
|
/* 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->val[OPT_FILE_SW].b = 0;
|
|
s->last_val[OPT_FILE_SW].b = 0;
|
|
|
|
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->val[OPT_EMAIL_SW].b = 0;
|
|
s->last_val[OPT_EMAIL_SW].b = 0;
|
|
|
|
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->val[OPT_COPY_SW].b = 0;
|
|
s->last_val[OPT_COPY_SW].b = 0;
|
|
|
|
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;
|
|
s->val[OPT_PAGE_LOADED_SW].b = 0;
|
|
s->last_val[OPT_PAGE_LOADED_SW].b = 0;
|
|
|
|
/* 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;
|
|
s->val[OPT_OCR_SW].b = 0;
|
|
s->last_val[OPT_OCR_SW].b = 0;
|
|
|
|
/* 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;
|
|
s->val[OPT_POWER_SW].b = 0;
|
|
s->last_val[OPT_POWER_SW].b = 0;
|
|
|
|
/* calibration needed */
|
|
s->opt[OPT_NEED_CALIBRATION_SW].name = "need-calibration";
|
|
s->opt[OPT_NEED_CALIBRATION_SW].title = SANE_I18N ("Need 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;
|
|
s->val[OPT_NEED_CALIBRATION_SW].b = 0;
|
|
s->last_val[OPT_NEED_CALIBRATION_SW].b = 0;
|
|
|
|
/* button group */
|
|
s->opt[OPT_BUTTON_GROUP].name = "Buttons";
|
|
s->opt[OPT_BUTTON_GROUP].title = SANE_I18N ("Buttons");
|
|
s->opt[OPT_BUTTON_GROUP].desc = "";
|
|
s->opt[OPT_BUTTON_GROUP].type = SANE_TYPE_GROUP;
|
|
s->opt[OPT_BUTTON_GROUP].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;
|
|
s->val[OPT_CALIBRATE].b = 0;
|
|
s->last_val[OPT_CALIBRATE].b = 0;
|
|
|
|
/* 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].cap =
|
|
SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED |
|
|
SANE_CAP_AUTOMATIC;
|
|
s->val[OPT_CLEAR_CALIBRATION].b = 0;
|
|
s->last_val[OPT_CLEAR_CALIBRATION].b = 0;
|
|
|
|
RIE (calc_parameters (s));
|
|
|
|
DBGCOMPLETED;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
static SANE_Bool present;
|
|
static SANE_Status
|
|
check_present (SANE_String_Const devname)
|
|
{
|
|
present=SANE_TRUE;
|
|
DBG (DBG_io, "check_present: %s detected.\n",devname);
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
static SANE_Status
|
|
attach (SANE_String_Const devname, Genesys_Device ** devp, SANE_Bool may_wait)
|
|
{
|
|
Genesys_Device *dev = 0;
|
|
SANE_Int dn, vendor, product;
|
|
SANE_Status status;
|
|
int i;
|
|
|
|
|
|
DBG (DBG_proc, "attach: start: devp %s NULL, may_wait = %d\n",
|
|
devp ? "!=" : "==", may_wait);
|
|
|
|
if (devp)
|
|
*devp = 0;
|
|
|
|
if (!devname)
|
|
{
|
|
DBG (DBG_error, "attach: devname == NULL\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
for (dev = first_dev; dev; dev = dev->next)
|
|
{
|
|
if (strcmp (dev->file_name, devname) == 0)
|
|
{
|
|
if (devp)
|
|
*devp = dev;
|
|
DBG (DBG_info, "attach: device `%s' was already in device list\n",
|
|
devname);
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
}
|
|
|
|
DBG (DBG_info, "attach: trying to open device `%s'\n", devname);
|
|
|
|
status = sanei_usb_open (devname, &dn);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_warn, "attach: couldn't open device `%s': %s\n", devname,
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
else
|
|
DBG (DBG_info, "attach: device `%s' successfully opened\n", devname);
|
|
|
|
status = sanei_usb_get_vendor_product (dn, &vendor, &product);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"attach: couldn't get vendor and product ids of device `%s': %s\n",
|
|
devname, sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
DBG (DBG_error,"attach: master device not present\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < MAX_SCANNERS && genesys_usb_device_list[i].model != 0; i++)
|
|
{
|
|
if (vendor == genesys_usb_device_list[i].vendor &&
|
|
product == genesys_usb_device_list[i].product)
|
|
{
|
|
dev = malloc (sizeof (*dev));
|
|
if (!dev)
|
|
return SANE_STATUS_NO_MEM;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!dev)
|
|
{
|
|
DBG (DBG_error,
|
|
"attach: vendor %d product %d is not supported by this backend\n",
|
|
vendor, product);
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
dev->file_name = strdup (devname);
|
|
if (!dev)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
dev->model = genesys_usb_device_list[i].model;
|
|
dev->vendorId = genesys_usb_device_list[i].vendor;
|
|
dev->productId = genesys_usb_device_list[i].product;
|
|
dev->already_initialized = SANE_FALSE;
|
|
|
|
DBG (DBG_info, "attach: found %s flatbed scanner %s at %s\n",
|
|
dev->model->vendor, dev->model->model, dev->file_name);
|
|
++num_devices;
|
|
dev->next = first_dev;
|
|
first_dev = dev;
|
|
|
|
if (devp)
|
|
*devp = dev;
|
|
sanei_usb_close (dn);
|
|
DBGCOMPLETED;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
static SANE_Status
|
|
attach_one_device (SANE_String_Const devname)
|
|
{
|
|
Genesys_Device *dev;
|
|
SANE_Status status;
|
|
|
|
RIE (attach (devname, &dev, SANE_FALSE));
|
|
|
|
if (dev)
|
|
{
|
|
/* Keep track of newly attached devices so we can set options as
|
|
necessary. */
|
|
if (new_dev_len >= new_dev_alloced)
|
|
{
|
|
new_dev_alloced += 4;
|
|
if (new_dev)
|
|
new_dev =
|
|
realloc (new_dev, new_dev_alloced * sizeof (new_dev[0]));
|
|
else
|
|
new_dev = malloc (new_dev_alloced * sizeof (new_dev[0]));
|
|
if (!new_dev)
|
|
{
|
|
DBG (DBG_error, "attach_one_device: out of memory\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
}
|
|
new_dev[new_dev_len++] = dev;
|
|
}
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* configuration framework functions */
|
|
static SANE_Status
|
|
config_attach_genesys (SANEI_Config * config, const char *devname)
|
|
{
|
|
/* no options yet for this backend */
|
|
config = config;
|
|
|
|
/* 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 */
|
|
#ifndef UNIT_TESTING
|
|
static
|
|
#endif
|
|
SANE_Status
|
|
probe_genesys_devices (void)
|
|
{
|
|
SANEI_Config config;
|
|
SANE_Status status;
|
|
|
|
DBGSTART;
|
|
|
|
new_dev = 0;
|
|
new_dev_len = 0;
|
|
new_dev_alloced = 0;
|
|
|
|
/* 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);
|
|
|
|
if (new_dev_alloced > 0)
|
|
{
|
|
new_dev_len = new_dev_alloced = 0;
|
|
free (new_dev);
|
|
}
|
|
|
|
DBGCOMPLETED;
|
|
|
|
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.
|
|
*/
|
|
#define CALIBRATION_VERSION 1
|
|
|
|
/**
|
|
* reads previously cached calibration data
|
|
* from file
|
|
*/
|
|
SANE_Status
|
|
sanei_genesys_read_calibration (Genesys_Device * dev)
|
|
{
|
|
FILE *fp;
|
|
uint8_t vers = 0;
|
|
uint32_t size = 0;
|
|
struct Genesys_Calibration_Cache *cache;
|
|
SANE_Status status=SANE_STATUS_GOOD;
|
|
|
|
DBGSTART;
|
|
fp = fopen (dev->calib_file, "rb");
|
|
if (!fp)
|
|
{
|
|
DBG (DBG_info, "Calibration: Cannot open %s\n", dev->calib_file);
|
|
DBGCOMPLETED;
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
/* these two checks ensure that most bad things cannot happen */
|
|
fread (&vers, 1, 1, fp);
|
|
if (vers != CALIBRATION_VERSION)
|
|
{
|
|
DBG (DBG_info, "Calibration: Bad version\n");
|
|
fclose (fp);
|
|
DBGCOMPLETED;
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
fread (&size, 4, 1, fp);
|
|
if (size != sizeof (struct Genesys_Calibration_Cache))
|
|
{
|
|
DBG (DBG_info,
|
|
"Calibration: Size of calibration cache struct differs\n");
|
|
fclose (fp);
|
|
DBGCOMPLETED;
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
while (!feof (fp) && status==SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_info, "sanei_genesys_read_calibration: reading one record\n");
|
|
cache = (struct Genesys_Calibration_Cache *) malloc (sizeof (*cache));
|
|
|
|
if (!cache)
|
|
{
|
|
DBG (DBG_error,
|
|
"sanei_genesys_read_calibration: could not allocate cache struct\n");
|
|
break;
|
|
}
|
|
|
|
#define BILT1( x ) \
|
|
do \
|
|
{ \
|
|
if ((x) < 1) \
|
|
{ \
|
|
free(cache); \
|
|
DBG (DBG_warn, "sanei_genesys_read_calibration: partial calibration record\n"); \
|
|
status=SANE_STATUS_EOF; \
|
|
break; \
|
|
} \
|
|
} while(0)
|
|
|
|
|
|
if (fread (&cache->used_setup, sizeof (cache->used_setup), 1, fp) < 1)
|
|
{ /* eof is only detected here */
|
|
free (cache);
|
|
status=SANE_STATUS_GOOD;
|
|
break;
|
|
}
|
|
BILT1 (fread (&cache->last_calibration, sizeof (cache->last_calibration), 1, fp));
|
|
BILT1 (fread (&cache->frontend, sizeof (cache->frontend), 1, fp));
|
|
/* the gamma (and later) fields are not stored */
|
|
BILT1 (fread (&cache->sensor, offsetof (Genesys_Sensor, red_gamma), 1, fp));
|
|
BILT1 (fread (&cache->calib_pixels, sizeof (cache->calib_pixels), 1, fp));
|
|
BILT1 (fread (&cache->calib_channels, sizeof (cache->calib_channels), 1, fp));
|
|
BILT1 (fread (&cache->average_size, sizeof (cache->average_size), 1, fp));
|
|
|
|
cache->white_average_data = (uint8_t *) malloc (cache->average_size);
|
|
cache->dark_average_data = (uint8_t *) malloc (cache->average_size);
|
|
|
|
if (!cache->white_average_data || !cache->dark_average_data)
|
|
{
|
|
status=SANE_STATUS_NO_MEM;
|
|
FREE_IFNOT_NULL (cache->white_average_data);
|
|
FREE_IFNOT_NULL (cache->dark_average_data);
|
|
free (cache);
|
|
DBG (DBG_error,
|
|
"sanei_genesys_read_calibration: could not allocate space for average data\n");
|
|
break;
|
|
}
|
|
|
|
if (fread (cache->white_average_data, cache->average_size, 1, fp) < 1)
|
|
{
|
|
status=SANE_STATUS_EOF;
|
|
DBG (DBG_warn, "sanei_genesys_read_calibration: partial calibration record\n");
|
|
free (cache->white_average_data);
|
|
free (cache->dark_average_data);
|
|
free (cache);
|
|
break;
|
|
}
|
|
if (fread (cache->dark_average_data, cache->average_size, 1, fp) < 1)
|
|
{
|
|
DBG (DBG_warn, "sanei_genesys_read_calibration: partial calibration record\n");
|
|
free (cache->white_average_data);
|
|
free (cache->dark_average_data);
|
|
free (cache);
|
|
status=SANE_STATUS_EOF;
|
|
break;
|
|
}
|
|
#undef BILT1
|
|
DBG (DBG_info, "sanei_genesys_read_calibration: adding record to list\n");
|
|
cache->next = dev->calibration_cache;
|
|
dev->calibration_cache = cache;
|
|
}
|
|
|
|
fclose (fp);
|
|
DBGCOMPLETED;
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
write_calibration (Genesys_Device * dev)
|
|
{
|
|
FILE *fp;
|
|
uint8_t vers = 0;
|
|
uint32_t size = 0;
|
|
struct Genesys_Calibration_Cache *cache;
|
|
|
|
DBGSTART;
|
|
fp = fopen (dev->calib_file, "wb");
|
|
if (!fp)
|
|
{
|
|
DBG (DBG_info, "write_calibration: Cannot open %s for writing\n", dev->calib_file);
|
|
return;
|
|
}
|
|
|
|
vers = CALIBRATION_VERSION;
|
|
fwrite (&vers, 1, 1, fp);
|
|
size = sizeof (struct Genesys_Calibration_Cache);
|
|
fwrite (&size, 4, 1, fp);
|
|
|
|
for (cache = dev->calibration_cache; cache; cache = cache->next)
|
|
{
|
|
fwrite (&cache->used_setup, sizeof (cache->used_setup), 1, fp);
|
|
fwrite (&cache->last_calibration, sizeof (cache->last_calibration), 1, fp);
|
|
fwrite (&cache->frontend, sizeof (cache->frontend), 1, fp);
|
|
/* the gamma (and later) fields are not stored */
|
|
fwrite (&cache->sensor, offsetof (Genesys_Sensor, red_gamma), 1, fp);
|
|
|
|
fwrite (&cache->calib_pixels, sizeof (cache->calib_pixels), 1, fp);
|
|
fwrite (&cache->calib_channels, sizeof (cache->calib_channels), 1, fp);
|
|
fwrite (&cache->average_size, sizeof (cache->average_size), 1, fp);
|
|
fwrite (cache->white_average_data, cache->average_size, 1, fp);
|
|
fwrite (cache->dark_average_data, cache->average_size, 1, fp);
|
|
}
|
|
DBGCOMPLETED;
|
|
fclose (fp);
|
|
}
|
|
|
|
/** @brief buffer scanned picture
|
|
* In order to allow digital processing, we must be able to put all the
|
|
* scanned picture in a buffer.
|
|
*/
|
|
static SANE_Status
|
|
genesys_buffer_image(Genesys_Scanner *s)
|
|
{
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
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", __FUNCTION__, lines,
|
|
s->params.bytes_per_line);
|
|
|
|
/* maximum bytes to read */
|
|
maximum = s->params.bytes_per_line * lines;
|
|
|
|
/* 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;
|
|
|
|
/* allocate memory */
|
|
dev->img_buffer = (SANE_Byte *) malloc (size);
|
|
if (dev->img_buffer == NULL)
|
|
{
|
|
DBG (DBG_error,
|
|
"%s: digital processing requires too much memory.\nConsider disabling it\n",
|
|
__FUNCTION__);
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
/* loop reading data until we reach maximum or EOF */
|
|
total = 0;
|
|
while (total < maximum && status != SANE_STATUS_EOF)
|
|
{
|
|
len = size - maximum;
|
|
if (len > read_size)
|
|
{
|
|
len = read_size;
|
|
}
|
|
status = genesys_read_ordered_data (dev, dev->img_buffer + total, &len);
|
|
if (status != SANE_STATUS_EOF && status != SANE_STATUS_GOOD)
|
|
{
|
|
free (s->dev->img_buffer);
|
|
DBG (DBG_error, "%s: %s buffering failed\n", __FUNCTION__,
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
total += len;
|
|
|
|
/* do we need to enlarge read buffer ? */
|
|
if (total + read_size > size && status != SANE_STATUS_EOF)
|
|
{
|
|
size += read_size;
|
|
dev->img_buffer = (SANE_Byte *) realloc (dev->img_buffer, size);
|
|
if (dev->img_buffer == NULL)
|
|
{
|
|
DBG (DBG_error0,
|
|
"%s: digital processing requires too much memory.\nConsider disabling it\n",
|
|
__FUNCTION__);
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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->model->cmd_set->slow_back_home (dev, dev->model->flags & GENESYS_FLAG_MUST_WAIT);
|
|
dev->parking = !(s->dev->model->flags & GENESYS_FLAG_MUST_WAIT);
|
|
}
|
|
|
|
/* 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 ("unprocessed.pnm",
|
|
dev->img_buffer,
|
|
s->params.depth,
|
|
s->params.format==SANE_FRAME_RGB ? 3:1,
|
|
s->params.pixels_per_line,
|
|
s->params.lines);
|
|
}
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* -------------------------- SANE API functions ------------------------- */
|
|
|
|
SANE_Status
|
|
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
|
|
{
|
|
SANE_Status status;
|
|
|
|
DBG_INIT ();
|
|
DBG (DBG_init, "SANE Genesys backend version %d.%d build %d from %s\n",
|
|
SANE_CURRENT_MAJOR, V_MINOR, BUILD, PACKAGE_STRING);
|
|
#ifdef HAVE_LIBUSB_1_0
|
|
DBG (DBG_init, "SANE Genesys backend built with libusb-1.0\n");
|
|
#endif
|
|
#ifdef HAVE_LIBUSB
|
|
DBG (DBG_init, "SANE Genesys backend built with libusb\n");
|
|
#endif
|
|
|
|
if (version_code)
|
|
*version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, BUILD);
|
|
|
|
DBG (DBG_proc, "sane_init: authorize %s null\n", authorize ? "!=" : "==");
|
|
|
|
/* init usb use */
|
|
sanei_usb_init ();
|
|
|
|
/* init sanei_magic */
|
|
sanei_magic_init();
|
|
|
|
DBG (DBG_info, "sane_init: %s endian machine\n",
|
|
#ifdef WORDS_BIGENDIAN
|
|
"big"
|
|
#else
|
|
"little"
|
|
#endif
|
|
);
|
|
|
|
/* set up to no devices at first */
|
|
num_devices = 0;
|
|
first_dev = 0;
|
|
first_handle = 0;
|
|
devlist = 0;
|
|
|
|
/* cold-plug case :detection of allready connected scanners */
|
|
status = probe_genesys_devices ();
|
|
|
|
DBGCOMPLETED;
|
|
|
|
return status;
|
|
}
|
|
|
|
void
|
|
sane_exit (void)
|
|
{
|
|
Genesys_Device *dev, *next;
|
|
|
|
DBGSTART;
|
|
for (dev = first_dev; dev; dev = next)
|
|
{
|
|
/* sane_close() free many fields, not much things left to
|
|
* do here */
|
|
next = dev->next;
|
|
free (dev->file_name);
|
|
free (dev);
|
|
}
|
|
first_dev = 0;
|
|
first_handle = 0;
|
|
if (devlist)
|
|
free (devlist);
|
|
devlist = 0;
|
|
|
|
DBGCOMPLETED;
|
|
}
|
|
|
|
SANE_Status
|
|
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
|
|
{
|
|
Genesys_Device *dev, *prev;
|
|
SANE_Int index;
|
|
SANE_Device *sane_device;
|
|
|
|
DBG (DBG_proc, "sane_get_devices: start: local_only = %s\n",
|
|
local_only == SANE_TRUE ? "true" : "false");
|
|
|
|
/* hot-plug case :detection of newly connected scanners */
|
|
sanei_usb_init ();
|
|
probe_genesys_devices ();
|
|
|
|
if (devlist)
|
|
free (devlist);
|
|
|
|
devlist = malloc ((num_devices + 1) * sizeof (devlist[0]));
|
|
if (!devlist)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
prev = NULL;
|
|
index = 0;
|
|
dev = first_dev;
|
|
while (dev != NULL)
|
|
{
|
|
/* check if device removed */
|
|
present = SANE_FALSE;
|
|
sanei_usb_find_devices (dev->vendorId, dev->productId, check_present);
|
|
if (present)
|
|
{
|
|
sane_device = malloc (sizeof (*sane_device));
|
|
if (!sane_device)
|
|
return SANE_STATUS_NO_MEM;
|
|
sane_device->name = dev->file_name;
|
|
sane_device->vendor = dev->model->vendor;
|
|
sane_device->model = dev->model->model;
|
|
sane_device->type = strdup ("flatbed scanner");
|
|
devlist[index] = sane_device;
|
|
index++;
|
|
prev = dev;
|
|
dev = dev->next;
|
|
}
|
|
else
|
|
{
|
|
/* remove device from internal list */
|
|
/* case 1 : removed device is first_dev */
|
|
if (prev == NULL)
|
|
{
|
|
/* test for another dev */
|
|
if (dev->next == NULL)
|
|
{
|
|
/* empty the whole list */
|
|
free (dev);
|
|
first_dev = NULL;
|
|
num_devices = 0;
|
|
dev = NULL;
|
|
}
|
|
else
|
|
{
|
|
/* assign new start */
|
|
first_dev = dev->next;
|
|
num_devices--;
|
|
free (dev);
|
|
dev = dev->next;
|
|
}
|
|
}
|
|
/* case 2 : removed device is not first_dev */
|
|
else
|
|
{
|
|
/* link previous dev to next dev */
|
|
prev->next = dev->next;
|
|
free (dev);
|
|
num_devices--;
|
|
|
|
/* next loop */
|
|
dev = prev->next;
|
|
}
|
|
}
|
|
}
|
|
devlist[index] = 0;
|
|
|
|
*device_list = devlist;
|
|
|
|
DBGCOMPLETED;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
SANE_Status
|
|
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
|
|
{
|
|
Genesys_Device *dev;
|
|
SANE_Status status;
|
|
Genesys_Scanner *s;
|
|
char tmp_str[PATH_MAX];
|
|
char *ptr;
|
|
|
|
DBG (DBG_proc, "sane_open: start (devicename = `%s')\n", devicename);
|
|
|
|
/* 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 (dev = first_dev; dev; dev = dev->next)
|
|
if (strcmp (dev->file_name, devicename) == 0)
|
|
break;
|
|
|
|
if (!dev)
|
|
{
|
|
DBG (DBG_info,
|
|
"sane_open: couldn't find `%s' in devlist, trying attach\n",
|
|
devicename);
|
|
RIE (attach (devicename, &dev, SANE_TRUE));
|
|
}
|
|
else
|
|
DBG (DBG_info, "sane_open: found `%s' in devlist\n",
|
|
dev->model->name);
|
|
}
|
|
else
|
|
{
|
|
/* empty devicename or "genesys" -> use first device */
|
|
dev = first_dev;
|
|
if (dev)
|
|
{
|
|
devicename = dev->file_name;
|
|
DBG (DBG_info, "sane_open: empty devicename, trying `%s'\n",
|
|
devicename);
|
|
}
|
|
}
|
|
|
|
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@lists.alioth.debian.org. 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");
|
|
}
|
|
|
|
status = sanei_usb_open (dev->file_name, &dev->dn);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_warn, "sane_open: couldn't open device `%s': %s\n",
|
|
dev->file_name, sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
|
|
s = malloc (sizeof (*s));
|
|
if (!s)
|
|
return SANE_STATUS_NO_MEM;
|
|
|
|
s->dev = dev;
|
|
s->scanning = SANE_FALSE;
|
|
s->dev->read_buffer.buffer = NULL;
|
|
s->dev->lines_buffer.buffer = NULL;
|
|
s->dev->shrink_buffer.buffer = NULL;
|
|
s->dev->out_buffer.buffer = NULL;
|
|
s->dev->parking = SANE_FALSE;
|
|
s->dev->read_active = SANE_FALSE;
|
|
s->dev->white_average_data = NULL;
|
|
s->dev->dark_average_data = NULL;
|
|
s->dev->calibration_cache = NULL;
|
|
s->dev->calib_file = NULL;
|
|
s->dev->img_buffer = NULL;
|
|
s->dev->line_interp = 0;
|
|
s->dev->line_count = 0;
|
|
s->dev->segnb = 0;
|
|
s->dev->oe_buffer.buffer=NULL;
|
|
s->dev->binary=NULL;
|
|
|
|
/* insert newly opened handle into list of open handles: */
|
|
s->next = first_handle;
|
|
first_handle = s;
|
|
*handle = s;
|
|
|
|
if (!dev->already_initialized)
|
|
sanei_genesys_init_structs (dev);
|
|
|
|
RIE (init_options (s));
|
|
|
|
if (sanei_genesys_init_cmd_set (s->dev) != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error0, "This device doesn't have a valid command set!!\n");
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
RIE (dev->model->cmd_set->init (dev));
|
|
|
|
/* here is the place to fetch a stored calibration cache */
|
|
|
|
/* create calibration-filename
|
|
lifted from plustek-usb.c
|
|
*/
|
|
/* we should add a unique identifying feature to the file name
|
|
to support multiple scanners of the same model, but to my
|
|
knowledge, there is no such thing in these scanners.
|
|
(At least the usb serial is always "0".)
|
|
TODO add an storedir option to genesys.conf
|
|
*/
|
|
|
|
ptr = getenv ("HOME");
|
|
if (NULL == ptr)
|
|
{
|
|
sprintf (tmp_str, "/tmp/%s.cal", s->dev->model->name);
|
|
}
|
|
else
|
|
{
|
|
#ifdef HAVE_MKDIR
|
|
/* make sure .sane directory exists */
|
|
sprintf (tmp_str, "%s/.sane", ptr);
|
|
mkdir(tmp_str,0700);
|
|
#endif
|
|
sprintf (tmp_str, "%s/.sane/%s.cal", ptr, s->dev->model->name);
|
|
}
|
|
|
|
s->dev->calib_file = strdup (tmp_str);
|
|
DBG (DBG_info, "Calibration filename set to:\n");
|
|
DBG (DBG_info, ">%s<\n", s->dev->calib_file);
|
|
|
|
/* now open file, fetch calibration records */
|
|
|
|
sanei_genesys_read_calibration (s->dev);
|
|
|
|
DBGCOMPLETED;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
void
|
|
sane_close (SANE_Handle handle)
|
|
{
|
|
Genesys_Scanner *prev, *s;
|
|
Genesys_Calibration_Cache *cache, *next_cache;
|
|
SANE_Status status;
|
|
SANE_Range *range;
|
|
|
|
DBGSTART;
|
|
|
|
/* remove handle from list of open handles: */
|
|
prev = 0;
|
|
for (s = first_handle; s; s = s->next)
|
|
{
|
|
if (s == handle)
|
|
break;
|
|
prev = s;
|
|
}
|
|
if (!s)
|
|
{
|
|
DBG (DBG_error, "sane_close: invalid handle %p\n", handle);
|
|
return; /* oops, not a handle we know about */
|
|
}
|
|
|
|
/* eject document for sheetfed scanners */
|
|
if (s->dev->model->is_sheetfed == SANE_TRUE)
|
|
{
|
|
s->dev->model->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)
|
|
{
|
|
status = sanei_genesys_wait_for_home (s->dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"sane_close: failed to wait for head to park: %s\n",
|
|
sane_strstatus (status));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* here is the place to store calibration cache */
|
|
write_calibration (s->dev);
|
|
|
|
for (cache = s->dev->calibration_cache; cache; cache = next_cache)
|
|
{
|
|
next_cache = cache->next;
|
|
free (cache->dark_average_data);
|
|
free (cache->white_average_data);
|
|
free (cache);
|
|
}
|
|
|
|
sanei_genesys_buffer_free (&(s->dev->read_buffer));
|
|
sanei_genesys_buffer_free (&(s->dev->lines_buffer));
|
|
sanei_genesys_buffer_free (&(s->dev->shrink_buffer));
|
|
sanei_genesys_buffer_free (&(s->dev->out_buffer));
|
|
FREE_IFNOT_NULL (s->dev->white_average_data);
|
|
FREE_IFNOT_NULL (s->dev->dark_average_data);
|
|
FREE_IFNOT_NULL (s->dev->calib_file);
|
|
|
|
/* free allocated gamma tables */
|
|
FREE_IFNOT_NULL (s->dev->sensor.red_gamma_table);
|
|
FREE_IFNOT_NULL (s->dev->sensor.green_gamma_table);
|
|
FREE_IFNOT_NULL (s->dev->sensor.blue_gamma_table);
|
|
|
|
/* for an handful of bytes .. */
|
|
free ((void *)s->opt[OPT_RESOLUTION].constraint.word_list);
|
|
free (s->val[OPT_SOURCE].s);
|
|
free (s->val[OPT_MODE].s);
|
|
free (s->val[OPT_COLOR_FILTER].s);
|
|
range=s->opt[OPT_TL_X].constraint.range;
|
|
FREE_IFNOT_NULL (range);
|
|
range=s->opt[OPT_TL_Y].constraint.range;
|
|
FREE_IFNOT_NULL (range);
|
|
|
|
if (prev)
|
|
prev->next = s->next;
|
|
else
|
|
first_handle = s->next;
|
|
|
|
/* LAMP OFF : same register across all the ASICs */
|
|
sanei_genesys_write_register (s->dev, 0x03, 0x00);
|
|
|
|
/* we need this to avoid ASIC getting stuck
|
|
* in bulk writes */
|
|
if(s->dev->model->asic_type==GENESYS_GL847
|
|
||s->dev->model->asic_type==GENESYS_GL843)
|
|
sanei_usb_reset (s->dev->dn);
|
|
|
|
sanei_usb_close (s->dev->dn);
|
|
free (s);
|
|
|
|
DBGCOMPLETED;
|
|
}
|
|
|
|
const SANE_Option_Descriptor *
|
|
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
|
|
{
|
|
Genesys_Scanner *s = handle;
|
|
|
|
if ((unsigned) option >= NUM_OPTIONS)
|
|
return 0;
|
|
DBG (DBG_io2, "sane_get_option_descriptor: option = %s (%d)\n",
|
|
s->opt[option].name, option);
|
|
return s->opt + option;
|
|
}
|
|
|
|
/* gets an option , called by sane_control_option */
|
|
static SANE_Status
|
|
get_option_value (Genesys_Scanner * s, int option, void *val)
|
|
{
|
|
unsigned int i;
|
|
SANE_Word *table ,tmp;
|
|
uint16_t *gamma;
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
Genesys_Calibration_Cache *cache;
|
|
|
|
switch (option)
|
|
{
|
|
/* geometry */
|
|
case OPT_TL_X:
|
|
case OPT_TL_Y:
|
|
case OPT_BR_X:
|
|
case OPT_BR_Y:
|
|
*(SANE_Word *) val = s->val[option].w;
|
|
/* switch coordinate to keep them coherent */
|
|
if (s->val[OPT_TL_X].w >= s->val[OPT_BR_X].w)
|
|
{
|
|
tmp=s->val[OPT_BR_X].w;
|
|
s->val[OPT_BR_X].w=s->val[OPT_TL_X].w;
|
|
s->val[OPT_TL_X].w=tmp;
|
|
}
|
|
if (s->val[OPT_TL_Y].w >= s->val[OPT_BR_Y].w)
|
|
{
|
|
tmp=s->val[OPT_BR_Y].w;
|
|
s->val[OPT_BR_Y].w=s->val[OPT_TL_Y].w;
|
|
s->val[OPT_TL_Y].w=tmp;
|
|
}
|
|
break;
|
|
/* word options: */
|
|
case OPT_NUM_OPTS:
|
|
case OPT_RESOLUTION:
|
|
case OPT_BIT_DEPTH:
|
|
case OPT_PREVIEW:
|
|
case OPT_THRESHOLD:
|
|
case OPT_THRESHOLD_CURVE:
|
|
case OPT_DISABLE_DYNAMIC_LINEART:
|
|
case OPT_CLEAR_CALIBRATION:
|
|
case OPT_DISABLE_INTERPOLATION:
|
|
case OPT_LAMP_OFF:
|
|
case OPT_LAMP_OFF_TIME:
|
|
case OPT_SWDESKEW:
|
|
case OPT_SWCROP:
|
|
case OPT_SWDESPECK:
|
|
case OPT_SWDEROTATE:
|
|
case OPT_SWSKIP:
|
|
case OPT_DESPECK:
|
|
*(SANE_Word *) val = s->val[option].w;
|
|
break;
|
|
case OPT_CUSTOM_GAMMA:
|
|
*(SANE_Word *) val = s->val[option].w;
|
|
break;
|
|
/* string options: */
|
|
case OPT_MODE:
|
|
case OPT_COLOR_FILTER:
|
|
case OPT_SOURCE:
|
|
strcpy (val, s->val[option].s);
|
|
break;
|
|
/* word array options */
|
|
case OPT_GAMMA_VECTOR:
|
|
table = (SANE_Word *) val;
|
|
if (strcmp (s->val[OPT_COLOR_FILTER].s, "Red") == 0)
|
|
{
|
|
gamma = s->dev->sensor.red_gamma_table;
|
|
}
|
|
else if (strcmp (s->val[OPT_COLOR_FILTER].s, "Blue") == 0)
|
|
{
|
|
gamma = s->dev->sensor.blue_gamma_table;
|
|
}
|
|
else
|
|
{
|
|
gamma = s->dev->sensor.green_gamma_table;
|
|
}
|
|
for (i = 0; i < s->opt[option].size / sizeof (SANE_Word); i++)
|
|
{
|
|
table[i] = gamma[i];
|
|
}
|
|
break;
|
|
case OPT_GAMMA_VECTOR_R:
|
|
table = (SANE_Word *) val;
|
|
for (i = 0; i < s->opt[option].size / sizeof (SANE_Word); i++)
|
|
{
|
|
table[i] = s->dev->sensor.red_gamma_table[i];
|
|
}
|
|
break;
|
|
case OPT_GAMMA_VECTOR_G:
|
|
table = (SANE_Word *) val;
|
|
for (i = 0; i < s->opt[option].size / sizeof (SANE_Word); i++)
|
|
{
|
|
table[i] = s->dev->sensor.green_gamma_table[i];
|
|
}
|
|
break;
|
|
case OPT_GAMMA_VECTOR_B:
|
|
table = (SANE_Word *) val;
|
|
for (i = 0; i < s->opt[option].size / sizeof (SANE_Word); i++)
|
|
{
|
|
table[i] = s->dev->sensor.blue_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:
|
|
RIE (s->dev->model->cmd_set->update_hardware_sensors (s));
|
|
*(SANE_Bool *) val = s->val[option].b;
|
|
s->last_val[option].b = *(SANE_Bool *) val;
|
|
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 (cache = s->dev->calibration_cache; cache; cache = cache->next)
|
|
{
|
|
if (s->dev->model->
|
|
cmd_set->is_compatible_calibration (s->dev, cache,
|
|
SANE_FALSE) ==
|
|
SANE_STATUS_GOOD)
|
|
{
|
|
*(SANE_Bool *) val = SANE_FALSE;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
DBG (DBG_warn, "get_option_value: can't get unknown option %d\n",
|
|
option);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/* sets an option , called by sane_control_option */
|
|
static SANE_Status
|
|
set_option_value (Genesys_Scanner * s, int option, void *val,
|
|
SANE_Int * myinfo)
|
|
{
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
SANE_Word *table;
|
|
unsigned int i;
|
|
SANE_Range *x_range, *y_range;
|
|
Genesys_Calibration_Cache *cache, *next_cache;
|
|
|
|
switch (option)
|
|
{
|
|
case OPT_TL_X:
|
|
case OPT_TL_Y:
|
|
case OPT_BR_X:
|
|
case OPT_BR_Y:
|
|
s->val[option].w = *(SANE_Word *) val;
|
|
RIE (calc_parameters (s));
|
|
*myinfo |= SANE_INFO_RELOAD_PARAMS;
|
|
break;
|
|
case OPT_RESOLUTION:
|
|
case OPT_THRESHOLD:
|
|
case OPT_THRESHOLD_CURVE:
|
|
case OPT_DISABLE_DYNAMIC_LINEART:
|
|
case OPT_SWCROP:
|
|
case OPT_SWDESKEW:
|
|
case OPT_DESPECK:
|
|
case OPT_SWDEROTATE:
|
|
case OPT_SWSKIP:
|
|
case OPT_DISABLE_INTERPOLATION:
|
|
case OPT_LAMP_OFF:
|
|
case OPT_PREVIEW:
|
|
s->val[option].w = *(SANE_Word *) val;
|
|
RIE (calc_parameters (s));
|
|
*myinfo |= SANE_INFO_RELOAD_PARAMS;
|
|
break;
|
|
case OPT_SWDESPECK:
|
|
s->val[option].w = *(SANE_Word *) val;
|
|
if (s->val[OPT_SWDESPECK].b == SANE_TRUE)
|
|
{
|
|
ENABLE(OPT_DESPECK);
|
|
}
|
|
else
|
|
{
|
|
DISABLE(OPT_DESPECK);
|
|
}
|
|
RIE (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->val[option].w = *(SANE_Word *) val;
|
|
if(s->val[OPT_BIT_DEPTH].w>8)
|
|
{
|
|
DISABLE(OPT_SWDESKEW);
|
|
DISABLE(OPT_SWDESPECK);
|
|
DISABLE(OPT_SWCROP);
|
|
DISABLE(OPT_DESPECK);
|
|
DISABLE(OPT_SWDEROTATE);
|
|
DISABLE(OPT_SWSKIP);
|
|
}
|
|
else
|
|
{
|
|
ENABLE(OPT_SWDESKEW);
|
|
ENABLE(OPT_SWDESPECK);
|
|
ENABLE(OPT_SWCROP);
|
|
ENABLE(OPT_DESPECK);
|
|
ENABLE(OPT_SWDEROTATE);
|
|
ENABLE(OPT_SWSKIP);
|
|
}
|
|
RIE (calc_parameters (s));
|
|
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
|
|
break;
|
|
case OPT_SOURCE:
|
|
if (strcmp (s->val[option].s, val) != 0)
|
|
{ /* something changed */
|
|
if (s->val[option].s)
|
|
free (s->val[option].s);
|
|
s->val[option].s = strdup (val);
|
|
|
|
/* change geometry constraint to the new source value */
|
|
if (strcmp (s->val[option].s, FLATBED) == 0)
|
|
{
|
|
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((SANE_Range *)s->opt[OPT_TL_X].constraint.range);
|
|
free((SANE_Range *)s->opt[OPT_TL_Y].constraint.range);
|
|
s->opt[OPT_TL_X].constraint.range = x_range;
|
|
s->val[OPT_TL_X].w = 0;
|
|
s->opt[OPT_TL_Y].constraint.range = y_range;
|
|
s->val[OPT_TL_Y].w = 0;
|
|
s->opt[OPT_BR_X].constraint.range = x_range;
|
|
s->val[OPT_BR_Y].w = y_range->max;
|
|
s->opt[OPT_BR_Y].constraint.range = y_range;
|
|
s->val[OPT_BR_X].w = x_range->max;
|
|
|
|
/* signals reload */
|
|
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
|
|
}
|
|
break;
|
|
case OPT_MODE:
|
|
if (s->val[option].s)
|
|
free (s->val[option].s);
|
|
s->val[option].s = strdup (val);
|
|
|
|
if (strcmp (s->val[option].s, SANE_VALUE_SCAN_MODE_LINEART) == 0)
|
|
{
|
|
ENABLE (OPT_THRESHOLD);
|
|
ENABLE (OPT_THRESHOLD_CURVE);
|
|
DISABLE (OPT_BIT_DEPTH);
|
|
if (s->dev->model->asic_type != GENESYS_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 (strcmp (s->val[option].s, SANE_VALUE_SCAN_MODE_GRAY) == 0)
|
|
{
|
|
if (s->dev->model->asic_type != GENESYS_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);
|
|
}
|
|
RIE (calc_parameters (s));
|
|
|
|
/* if custom gamma, toggle gamma table options according to the mode */
|
|
if (s->val[OPT_CUSTOM_GAMMA].b == SANE_TRUE)
|
|
{
|
|
if (strcmp (s->val[option].s, SANE_VALUE_SCAN_MODE_COLOR) == 0)
|
|
{
|
|
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:
|
|
if (s->val[option].s)
|
|
free (s->val[option].s);
|
|
s->val[option].s = strdup (val);
|
|
RIE (calc_parameters (s));
|
|
break;
|
|
case OPT_LAMP_OFF_TIME:
|
|
if (*(SANE_Word *) val != s->val[option].w)
|
|
{
|
|
s->val[option].w = *(SANE_Word *) val;
|
|
RIE (s->dev->model->cmd_set->
|
|
set_powersaving (s->dev, s->val[option].w));
|
|
}
|
|
break;
|
|
|
|
case OPT_CUSTOM_GAMMA:
|
|
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
|
|
s->val[OPT_CUSTOM_GAMMA].b = *(SANE_Bool *) val;
|
|
|
|
if (s->val[OPT_CUSTOM_GAMMA].b == SANE_TRUE)
|
|
{
|
|
if (strcmp (s->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_COLOR) == 0)
|
|
{
|
|
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);
|
|
/* restore default sensor gamma table */
|
|
/* currently there is no sensor's specific gamma table,
|
|
* tables are built by sanei_genesys_create_gamma_table */
|
|
sanei_genesys_create_gamma_table (s->dev->sensor.red_gamma_table,
|
|
s->opt[OPT_GAMMA_VECTOR_R].size /
|
|
sizeof (SANE_Word),
|
|
s->opt
|
|
[OPT_GAMMA_VECTOR_R].
|
|
constraint.range->max,
|
|
s->opt[OPT_GAMMA_VECTOR_R].
|
|
constraint.range->max,
|
|
s->dev->sensor.red_gamma);
|
|
sanei_genesys_create_gamma_table (s->dev->sensor.green_gamma_table,
|
|
s->opt[OPT_GAMMA_VECTOR_G].size /
|
|
sizeof (SANE_Word),
|
|
s->opt[OPT_GAMMA_VECTOR_G].
|
|
constraint.range->max,
|
|
s->opt[OPT_GAMMA_VECTOR_G].
|
|
constraint.range->max,
|
|
s->dev->sensor.red_gamma);
|
|
sanei_genesys_create_gamma_table (s->dev->sensor.blue_gamma_table,
|
|
s->opt[OPT_GAMMA_VECTOR_B].size /
|
|
sizeof (SANE_Word),
|
|
s->opt[OPT_GAMMA_VECTOR_B].
|
|
constraint.range->max,
|
|
s->opt[OPT_GAMMA_VECTOR_B].
|
|
constraint.range->max,
|
|
s->dev->sensor.red_gamma);
|
|
}
|
|
break;
|
|
|
|
case OPT_GAMMA_VECTOR:
|
|
table = (SANE_Word *) val;
|
|
for (i = 0; i < s->opt[option].size / sizeof (SANE_Word); i++)
|
|
{
|
|
s->dev->sensor.red_gamma_table[i] = table[i];
|
|
s->dev->sensor.green_gamma_table[i] = table[i];
|
|
s->dev->sensor.blue_gamma_table[i] = table[i];
|
|
}
|
|
break;
|
|
case OPT_GAMMA_VECTOR_R:
|
|
table = (SANE_Word *) val;
|
|
for (i = 0; i < s->opt[option].size / sizeof (SANE_Word); i++)
|
|
{
|
|
s->dev->sensor.red_gamma_table[i] = table[i];
|
|
}
|
|
break;
|
|
case OPT_GAMMA_VECTOR_G:
|
|
table = (SANE_Word *) val;
|
|
for (i = 0; i < s->opt[option].size / sizeof (SANE_Word); i++)
|
|
{
|
|
s->dev->sensor.green_gamma_table[i] = table[i];
|
|
}
|
|
break;
|
|
case OPT_GAMMA_VECTOR_B:
|
|
table = (SANE_Word *) val;
|
|
for (i = 0; i < s->opt[option].size / sizeof (SANE_Word); i++)
|
|
{
|
|
s->dev->sensor.blue_gamma_table[i] = table[i];
|
|
}
|
|
break;
|
|
case OPT_CALIBRATE:
|
|
status = s->dev->model->cmd_set->save_power (s->dev, SANE_FALSE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"%s: failed to disable power saving mode: %s\n",
|
|
__FUNCTION__, sane_strstatus (status));
|
|
}
|
|
else
|
|
status = genesys_scanner_calibration (s->dev);
|
|
/* not critical if this fails*/
|
|
s->dev->model->cmd_set->save_power (s->dev, SANE_TRUE);
|
|
/* signals that sensors will have to be read again */
|
|
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
|
|
break;
|
|
case OPT_CLEAR_CALIBRATION:
|
|
/* clear calibration cache */
|
|
if (s->dev->calibration_cache != NULL)
|
|
{
|
|
for (cache = s->dev->calibration_cache; cache; cache = next_cache)
|
|
{
|
|
next_cache = cache->next;
|
|
free (cache->dark_average_data);
|
|
free (cache->white_average_data);
|
|
free (cache);
|
|
}
|
|
}
|
|
s->dev->calibration_cache = NULL;
|
|
/* remove file */
|
|
unlink (s->dev->calib_file);
|
|
/* signals that sensors will have to be read again */
|
|
*myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
|
|
break;
|
|
|
|
default:
|
|
DBG (DBG_warn, "set_option_value: can't set unknown option %d\n",
|
|
option);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
/* sets and gets scanner option values */
|
|
SANE_Status
|
|
sane_control_option (SANE_Handle handle, SANE_Int option,
|
|
SANE_Action action, void *val, SANE_Int * info)
|
|
{
|
|
Genesys_Scanner *s = handle;
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
SANE_Word cap;
|
|
SANE_Int myinfo = 0;
|
|
|
|
DBG (DBG_io2,
|
|
"sane_control_option: start: action = %s, option = %s (%d)\n",
|
|
(action == SANE_ACTION_GET_VALUE) ? "get" : (action ==
|
|
SANE_ACTION_SET_VALUE) ?
|
|
"set" : (action == SANE_ACTION_SET_AUTO) ? "set_auto" : "unknown",
|
|
s->opt[option].name, option);
|
|
|
|
if (info)
|
|
*info = 0;
|
|
|
|
if (s->scanning)
|
|
{
|
|
DBG (DBG_warn, "sane_control_option: don't call this function while "
|
|
"scanning (option = %s (%d))\n", s->opt[option].name, option);
|
|
|
|
return SANE_STATUS_DEVICE_BUSY;
|
|
}
|
|
if (option >= NUM_OPTIONS || option < 0)
|
|
{
|
|
DBG (DBG_warn,
|
|
"sane_control_option: option %d >= NUM_OPTIONS || option < 0\n",
|
|
option);
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
cap = s->opt[option].cap;
|
|
|
|
if (!SANE_OPTION_IS_ACTIVE (cap))
|
|
{
|
|
DBG (DBG_warn, "sane_control_option: option %d is inactive\n", 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, "sane_control_option: option %d is not settable\n",
|
|
option);
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
status = sanei_constrain_value (s->opt + option, val, &myinfo);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_warn,
|
|
"sane_control_option: sanei_constrain_value returned %s\n",
|
|
sane_strstatus (status));
|
|
return status;
|
|
}
|
|
|
|
status = set_option_value (s, option, val, &myinfo);
|
|
break;
|
|
|
|
case SANE_ACTION_SET_AUTO:
|
|
DBG (DBG_error,
|
|
"sane_control_option: SANE_ACTION_SET_AUTO unsupported since no option has SANE_CAP_AUTOMATIC\n");
|
|
status = SANE_STATUS_INVAL;
|
|
break;
|
|
|
|
default:
|
|
DBG (DBG_warn, "sane_control_option: unknown action %d for option %d\n",
|
|
action, option);
|
|
status = SANE_STATUS_INVAL;
|
|
break;
|
|
}
|
|
|
|
if (info)
|
|
*info = myinfo;
|
|
|
|
DBG (DBG_io2, "sane_control_option: exit\n");
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
SANE_Status
|
|
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
|
|
{
|
|
Genesys_Scanner *s = handle;
|
|
SANE_Status status;
|
|
|
|
DBGSTART;
|
|
|
|
/* don't recompute parameters once data reading is active, ie during scan */
|
|
if(s->dev->read_active == SANE_FALSE)
|
|
{
|
|
RIE (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->val[OPT_BR_Y].w == s->opt[OPT_BR_Y].constraint.range->max)
|
|
{
|
|
params->lines = -1;
|
|
}
|
|
}
|
|
|
|
DBGCOMPLETED;
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
SANE_Status
|
|
sane_start (SANE_Handle handle)
|
|
{
|
|
Genesys_Scanner *s = handle;
|
|
SANE_Status status=SANE_STATUS_GOOD;
|
|
|
|
DBGSTART;
|
|
|
|
if (s->val[OPT_TL_X].w >= s->val[OPT_BR_X].w)
|
|
{
|
|
DBG (DBG_error0,
|
|
"sane_start: top left x >= bottom right x --- exiting\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
if (s->val[OPT_TL_Y].w >= s->val[OPT_BR_Y].w)
|
|
{
|
|
DBG (DBG_error0,
|
|
"sane_start: top left y >= bottom right y --- exiting\n");
|
|
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. */
|
|
|
|
RIE (calc_parameters (s));
|
|
RIE (genesys_start_scan (s->dev, s->val[OPT_LAMP_OFF].w));
|
|
|
|
s->scanning = SANE_TRUE;
|
|
|
|
/* 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)
|
|
{
|
|
RIE(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->val[OPT_SWSKIP].w && IS_ACTIVE(OPT_SWSKIP))
|
|
{
|
|
status = sanei_magic_isBlank(&s->params,
|
|
s->dev->img_buffer,
|
|
SANE_UNFIX(s->val[OPT_SWSKIP].w));
|
|
if(status == SANE_STATUS_NO_DOCS)
|
|
{
|
|
if (s->dev->model->is_sheetfed == SANE_TRUE)
|
|
{
|
|
DBG (DBG_info, "sane_start: blank page, recurse\n");
|
|
return sane_start(handle);
|
|
}
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/* deskew image if required */
|
|
if(s->val[OPT_SWDESKEW].b == SANE_TRUE)
|
|
{
|
|
RIE(genesys_deskew(s));
|
|
}
|
|
|
|
/* despeck image if required */
|
|
if(s->val[OPT_SWDESPECK].b == SANE_TRUE)
|
|
{
|
|
RIE(genesys_despeck(s));
|
|
}
|
|
|
|
/* crop image if required */
|
|
if(s->val[OPT_SWCROP].b == SANE_TRUE)
|
|
{
|
|
RIE(genesys_crop(s));
|
|
}
|
|
|
|
/* de-rotate image if required */
|
|
if(s->val[OPT_SWDEROTATE].b == SANE_TRUE)
|
|
{
|
|
RIE(genesys_derotate(s));
|
|
}
|
|
}
|
|
|
|
DBGCOMPLETED;
|
|
return status;
|
|
}
|
|
|
|
SANE_Status
|
|
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
|
|
SANE_Int * len)
|
|
{
|
|
Genesys_Scanner *s = handle;
|
|
Genesys_Device *dev=s->dev;
|
|
SANE_Status status=SANE_STATUS_GOOD;
|
|
size_t local_len;
|
|
|
|
if (!s)
|
|
{
|
|
DBG (DBG_error, "sane_read: handle is null!\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
if (!buf)
|
|
{
|
|
DBG (DBG_error, "sane_read: buf is null!\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
if (!len)
|
|
{
|
|
DBG (DBG_error, "sane_read: len is null!\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
*len = 0;
|
|
|
|
if (!s->scanning)
|
|
{
|
|
DBG (DBG_warn, "sane_read: scan was cancelled, is over or has not been "
|
|
"initiated yet\n");
|
|
return SANE_STATUS_CANCELLED;
|
|
}
|
|
|
|
DBG (DBG_proc, "sane_read: start, %d maximum bytes required\n", max_len);
|
|
|
|
if(dev->total_bytes_read>=dev->total_bytes_to_read)
|
|
{
|
|
return SANE_STATUS_EOF;
|
|
}
|
|
|
|
local_len = max_len;
|
|
|
|
/* if image hasn't been buffered, read data from scanner */
|
|
if(!dev->buffer_image)
|
|
{
|
|
status = 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+dev->total_bytes_read,local_len);
|
|
dev->total_bytes_read+=local_len;
|
|
}
|
|
|
|
*len = local_len;
|
|
return status;
|
|
}
|
|
|
|
void
|
|
sane_cancel (SANE_Handle handle)
|
|
{
|
|
Genesys_Scanner *s = handle;
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
|
|
DBGSTART;
|
|
|
|
/* 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;
|
|
if(s->dev->img_buffer!=NULL)
|
|
{
|
|
free(s->dev->img_buffer);
|
|
s->dev->img_buffer=NULL;
|
|
}
|
|
|
|
/* no need to end scan if we are parking the head */
|
|
if(s->dev->parking==SANE_FALSE)
|
|
{
|
|
status = s->dev->model->cmd_set->end_scan (s->dev, s->dev->reg, SANE_TRUE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error, "sane_cancel: failed to end scan: %s\n",
|
|
sane_strstatus (status));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* park head if flatbed scanner */
|
|
if (s->dev->model->is_sheetfed == SANE_FALSE)
|
|
{
|
|
if(s->dev->parking==SANE_FALSE)
|
|
{
|
|
status = s->dev->model->cmd_set->slow_back_home (s->dev, s->dev->model->flags & GENESYS_FLAG_MUST_WAIT);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"sane_cancel: failed to move scanhead to home position: %s\n",
|
|
sane_strstatus (status));
|
|
return;
|
|
}
|
|
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 */
|
|
status = s->dev->model->cmd_set->eject_document (s->dev);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error, "sane_cancel: failed to eject document: %s\n",
|
|
sane_strstatus (status));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* enable power saving mode */
|
|
status = s->dev->model->cmd_set->save_power (s->dev, SANE_TRUE);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error,
|
|
"sane_cancel: failed to enable power saving mode: %s\n",
|
|
sane_strstatus (status));
|
|
return;
|
|
}
|
|
|
|
DBGCOMPLETED;
|
|
return;
|
|
}
|
|
|
|
SANE_Status
|
|
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
|
|
{
|
|
Genesys_Scanner *s = handle;
|
|
|
|
DBG (DBG_proc, "sane_set_io_mode: handle = %p, non_blocking = %s\n",
|
|
handle, non_blocking == SANE_TRUE ? "true" : "false");
|
|
|
|
if (!s->scanning)
|
|
{
|
|
DBG (DBG_error, "sane_set_io_mode: not scanning\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
if (non_blocking)
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
SANE_Status
|
|
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
|
|
{
|
|
Genesys_Scanner *s = handle;
|
|
|
|
DBG (DBG_proc, "sane_get_select_fd: handle = %p, fd = %p\n", handle,
|
|
(void *) fd);
|
|
|
|
if (!s->scanning)
|
|
{
|
|
DBG (DBG_error, "sane_get_select_fd: not scanning\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
/* vim: set sw=2 cino=>2se-1sn-1s{s^-1st0(0u0 smarttab expandtab: */
|