diff options
Diffstat (limited to 'backend/genesys.c')
-rw-r--r-- | backend/genesys.c | 7897 |
1 files changed, 7897 insertions, 0 deletions
diff --git a/backend/genesys.c b/backend/genesys.c new file mode 100644 index 0000000..6e7caad --- /dev/null +++ b/backend/genesys.c @@ -0,0 +1,7897 @@ +/* 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-2013 Stéphane Voltz <stef.dev@free.fr> + Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org> + Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com> + Copyright (C) 2007 Luke <iceyfor@gmail.com> + Copyright (C) 2010 Chris Berry <s0457957@sms.ed.ac.uk> and Michael Rickmann <mrickma@gwdg.de> + for Plustek Opticbook 3600 support + + Dynamic rasterization code was taken from the epjistsu backend by + m. allan noah <kitno455 at gmail dot com> + + Software processing for deskew, crop and dspeckle are inspired by allan's + noah work in the fujitsu backend + + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. +*/ + +/* + * SANE backend for Genesys Logic GL646/GL841/GL842/GL843/GL846/GL847/GL124 based scanners + */ + +#define BUILD 2411 +#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 u12_range = { + 0, /* minimum */ + 4095, /* maximum */ + 0 /* quantization */ +}; + +static const SANE_Range u14_range = { + 0, /* minimum */ + 16383, /* maximum */ + 0 /* quantization */ +}; + +static const SANE_Range u16_range = { + 0, /* minimum */ + 65535, /* maximum */ + 0 /* quantization */ +}; + +static const SANE_Range percentage_range = { + SANE_FIX (0), /* minimum */ + SANE_FIX (100), /* maximum */ + SANE_FIX (1) /* quantization */ +}; + +static const SANE_Range threshold_curve_range = { + 0, /* minimum */ + 127, /* maximum */ + 1 /* quantization */ +}; + +/** + * range for brightness and contrast + */ +static const SANE_Range enhance_range = { + -100, /* minimum */ + 100, /* maximum */ + 1 /* quantization */ +}; + +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_steps Size of slope_table in steps + * @param use_steps Maximum number of steps to use for acceleration + * @param stop_at Minimum step time to use + * @param vstart Start step time of default slope + * @param vend End step time of default slope + * @param steps Step count of default slope + * @param g Power for default slope + * @param used_steps Final number of steps is stored here + * @param vfinal Final step time is stored here + * @return Time for acceleration + * @note All times in pixel time. Correction for other motor timings is not + * done. + */ +SANE_Int +sanei_genesys_generate_slope_table (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 + * @param power_mode Power mode (related to the Vref used) of the motor + * @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; + + 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; +} + +/** @brief computes gamma table + * Generates a gamma table of the given length within 0 and the given + * maximum value + * @param gamma_table gamma table to fill + * @param size size of the table + * @param maximum value allowed for gamma + * @param gamma_max maximum gamma value + * @param gamma gamma to compute values + * @return a gamma table filled with the computed values + * */ +void +sanei_genesys_create_gamma_table (uint16_t * gamma_table, int size, + float maximum, float gamma_max, float gamma) +{ + int i; + float value; + + if(gamma_table==NULL) + { + DBG (DBG_proc, "sanei_genesys_create_gamma_table: gamma tbale is NULL\n"); + return; + } + 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_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_PLUSTEK3800 + && dev->model->ccd_type != CCD_KVSS080 + && dev->model->ccd_type != CCD_G4050 + && dev->model->ccd_type != CCD_CS4400F + && dev->model->ccd_type != CCD_CS8400F + && dev->model->ccd_type != CCD_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->ccd_type==CCD_CS4400F + || dev->model->ccd_type==CCD_CS8400F + || 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 (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; + + 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 (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); + + 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, "%s: failed to set frontend: %s\n", __FUNCTION__, + sane_strstatus (status)); + free(all_data); + free(calibration_data); + return status; + } + + dev->frontend.sign[0] = 0; + dev->frontend.sign[1] = 0; + dev->frontend.sign[2] = 0; + dev->frontend.gain[0] = 2; + dev->frontend.gain[1] = 2; + dev->frontend.gain[2] = 2; /* todo: ? was 2 */ + dev->frontend.offset[0] = offset[0]; + dev->frontend.offset[1] = offset[0]; + 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] = offset[i]; + dev->frontend.offset[1] = offset[i]; + 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 (&applied_multi, + &dev->frontend.gain[0], + gain_white_ref / (white[0] - dark[0]), + dev->frontend.gain[0]); + genesys_adjust_gain (&applied_multi, + &dev->frontend.gain[1], + gain_white_ref / (white[1] - dark[1]), + dev->frontend.gain[1]); + genesys_adjust_gain (&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++) */ + + free(all_data); + 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_CS4400F + || dev->model->ccd_type==CCD_CS8400F + || 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", + (unsigned int)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 status; +} + +/* 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", + (unsigned int)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 +*/ +#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) +{ + unsigned int x, i, j, br, dk, res, avgpixels, basepixels, val; + + 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); + + /* + 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 */ + basepixels = 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 (basepixels < 1) + avgpixels = 1; + else if (basepixels < 6) + avgpixels = basepixels; + else if (basepixels < 8) + avgpixels = 6; + else if (basepixels < 10) + avgpixels = 8; + else if (basepixels < 12) + avgpixels = 10; + else if (basepixels < 15) + avgpixels = 12; + else + avgpixels = 15; + + DBG (DBG_info, "%s: 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 < 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]; + } + } + + } +} + +/** + * Computes shading coefficient using formula in data sheet. 16bit data values + * manipulated here are little endian. For now we assume deletion scanning type + * and that there is always 3 channels. + * @param dev scanner's device + * @param shading_data memory area where to store the computed shading coefficients + * @param pixels_per_line number of pixels per line + * @param channels number of color channels (actually 1 or 3) + * @param cmat color transposition matrix + * @param offset shading coefficients left offset + * @param coeff 4000h or 2000h depending on fast scan mode or not + * @param target value of the target code + */ +#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 + * @param shading_data memory area where to store the computed shading coefficients + * @param factor averaging factor when the calibration scan is done at a higher resolution + * than the final scan + * @param pixels_per_line number of pixels per line + * @param words_per_color total number of shading data words for one color element + * @param channels number of color channels (actually 1 or 3) + * @param cmat transcoding matrix for color channel order + * @param offset shading coefficients left offset + * @param coeff 4000h or 2000h depending on fast scan mode or not + * @param target white target value + */ +#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, basepixels, i, j, val1, val2; + unsigned int br_tmp [3], dk_tmp [3]; + uint8_t *ptr = shading_data + offset * 3 * 4; /* contain 16bit words in little endian */ + unsigned int patch_cnt = offset * 3; /* at start, offset of first patch */ + + 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 */ + basepixels = 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 (basepixels < 1) + avgpixels = 1; + else if (basepixels < 6) + avgpixels = basepixels; + else if (basepixels < 8) + avgpixels = 6; + else if (basepixels < 10) + avgpixels = 8; + else if (basepixels < 12) + avgpixels = 10; + else if (basepixels < 15) + avgpixels = 12; + else + avgpixels = 15; + DBG (DBG_info, "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 factor; + unsigned int cmat[3]; /**> matrix of color channels */ + unsigned int coeff, target_code, words_per_color = 0; + + 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. + */ + + /* TODO setup a 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_PLUSTEK3800: + case CCD_G4050: + case CCD_CS4400F: + case CCD_CS8400F: + 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: + case CIS_CANONLIDE210: + /* TODO store this in a data struct so we avoid + * growing this switch */ + if(dev->model->ccd_type!=CIS_CANONLIDE110 + && dev->model->ccd_type!=CIS_CANONLIDE210) + target_code=0xdc00; + else + target_code=0xf000; + 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, + target_code); + break; + case CCD_CANONLIDE35: + compute_averaged_planar (dev, + shading_data, + pixels_per_line, + words_per_color, + channels, + 4, + coeff, + 0xfa00, + 0x0a00); + 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 */ + 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 + + +/* ------------------------------------------------------------------------ */ +/* 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) + { + free(first_line); + DBGCOMPLETED; + return SANE_STATUS_NO_MEM; + } + + do + { + DBG (DBG_info, "genesys_warmup_lamp: one more loop\n"); + RIEF2 (dev->model->cmd_set->begin_scan (dev, dev->reg, SANE_FALSE), first_line, second_line); + 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) + { + RIEF2 (sanei_genesys_read_data_from_scanner + (dev, first_line, total_size), first_line, second_line); + } + + RIEF2 (dev->model->cmd_set->end_scan (dev, dev->reg, SANE_TRUE), first_line, second_line); + + sleep (1); /* sleep 1 s */ + seconds++; + + RIEF2 (dev->model->cmd_set->begin_scan (dev, dev->reg, SANE_FALSE), first_line, second_line); + do + { + sanei_genesys_test_buffer_empty (dev, &empty); + usleep (100 * 1000); + } + while (empty); + RIEF2 (sanei_genesys_read_data_from_scanner (dev, second_line, total_size), first_line, second_line); + RIEF2 (dev->model->cmd_set->end_scan (dev, dev->reg, SANE_TRUE), first_line, second_line); + + /* 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 gamma tables. They have been set to device or user value + * when setting option value */ + status = dev->model->cmd_set->send_gamma_table (dev); + 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 = sanei_genesys_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 at bit level */ + for(i=0;i<8;i++) + { + k=count+(i*dev->segnb)/8; + for(n=0;n<dev->segnb;n++) + { + work_buffer_dst[k] = work_buffer_dst[k] << 1; + if((dev->oe_buffer.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; + 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; + + DBG (DBG_info, + "genesys_read_ordered_data: using filters:%s%s%s%s\n", + needs_reorder ? " reorder" : "", + needs_ccd ? " ccd" : "", + needs_shrink ? " shrink" : "", + needs_reverse ? " reverse" : ""); + + 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; + + /* issue park command immediatly in case scanner can handle it + * so we save time */ + if (dev->model->is_sheetfed == SANE_FALSE + && !(dev->model->flags & GENESYS_FLAG_MUST_WAIT) + && dev->parking == SANE_FALSE) + { + dev->model->cmd_set->slow_back_home (dev, SANE_FALSE); + dev->parking = SANE_TRUE; + } + 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 + { + 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 */ + if(dev->total_bytes_read >= dev->total_bytes_to_read) + { + dev->model->cmd_set->end_scan (dev, dev->reg, SANE_TRUE); + if (dev->model->is_sheetfed == SANE_TRUE) + { + dev->model->cmd_set->eject_document (dev); + } + } + + 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 pixels number + * TODO invert test logic or generalize behaviour across all ASICs */ + if ((s->dev->model->flags & GENESYS_FLAG_SIS_SENSOR) + || s->dev->model->asic_type == GENESYS_GL847 + || s->dev->model->asic_type == GENESYS_GL124 + || s->dev->model->asic_type == GENESYS_GL845 + || s->dev->model->asic_type == GENESYS_GL846 + || 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->model->asic_type == GENESYS_GL847 + || 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 = SANE_FALSE; + s->dev->settings.threshold_curve=0; + if(s->val[OPT_DISABLE_DYNAMIC_LINEART].w ==SANE_FALSE + &&s->dev->settings.scan_mode == SCAN_MODE_LINEART) + { + s->dev->settings.dynamic_lineart = SANE_TRUE; + } + + /* hardware lineart works only when we don't have interleave data + * for GL847 scanners, ie up to 600 DPI, then we have to rely on + * dynamic_lineart */ + if(s->dev->settings.xres > 600 + && s->dev->model->asic_type==GENESYS_GL847 + && s->dev->settings.scan_mode == SCAN_MODE_LINEART) + { + s->dev->settings.dynamic_lineart = SANE_TRUE; + } + + /* threshold curve for dynamic rasterization */ + s->dev->settings.threshold_curve=s->val[OPT_THRESHOLD_CURVE].w; + + /* 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; + } + + /* brigthness and contrast only for for 8 bit scans */ + if(s->val[OPT_BIT_DEPTH].w <= 8) + { + s->dev->settings.contrast=(s->val[OPT_CONTRAST].w*127)/100; + s->dev->settings.brightness=(s->val[OPT_BRIGHTNESS].w*127)/100; + } + else + { + s->dev->settings.contrast=0; + s->dev->settings.brightness=0; + } + + 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; +} + +/** @brief this function initialize a gamma vector based on the ASIC: + * Set up a default gamma table vector based on device description + * gl646: 12 or 14 bits gamma table depending on GENESYS_FLAG_14BIT_GAMMA + * gl84x: 16 bits + * gl12x: 16 bits + * @param scanner pointer to scanner session to get options + * @param option option number of the gamma table to set + */ +static void +init_gamma_vector_option (Genesys_Scanner * scanner, int option) +{ + /* the option is inactive until the custom gamma control + * is enabled */ + scanner->opt[option].type = SANE_TYPE_INT; + scanner->opt[option].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED; + scanner->opt[option].unit = SANE_UNIT_NONE; + scanner->opt[option].constraint_type = SANE_CONSTRAINT_RANGE; + if (scanner->dev->model->asic_type == 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 + { /* other asics have 16 bits words 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 pointer to a valid range or NULL + */ +static SANE_Range *create_range(SANE_Fixed size) +{ +SANE_Range *range=NULL; + + range=(SANE_Range *)malloc(sizeof(SANE_Range)); + if(range!=NULL) + { + range->min = SANE_FIX (0.0); + range->max = size; + range->quant = SANE_FIX (0.0); + } + return range; +} + + +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_Range *x_range, *y_range; + + DBGSTART; + + 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[OPT_SWDESKEW].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[OPT_SWDESPECK].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); + s->opt[OPT_SWSKIP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + /* 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_SWDEROTATE].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; + + /* Software brightness */ + s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT; + s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE; + s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BRIGHTNESS].constraint.range = &(enhance_range); + s->opt[OPT_BRIGHTNESS].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + /* disable by default */ + s->val[OPT_BRIGHTNESS].w = 0; + + /* Sowftware contrast */ + s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST; + s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST; + s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST; + s->opt[OPT_CONTRAST].type = SANE_TYPE_INT; + s->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE; + s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CONTRAST].constraint.range = &(enhance_range); + s->opt[OPT_CONTRAST].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + /* disable by default */ + s->val[OPT_CONTRAST].w = 0; + + /* "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; + + /* fastmod is required for hw lineart to work */ + if ((s->dev->model->asic_type == GENESYS_GL646) + &&(s->dev->model->motor_type != MOTOR_XP200)) + { + s->opt[OPT_DISABLE_DYNAMIC_LINEART].cap = SANE_CAP_INACTIVE; + } + + /* disable_interpolation */ + s->opt[OPT_DISABLE_INTERPOLATION].name = "disable-interpolation"; + s->opt[OPT_DISABLE_INTERPOLATION].title = + SANE_I18N ("Disable interpolation"); + s->opt[OPT_DISABLE_INTERPOLATION].desc = + SANE_I18N + ("When using high resolutions where the horizontal resolution is smaller " + "than the vertical resolution this disables horizontal interpolation."); + s->opt[OPT_DISABLE_INTERPOLATION].type = SANE_TYPE_BOOL; + s->opt[OPT_DISABLE_INTERPOLATION].unit = SANE_UNIT_NONE; + s->opt[OPT_DISABLE_INTERPOLATION].constraint_type = SANE_CONSTRAINT_NONE; + s->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 and GL124 scanners */ + if(!model->is_cis || model->asic_type==GENESYS_GL847 || model->asic_type==GENESYS_GL124) + { + s->opt[OPT_COLOR_FILTER].size = max_string_size (color_filter_list); + s->opt[OPT_COLOR_FILTER].constraint.string_list = color_filter_list; + s->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); + } + + /* calibration stor file name */ + s->opt[OPT_CALIBRATION_FILE].name = "calibration-file"; + s->opt[OPT_CALIBRATION_FILE].title = SANE_I18N ("Calibration file"); + s->opt[OPT_CALIBRATION_FILE].desc = SANE_I18N ("Specify the calibration file to use"); + s->opt[OPT_CALIBRATION_FILE].type = SANE_TYPE_STRING; + s->opt[OPT_CALIBRATION_FILE].unit = SANE_UNIT_NONE; + s->opt[OPT_CALIBRATION_FILE].size = PATH_MAX; + s->opt[OPT_CALIBRATION_FILE].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED; + s->opt[OPT_CALIBRATION_FILE].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_CALIBRATION_FILE].s = NULL; + /* disable option if ran as root */ +#ifdef HAVE_GETUID + if(geteuid()==0) + { + DISABLE (OPT_CALIBRATION_FILE); + } +#endif + + /* 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].title = SANE_TITLE_SENSORS; + s->opt[OPT_SENSOR_GROUP].desc = SANE_DESC_SENSORS; + s->opt[OPT_SENSOR_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_SENSOR_GROUP].cap = SANE_CAP_ADVANCED; + s->opt[OPT_SENSOR_GROUP].size = 0; + s->opt[OPT_SENSOR_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + s->opt[OPT_SCAN_SW].name = SANE_NAME_SCAN; + s->opt[OPT_SCAN_SW].title = SANE_TITLE_SCAN; + s->opt[OPT_SCAN_SW].desc = SANE_DESC_SCAN; + s->opt[OPT_SCAN_SW].type = SANE_TYPE_BOOL; + s->opt[OPT_SCAN_SW].unit = SANE_UNIT_NONE; + if (model->buttons & GENESYS_HAS_SCAN_SW) + s->opt[OPT_SCAN_SW].cap = + SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; + else + s->opt[OPT_SCAN_SW].cap = SANE_CAP_INACTIVE; + 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; + + /* extra button */ + s->opt[OPT_EXTRA_SW].name = "extra"; + s->opt[OPT_EXTRA_SW].title = SANE_I18N ("Extra button"); + s->opt[OPT_EXTRA_SW].desc = SANE_I18N ("Extra button"); + s->opt[OPT_EXTRA_SW].type = SANE_TYPE_BOOL; + s->opt[OPT_EXTRA_SW].unit = SANE_UNIT_NONE; + if (model->buttons & GENESYS_HAS_EXTRA_SW) + s->opt[OPT_EXTRA_SW].cap = + SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; + else + s->opt[OPT_EXTRA_SW].cap = SANE_CAP_INACTIVE; + s->val[OPT_EXTRA_SW].b = 0; + s->last_val[OPT_EXTRA_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].title = SANE_I18N ("Buttons"); + s->opt[OPT_BUTTON_GROUP].desc = ""; + s->opt[OPT_BUTTON_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_BUTTON_GROUP].cap = SANE_CAP_ADVANCED; + s->opt[OPT_BUTTON_GROUP].size = 0; + s->opt[OPT_BUTTON_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* calibrate button */ + s->opt[OPT_CALIBRATE].name = "calibrate"; + s->opt[OPT_CALIBRATE].title = SANE_I18N ("Calibrate"); + s->opt[OPT_CALIBRATE].desc = + SANE_I18N ("Start calibration using special sheet"); + s->opt[OPT_CALIBRATE].type = SANE_TYPE_BUTTON; + s->opt[OPT_CALIBRATE].unit = SANE_UNIT_NONE; + if (model->buttons & GENESYS_HAS_CALIBRATE) + s->opt[OPT_CALIBRATE].cap = + SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED | + SANE_CAP_AUTOMATIC; + else + s->opt[OPT_CALIBRATE].cap = SANE_CAP_INACTIVE; + 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].size = 0; + s->opt[OPT_CLEAR_CALIBRATION].constraint_type = SANE_CONSTRAINT_NONE; + s->opt[OPT_CLEAR_CALIBRATION].cap = + SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED; + 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->file_name) + 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; + Genesys_Device **tmp_dev; + + RIE (attach (devname, &dev, SANE_FALSE)); + + if (dev) + { + /* Keep track of newly attached devices so we can set options as + necessary. */ + tmp_dev=NULL; + /* increase device list capacity if needed */ + if (new_dev_len >= new_dev_alloced) + { + new_dev_alloced += 4; + if (new_dev) + { + tmp_dev = 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])); + tmp_dev = NULL; + } + if (!new_dev) + { + FREE_IFNOT_NULL(tmp_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 __sane_unused__ *config, const char *devname) +{ + /* 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 define in dev->calib_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; + + /* open calibration cache file */ + 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; + } + + /* clear device calibration cache */ + while(dev->calibration_cache!=NULL) + { + cache=dev->calibration_cache; + dev->calibration_cache=dev->calibration_cache->next; + free(cache); + } + + /* loop on cache records in file */ + 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, gamma[0]), 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, gamma[0]), 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; + SANE_Byte *lineart=NULL; + + /* 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; + if(s->dev->settings.dynamic_lineart==SANE_TRUE) + { + maximum *= 8; + } + + /* initial size of the read buffer */ + size = + ((2048 * 2048) / s->params.bytes_per_line) * s->params.bytes_per_line; + + /* read size */ + read_size = size / 2; + + /* 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->parking == 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); + } + + /* in case of dynamic lineart, we have buffered gray data which + * must be converted to lineart first */ + if(s->dev->settings.dynamic_lineart==SANE_TRUE) + { + total/=8; + lineart=(SANE_Byte *)malloc(total); + if (lineart == NULL) + { + DBG (DBG_error0, + "%s: digital processing requires too much memory.\nConsider disabling it\n", + __FUNCTION__); + return SANE_STATUS_NO_MEM; + } + genesys_gray_lineart (dev, + dev->img_buffer, + lineart, + dev->settings.pixels, + (total*8)/dev->settings.pixels, + dev->settings.threshold); + free(dev->img_buffer); + dev->img_buffer = lineart; + } + + /* update counters */ + dev->total_bytes_to_read = total; + dev->total_bytes_read = 0; + + /* update params */ + s->params.lines = total / s->params.bytes_per_line; + if (DBG_LEVEL >= DBG_io2) + { + sanei_genesys_write_pnm_file ("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; + + sanei_usb_exit(); + + 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_scan_devices (); + 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 = prev->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->binarize_buffer.buffer = NULL; + s->dev->local_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->val[OPT_CALIBRATION_FILE].s = strdup (tmp_str); + 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; + + 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)); + } + } + } + + /* enable power saving before leaving */ + status = s->dev->model->cmd_set->save_power (s->dev, SANE_TRUE); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, + "sane_close: failed to enable power saving mode: %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)); + sanei_genesys_buffer_free (&(s->dev->binarize_buffer)); + sanei_genesys_buffer_free (&(s->dev->local_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.gamma_table[0]); + FREE_IFNOT_NULL (s->dev->sensor.gamma_table[1]); + FREE_IFNOT_NULL (s->dev->sensor.gamma_table[2]); + + /* for an handful of bytes .. */ + free ((void *)(size_t)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); + free ((void *)(size_t)s->opt[OPT_TL_X].constraint.range); + free ((void *)(size_t)s->opt[OPT_TL_Y].constraint.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_GL845 + ||s->dev->model->asic_type==GENESYS_GL845 + ||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_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: + case OPT_CONTRAST: + case OPT_BRIGHTNESS: + *(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_CALIBRATION_FILE: + 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.gamma_table[GENESYS_RED]; + } + else if (strcmp (s->val[OPT_COLOR_FILTER].s, "Blue") == 0) + { + gamma = s->dev->sensor.gamma_table[GENESYS_BLUE]; + } + else + { + gamma = s->dev->sensor.gamma_table[GENESYS_GREEN]; + } + 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.gamma_table[GENESYS_RED][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.gamma_table[GENESYS_GREEN][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.gamma_table[GENESYS_BLUE][i]; + } + break; + /* sensors */ + case OPT_SCAN_SW: + case OPT_FILE_SW: + case OPT_EMAIL_SW: + case OPT_COPY_SW: + case OPT_PAGE_LOADED_SW: + case OPT_OCR_SW: + case OPT_POWER_SW: + case OPT_EXTRA_SW: + 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; +} + +/** @brief set calibration file value + * Set calibration file value. Load new cache values from file if it exists, + * else creates the file*/ +static SANE_Status set_calibration_value (Genesys_Scanner * s, int option, void *val) +{ + SANE_Status status=SANE_STATUS_GOOD; + char *tmp; + Genesys_Calibration_Cache *cache; + Genesys_Device *dev=s->dev; + + /* try to load file */ + tmp=dev->calib_file; + dev->calib_file=val; + status=sanei_genesys_read_calibration (dev); + + /* file exists but is invalid */ + if (status!=SANE_STATUS_IO_ERROR && status!=SANE_STATUS_GOOD) + { + dev->calib_file=tmp; + return status; + } + + /* we can set no file name value */ + if (s->val[option].s) + free (s->val[option].s); + s->val[option].s = strdup (val); + if (tmp) + free (tmp); + dev->calib_file = strdup (val); + + /* clear device calibration cache */ + while(dev->calibration_cache!=NULL) + { + cache=dev->calibration_cache; + dev->calibration_cache=dev->calibration_cache->next; + free(cache); + } + + return SANE_STATUS_GOOD; +} + +/* 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: + case OPT_BRIGHTNESS: + case OPT_CONTRAST: + 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); + DISABLE(OPT_CONTRAST); + DISABLE(OPT_BRIGHTNESS); + } + else + { + ENABLE(OPT_SWDESKEW); + ENABLE(OPT_SWDESPECK); + ENABLE(OPT_SWCROP); + ENABLE(OPT_DESPECK); + ENABLE(OPT_SWDEROTATE); + ENABLE(OPT_SWSKIP); + ENABLE(OPT_CONTRAST); + ENABLE(OPT_BRIGHTNESS); + } + 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((void *)(size_t)s->opt[OPT_TL_X].constraint.range); + free((void *)(size_t)s->opt[OPT_TL_Y].constraint.range); + s->opt[OPT_TL_X].constraint.range = x_range; + s->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_CALIBRATION_FILE: + RIE(set_calibration_value (s, option, val)); + 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.gamma_table[GENESYS_RED], + 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.gamma[GENESYS_RED]); + sanei_genesys_create_gamma_table (s->dev->sensor.gamma_table[GENESYS_GREEN], + 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.gamma[GENESYS_GREEN]); + sanei_genesys_create_gamma_table (s->dev->sensor.gamma_table[GENESYS_BLUE], + 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.gamma[GENESYS_BLUE]); + } + break; + + case OPT_GAMMA_VECTOR: + table = (SANE_Word *) val; + for (i = 0; i < s->opt[option].size / sizeof (SANE_Word); i++) + { + s->dev->sensor.gamma_table[GENESYS_RED][i] = table[i]; + s->dev->sensor.gamma_table[GENESYS_GREEN][i] = table[i]; + s->dev->sensor.gamma_table[GENESYS_BLUE][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.gamma_table[GENESYS_RED][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.gamma_table[GENESYS_GREEN][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.gamma_table[GENESYS_BLUE][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; + + /* allocate intermediate buffer when doing dynamic lineart */ + if(s->dev->settings.dynamic_lineart==SANE_TRUE) + { + RIE (sanei_genesys_buffer_free (&(s->dev->binarize_buffer))); + RIE (sanei_genesys_buffer_alloc (&(s->dev->binarize_buffer), s->dev->settings.pixels)); + RIE (sanei_genesys_buffer_free (&(s->dev->local_buffer))); + RIE (sanei_genesys_buffer_alloc (&(s->dev->local_buffer), s->dev->binarize_buffer.size * 8)); + } + + /* if one of the software enhancement option is selected, + * we do the scan internally, process picture then put it an internal + * buffer. Since cropping may change scan parameters, we recompute them + * at the end */ + if (s->dev->buffer_image) + { + 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; + 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; + } + + dev=s->dev; + if (!dev) + { + DBG (DBG_error, "sane_read: dev 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); + DBG (DBG_io2, "sane_read: bytes_to_read=%lu, total_bytes_read=%lu\n", + (u_long) dev->total_bytes_to_read, (u_long) dev->total_bytes_read); + DBG (DBG_io2, "sane_read: physical bytes to read = %lu\n", (u_long) dev->read_bytes_left); + + if(dev->total_bytes_read>=dev->total_bytes_to_read) + { + DBG (DBG_proc, "sane_read: nothing more to scan: EOF\n"); + + /* issue park command immediatly in case scanner can handle it + * so we save time */ + if (dev->model->is_sheetfed == SANE_FALSE + && !(dev->model->flags & GENESYS_FLAG_MUST_WAIT) + && dev->parking == SANE_FALSE) + { + dev->model->cmd_set->slow_back_home (dev, SANE_FALSE); + dev->parking = SANE_TRUE; + } + return SANE_STATUS_EOF; + } + + local_len = max_len; + + /* in case of image processing, all data has been stored in + * buffer_image. So read data from it if it exists, else from scanner */ + if(!dev->buffer_image) + { + /* dynamic lineart is another kind of digital processing that needs + * another layer of buffering on top of genesys_read_ordered_data */ + if(dev->settings.dynamic_lineart==SANE_TRUE) + { + /* if buffer is empty, fill it with genesys_read_ordered_data */ + if(dev->binarize_buffer.avail==0) + { + /* store gray data */ + local_len=dev->local_buffer.size; + status = genesys_read_ordered_data (dev, dev->local_buffer.buffer, &local_len); + + /* binarize data is read successful */ + if(status==SANE_STATUS_GOOD) + { + dev->local_buffer.avail=local_len; + dev->local_buffer.pos=0; + dev->binarize_buffer.avail=local_len/8; + dev->binarize_buffer.pos=0; + genesys_gray_lineart (dev, + dev->local_buffer.buffer, + dev->binarize_buffer.buffer, + dev->settings.pixels, + local_len/dev->settings.pixels, + dev->settings.threshold); + } + + } + + /* return data from lineart buffer if any, up to the available amount */ + local_len = max_len; + if((size_t)max_len>dev->binarize_buffer.avail) + { + local_len=dev->binarize_buffer.avail; + } + if(local_len) + { + memcpy(buf,sanei_genesys_buffer_get_read_pos (&(dev->binarize_buffer)),local_len); + RIE (sanei_genesys_buffer_consume (&(dev->binarize_buffer), local_len)); + } + } + else + { + /* most usual case, direct read of data from scanner */ + 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; + if(local_len>(size_t)max_len) + { + fprintf (stderr, "[genesys] sane_read: returning incorrect length!!\n"); + } + DBG (DBG_proc, "sane_read: %d bytes returned\n", *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 unless we are parking .... */ + if(s->dev->parking==SANE_FALSE) + { + 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: */ |