sane-project-backends/backend/epjitsu.c

4331 wiersze
124 KiB
C

/* sane - Scanner Access Now Easy.
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.
--------------------------------------------------------------------------
This file implements a SANE backend for the Fujitsu fi-60F, the
ScanSnap S300/S1300, and (hopefully) other Epson-based scanners.
Copyright 2007-2010 by m. allan noah <kitno455 at gmail dot com>
Copyright 2009 by Richard Goedeken <richard at fascinationsoftware dot com>
Development funded by Microdea, Inc., TrueCheck, Inc. and Archivista, GmbH
--------------------------------------------------------------------------
The source code is divided in sections which you can easily find by
searching for the tag "@@".
Section 1 - Init & static stuff
Section 2 - sane_init, _get_devices, _open & friends
Section 3 - sane_*_option functions
Section 4 - sane_start, _get_param, _read & friends
Section 5 - sane_close functions
Section 6 - misc functions
Changes:
v0, 2007-08-08, MAN
- initial alpha release, S300 raw data only
v1, 2007-09-03, MAN
- only supports 300dpi duplex binary for S300
v2, 2007-09-05, MAN
- add resolution option (only one choice)
- add simplex option
v3, 2007-09-12, MAN
- add support for 150 dpi resolution
v4, 2007-10-03, MAN
- change binarization algo to use average of all channels
v5, 2007-10-10, MAN
- move data blocks to separate file
- add basic fi-60F support (600dpi color)
v6, 2007-11-12, MAN
- move various data vars into transfer structs
- move most of read_from_scanner to sane_read
- add single line reads to calibration code
- generate calibration buffer from above reads
v7, 2007-12-05, MAN
- split calibration into fine and coarse functions
- add S300 fine calibration code
- add S300 color and grayscale support
v8, 2007-12-06, MAN
- change sane_start to call ingest earlier
- enable SOURCE_ADF_BACK
- add if() around memcopy and better debugs in sane_read
- shorten default scan sizes from 15.4 to 11.75 inches
v9, 2007-12-17, MAN
- fi-60F 300 & 600 dpi support (150 is non-square?)
- fi-60F gray & binary support
- fi-60F improved calibration
v10, 2007-12-19, MAN (SANE v1.0.19)
- fix missing function (and memory leak)
v11 2008-02-14, MAN
- sanei_config_read has already cleaned string (#310597)
v12 2008-02-28, MAN
- cleanup double free bug with new destroy()
v13 2008-09-18, MAN
- add working page-height control
- add working brightness, contrast and threshold controls
- add disabled threshold curve and geometry controls
- move initialization code to sane_get_devices, for hotplugging
v14 2008-09-24, MAN
- support S300 on USB power
- support S300 225x200 and 600x600 scans
- support for automatic paper length detection (parm.lines = -1)
v15 2008-09-24, MAN
- expose hardware buttons/sensors as options for S300
v16 2008-10-01, MAN
- split fill_frontback_buffers_S300 into 3 functions
- enable threshold_curve option
- add 1-D dynamic binary thresholding code
- remove y-resolution option
- pad 225x200 data to 225x225
v17 2008-10-03, MAN
- increase scan height ~1/2 inch due to head offset
- change page length autodetection condition
v18 2009-01-21, MAN
- dont export private symbols
v19 2009-08-31, RG
- rewritten calibration routines
v20 2010-02-09, MAN (SANE 1.0.21 & 1.0.22)
- cleanup #include lines & copyright
- add S1300
SANE FLOW DIAGRAM
- sane_init() : initialize backend
. - sane_get_devices() : query list of scanner devices
. - sane_open() : open a particular scanner device
. . - sane_set_io_mode : set blocking mode
. . - sane_get_select_fd : get scanner fd
. .
. . - sane_get_option_descriptor() : get option information
. . - sane_control_option() : change option values
. . - sane_get_parameters() : returns estimated scan parameters
. . - (repeat previous 3 functions)
. .
. . - sane_start() : start image acquisition
. . - sane_get_parameters() : returns actual scan parameters
. . - sane_read() : read image data (from pipe)
. . (sane_read called multiple times; after sane_read returns EOF,
. . loop may continue with sane_start which may return a 2nd page
. . when doing duplex scans, or load the next page from the ADF)
. .
. . - sane_cancel() : cancel operation
. - sane_close() : close opened scanner device
- sane_exit() : terminate use of backend
*/
/*
* @@ Section 1 - Init
*/
#include "../include/sane/config.h"
#include <string.h> /*memcpy...*/
#include <ctype.h> /*isspace*/
#include <math.h> /*tan*/
#include <unistd.h> /*usleep*/
#include <time.h> /*time*/
#include "../include/sane/sanei_backend.h"
#include "../include/sane/sanei_usb.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_config.h"
#include "epjitsu.h"
#include "epjitsu-cmd.h"
#define DEBUG 1
#define BUILD 20
#ifndef MAX3
#define MAX3(a,b,c) ((a) > (b) ? ((a) > (c) ? a : c) : ((b) > (c) ? b : c))
#endif
unsigned char global_firmware_filename[PATH_MAX];
/* values for SANE_DEBUG_EPJITSU env var:
- errors 5
- function trace 10
- function detail 15
- get/setopt cmds 20
- usb cmd trace 25
- usb cmd detail 30
- useless noise 35
*/
/* Calibration settings */
#define COARSE_OFFSET_TARGET 15
static int coarse_gain_min[3] = { 88, 88, 88 }; /* front, back, FI-60F 3rd plane */
static int coarse_gain_max[3] = { 92, 92, 92 };
static int fine_gain_target[3] = {185, 150, 170}; /* front, back, FI-60F is this ok? */
static float white_factor[3] = {1.0, 0.93, 0.98}; /* Blue, Red, Green */
/* ------------------------------------------------------------------------- */
#define STRING_FLATBED SANE_I18N("Flatbed")
#define STRING_ADFFRONT SANE_I18N("ADF Front")
#define STRING_ADFBACK SANE_I18N("ADF Back")
#define STRING_ADFDUPLEX SANE_I18N("ADF Duplex")
#define STRING_LINEART SANE_VALUE_SCAN_MODE_LINEART
#define STRING_GRAYSCALE SANE_VALUE_SCAN_MODE_GRAY
#define STRING_COLOR SANE_VALUE_SCAN_MODE_COLOR
/*
* used by attach* and sane_get_devices
* a ptr to a null term array of ptrs to SANE_Device structs
* a ptr to a single-linked list of scanner structs
*/
static const SANE_Device **sane_devArray = NULL;
static struct scanner *scanner_devList = NULL;
/*
* @@ Section 2 - SANE & scanner init code
*/
/*
* Called by SANE initially.
*
* From the SANE spec:
* This function must be called before any other SANE function can be
* called. The behavior of a SANE backend is undefined if this
* function is not called first. The version code of the backend is
* returned in the value pointed to by version_code. If that pointer
* is NULL, no version code is returned. Argument authorize is either
* a pointer to a function that is invoked when the backend requires
* authentication for a specific resource or NULL if the frontend does
* not support authentication.
*/
SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
authorize = authorize; /* get rid of compiler warning */
DBG_INIT ();
DBG (10, "sane_init: start\n");
if (version_code)
*version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, BUILD);
DBG (5, "sane_init: epjitsu backend %d.%d.%d, from %s\n",
SANE_CURRENT_MAJOR, V_MINOR, BUILD, PACKAGE_STRING);
DBG (10, "sane_init: finish\n");
return SANE_STATUS_GOOD;
}
/*
* Called by SANE to find out about supported devices.
*
* From the SANE spec:
* This function can be used to query the list of devices that are
* available. If the function executes successfully, it stores a
* pointer to a NULL terminated array of pointers to SANE_Device
* structures in *device_list. The returned list is guaranteed to
* remain unchanged and valid until (a) another call to this function
* is performed or (b) a call to sane_exit() is performed. This
* function can be called repeatedly to detect when new devices become
* available. If argument local_only is true, only local devices are
* returned (devices directly attached to the machine that SANE is
* running on). If it is false, the device list includes all remote
* devices that are accessible to the SANE library.
*
* SANE does not require that this function is called before a
* sane_open() call is performed. A device name may be specified
* explicitly by a user which would make it unnecessary and
* undesirable to call this function first.
*
* Read the config file, find scanners with help from sanei_*
* store in global device structs
*/
SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
SANE_Status ret = SANE_STATUS_GOOD;
struct scanner * s;
struct scanner * prev = NULL;
char line[PATH_MAX];
const char *lp;
FILE *fp;
int num_devices=0;
int i=0;
local_only = local_only; /* get rid of compiler warning */
DBG (10, "sane_get_devices: start\n");
/* mark all existing scanners as missing, attach_one will remove mark */
for (s = scanner_devList; s; s = s->next) {
s->missing = 1;
}
sanei_usb_init();
fp = sanei_config_open (CONFIG_FILE);
if (fp) {
DBG (15, "sane_get_devices: reading config file %s\n", CONFIG_FILE);
while (sanei_config_read (line, PATH_MAX, fp)) {
lp = line;
/* ignore comments */
if (*lp == '#')
continue;
/* skip empty lines */
if (*lp == 0)
continue;
if ((strncmp ("firmware", lp, 8) == 0) && isspace (lp[8])) {
lp += 8;
lp = sanei_config_skip_whitespace (lp);
DBG (15, "sane_get_devices: firmware '%s'\n", lp);
strncpy((char *)global_firmware_filename,lp,PATH_MAX);
}
else if ((strncmp ("usb", lp, 3) == 0) && isspace (lp[3])) {
DBG (15, "sane_get_devices: looking for '%s'\n", lp);
sanei_usb_attach_matching_devices(lp, attach_one);
}
else{
DBG (5, "sane_get_devices: config line \"%s\" ignored.\n", lp);
}
}
fclose (fp);
}
else {
DBG (5, "sane_get_devices: no config file '%s'!\n",
CONFIG_FILE);
}
/*delete missing scanners from list*/
for (s = scanner_devList; s;) {
if(s->missing){
DBG (5, "sane_get_devices: missing scanner %s\n",s->sane.name);
/*splice s out of list by changing pointer in prev to next*/
if(prev){
prev->next = s->next;
free(s);
s=prev->next;
}
/*remove s from head of list, using prev to cache it*/
else{
prev = s;
s = s->next;
free(prev);
prev=NULL;
/*reset head to next s*/
scanner_devList = s;
}
}
else{
prev = s;
s=prev->next;
}
}
for (s = scanner_devList; s; s=s->next) {
DBG (15, "sane_get_devices: found scanner %s\n",s->sane.name);
num_devices++;
}
DBG (15, "sane_get_devices: found %d scanner(s)\n",num_devices);
if (sane_devArray)
free (sane_devArray);
sane_devArray = calloc (num_devices + 1, sizeof (SANE_Device*));
if (!sane_devArray)
return SANE_STATUS_NO_MEM;
for (s = scanner_devList; s; s=s->next) {
sane_devArray[i++] = (SANE_Device *)&s->sane;
}
sane_devArray[i] = 0;
if(device_list){
*device_list = sane_devArray;
}
DBG (10, "sane_get_devices: finish\n");
return ret;
}
/* callback used by sane_init
* build the scanner struct and link to global list
* unless struct is already loaded, then pretend
*/
static SANE_Status
attach_one (const char *name)
{
struct scanner *s;
int ret, i;
DBG (10, "attach_one: start '%s'\n", name);
for (s = scanner_devList; s; s = s->next) {
if (strcmp (s->sane.name, name) == 0) {
DBG (10, "attach_one: already attached!\n");
s->missing = 0;
return SANE_STATUS_GOOD;
}
}
/* build a scanner struct to hold it */
DBG (15, "attach_one: init struct\n");
if ((s = calloc (sizeof (*s), 1)) == NULL)
return SANE_STATUS_NO_MEM;
/* copy the device name */
s->sane.name = strdup (name);
if (!s->sane.name){
destroy(s);
return SANE_STATUS_NO_MEM;
}
/* connect the fd */
DBG (15, "attach_one: connect fd\n");
s->fd = -1;
ret = connect_fd(s);
if(ret != SANE_STATUS_GOOD){
destroy(s);
return ret;
}
/* load the firmware file into scanner */
ret = load_fw(s);
if (ret != SANE_STATUS_GOOD) {
destroy(s);
DBG (5, "attach_one: firmware load failed\n");
return ret;
}
/* Now query the device to load its vendor/model/version */
ret = get_ident(s);
if (ret != SANE_STATUS_GOOD) {
destroy(s);
DBG (5, "attach_one: identify failed\n");
return ret;
}
DBG (15, "attach_one: Found %s scanner %s at %s\n",
s->sane.vendor, s->sane.model, s->sane.name);
if (strstr (s->sane.model, "S300") || strstr (s->sane.model, "S1300")){
unsigned char stat;
DBG (15, "attach_one: Found S300/S1300\n");
stat = get_stat(s);
if(stat & 0x01){
DBG (5, "attach_one: on USB power?\n");
s->usb_power=1;
}
s->model = MODEL_S300;
s->has_adf = 1;
s->x_res_150 = 1;
s->x_res_225 = 1;
s->x_res_300 = 1;
s->x_res_600 = 1;
s->y_res_150 = 1;
s->y_res_225 = 1;
s->y_res_300 = 1;
s->y_res_600 = 1;
s->source = SOURCE_ADF_FRONT;
s->mode = MODE_LINEART;
s->resolution_x = 300;
s->page_height = 11.5 * 1200;
s->threshold = 120;
s->threshold_curve = 55;
}
else if (strstr (s->sane.model, "fi-60F")){
DBG (15, "attach_one: Found fi-60F\n");
s->model = MODEL_FI60F;
s->has_fb = 1;
s->x_res_150 = 0;
s->x_res_300 = 1;
s->x_res_600 = 1;
s->y_res_150 = 0;
s->y_res_300 = 1;
s->y_res_600 = 1;
s->source = SOURCE_FLATBED;
s->mode = MODE_COLOR;
s->resolution_x = 300;
s->page_height = 5.83 * 1200;
s->threshold = 120;
s->threshold_curve = 55;
}
else{
DBG (15, "attach_one: Found other\n");
}
/* set SANE option 'values' to good defaults */
DBG (15, "attach_one: init options\n");
/* go ahead and setup the first opt, because
* frontend may call control_option on it
* before calling get_option_descriptor
*/
memset (s->opt, 0, sizeof (s->opt));
for (i = 0; i < NUM_OPTIONS; ++i) {
s->opt[i].name = "filler";
s->opt[i].size = sizeof (SANE_Word);
s->opt[i].cap = SANE_CAP_INACTIVE;
}
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].cap = SANE_CAP_SOFT_DETECT;
DBG (15, "attach_one: init settings\n");
ret = change_params(s);
/* we close the connection, so that another backend can talk to scanner */
disconnect_fd(s);
s->next = scanner_devList;
scanner_devList = s;
DBG (10, "attach_one: finish\n");
return SANE_STATUS_GOOD;
}
/*
* connect the fd in the scanner struct
*/
static SANE_Status
connect_fd (struct scanner *s)
{
SANE_Status ret;
DBG (10, "connect_fd: start\n");
if(s->fd > -1){
DBG (5, "connect_fd: already open\n");
ret = SANE_STATUS_GOOD;
}
else {
DBG (15, "connect_fd: opening USB device\n");
ret = sanei_usb_open (s->sane.name, &(s->fd));
}
if(ret != SANE_STATUS_GOOD){
DBG (5, "connect_fd: could not open device: %d\n", ret);
}
DBG (10, "connect_fd: finish\n");
return ret;
}
/*
* try to load fw into scanner
*/
static SANE_Status
load_fw (struct scanner *s)
{
SANE_Status ret = SANE_STATUS_GOOD;
int file, i;
int len = 0;
unsigned char * buf;
unsigned char cmd[4];
size_t cmdLen;
unsigned char stat[2];
size_t statLen;
DBG (10, "load_fw: start\n");
/*check status*/
/*reuse stat buffer*/
stat[0] = get_stat(s);
if(stat[0] & 0x10){
DBG (5, "load_fw: firmware already loaded?\n");
return SANE_STATUS_GOOD;
}
if(!global_firmware_filename[0]){
DBG (5, "load_fw: missing filename\n");
return SANE_STATUS_NO_DOCS;
}
file = open((char *)global_firmware_filename,O_RDONLY);
if(!file){
DBG (5, "load_fw: failed to open file %s\n",global_firmware_filename);
return SANE_STATUS_NO_DOCS;
}
if(lseek(file,0x100,SEEK_SET) != 0x100){
DBG (5, "load_fw: failed to lseek file %s\n",global_firmware_filename);
close(file);
return SANE_STATUS_NO_DOCS;
}
buf = malloc(FIRMWARE_LENGTH);
if(!buf){
DBG (5, "load_fw: failed to alloc mem\n");
close(file);
return SANE_STATUS_NO_MEM;
}
len = read(file,buf,FIRMWARE_LENGTH);
close(file);
if(len != FIRMWARE_LENGTH){
DBG (5, "load_fw: firmware file %s wrong length\n",
global_firmware_filename);
free(buf);
return SANE_STATUS_NO_DOCS;
}
DBG (15, "load_fw: read firmware file %s ok\n", global_firmware_filename);
/* firmware upload is in three commands */
/*start/status*/
cmd[0] = 0x1b;
cmd[1] = 0x06;
cmdLen = 2;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "load_fw: error on cmd 1\n");
free(buf);
return ret;
}
if(stat[0] != 6){
DBG (5, "load_fw: bad stat on cmd 1\n");
free(buf);
return SANE_STATUS_IO_ERROR;
}
/*length/data*/
cmd[0] = 0x01;
cmd[1] = 0x00;
cmd[2] = 0x01;
cmd[3] = 0x00;
cmdLen = 4;
ret = do_cmd(
s, 0,
cmd, cmdLen,
buf, FIRMWARE_LENGTH,
NULL, 0
);
if(ret){
DBG (5, "load_fw: error on cmd 2\n");
free(buf);
return ret;
}
/*checksum/status*/
cmd[0] = 0;
for(i=0;i<FIRMWARE_LENGTH;i++){
cmd[0] += buf[i];
}
free(buf);
cmdLen = 1;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "load_fw: error on cmd 3\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "load_fw: bad stat on cmd 3\n");
return SANE_STATUS_IO_ERROR;
}
/*reinit*/
cmd[0] = 0x1b;
cmd[1] = 0x16;
cmdLen = 2;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "load_fw: error reinit cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "load_fw: reinit cmd bad status?\n");
return SANE_STATUS_IO_ERROR;
}
cmd[0] = 0x80;
cmdLen = 1;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "load_fw: error reinit payload\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "load_fw: reinit payload bad status?\n");
return SANE_STATUS_IO_ERROR;
}
/*reuse stat buffer*/
stat[0] = get_stat(s);
if(!(stat[0] & 0x10)){
DBG (5, "load_fw: firmware not loaded? %#x\n",stat[0]);
return SANE_STATUS_IO_ERROR;
}
return ret;
}
/*
* try to load fw into scanner
*/
static unsigned char
get_stat(struct scanner *s)
{
SANE_Status ret = SANE_STATUS_GOOD;
unsigned char cmd[2];
size_t cmdLen;
unsigned char stat[2];
size_t statLen;
DBG (10, "get_stat: start\n");
/*check status*/
cmd[0] = 0x1b;
cmd[1] = 0x03;
cmdLen = 2;
statLen = 2;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "get_stat: error checking status\n");
return 0;
}
return stat[0];
}
static SANE_Status
get_ident(struct scanner *s)
{
int i;
SANE_Status ret;
unsigned char cmd[] = {0x1b,0x13};
size_t cmdLen = 2;
unsigned char in[0x20];
size_t inLen = sizeof(in);
DBG (10, "get_ident: start\n");
ret = do_cmd (
s, 0,
cmd, cmdLen,
NULL, 0,
in, &inLen
);
if (ret != SANE_STATUS_GOOD){
return ret;
}
/*hmm, similar to scsi?*/
for (i = 7; (in[i] == ' ' || in[i] == 0xff) && i >= 0; i--){
in[i] = 0;
}
s->sane.vendor = strndup((char *)in, 8);
for (i = 23; (in[i] == ' ' || in[i] == 0xff) && i >= 8; i--){
in[i] = 0;
}
s->sane.model= strndup((char *)in+8, 24);
s->sane.type = "scanner";
DBG (10, "get_ident: finish\n");
return ret;
}
/*
* From the SANE spec:
* This function is used to establish a connection to a particular
* device. The name of the device to be opened is passed in argument
* name. If the call completes successfully, a handle for the device
* is returned in *h. As a special case, specifying a zero-length
* string as the device requests opening the first available device
* (if there is such a device).
*/
SANE_Status
sane_open (SANE_String_Const name, SANE_Handle * handle)
{
struct scanner *dev = NULL;
struct scanner *s = NULL;
SANE_Status ret;
DBG (10, "sane_open: start\n");
if(scanner_devList){
DBG (15, "sane_open: searching currently attached scanners\n");
}
else{
DBG (15, "sane_open: no scanners currently attached, attaching\n");
ret = sane_get_devices(NULL,0);
if(ret != SANE_STATUS_GOOD){
return ret;
}
}
if(name[0] == 0){
DBG (15, "sane_open: no device requested, using default\n");
s = scanner_devList;
}
else{
DBG (15, "sane_open: device %s requested, attaching\n", name);
for (dev = scanner_devList; dev; dev = dev->next) {
if (strcmp (dev->sane.name, name) == 0) {
s = dev;
break;
}
}
}
if (!s) {
DBG (5, "sane_open: no device found\n");
return SANE_STATUS_INVAL;
}
DBG (15, "sane_open: device %s found\n", s->sane.name);
*handle = s;
/* connect the fd so we can talk to scanner */
ret = connect_fd(s);
if(ret != SANE_STATUS_GOOD){
return ret;
}
DBG (10, "sane_open: finish\n");
return SANE_STATUS_GOOD;
}
/*
* @@ Section 3 - SANE Options functions
*/
/*
* Returns the options we know.
*
* From the SANE spec:
* This function is used to access option descriptors. The function
* returns the option descriptor for option number n of the device
* represented by handle h. Option number 0 is guaranteed to be a
* valid option. Its value is an integer that specifies the number of
* options that are available for device handle h (the count includes
* option 0). If n is not a valid option index, the function returns
* NULL. The returned option descriptor is guaranteed to remain valid
* (and at the returned address) until the device is closed.
*/
const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
struct scanner *s = handle;
int i;
SANE_Option_Descriptor *opt = &s->opt[option];
DBG (20, "sane_get_option_descriptor: %d\n", option);
if ((unsigned) option >= NUM_OPTIONS)
return NULL;
/* "Mode" group -------------------------------------------------------- */
if(option==OPT_MODE_GROUP){
opt->title = "Scan Mode";
opt->desc = "";
opt->type = SANE_TYPE_GROUP;
opt->constraint_type = SANE_CONSTRAINT_NONE;
}
/* source */
else if(option==OPT_SOURCE){
i=0;
if(s->has_fb){
s->source_list[i++]=STRING_FLATBED;
}
if(s->has_adf){
s->source_list[i++]=STRING_ADFFRONT;
s->source_list[i++]=STRING_ADFBACK;
s->source_list[i++]=STRING_ADFDUPLEX;
}
s->source_list[i]=NULL;
opt->name = SANE_NAME_SCAN_SOURCE;
opt->title = SANE_TITLE_SCAN_SOURCE;
opt->desc = SANE_DESC_SCAN_SOURCE;
opt->type = SANE_TYPE_STRING;
opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
opt->constraint.string_list = s->source_list;
opt->size = maxStringSize (opt->constraint.string_list);
if(i > 1){
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
}
}
/* scan mode */
else if(option==OPT_MODE){
i=0;
s->mode_list[i++]=STRING_LINEART;
s->mode_list[i++]=STRING_GRAYSCALE;
s->mode_list[i++]=STRING_COLOR;
s->mode_list[i]=NULL;
opt->name = SANE_NAME_SCAN_MODE;
opt->title = SANE_TITLE_SCAN_MODE;
opt->desc = SANE_DESC_SCAN_MODE;
opt->type = SANE_TYPE_STRING;
opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
opt->constraint.string_list = s->mode_list;
opt->size = maxStringSize (opt->constraint.string_list);
if(i > 1){
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
}
}
else if(option==OPT_X_RES){
i=0;
if(s->x_res_150){
s->x_res_list[++i] = 150;
}
if(s->x_res_225){
s->x_res_list[++i] = 225;
}
if(s->x_res_300){
s->x_res_list[++i] = 300;
}
if(s->x_res_600){
s->x_res_list[++i] = 600;
}
s->x_res_list[0] = i;
opt->name = SANE_NAME_SCAN_RESOLUTION;
opt->title = SANE_TITLE_SCAN_X_RESOLUTION;
opt->desc = SANE_DESC_SCAN_X_RESOLUTION;
opt->type = SANE_TYPE_INT;
opt->unit = SANE_UNIT_DPI;
if(i > 1){
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
}
opt->constraint_type = SANE_CONSTRAINT_WORD_LIST;
opt->constraint.word_list = s->x_res_list;
}
/* "Geometry" group ---------------------------------------------------- */
if(option==OPT_GEOMETRY_GROUP){
opt->name = SANE_NAME_GEOMETRY;
opt->title = SANE_TITLE_GEOMETRY;
opt->desc = SANE_DESC_GEOMETRY;
opt->type = SANE_TYPE_GROUP;
opt->constraint_type = SANE_CONSTRAINT_NONE;
}
/* top-left x */
if(option==OPT_TL_X){
/* values stored in 1200 dpi units */
/* must be converted to MM for sane */
s->tl_x_range.min = SCANNER_UNIT_TO_FIXED_MM(0);
s->tl_x_range.max = SCANNER_UNIT_TO_FIXED_MM(get_page_width(s)-s->min_x);
s->tl_x_range.quant = MM_PER_UNIT_FIX;
opt->name = SANE_NAME_SCAN_TL_X;
opt->title = SANE_TITLE_SCAN_TL_X;
opt->desc = SANE_DESC_SCAN_TL_X;
opt->type = SANE_TYPE_FIXED;
opt->unit = SANE_UNIT_MM;
opt->constraint_type = SANE_CONSTRAINT_RANGE;
opt->constraint.range = &(s->tl_x_range);
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
opt->cap = SANE_CAP_INACTIVE;
}
/* top-left y */
if(option==OPT_TL_Y){
/* values stored in 1200 dpi units */
/* must be converted to MM for sane */
s->tl_y_range.min = SCANNER_UNIT_TO_FIXED_MM(0);
s->tl_y_range.max = SCANNER_UNIT_TO_FIXED_MM(get_page_height(s)-s->min_y);
s->tl_y_range.quant = MM_PER_UNIT_FIX;
opt->name = SANE_NAME_SCAN_TL_Y;
opt->title = SANE_TITLE_SCAN_TL_Y;
opt->desc = SANE_DESC_SCAN_TL_Y;
opt->type = SANE_TYPE_FIXED;
opt->unit = SANE_UNIT_MM;
opt->constraint_type = SANE_CONSTRAINT_RANGE;
opt->constraint.range = &(s->tl_y_range);
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
opt->cap = SANE_CAP_INACTIVE;
}
/* bottom-right x */
if(option==OPT_BR_X){
/* values stored in 1200 dpi units */
/* must be converted to MM for sane */
s->br_x_range.min = SCANNER_UNIT_TO_FIXED_MM(s->min_x);
s->br_x_range.max = SCANNER_UNIT_TO_FIXED_MM(get_page_width(s));
s->br_x_range.quant = MM_PER_UNIT_FIX;
opt->name = SANE_NAME_SCAN_BR_X;
opt->title = SANE_TITLE_SCAN_BR_X;
opt->desc = SANE_DESC_SCAN_BR_X;
opt->type = SANE_TYPE_FIXED;
opt->unit = SANE_UNIT_MM;
opt->constraint_type = SANE_CONSTRAINT_RANGE;
opt->constraint.range = &(s->br_x_range);
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
opt->cap = SANE_CAP_INACTIVE;
}
/* bottom-right y */
if(option==OPT_BR_Y){
/* values stored in 1200 dpi units */
/* must be converted to MM for sane */
s->br_y_range.min = SCANNER_UNIT_TO_FIXED_MM(s->min_y);
s->br_y_range.max = SCANNER_UNIT_TO_FIXED_MM(get_page_height(s));
s->br_y_range.quant = MM_PER_UNIT_FIX;
opt->name = SANE_NAME_SCAN_BR_Y;
opt->title = SANE_TITLE_SCAN_BR_Y;
opt->desc = SANE_DESC_SCAN_BR_Y;
opt->type = SANE_TYPE_FIXED;
opt->unit = SANE_UNIT_MM;
opt->constraint_type = SANE_CONSTRAINT_RANGE;
opt->constraint.range = &(s->br_y_range);
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
opt->cap = SANE_CAP_INACTIVE;
}
/* page width */
if(option==OPT_PAGE_WIDTH){
/* values stored in 1200 dpi units */
/* must be converted to MM for sane */
s->paper_x_range.min = SCANNER_UNIT_TO_FIXED_MM(s->min_x);
s->paper_x_range.max = SCANNER_UNIT_TO_FIXED_MM(s->max_x);
s->paper_x_range.quant = MM_PER_UNIT_FIX;
opt->name = SANE_NAME_PAGE_WIDTH;
opt->title = SANE_TITLE_PAGE_WIDTH;
opt->desc = SANE_DESC_PAGE_WIDTH;
opt->type = SANE_TYPE_FIXED;
opt->unit = SANE_UNIT_MM;
opt->constraint_type = SANE_CONSTRAINT_RANGE;
opt->constraint.range = &s->paper_x_range;
if(s->has_adf){
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
if(s->source == SOURCE_FLATBED){
opt->cap |= SANE_CAP_INACTIVE;
}
}
else{
opt->cap = SANE_CAP_INACTIVE;
}
opt->cap = SANE_CAP_INACTIVE;
}
/* page height */
if(option==OPT_PAGE_HEIGHT){
/* values stored in 1200 dpi units */
/* must be converted to MM for sane */
s->paper_y_range.min = SCANNER_UNIT_TO_FIXED_MM(0);
s->paper_y_range.max = SCANNER_UNIT_TO_FIXED_MM(s->max_y);
s->paper_y_range.quant = MM_PER_UNIT_FIX;
opt->name = SANE_NAME_PAGE_HEIGHT;
opt->title = SANE_TITLE_PAGE_HEIGHT;
opt->desc = "Specifies the height of the media, 0 will auto-detect.";
opt->type = SANE_TYPE_FIXED;
opt->unit = SANE_UNIT_MM;
opt->constraint_type = SANE_CONSTRAINT_RANGE;
opt->constraint.range = &s->paper_y_range;
if(s->has_adf){
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
if(s->source == SOURCE_FLATBED){
opt->cap |= SANE_CAP_INACTIVE;
}
}
else{
opt->cap = SANE_CAP_INACTIVE;
}
}
/* "Enhancement" group ------------------------------------------------- */
if(option==OPT_ENHANCEMENT_GROUP){
opt->name = SANE_NAME_ENHANCEMENT;
opt->title = SANE_TITLE_ENHANCEMENT;
opt->desc = SANE_DESC_ENHANCEMENT;
opt->type = SANE_TYPE_GROUP;
opt->constraint_type = SANE_CONSTRAINT_NONE;
}
/* brightness */
if(option==OPT_BRIGHTNESS){
opt->name = SANE_NAME_BRIGHTNESS;
opt->title = SANE_TITLE_BRIGHTNESS;
opt->desc = SANE_DESC_BRIGHTNESS;
opt->type = SANE_TYPE_INT;
opt->unit = SANE_UNIT_NONE;
opt->constraint_type = SANE_CONSTRAINT_RANGE;
opt->constraint.range = &s->brightness_range;
s->brightness_range.quant=1;
s->brightness_range.min=-127;
s->brightness_range.max=127;
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
}
/* contrast */
if(option==OPT_CONTRAST){
opt->name = SANE_NAME_CONTRAST;
opt->title = SANE_TITLE_CONTRAST;
opt->desc = SANE_DESC_CONTRAST;
opt->type = SANE_TYPE_INT;
opt->unit = SANE_UNIT_NONE;
opt->constraint_type = SANE_CONSTRAINT_RANGE;
opt->constraint.range = &s->contrast_range;
s->contrast_range.quant=1;
s->contrast_range.min=-127;
s->contrast_range.max=127;
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
}
/* gamma */
if(option==OPT_GAMMA){
opt->name = "gamma";
opt->title = "Gamma function exponent";
opt->desc = "Changes intensity of midtones";
opt->type = SANE_TYPE_FIXED;
opt->unit = SANE_UNIT_NONE;
opt->constraint_type = SANE_CONSTRAINT_RANGE;
opt->constraint.range = &s->gamma_range;
/* value ranges from .3 to 5, should be log scale? */
s->gamma_range.quant=SANE_FIX(0.01);
s->gamma_range.min=SANE_FIX(0.3);
s->gamma_range.max=SANE_FIX(5);
/*if (s->num_download_gamma){
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
}*/
opt->cap = SANE_CAP_INACTIVE;
}
/*threshold*/
if(option==OPT_THRESHOLD){
opt->name = SANE_NAME_THRESHOLD;
opt->title = SANE_TITLE_THRESHOLD;
opt->desc = SANE_DESC_THRESHOLD;
opt->type = SANE_TYPE_INT;
opt->unit = SANE_UNIT_NONE;
opt->constraint_type = SANE_CONSTRAINT_RANGE;
opt->constraint.range = &s->threshold_range;
s->threshold_range.min=0;
s->threshold_range.max=255;
s->threshold_range.quant=1;
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
if(s->mode != MODE_LINEART){
opt->cap |= SANE_CAP_INACTIVE;
}
}
if(option==OPT_THRESHOLD_CURVE){
opt->name = "threshold-curve";
opt->title = "Threshold curve";
opt->desc = "Dynamic threshold curve, from light to dark, normally 50-65";
opt->type = SANE_TYPE_INT;
opt->unit = SANE_UNIT_NONE;
opt->constraint_type = SANE_CONSTRAINT_RANGE;
opt->constraint.range = &s->threshold_curve_range;
s->threshold_curve_range.min=0;
s->threshold_curve_range.max=127;
s->threshold_curve_range.quant=1;
opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
if(s->mode != MODE_LINEART){
opt->cap |= SANE_CAP_INACTIVE;
}
}
/* "Sensor" group ------------------------------------------------------ */
if(option==OPT_SENSOR_GROUP){
opt->name = SANE_NAME_SENSORS;
opt->title = SANE_TITLE_SENSORS;
opt->desc = SANE_DESC_SENSORS;
opt->type = SANE_TYPE_GROUP;
opt->constraint_type = SANE_CONSTRAINT_NONE;
/*flaming hack to get scanimage to hide group*/
if (!s->has_adf)
opt->type = SANE_TYPE_BOOL;
}
if(option==OPT_SCAN_SW){
opt->name = SANE_NAME_SCAN;
opt->title = SANE_TITLE_SCAN;
opt->desc = SANE_DESC_SCAN;
opt->type = SANE_TYPE_BOOL;
opt->unit = SANE_UNIT_NONE;
if (s->has_adf)
opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
opt->cap = SANE_CAP_INACTIVE;
}
if(option==OPT_HOPPER){
opt->name = SANE_NAME_PAGE_LOADED;
opt->title = SANE_TITLE_PAGE_LOADED;
opt->desc = SANE_DESC_PAGE_LOADED;
opt->type = SANE_TYPE_BOOL;
opt->unit = SANE_UNIT_NONE;
if (s->has_adf)
opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
opt->cap = SANE_CAP_INACTIVE;
}
if(option==OPT_TOP){
opt->name = "top-edge";
opt->title = "Top edge";
opt->desc = "Paper is pulled partly into adf";
opt->type = SANE_TYPE_BOOL;
opt->unit = SANE_UNIT_NONE;
if (s->has_adf)
opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
opt->cap = SANE_CAP_INACTIVE;
}
if(option==OPT_ADF_OPEN){
opt->name = SANE_NAME_COVER_OPEN;
opt->title = SANE_TITLE_COVER_OPEN;
opt->desc = SANE_DESC_COVER_OPEN;
opt->type = SANE_TYPE_BOOL;
opt->unit = SANE_UNIT_NONE;
if (s->has_adf)
opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
opt->cap = SANE_CAP_INACTIVE;
}
if(option==OPT_SLEEP){
opt->name = "power-save";
opt->title = "Power saving";
opt->desc = "Scanner in power saving mode";
opt->type = SANE_TYPE_BOOL;
opt->unit = SANE_UNIT_NONE;
if (s->has_adf)
opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
else
opt->cap = SANE_CAP_INACTIVE;
}
return opt;
}
/**
* Gets or sets an option value.
*
* From the SANE spec:
* This function is used to set or inquire the current value of option
* number n of the device represented by handle h. The manner in which
* the option is controlled is specified by parameter action. The
* possible values of this parameter are described in more detail
* below. The value of the option is passed through argument val. It
* is a pointer to the memory that holds the option value. The memory
* area pointed to by v must be big enough to hold the entire option
* value (determined by member size in the corresponding option
* descriptor).
*
* The only exception to this rule is that when setting the value of a
* string option, the string pointed to by argument v may be shorter
* since the backend will stop reading the option value upon
* encountering the first NUL terminator in the string. If argument i
* is not NULL, the value of *i will be set to provide details on how
* well the request has been met.
*/
SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
SANE_Action action, void *val, SANE_Int * info)
{
struct scanner *s = (struct scanner *) handle;
SANE_Int dummy = 0;
/* Make sure that all those statements involving *info cannot break (better
* than having to do "if (info) ..." everywhere!)
*/
if (info == 0)
info = &dummy;
if (option >= NUM_OPTIONS) {
DBG (5, "sane_control_option: %d too big\n", option);
return SANE_STATUS_INVAL;
}
if (!SANE_OPTION_IS_ACTIVE (s->opt[option].cap)) {
DBG (5, "sane_control_option: %d inactive\n", option);
return SANE_STATUS_INVAL;
}
/*
* SANE_ACTION_GET_VALUE: We have to find out the current setting and
* return it in a human-readable form (often, text).
*/
if (action == SANE_ACTION_GET_VALUE) {
SANE_Word * val_p = (SANE_Word *) val;
DBG (20, "sane_control_option: get value for '%s' (%d)\n", s->opt[option].name,option);
switch (option) {
case OPT_NUM_OPTS:
*val_p = NUM_OPTIONS;
return SANE_STATUS_GOOD;
case OPT_SOURCE:
if(s->source == SOURCE_FLATBED){
strcpy (val, STRING_FLATBED);
}
else if(s->source == SOURCE_ADF_FRONT){
strcpy (val, STRING_ADFFRONT);
}
else if(s->source == SOURCE_ADF_BACK){
strcpy (val, STRING_ADFBACK);
}
else if(s->source == SOURCE_ADF_DUPLEX){
strcpy (val, STRING_ADFDUPLEX);
}
else{
DBG(5,"missing option val for source\n");
}
return SANE_STATUS_GOOD;
case OPT_MODE:
if(s->mode == MODE_LINEART){
strcpy (val, STRING_LINEART);
}
else if(s->mode == MODE_GRAYSCALE){
strcpy (val, STRING_GRAYSCALE);
}
else if(s->mode == MODE_COLOR){
strcpy (val, STRING_COLOR);
}
return SANE_STATUS_GOOD;
case OPT_X_RES:
*val_p = s->resolution_x;
return SANE_STATUS_GOOD;
case OPT_TL_X:
*val_p = SCANNER_UNIT_TO_FIXED_MM(s->tl_x);
return SANE_STATUS_GOOD;
case OPT_TL_Y:
*val_p = SCANNER_UNIT_TO_FIXED_MM(s->tl_y);
return SANE_STATUS_GOOD;
case OPT_BR_X:
*val_p = SCANNER_UNIT_TO_FIXED_MM(s->br_x);
return SANE_STATUS_GOOD;
case OPT_BR_Y:
*val_p = SCANNER_UNIT_TO_FIXED_MM(s->br_y);
return SANE_STATUS_GOOD;
case OPT_PAGE_WIDTH:
*val_p = SCANNER_UNIT_TO_FIXED_MM(s->page_width);
return SANE_STATUS_GOOD;
case OPT_PAGE_HEIGHT:
*val_p = SCANNER_UNIT_TO_FIXED_MM(s->page_height);
return SANE_STATUS_GOOD;
case OPT_BRIGHTNESS:
*val_p = s->brightness;
return SANE_STATUS_GOOD;
case OPT_CONTRAST:
*val_p = s->contrast;
return SANE_STATUS_GOOD;
case OPT_GAMMA:
*val_p = SANE_FIX(s->gamma);
return SANE_STATUS_GOOD;
case OPT_THRESHOLD:
*val_p = s->threshold;
return SANE_STATUS_GOOD;
case OPT_THRESHOLD_CURVE:
*val_p = s->threshold_curve;
return SANE_STATUS_GOOD;
/* Sensor Group */
case OPT_SCAN_SW:
get_hardware_status(s);
*val_p = s->hw_scan_sw;
return SANE_STATUS_GOOD;
case OPT_HOPPER:
get_hardware_status(s);
*val_p = s->hw_hopper;
return SANE_STATUS_GOOD;
case OPT_TOP:
get_hardware_status(s);
*val_p = s->hw_top;
return SANE_STATUS_GOOD;
case OPT_ADF_OPEN:
get_hardware_status(s);
*val_p = s->hw_adf_open;
return SANE_STATUS_GOOD;
case OPT_SLEEP:
get_hardware_status(s);
*val_p = s->hw_sleep;
return SANE_STATUS_GOOD;
}
}
else if (action == SANE_ACTION_SET_VALUE) {
int tmp;
SANE_Word val_c;
SANE_Status status;
DBG (20, "sane_control_option: set value for '%s' (%d)\n", s->opt[option].name,option);
if ( s->started ) {
DBG (5, "sane_control_option: cant set, device busy\n");
return SANE_STATUS_DEVICE_BUSY;
}
if (!SANE_OPTION_IS_SETTABLE (s->opt[option].cap)) {
DBG (5, "sane_control_option: not settable\n");
return SANE_STATUS_INVAL;
}
status = sanei_constrain_value (s->opt + option, val, info);
if (status != SANE_STATUS_GOOD) {
DBG (5, "sane_control_option: bad value\n");
return status;
}
/* may have been changed by constrain, so dont copy until now */
val_c = *(SANE_Word *)val;
/*
* Note - for those options which can assume one of a list of
* valid values, we can safely assume that they will have
* exactly one of those values because that's what
* sanei_constrain_value does. Hence no "else: invalid" branches
* below.
*/
switch (option) {
/* Mode Group */
case OPT_SOURCE:
if (!strcmp (val, STRING_ADFFRONT)) {
tmp = SOURCE_ADF_FRONT;
}
else if (!strcmp (val, STRING_ADFBACK)) {
tmp = SOURCE_ADF_BACK;
}
else if (!strcmp (val, STRING_ADFDUPLEX)) {
tmp = SOURCE_ADF_DUPLEX;
}
else{
tmp = SOURCE_FLATBED;
}
if (s->source == tmp)
return SANE_STATUS_GOOD;
s->source = tmp;
*info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
return SANE_STATUS_GOOD;
case OPT_MODE:
if (!strcmp (val, STRING_LINEART)) {
tmp = MODE_LINEART;
}
else if (!strcmp (val, STRING_GRAYSCALE)) {
tmp = MODE_GRAYSCALE;
}
else{
tmp = MODE_COLOR;
}
if (tmp == s->mode)
return SANE_STATUS_GOOD;
s->mode = tmp;
*info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
return change_params(s);
case OPT_X_RES:
if (s->resolution_x == val_c)
return SANE_STATUS_GOOD;
s->resolution_x = val_c;
*info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
return change_params(s);
/* Geometry Group */
case OPT_TL_X:
if (s->tl_x == FIXED_MM_TO_SCANNER_UNIT(val_c))
return SANE_STATUS_GOOD;
s->tl_x = FIXED_MM_TO_SCANNER_UNIT(val_c);
*info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
return SANE_STATUS_GOOD;
case OPT_TL_Y:
if (s->tl_y == FIXED_MM_TO_SCANNER_UNIT(val_c))
return SANE_STATUS_GOOD;
s->tl_y = FIXED_MM_TO_SCANNER_UNIT(val_c);
*info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
return SANE_STATUS_GOOD;
case OPT_BR_X:
if (s->br_x == FIXED_MM_TO_SCANNER_UNIT(val_c))
return SANE_STATUS_GOOD;
s->br_x = FIXED_MM_TO_SCANNER_UNIT(val_c);
*info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
return SANE_STATUS_GOOD;
case OPT_BR_Y:
if (s->br_y == FIXED_MM_TO_SCANNER_UNIT(val_c))
return SANE_STATUS_GOOD;
s->br_y = FIXED_MM_TO_SCANNER_UNIT(val_c);
*info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
return SANE_STATUS_GOOD;
case OPT_PAGE_WIDTH:
if (s->page_width == FIXED_MM_TO_SCANNER_UNIT(val_c))
return SANE_STATUS_GOOD;
s->page_width = FIXED_MM_TO_SCANNER_UNIT(val_c);
*info |= SANE_INFO_RELOAD_OPTIONS;
return SANE_STATUS_GOOD;
case OPT_PAGE_HEIGHT:
if (s->page_height == FIXED_MM_TO_SCANNER_UNIT(val_c))
return SANE_STATUS_GOOD;
s->page_height = FIXED_MM_TO_SCANNER_UNIT(val_c);
*info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
return change_params(s);
/* Enhancement Group */
case OPT_BRIGHTNESS:
s->brightness = val_c;
return SANE_STATUS_GOOD;
case OPT_CONTRAST:
s->contrast = val_c;
return SANE_STATUS_GOOD;
case OPT_GAMMA:
s->gamma = SANE_UNFIX(val_c);
return SANE_STATUS_GOOD;
case OPT_THRESHOLD:
s->threshold = val_c;
return SANE_STATUS_GOOD;
case OPT_THRESHOLD_CURVE:
s->threshold_curve = val_c;
return SANE_STATUS_GOOD;
} /* switch */
} /* else */
return SANE_STATUS_INVAL;
}
/* use height and width to initialize rest of transfer vals */
static void
update_transfer_totals(struct transfer * t)
{
if (t->image == NULL) return;
t->total_bytes = t->line_stride * t->image->height;
t->rx_bytes = 0;
t->done = 0;
}
/* each model has various settings that differ based on X resolution */
/* we hard-code the list (determined from usb snoops) here */
struct model_res {
int model;
int x_res;
int y_res;
int usb_power;
int max_x;
int min_x;
int max_y;
int min_y;
int act_width; /* total data width output, in pixels per side (always 3 sides) */
int req_width; /* stride in pixels per side between color planes (always 3 sides) */
int head_width;
int pad_width;
int block_height;
int cal_width;
int cal_headwidth;
int cal_reqwidth;
unsigned char * sw_coarsecal;
unsigned char * sw_finecal;
unsigned char * sw_sendcal;
unsigned char * head_cal1;
unsigned char * head_cal2;
unsigned char * sw_scan;
};
static struct model_res settings[] = {
/*S300 AC*/
/* model xres yres u mxx mnx mxy mny actw reqw hedw padw bh calw cal_hedw cal_reqw */
{ MODEL_S300, 150, 150, 0, 1296, 32, 2662, 32, 4256, 1480, 1296, 184, 41, 8512, 2592, 2960,
setWindowCoarseCal_S300_150, setWindowFineCal_S300_150,
setWindowSendCal_S300_150, sendCal1Header_S300_150,
sendCal2Header_S300_150, setWindowScan_S300_150 },
{ MODEL_S300, 225, 200, 0, 1944, 32, 3993, 32, 6144, 2100, 1944, 156, 28, 8192, 2592, 2800,
setWindowCoarseCal_S300_225, setWindowFineCal_S300_225,
setWindowSendCal_S300_225, sendCal1Header_S300_225,
sendCal2Header_S300_225, setWindowScan_S300_225 },
{ MODEL_S300, 300, 300, 0, 2592, 32, 5324, 32, 8192, 2800, 2592, 208, 21, 8192, 2592, 2800,
setWindowCoarseCal_S300_300, setWindowFineCal_S300_300,
setWindowSendCal_S300_300, sendCal1Header_S300_300,
sendCal2Header_S300_300, setWindowScan_S300_300 },
{ MODEL_S300, 600, 600, 0, 5184, 32, 10648, 32, 16064, 5440, 5184, 256, 10, 16064, 5184, 5440,
setWindowCoarseCal_S300_600, setWindowFineCal_S300_600,
setWindowSendCal_S300_600, sendCal1Header_S300_600,
sendCal2Header_S300_600, setWindowScan_S300_600 },
/*S300 USB*/
/* model xres yres u mxx mnx mxy mny actw reqw hedw padw bh calw cal_hedw cal_reqw */
{ MODEL_S300, 150, 150, 1, 1296, 32, 2662, 32, 7216, 2960, 1296, 1664, 24, 14432, 2592, 5920,
setWindowCoarseCal_S300_150_U, setWindowFineCal_S300_150_U,
setWindowSendCal_S300_150_U, sendCal1Header_S300_150_U,
sendCal2Header_S300_150_U, setWindowScan_S300_150_U },
{ MODEL_S300, 225, 200, 1, 1944, 32, 3993, 32, 10584, 4320, 1944, 2376, 16, 14112, 2592, 5760,
setWindowCoarseCal_S300_225_U, setWindowFineCal_S300_225_U,
setWindowSendCal_S300_225_U, sendCal1Header_S300_225_U,
sendCal2Header_S300_225_U, setWindowScan_S300_225_U },
{ MODEL_S300, 300, 300, 1, 2592, 32, 5324, 32, 15872, 6640, 2592, 4048, 11, 15872, 2592, 6640,
setWindowCoarseCal_S300_300_U, setWindowFineCal_S300_300_U,
setWindowSendCal_S300_300_U, sendCal1Header_S300_300_U,
sendCal2Header_S300_300_U, setWindowScan_S300_300_U },
{ MODEL_S300, 600, 600, 1, 5184, 32, 10648, 32, 16064, 5440, 5184, 256, 10, 16064, 5184, 5440,
setWindowCoarseCal_S300_600, setWindowFineCal_S300_600,
setWindowSendCal_S300_600, sendCal1Header_S300_600,
sendCal2Header_S300_600, setWindowScan_S300_600 },
/*fi-60F*/
/* model xres yres u mxx mnx mxy mny actw reqw hedw padw bh calw cal_hedw cal_reqw */
{ MODEL_FI60F, 150, 150, 0, 648, 32, 875, 32, 1480, 632, 216, 416, 41, 1480, 216, 632,
setWindowCoarseCal_FI60F_150, setWindowFineCal_FI60F_150,
setWindowSendCal_FI60F_150, sendCal1Header_FI60F_150,
sendCal2Header_FI60F_150, setWindowScan_FI60F_150 },
{ MODEL_FI60F, 300, 300, 0, 1296, 32, 1749, 32, 2400, 958, 432, 526, 72, 2400, 432, 958,
setWindowCoarseCal_FI60F_300, setWindowFineCal_FI60F_300,
setWindowSendCal_FI60F_300, sendCal1Header_FI60F_300,
sendCal2Header_FI60F_300, setWindowScan_FI60F_300 },
{ MODEL_FI60F, 600, 600, 0, 2592, 32, 3498, 32, 2848, 978, 864, 114, 61, 2848, 864, 978,
setWindowCoarseCal_FI60F_600, setWindowFineCal_FI60F_600,
setWindowSendCal_FI60F_600, sendCal1Header_FI60F_600,
sendCal2Header_FI60F_600, setWindowScan_FI60F_600 },
{ MODEL_NONE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
NULL, NULL, NULL, NULL, NULL, NULL },
};
/*
* clean up scanner struct vals when user changes mode, res, etc
*/
static SANE_Status
change_params(struct scanner *s)
{
SANE_Status ret = SANE_STATUS_GOOD;
int img_heads, img_pages, width;
int i=0;
DBG (10, "change_params: start\n");
do {
if(settings[i].model == s->model
&& settings[i].x_res == s->resolution_x
&& settings[i].usb_power == s->usb_power){
/*pull in closest y resolution*/
s->resolution_y = settings[i].y_res;
/*1200 dpi*/
s->max_x = settings[i].max_x * 1200/s->resolution_x;
s->min_x = settings[i].min_x * 1200/s->resolution_x;
s->max_y = settings[i].max_y * 1200/s->resolution_y;
s->min_y = settings[i].min_y * 1200/s->resolution_y;
s->page_width = s->max_x;
s->br_x = s->max_x;
s->br_y = s->max_y;
/*current dpi*/
s->setWindowCoarseCal = settings[i].sw_coarsecal;
s->setWindowCoarseCalLen = SET_WINDOW_LEN;
s->setWindowFineCal = settings[i].sw_finecal;
s->setWindowFineCalLen = SET_WINDOW_LEN;
s->setWindowSendCal = settings[i].sw_sendcal;
s->setWindowSendCalLen = SET_WINDOW_LEN;
s->sendCal1Header = settings[i].head_cal1;
s->sendCal1HeaderLen = 14;
s->sendCal2Header = settings[i].head_cal2;
s->sendCal2HeaderLen = 7;
s->setWindowScan = settings[i].sw_scan;
s->setWindowScanLen = SET_WINDOW_LEN;
break;
}
i++;
} while (settings[i].model);
if (!settings[i].model)
{
return SANE_STATUS_INVAL;
}
if (s->model == MODEL_S300)
{
img_heads = 1; /* image width is the same as the plane width on the S300 */
img_pages = 2;
}
else /* (s->model == MODEL_FI60F) */
{
img_heads = 3; /* image width is 3* the plane width on the FI-60F */
img_pages = 1;
}
/* set up the transfer structs */
s->cal_image.plane_width = settings[i].cal_headwidth;
s->cal_image.plane_stride = settings[i].cal_reqwidth * 3;
s->cal_image.line_stride = settings[i].cal_width * 3;
s->cal_image.raw_data = NULL;
s->cal_image.image = NULL;
s->cal_data.plane_width = settings[i].cal_headwidth; /* width is the same, but there are 2 bytes per pixel component */
s->cal_data.plane_stride = settings[i].cal_reqwidth * 6;
s->cal_data.line_stride = settings[i].cal_width * 6;
s->cal_data.raw_data = NULL;
s->cal_data.image = &s->sendcal;
s->block_xfr.plane_width = settings[i].head_width;
s->block_xfr.plane_stride = settings[i].req_width * 3;
s->block_xfr.line_stride = settings[i].act_width * 3;
s->block_xfr.raw_data = NULL;
s->block_xfr.image = &s->block_img;
/* set up the block image used during scanning operation */
width = s->block_xfr.plane_width * img_heads;
s->block_img.width_pix = width;
s->block_img.width_bytes = width * 3;
s->block_img.height = settings[i].block_height;
s->block_img.pages = img_pages;
s->block_img.buffer = NULL;
/* set up the calibration image blocks */
width = s->cal_image.plane_width * img_heads;
s->coarsecal.width_pix = s->darkcal.width_pix = s->lightcal.width_pix = width;
s->coarsecal.width_bytes = s->darkcal.width_bytes = s->lightcal.width_bytes = width * 3;
s->coarsecal.height = 1;
s->darkcal.height = s->lightcal.height = 16;
s->coarsecal.pages = s->darkcal.pages = s->lightcal.pages = img_pages;
s->coarsecal.buffer = s->darkcal.buffer = s->lightcal.buffer = NULL;
/* set up the calibration data block */
width = s->cal_data.plane_width * img_heads;
s->sendcal.width_pix = width;
s->sendcal.width_bytes = width * 6; /* 2 bytes of cal data per pixel component */
s->sendcal.height = 1;
s->sendcal.pages = img_pages;
s->sendcal.buffer = NULL;
/* set up the fullscan parameters */
s->fullscan.width_bytes = s->block_xfr.line_stride;
if(s->source == SOURCE_FLATBED || !s->page_height)
{
/* flatbed and adf in autodetect always ask for all*/
s->fullscan.height = s->max_y * s->resolution_y / 1200;
}
else
{
/* adf with specified paper size requires padding (~1/2in) */
s->fullscan.height = (s->page_height+600) * s->resolution_y / 1200;
}
/* fill in front settings */
s->front.width_pix = s->block_img.width_pix;
switch (s->mode) {
case MODE_COLOR:
s->front.width_bytes = s->front.width_pix*3;
break;
case MODE_GRAYSCALE:
s->front.width_bytes = s->front.width_pix;
break;
default: /*binary*/
s->front.width_bytes = s->front.width_pix/8;
break;
}
/*output image might be taller than scan due to interpolation*/
s->front.height = s->fullscan.height * s->resolution_x / s->resolution_y;
s->front.pages = 1;
s->front.buffer = NULL;
/* back settings always same as front settings */
s->back.width_pix = s->front.width_pix;
s->back.width_bytes = s->front.width_bytes;
s->back.height = s->front.height;
s->back.pages = 1;
s->back.buffer = NULL;
/* dynamic threshold temp buffer, in gray */
s->dt.width_pix = s->front.width_pix;
s->dt.width_bytes = s->front.width_pix;
s->dt.height = 1;
s->dt.pages = 1;
s->dt.buffer = NULL;
/* set up the pointers to the page images in the page structs */
s->pages[SIDE_FRONT].image = &s->front;
s->pages[SIDE_BACK].image = &s->back;
s->pages[SIDE_FRONT].done = 0;
s->pages[SIDE_BACK].done = 0;
DBG (10, "change_params: finish\n");
return ret;
}
/* Function to build a lookup table (LUT), often
used by scanners to implement brightness/contrast/gamma
or by backends to speed binarization/thresholding
offset and slope inputs are -127 to +127
slope rotates line around central input/output val,
0 makes horizontal line
pos zero neg
. x . . x
. x . . x
out . x .xxxxxxxxxxx . x
. x . . x
....x....... ............ .......x....
in in in
offset moves line vertically, and clamps to output range
0 keeps the line crossing the center of the table
high low
. xxxxxxxx .
. x .
out x . x
. . x
............ xxxxxxxx....
in in
out_min/max provide bounds on output values,
useful when building thresholding lut.
0 and 255 are good defaults otherwise.
*/
static SANE_Status
load_lut (unsigned char * lut,
int in_bits, int out_bits,
int out_min, int out_max,
int slope, int offset)
{
SANE_Status ret = SANE_STATUS_GOOD;
int i, j;
double shift, rise;
int max_in_val = (1 << in_bits) - 1;
int max_out_val = (1 << out_bits) - 1;
unsigned char * lut_p = lut;
DBG (10, "load_lut: start\n");
/* slope is converted to rise per unit run:
* first [-127,127] to [-1,1]
* then multiply by PI/2 to convert to radians
* then take the tangent (T.O.A)
* then multiply by the normal linear slope
* because the table may not be square, i.e. 1024x256*/
rise = tan((double)slope/127 * M_PI/2) * max_out_val / max_in_val;
/* line must stay vertically centered, so figure
* out vertical offset at central input value */
shift = (double)max_out_val/2 - (rise*max_in_val/2);
/* convert the user offset setting to scale of output
* first [-127,127] to [-1,1]
* then to [-max_out_val/2,max_out_val/2]*/
shift += (double)offset / 127 * max_out_val / 2;
for(i=0;i<=max_in_val;i++){
j = rise*i + shift;
if(j<out_min){
j=out_min;
}
else if(j>out_max){
j=out_max;
}
*lut_p=j;
lut_p++;
}
hexdump(5, "load_lut: ", lut, max_in_val+1);
DBG (10, "load_lut: finish\n");
return ret;
}
/*
* @@ Section 4 - SANE scanning functions
*/
/*
* Called by SANE to retrieve information about the type of data
* that the current scan will return.
*
* From the SANE spec:
* This function is used to obtain the current scan parameters. The
* returned parameters are guaranteed to be accurate between the time
* a scan has been started (sane_start() has been called) and the
* completion of that request. Outside of that window, the returned
* values are best-effort estimates of what the parameters will be
* when sane_start() gets invoked.
*
* Calling this function before a scan has actually started allows,
* for example, to get an estimate of how big the scanned image will
* be. The parameters passed to this function are the handle h of the
* device for which the parameters should be obtained and a pointer p
* to a parameter structure.
*/
SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
struct scanner *s = (struct scanner *) handle;
DBG (10, "sane_get_parameters: start\n");
params->pixels_per_line = s->front.width_pix;
params->bytes_per_line = s->front.width_bytes;
if(!s->page_height){
params->lines = -1;
}
else{
params->lines = s->front.height;
}
params->last_frame = 1;
if (s->mode == MODE_COLOR) {
params->format = SANE_FRAME_RGB;
params->depth = 8;
}
else if (s->mode == MODE_GRAYSCALE) {
params->format = SANE_FRAME_GRAY;
params->depth = 8;
}
else if (s->mode == MODE_LINEART) {
params->format = SANE_FRAME_GRAY;
params->depth = 1;
}
DBG (15, "\tdepth %d\n", params->depth);
DBG (15, "\tlines %d\n", params->lines);
DBG (15, "\tpixels_per_line %d\n", params->pixels_per_line);
DBG (15, "\tbytes_per_line %d\n", params->bytes_per_line);
DBG (10, "sane_get_parameters: finish\n");
return SANE_STATUS_GOOD;
}
/*
* Called by SANE when a page acquisition operation is to be started.
* FIXME: wont handle SOURCE_ADF_BACK
*/
SANE_Status
sane_start (SANE_Handle handle)
{
struct scanner *s = handle;
SANE_Status ret;
int i;
DBG (10, "sane_start: start\n");
/* set side marker on first page */
if(!s->started){
if(s->source == SOURCE_ADF_BACK){
s->side = SIDE_BACK;
}
else{
s->side = SIDE_FRONT;
}
}
/* if already running, duplex needs to switch sides */
else if(s->source == SOURCE_ADF_DUPLEX){
s->side = !s->side;
}
/* ingest paper with adf */
if( s->source == SOURCE_ADF_BACK || s->source == SOURCE_ADF_FRONT
|| (s->source == SOURCE_ADF_DUPLEX && s->side == SIDE_FRONT) ){
ret = ingest(s);
if (ret != SANE_STATUS_GOOD) {
DBG (5, "sane_start: ERROR: failed to ingest\n");
sane_cancel((SANE_Handle)s);
return ret;
}
}
/* first page requires buffers, etc */
if(!s->started){
DBG(15,"sane_start: first page\n");
s->started=1;
ret = teardown_buffers(s);
if (ret != SANE_STATUS_GOOD) {
DBG (5, "sane_start: ERROR: failed to teardown buffers\n");
sane_cancel((SANE_Handle)s);
return SANE_STATUS_NO_MEM;
}
ret = change_params(s);
if (ret != SANE_STATUS_GOOD) {
DBG (5, "sane_start: ERROR: failed to change_params\n");
sane_cancel((SANE_Handle)s);
return SANE_STATUS_NO_MEM;
}
ret = setup_buffers(s);
if (ret != SANE_STATUS_GOOD) {
DBG (5, "sane_start: ERROR: failed to setup buffers\n");
sane_cancel((SANE_Handle)s);
return SANE_STATUS_NO_MEM;
}
ret = load_lut(s->dt_lut, 8, 8, 50, 205,
s->threshold_curve, s->threshold-127);
if (ret != SANE_STATUS_GOOD) {
DBG (5, "sane_start: ERROR: failed to load_lut for dt\n");
sane_cancel((SANE_Handle)s);
return ret;
}
ret = coarsecal(s);
if (ret != SANE_STATUS_GOOD) {
DBG (5, "sane_start: ERROR: failed to coarsecal\n");
sane_cancel((SANE_Handle)s);
return ret;
}
ret = finecal(s);
if (ret != SANE_STATUS_GOOD) {
DBG (5, "sane_start: ERROR: failed to finecal\n");
sane_cancel((SANE_Handle)s);
return ret;
}
ret = send_lut(s);
if (ret != SANE_STATUS_GOOD) {
DBG (5, "sane_start: ERROR: failed to send lut\n");
sane_cancel((SANE_Handle)s);
return ret;
}
ret = lamp(s,1);
if (ret != SANE_STATUS_GOOD) {
DBG (5, "sane_start: ERROR: failed to heat lamp\n");
sane_cancel((SANE_Handle)s);
return ret;
}
/*should this be between each page*/
ret = set_window(s,WINDOW_SCAN);
if (ret != SANE_STATUS_GOOD) {
DBG (5, "sane_start: ERROR: failed to set window\n");
sane_cancel((SANE_Handle)s);
return ret;
}
}
/* reset everything when starting any front, or just back */
if(s->side == SIDE_FRONT || s->source == SOURCE_ADF_BACK){
DBG(15,"sane_start: reset counters\n");
/* reset scan */
s->fullscan.done = 0;
s->fullscan.rx_bytes = 0;
s->fullscan.total_bytes = s->fullscan.width_bytes * s->fullscan.height;
/* reset block */
update_transfer_totals(&s->block_xfr);
/* reset front and back page counters */
for (i = 0; i < 2; i++)
{
struct image *page_img = s->pages[i].image;
s->pages[i].bytes_total = page_img->width_bytes * page_img->height;
s->pages[i].bytes_scanned = 0;
s->pages[i].bytes_read = 0;
s->pages[i].done = 0;
}
ret = scan(s);
if (ret != SANE_STATUS_GOOD) {
DBG (5, "sane_start: ERROR: failed to start scan\n");
sane_cancel((SANE_Handle)s);
return ret;
}
}
else{
DBG(15,"sane_start: back side\n");
}
DBG (10, "sane_start: finish\n");
return SANE_STATUS_GOOD;
}
/* the +8 on all the lengths is to makeup for potential block trailers */
static SANE_Status
setup_buffers(struct scanner *s)
{
SANE_Status ret = SANE_STATUS_GOOD;
DBG (10, "setup_buffers: start\n");
/* temporary cal data */
s->coarsecal.buffer = calloc (1,s->coarsecal.width_bytes * s->coarsecal.height * s->coarsecal.pages);
if(!s->coarsecal.buffer){
DBG (5, "setup_buffers: ERROR: failed to setup coarse cal buffer\n");
return SANE_STATUS_NO_MEM;
}
s->darkcal.buffer = calloc (1,s->darkcal.width_bytes * s->darkcal.height * s->darkcal.pages);
if(!s->darkcal.buffer){
DBG (5, "setup_buffers: ERROR: failed to setup fine cal buffer\n");
return SANE_STATUS_NO_MEM;
}
s->lightcal.buffer = calloc (1,s->lightcal.width_bytes * s->lightcal.height * s->lightcal.pages);
if(!s->lightcal.buffer){
DBG (5, "setup_buffers: ERROR: failed to setup fine cal buffer\n");
return SANE_STATUS_NO_MEM;
}
s->sendcal.buffer = calloc (1,s->sendcal.width_bytes * s->sendcal.height * s->sendcal.pages);
if(!s->sendcal.buffer){
DBG (5, "setup_buffers: ERROR: failed to setup send cal buffer\n");
return SANE_STATUS_NO_MEM;
}
s->cal_image.raw_data = calloc(1, s->cal_image.line_stride * 16 + 8); /* maximum 16 lines input for fine calibration */
if(!s->cal_image.raw_data){
DBG (5, "setup_buffers: ERROR: failed to setup calibration input raw data buffer\n");
return SANE_STATUS_NO_MEM;
}
s->cal_data.raw_data = calloc(1, s->cal_data.line_stride); /* only 1 line of data is sent */
if(!s->cal_data.raw_data){
DBG (5, "setup_buffers: ERROR: failed to setup calibration output raw data buffer\n");
return SANE_STATUS_NO_MEM;
}
/* grab up to 512K at a time */
s->block_img.buffer = calloc (1,s->block_img.width_bytes * s->block_img.height * s->block_img.pages);
if(!s->block_img.buffer){
DBG (5, "setup_buffers: ERROR: failed to setup block image buffer\n");
return SANE_STATUS_NO_MEM;
}
s->block_xfr.raw_data = calloc(1, s->block_xfr.line_stride * s->block_img.height + 8);
if(!s->block_xfr.raw_data){
DBG (5, "setup_buffers: ERROR: failed to setup block raw data buffer\n");
return SANE_STATUS_NO_MEM;
}
/* one grayscale line for dynamic threshold */
s->dt.buffer = calloc (1,s->dt.width_bytes * s->dt.height * s->dt.pages);
if(!s->dt.buffer){
DBG (5, "setup_buffers: ERROR: failed to setup dt buffer\n");
return SANE_STATUS_NO_MEM;
}
/* make image buffer to hold frontside data */
if(s->source != SOURCE_ADF_BACK){
s->front.buffer = calloc (1,s->front.width_bytes * s->front.height * s->front.pages);
if(!s->front.buffer){
DBG (5, "setup_buffers: ERROR: failed to setup front buffer\n");
return SANE_STATUS_NO_MEM;
}
}
/* make image buffer to hold backside data */
if(s->source == SOURCE_ADF_DUPLEX || s->source == SOURCE_ADF_BACK){
s->back.buffer = calloc (1,s->back.width_bytes * s->back.height * s->back.pages);
if(!s->back.buffer){
DBG (5, "setup_buffers: ERROR: failed to setup back buffer\n");
return SANE_STATUS_NO_MEM;
}
}
DBG (10, "setup_buffers: finish\n");
return ret;
}
/*
coarse calibration consists of:
1. turn lamp off (d0)
2. set window for single line of data (d1)
3. get line (d2)
4. update dark coarse cal (c6)
5. return to #3 if not dark enough
6. turn lamp on (d0)
7. get line (d2)
8. update light coarse cal (c6)
9. return to #7 if not light enough
*/
static SANE_Status
coarsecal(struct scanner *s)
{
SANE_Status ret = SANE_STATUS_GOOD;
size_t cmdLen = 2;
unsigned char cmd[2];
size_t statLen = 1;
unsigned char stat[1];
size_t payLen = 28;
unsigned char pay[28];
int try_count, cal_good[2], x, i, j;
int param[2], zcount[2], high_param[2], low_param[2], avg[2], maxval[2];
int rgb_avg[2][3], rgb_hicount[2][3];
DBG (10, "coarsecal: start\n");
if(s->model == MODEL_S300){
memcpy(pay,coarseCalData_S300,payLen);
}
else{
memcpy(pay,coarseCalData_FI60F,payLen);
}
/* ask for 1 line */
ret = set_window(s, WINDOW_COARSECAL);
if(ret){
DBG (5, "coarsecal: error sending setwindow\n");
return ret;
}
/* dark cal, lamp off */
ret = lamp(s,0);
if(ret){
DBG (5, "coarsecal: error lamp off\n");
return ret;
}
try_count = 8;
param[0] = 63;
param[1] = 63;
low_param[0] = low_param[1] = -64; /* The S300 will accept coarse offsets from -128 to 127 */
high_param[0] = high_param[1] = 63; /* By our range is limited to converge faster */
cal_good[0] = cal_good[1] = 0;
while (try_count > 0){
try_count--;
/* update the coarsecal payload to use our new dark offset parameters */
if (s->model == MODEL_S300)
{
pay[5] = param[0];
pay[7] = param[1];
}
else /* (s->model == MODEL_FI60F) */
{
pay[5] = param[0];
pay[7] = param[0];
pay[9] = param[0];
}
/* send coarse cal (c6) */
cmd[0] = 0x1b;
cmd[1] = 0xc6;
stat[0] = 0;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "coarsecal: error sending c6 cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "coarsecal: cmd bad c6 status?\n");
return SANE_STATUS_IO_ERROR;
}
/*send coarse cal payload*/
stat[0] = 0;
statLen = 1;
ret = do_cmd(
s, 0,
pay, payLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "coarsecal: error sending c6 payload\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "coarsecal: c6 payload bad status?\n");
return SANE_STATUS_IO_ERROR;
}
DBG(15, "coarsecal offset: parameter front: %i back: %i\n", param[0], param[1]);
/* send scan d2 command */
cmd[0] = 0x1b;
cmd[1] = 0xd2;
stat[0] = 0;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "coarsecal: error sending d2 cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "coarsecal: cmd bad d2 status?\n");
return SANE_STATUS_IO_ERROR;
}
s->cal_image.image = &s->coarsecal;
update_transfer_totals(&s->cal_image);
while(!s->cal_image.done){
ret = read_from_scanner(s,&s->cal_image);
if(ret){
DBG (5, "coarsecal: cant read from scanner\n");
return ret;
}
}
/* convert the raw data into normal packed pixel data */
descramble_raw(s, &s->cal_image);
/* gather statistics: count the proportion of 0-valued pixels */
/* since the lamp is off, there's no point in looking at the green or blue data - they're all from the same sensor anyway */
zcount[0] = zcount[1] = 0;
avg[0] = avg[1] = 0;
maxval[0] = maxval[1] = 0;
for (j = 0; j < s->coarsecal.pages; j++)
{
int page_offset = j * s->coarsecal.width_bytes * s->coarsecal.height;
for (x = 0; x < s->coarsecal.width_bytes; x++)
{
int val = s->coarsecal.buffer[page_offset + x];
avg[j] += val;
if (val == 0) zcount[j]++;
if (val > maxval[j]) maxval[j] = val;
}
}
/* convert the zero counts from a pixel count to a proportion in tenths of a percent */
for (j = 0; j < s->coarsecal.pages; j++)
{
avg[j] /= s->coarsecal.width_bytes;
zcount[j] = zcount[j] * 1000 / s->coarsecal.width_bytes;
}
DBG(15, "coarsecal offset: average pixel values front: %i back: %i\n", avg[0], avg[1]);
DBG(15, "coarsecal offset: maximum pixel values front: %i back: %i\n", maxval[0], maxval[1]);
DBG(15, "coarsecal offset: 0-valued pixel count front: %f%% back: %f%%\n", zcount[0] / 10.0f, zcount[1] / 10.0f);
/* check the values, adjust parameters if they are not within the target range */
for (j = 0; j < s->coarsecal.pages; j++)
{
if (!cal_good[j])
{
if (avg[j] > COARSE_OFFSET_TARGET)
{
high_param[j] = param[j];
param[j] = (low_param[j] + high_param[j]) / 2;
}
else if (avg[j] < COARSE_OFFSET_TARGET)
{
low_param[j] = param[j];
param[j] = (low_param[j] + high_param[j]) / 2;
}
else cal_good[j] = 1;
}
}
if (cal_good[0] + cal_good[1] == s->coarsecal.pages) break;
} /* continue looping for up to 8 tries */
/* light cal, lamp on */
ret = lamp(s,1);
if(ret){
DBG (5, "coarsecal: error lamp on\n");
return ret;
}
try_count = 8;
param[0] = pay[11];
param[1] = pay[13];
low_param[0] = low_param[1] = 0;
high_param[0] = high_param[1] = 63;
cal_good[0] = cal_good[1] = 0;
while (try_count > 0){
try_count--;
/* send coarse cal (c6) */
cmd[0] = 0x1b;
cmd[1] = 0xc6;
stat[0] = 0;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "coarsecal: error sending c6 cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "coarsecal: cmd bad c6 status?\n");
return SANE_STATUS_IO_ERROR;
}
/*send coarse cal payload*/
stat[0] = 0;
statLen = 1;
ret = do_cmd(
s, 0,
pay, payLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "coarsecal: error sending c6 payload\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "coarsecal: c6 payload bad status?\n");
return SANE_STATUS_IO_ERROR;
}
DBG(15, "coarsecal gain: parameter front: %i back: %i\n", param[0], param[1]);
/* send scan d2 command */
cmd[0] = 0x1b;
cmd[1] = 0xd2;
stat[0] = 0;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "coarsecal: error sending d2 cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "coarsecal: cmd bad d2 status?\n");
return SANE_STATUS_IO_ERROR;
}
s->cal_image.image = &s->coarsecal;
update_transfer_totals(&s->cal_image);
while(!s->cal_image.done){
ret = read_from_scanner(s,&s->cal_image);
if(ret){
DBG (5, "coarsecal: cant read from scanner\n");
return ret;
}
}
/* convert the raw data into normal packed pixel data */
descramble_raw(s, &s->cal_image);
/* gather statistics: count the proportion of 255-valued pixels in each color channel */
/* count the average pixel value in each color channel */
for (i = 0; i < s->coarsecal.pages; i++)
for (j = 0; j < 3; j++)
rgb_avg[i][j] = rgb_hicount[i][j] = 0;
for (i = 0; i < s->coarsecal.pages; i++)
{
for (x = 0; x < s->coarsecal.width_pix; x++)
{
/* get color channel values and count of pixels pegged at 255 */
unsigned char *rgbpix = s->coarsecal.buffer + (i * s->coarsecal.width_bytes * s->coarsecal.height) + x * 3;
for (j = 0; j < 3; j++)
{
rgb_avg[i][j] += rgbpix[j];
if (rgbpix[j] == 255)
rgb_hicount[i][j]++;
}
}
}
/* apply the color correction factors to the averages */
for (i = 0; i < s->coarsecal.pages; i++)
for (j = 0; j < 3; j++)
rgb_avg[i][j] *= white_factor[j];
/* set the gain so that none of the color channels are clipping, ie take the highest channel values */
for (i = 0; i < s->coarsecal.pages; i++)
{
avg[i] = MAX3(rgb_avg[i][0], rgb_avg[i][1], rgb_avg[i][2]) / s->coarsecal.width_pix;
for (j = 0; j < 3; j++)
rgb_avg[i][j] /= s->coarsecal.width_pix;
}
/* convert the 255-counts from a pixel count to a proportion in tenths of a percent */
for (i = 0; i < s->coarsecal.pages; i++)
{
for (j = 0; j < 3; j++)
{
rgb_hicount[i][j] = rgb_hicount[i][j] * 1000 / s->coarsecal.width_pix;
}
zcount[i] = MAX3(rgb_hicount[i][0], rgb_hicount[i][1], rgb_hicount[i][2]);
}
DBG(15, "coarsecal gain: average RGB values front: (%i,%i,%i) back: (%i,%i,%i)\n",
rgb_avg[0][0], rgb_avg[0][1], rgb_avg[0][2], rgb_avg[1][0], rgb_avg[1][1], rgb_avg[1][2]);
DBG(15, "coarsecal gain: 255-valued pixel count front: (%g,%g,%g) back: (%g,%g,%g)\n",
rgb_hicount[0][0]/10.0f, rgb_hicount[0][1]/10.0f, rgb_hicount[0][2]/10.0f,
rgb_hicount[1][0]/10.0f, rgb_hicount[1][1]/10.0f, rgb_hicount[1][2]/10.0f);
/* check the values, adjust parameters if they are not within the target range */
for (x = 0; x < s->coarsecal.pages; x++)
{
if (!cal_good[x])
{
if (zcount[x] > 9 || avg[x] > coarse_gain_max[x])
{
high_param[x] = param[x];
param[x] = (low_param[x] + high_param[x]) / 2;
}
else if (avg[x] < coarse_gain_min[x])
{
low_param[x] = param[x];
param[x] = (low_param[x] + high_param[x]) / 2;
}
else cal_good[x] = 1;
}
}
if (cal_good[0] + cal_good[1] == s->coarsecal.pages) break;
/* update the coarsecal payload to use the new gain parameters */
if (s->model == MODEL_S300)
{
pay[11] = param[0];
pay[13] = param[1];
}
else /* (s->model == MODEL_FI60F) */
{
pay[11] = param[0];
pay[13] = param[0];
pay[15] = param[0];
}
}
DBG (10, "coarsecal: finish\n");
return ret;
}
static SANE_Status
finecal_send_cal(struct scanner *s)
{
SANE_Status ret = SANE_STATUS_GOOD;
size_t cmdLen = 2;
unsigned char cmd[2];
size_t statLen = 1;
unsigned char stat[2];
int i, j, k;
unsigned short *p_out, *p_in = (unsigned short *) s->sendcal.buffer;
int planes = (s->model == MODEL_S300) ? 2 : 3;
/* scramble the raster buffer data into scanner raw format */
memset(s->cal_data.raw_data, 0, s->cal_data.line_stride);
for (i = 0; i < planes; i++)
for (j = 0; j < s->cal_data.plane_width; j++)
for (k = 0; k < 3; k++)
{
p_out = (unsigned short *) (s->cal_data.raw_data + k * s->cal_data.plane_stride + j * 6 + i * 2);
*p_out = *p_in++; /* dark offset, gain */
}
ret = set_window(s, WINDOW_SENDCAL);
if(ret){
DBG (5, "finecal_send_cal: error sending setwindow\n");
return ret;
}
/*first unknown cal block*/
cmd[0] = 0x1b;
cmd[1] = 0xc3;
stat[0] = 0;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "finecal_send_cal: error sending c3 cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "finecal_send_cal: cmd bad c3 status?\n");
return SANE_STATUS_IO_ERROR;
}
/*send header*/
/*send payload*/
statLen = 1;
ret = do_cmd(
s, 0,
s->sendCal1Header, s->sendCal1HeaderLen,
s->cal_data.raw_data, s->cal_data.line_stride,
stat, &statLen
);
if(ret){
DBG (5, "finecal_send_cal: error sending c3 payload\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "finecal_send_cal: payload bad c3 status?\n");
return SANE_STATUS_IO_ERROR;
}
/*second unknown cal block*/
cmd[1] = 0xc4;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "finecal_send_cal: error sending c4 cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "finecal_send_cal: cmd bad c4 status?\n");
return SANE_STATUS_IO_ERROR;
}
/*send header*/
/*send payload*/
statLen = 1;
ret = do_cmd(
s, 0,
s->sendCal2Header, s->sendCal2HeaderLen,
s->cal_data.raw_data, s->cal_data.line_stride,
stat, &statLen
);
if(ret){
DBG (5, "finecal_send_cal: error sending c4 payload\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "finecal_send_cal: payload bad c4 status?\n");
return SANE_STATUS_IO_ERROR;
}
return ret;
}
static SANE_Status
finecal_get_line(struct scanner *s, struct image *img)
{
SANE_Status ret = SANE_STATUS_GOOD;
size_t cmdLen = 2;
unsigned char cmd[2];
size_t statLen = 1;
unsigned char stat[2];
int round_offset = img->height / 2;
int i, j, k;
/* ask for 16 lines */
ret = set_window(s, WINDOW_FINECAL);
if(ret){
DBG (5, "finecal_get_line: error sending setwindowcal\n");
return ret;
}
/* send scan d2 command */
cmd[0] = 0x1b;
cmd[1] = 0xd2;
stat[0] = 0;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "finecal_get_line: error sending d2 cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "finecal_get_line: cmd bad d2 status?\n");
return SANE_STATUS_IO_ERROR;
}
s->cal_image.image = img;
update_transfer_totals(&s->cal_image);
while(!s->cal_image.done){
ret = read_from_scanner(s,&s->cal_image);
if(ret){
DBG (5, "finecal_get_line: cant read from scanner\n");
return ret;
}
}
/* convert the raw data into normal packed pixel data */
descramble_raw(s, &s->cal_image);
/* average the columns of pixels together and put the results in the top line(s) */
for (i = 0; i < img->pages; i++)
{
unsigned char *linepix = img->buffer + i * img->width_bytes * img->height;
unsigned char *avgpix = img->buffer + i * img->width_bytes;
for (j = 0; j < img->width_bytes; j++)
{
int total = 0;
for (k = 0; k < img->height; k++)
total += linepix[j + k * img->width_bytes];
avgpix[j] = (total + round_offset) / img->height;
}
}
return ret;
}
/* roundf() is c99, so we provide our own, though this version wont return -0 */
static float
round2(float x)
{
return (float)(x >= 0.0) ? (int)(x+0.5) : (int)(x-0.5);
}
static SANE_Status
finecal(struct scanner *s)
{
SANE_Status ret = SANE_STATUS_GOOD;
const int max_pages = (s->model == MODEL_S300 ? 2 : 1);
int gain_delta = 0xff - 0xbf;
float *gain_slope, *last_error;
int i, j, k, idx, try_count, cal_good;
DBG (10, "finecal: start\n");
/* set fine dark offset to 0 and fix all fine gains to lowest parameter (0xFF) */
for (i = 0; i < s->sendcal.width_bytes * s->sendcal.pages / 2; i++)
{
s->sendcal.buffer[i*2] = 0;
s->sendcal.buffer[i*2+1] = 0xff;
}
ret = finecal_send_cal(s);
if(ret) return ret;
/* grab rows with lamp on */
ret = lamp(s,1);
if(ret){
DBG (5, "finecal: error lamp on\n");
return ret;
}
/* read the low-gain average of 16 lines */
ret = finecal_get_line(s, &s->darkcal);
if(ret) return ret;
/* set fine dark offset to 0 and fine gain to a fixed higher-gain parameter (0xBF) */
for (i = 0; i < s->sendcal.width_bytes * s->sendcal.pages / 2; i++)
{
s->sendcal.buffer[i*2] = 0;
s->sendcal.buffer[i*2+1] = 0xbf;
}
ret = finecal_send_cal(s);
if(ret) return ret;
/* read the high-gain average of 16 lines */
ret = finecal_get_line(s, &s->lightcal);
if(ret) return ret;
/* calculate the per pixel slope of pixel value delta over gain delta */
gain_slope = malloc(s->lightcal.width_bytes * s->lightcal.pages * sizeof(float));
if (!gain_slope)
return SANE_STATUS_NO_MEM;
idx = 0;
for (i = 0; i < s->lightcal.pages; i++)
{
for (j = 0; j < s->lightcal.width_pix; j++)
{
for (k = 0; k < 3; k++)
{
int value_delta = s->lightcal.buffer[idx] - s->darkcal.buffer[idx];
/* limit this slope to 1 or less, to avoid overshoot if the lightcal ref input is clipped at 255 */
if (value_delta < gain_delta)
gain_slope[idx] = -1.0;
else
gain_slope[idx] = (float) -gain_delta / value_delta;
idx++;
}
}
}
/* keep track of the last iteration's pixel error. If we overshoot, we can reduce the value of the gain slope */
last_error = malloc(s->lightcal.width_bytes * s->lightcal.pages * sizeof(float));
if (!last_error)
{
free(gain_slope);
return SANE_STATUS_NO_MEM;
}
for (i = 0; i < s->lightcal.width_bytes * s->lightcal.pages; i++)
last_error[i] = 0.0;
/* fine calibration feedback loop */
try_count = 8;
while (try_count > 0)
{
int min_value[2][3], max_value[2][3];
float avg_value[2][3], variance[2][3];
int high_pegs = 0, low_pegs = 0;
try_count--;
/* clear statistics arrays */
for (i = 0; i < max_pages; i++)
{
for (k = 0; k < 3; k++)
{
min_value[i][k] = 0xff;
max_value[i][k] = 0;
avg_value[i][k] = 0;
variance[i][k] = 0;
}
}
/* gather statistics and calculate new fine gain parameters based on observed error and the value/gain slope */
idx = 0;
for (i = 0; i < max_pages; i++)
{
for (j = 0; j < s->lightcal.width_pix; j++)
{
for (k = 0; k < 3; k++)
{
int pixvalue = s->lightcal.buffer[idx];
float pixerror = (fine_gain_target[i] * white_factor[k] - pixvalue);
int oldgain = s->sendcal.buffer[idx * 2 + 1];
int newgain;
/* if we overshot the last correction, reduce the gain_slope */
if (pixerror * last_error[idx] < 0.0)
gain_slope[idx] *= 0.75;
last_error[idx] = pixerror;
/* set the new gain */
newgain = oldgain + (int) round2(pixerror * gain_slope[idx]);
if (newgain < 0)
{
low_pegs++;
s->sendcal.buffer[idx * 2 + 1] = 0;
}
else if (newgain > 0xff)
{
high_pegs++;
s->sendcal.buffer[idx * 2 + 1] = 0xff;
}
else
s->sendcal.buffer[idx * 2 + 1] = newgain;
/* update statistics */
if (pixvalue < min_value[i][k]) min_value[i][k] = pixvalue;
if (pixvalue > max_value[i][k]) max_value[i][k] = pixvalue;
avg_value[i][k] += pixerror;
variance[i][k] += (pixerror * pixerror);
idx++;
}
}
}
/* finish the statistics calculations */
cal_good = 1;
for (i = 0; i < max_pages; i++)
{
for (k = 0; k < 3; k++)
{
float sum = avg_value[i][k];
float sum2 = variance[i][k];
avg_value[i][k] = sum / s->lightcal.width_pix;
variance[i][k] = ((sum2 - (sum * sum / s->lightcal.width_pix)) / s->lightcal.width_pix);
/* if any color channel is too far out of whack, set cal_good to 0 so we'll iterate again */
if (fabs(avg_value[i][k]) > 1.0 || variance[i][k] > 3.0)
cal_good = 0;
}
}
/* print debug info */
DBG (15, "finecal: -------------------- Gain\n");
DBG (15, "finecal: RGB Average Error - Front: (%.1f,%.1f,%.1f) - Back: (%.1f,%.1f,%.1f)\n",
avg_value[0][0], avg_value[0][1], avg_value[0][2], avg_value[1][0], avg_value[1][1], avg_value[1][2]);
DBG (15, "finecal: RGB Maximum - Front: (%i,%i,%i) - Back: (%i,%i,%i)\n",
max_value[0][0], max_value[0][1], max_value[0][2], max_value[1][0], max_value[1][1], max_value[1][2]);
DBG (15, "finecal: RGB Minimum - Front: (%i,%i,%i) - Back: (%i,%i,%i)\n",
min_value[0][0], min_value[0][1], min_value[0][2], min_value[1][0], min_value[1][1], min_value[1][2]);
DBG (15, "finecal: Variance - Front: (%.1f,%.1f,%.1f) - Back: (%.1f,%.1f,%.1f)\n",
variance[0][0], variance[0][1], variance[0][2], variance[1][0], variance[1][1], variance[1][2]);
DBG (15, "finecal: Pegged gain parameters - High (0xff): %i - Low (0): %i\n", high_pegs, low_pegs);
/* break out of the loop if our calibration is done */
if (cal_good) break;
/* send the new calibration and read a new line */
ret = finecal_send_cal(s);
if(ret) { free(gain_slope); free(last_error); return ret; }
ret = finecal_get_line(s, &s->lightcal);
if(ret) { free(gain_slope); free(last_error); return ret; }
}
/* release the memory for the reference slope data */
free(gain_slope);
free(last_error);
DBG (10, "finecal: finish\n");
return ret;
}
static SANE_Status
lamp(struct scanner *s, unsigned char set)
{
SANE_Status ret = SANE_STATUS_GOOD;
unsigned char cmd[2];
size_t cmdLen = 2;
unsigned char stat[1];
size_t statLen = 1;
DBG (10, "lamp: start (%d)\n", set);
/*send cmd*/
cmd[0] = 0x1b;
cmd[1] = 0xd0;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "lamp: error sending cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "lamp: cmd bad status?\n");
return SANE_STATUS_IO_ERROR;
}
/*send payload*/
cmd[0] = set;
cmdLen = 1;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "lamp: error sending payload\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "lamp: payload bad status?\n");
return SANE_STATUS_IO_ERROR;
}
DBG (10, "lamp: finish\n");
return ret;
}
static SANE_Status
set_window(struct scanner *s, int window)
{
SANE_Status ret = SANE_STATUS_GOOD;
unsigned char cmd[] = {0x1b, 0xd1};
size_t cmdLen = sizeof(cmd);
unsigned char stat[] = {0};
size_t statLen = sizeof(stat);
unsigned char * payload;
size_t paylen = SET_WINDOW_LEN;
DBG (10, "set_window: start, window %d\n",window);
switch (window) {
case WINDOW_COARSECAL:
payload = s->setWindowCoarseCal;
paylen = s->setWindowCoarseCalLen;
break;
case WINDOW_FINECAL:
payload = s->setWindowFineCal;
paylen = s->setWindowFineCalLen;
break;
case WINDOW_SENDCAL:
payload = s->setWindowSendCal;
paylen = s->setWindowSendCalLen;
break;
case WINDOW_SCAN:
payload = s->setWindowScan;
paylen = s->setWindowScanLen;
set_SW_ypix(payload,s->fullscan.height);
break;
default:
DBG (5, "set_window: unknown window\n");
return SANE_STATUS_INVAL;
}
/*send cmd*/
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "set_window: error sending cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "set_window: cmd bad status?\n");
return SANE_STATUS_IO_ERROR;
}
/*send payload*/
statLen = 1;
ret = do_cmd(
s, 0,
payload, paylen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "set_window: error sending payload\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "set_window: payload bad status?\n");
return SANE_STATUS_IO_ERROR;
}
DBG (10, "set_window: finish\n");
return ret;
}
/* instead of internal brightness/contrast/gamma
scanners uses 12bit x 12bit LUT
default is linear table of slope 1
brightness and contrast inputs are -127 to +127
contrast rotates slope of line around central input val
high low
. x .
. x . xx
out . x . xxxxxxxx
. x xx
....x....... ............
in in
then brightness moves line vertically, and clamps to 8bit
bright dark
. xxxxxxxx .
. x .
out x . x
. . x
............ xxxxxxxx....
in in
*/
static SANE_Status
send_lut (struct scanner *s)
{
SANE_Status ret=SANE_STATUS_GOOD;
unsigned char cmd[] = {0x1b, 0xc5};
size_t cmdLen = 2;
unsigned char stat[1];
size_t statLen = 1;
unsigned char out[0x6000];
size_t outLen = 0x6000;
int i, j;
double b, slope, offset;
int width = outLen / 6; /* 3 colors, 2 bytes */
int height = width; /* square table */
DBG (10, "send_lut: start\n");
/* contrast is converted to a slope [0,90] degrees:
* first [-127,127] to [0,254] then to [0,1]
* then multiply by PI/2 to convert to radians
* then take the tangent to get slope (T.O.A)
* then multiply by the normal linear slope
* because the table may not be square, i.e. 1024x256*/
slope = tan(((double)s->contrast+127)/254 * M_PI/2);
/* contrast slope must stay centered, so figure
* out vertical offset at central input value */
offset = height/2 - slope*width/2;
/* convert the user brightness setting (-127 to +127)
* into a scale that covers the range required
* to slide the contrast curve entirely off the table */
b = ((double)s->brightness/127) * (slope*(width-1) + offset);
DBG (15, "send_lut: %d %f %d %f %f\n", s->brightness, b,
s->contrast, slope, offset);
for(i=0;i<width;i++){
j=slope*i + offset + b;
if(j<0){
j=0;
}
if(j>(height-1)){
j=height-1;
}
/*first table, le order*/
out[i*2] = j & 0xff;
out[i*2+1] = (j >> 8) & 0x0f;
/*second table, le order*/
out[width*2 + i*2] = j & 0xff;
out[width*2 + i*2+1] = (j >> 8) & 0x0f;
/*third table, le order*/
out[width*4 + i*2] = j & 0xff;
out[width*4 + i*2+1] = (j >> 8) & 0x0f;
}
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "send_lut: error sending cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "send_lut: cmd bad status?\n");
return SANE_STATUS_IO_ERROR;
}
statLen = 1;
ret = do_cmd(
s, 0,
out, outLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "send_lut: error sending out\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "send_lut: out bad status?\n");
return SANE_STATUS_IO_ERROR;
}
DBG (10, "send_lut: finish\n");
return ret;
}
static SANE_Status
get_hardware_status (struct scanner *s)
{
SANE_Status ret = SANE_STATUS_GOOD;
DBG (10, "get_hardware_status: start\n");
/* only run this once every second */
if (s->last_ghs < time(NULL)) {
unsigned char cmd[2];
size_t cmdLen = sizeof(cmd);
unsigned char pay[4];
size_t payLen = sizeof(pay);
DBG (15, "get_hardware_status: running\n");
cmd[0] = 0x1b;
cmd[1] = 0x33;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
pay, &payLen
);
if(ret){
DBG (5, "get_hardware_status: error sending cmd\n");
return ret;
}
hexdump(5,"ghspayload: ", pay, payLen);
s->last_ghs = time(NULL);
s->hw_top = ((pay[0] >> 7) & 0x01);
s->hw_hopper = !((pay[0] >> 6) & 0x01);
s->hw_adf_open = ((pay[0] >> 5) & 0x01);
s->hw_sleep = ((pay[1] >> 7) & 0x01);
s->hw_scan_sw = ((pay[1] >> 0) & 0x01);
}
DBG (10, "get_hardware_status: finish\n");
return ret;
}
static SANE_Status
ingest(struct scanner *s)
{
SANE_Status ret = SANE_STATUS_GOOD;
int i;
unsigned char cmd[2];
size_t cmdLen = sizeof(cmd);
unsigned char stat[1];
size_t statLen = sizeof(stat);
unsigned char pay[2];
size_t payLen = sizeof(pay);
DBG (10, "ingest: start\n");
for(i=0;i<5;i++){
/*send paper load cmd*/
cmd[0] = 0x1b;
cmd[1] = 0xd4;
statLen = 1;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "ingest: error sending cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "ingest: cmd bad status? %d\n",stat[0]);
continue;
}
/*send payload*/
statLen = 1;
payLen = 1;
pay[0] = 1;
ret = do_cmd(
s, 0,
pay, payLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "ingest: error sending payload\n");
return ret;
}
if(stat[0] == 6){
DBG (5, "ingest: found paper?\n");
break;
}
else if(stat[0] == 0x15 || stat[0] == 0){
DBG (5, "ingest: no paper?\n");
ret=SANE_STATUS_NO_DOCS;
continue;
}
else{
DBG (5, "ingest: payload bad status?\n");
return SANE_STATUS_IO_ERROR;
}
}
DBG (10, "ingest: finish\n");
return ret;
}
static SANE_Status
scan(struct scanner *s)
{
SANE_Status ret=SANE_STATUS_GOOD;
unsigned char cmd[] = {0x1b, 0xd2};
size_t cmdLen = 2;
unsigned char stat[1];
size_t statLen = 1;
DBG (10, "scan: start\n");
if(s->model == MODEL_S300){
cmd[1] = 0xd6;
}
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "scan: error sending cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "scan: cmd bad status?\n");
return SANE_STATUS_IO_ERROR;
}
DBG (10, "scan: finish\n");
return ret;
}
/*
* Called by SANE to read data.
*
* From the SANE spec:
* This function is used to read image data from the device
* represented by handle h. Argument buf is a pointer to a memory
* area that is at least maxlen bytes long. The number of bytes
* returned is stored in *len. A backend must set this to zero when
* the call fails (i.e., when a status other than SANE_STATUS_GOOD is
* returned).
*
* When the call succeeds, the number of bytes returned can be
* anywhere in the range from 0 to maxlen bytes.
*/
SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int * len)
{
struct scanner *s = (struct scanner *) handle;
SANE_Status ret=SANE_STATUS_GOOD;
struct page * page;
DBG (10, "sane_read: start si:%d len:%d max:%d\n",s->side,*len,max_len);
*len = 0;
/* cancelled? */
if(!s->started){
DBG (5, "sane_read: call sane_start first\n");
return SANE_STATUS_CANCELLED;
}
page = &s->pages[s->side];
/* have sent all of current buffer */
if(page->done){
DBG (10, "sane_read: returning eof\n");
return SANE_STATUS_EOF;
}
/* scan not finished, get more into block buffer */
if(!s->fullscan.done)
{
/* block buffer currently empty, clean up */
if(!s->block_xfr.rx_bytes)
{
/* block buffer bigger than remainder of scan, shrink block */
int remainTotal = s->fullscan.total_bytes - s->fullscan.rx_bytes;
if(remainTotal < s->block_xfr.total_bytes)
{
DBG (15, "sane_read: shrinking block to %lu\n", (unsigned long)remainTotal);
s->block_xfr.total_bytes = remainTotal;
}
/* send d3 cmd for S300 */
if(s->model == MODEL_S300)
{
unsigned char cmd[] = {0x1b, 0xd3};
size_t cmdLen = 2;
unsigned char stat[1];
size_t statLen = 1;
DBG (15, "sane_read: d3\n");
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
stat, &statLen
);
if(ret){
DBG (5, "sane_read: error sending d3 cmd\n");
return ret;
}
if(stat[0] != 6){
DBG (5, "sane_read: cmd bad status?\n");
return SANE_STATUS_IO_ERROR;
}
}
}
ret = read_from_scanner(s, &s->block_xfr);
if(ret){
DBG (5, "sane_read: cant read from scanner\n");
return ret;
}
/* block filled, copy to front/back */
if(s->block_xfr.done)
{
DBG (15, "sane_read: block buffer full\n");
/* convert the raw data into normal packed pixel data */
descramble_raw(s, &s->block_xfr);
s->block_xfr.done = 0;
/* get the 0x43 cmd for the S300 */
if(s->model == MODEL_S300){
unsigned char cmd[] = {0x1b, 0x43};
size_t cmdLen = 2;
unsigned char in[10];
size_t inLen = 10;
ret = do_cmd(
s, 0,
cmd, cmdLen,
NULL, 0,
in, &inLen
);
hexdump(15, "cmd 43: ", in, inLen);
if(ret){
DBG (5, "sane_read: error sending 43 cmd\n");
return ret;
}
/*copy backside data into buffer*/
if( s->source == SOURCE_ADF_DUPLEX || s->source == SOURCE_ADF_BACK )
ret = copy_block_to_page(s, SIDE_BACK);
/*copy frontside data into buffer*/
if( s->source != SOURCE_ADF_BACK )
ret = copy_block_to_page(s, SIDE_FRONT);
if(ret){
DBG (5, "sane_read: cant copy to front/back\n");
return ret;
}
s->fullscan.rx_bytes += s->block_xfr.rx_bytes;
/* autodetect mode, check for change length */
if( s->source != SOURCE_FLATBED && !s->page_height ){
int get = (in[6] << 8) | in[7];
/*always have to get full blocks*/
if(get % s->block_img.height){
get += s->block_img.height - (get % s->block_img.height);
}
if(get < s->fullscan.height){
DBG (15, "sane_read: paper out? %d\n",get);
s->fullscan.total_bytes = s->fullscan.width_bytes * get;
}
}
}
else { /*fi-60f*/
ret = copy_block_to_page(s, SIDE_FRONT);
if(ret){
DBG (5, "sane_read: cant copy to front/back\n");
return ret;
}
s->fullscan.rx_bytes += s->block_xfr.rx_bytes;
}
/* reset for next pass */
update_transfer_totals(&s->block_xfr);
/* scan now finished */
if(s->fullscan.rx_bytes == s->fullscan.total_bytes){
DBG (15, "sane_read: last block\n");
s->fullscan.done = 1;
}
}
}
*len = page->bytes_scanned - page->bytes_read;
if(*len > max_len){
*len = max_len;
}
if(*len){
DBG (10, "sane_read: copy rx:%d tx:%d tot:%d len:%d\n",
page->bytes_scanned, page->bytes_read, page->bytes_total,*len);
memcpy(buf, page->image->buffer + page->bytes_read, *len);
page->bytes_read += *len;
/* sent it all, return eof on next read */
if(s->fullscan.done && page->bytes_read == page->bytes_scanned){
DBG (10, "sane_read: side done\n");
page->done = 1;
}
}
DBG (10, "sane_read: finish si:%d len:%d max:%d\n",s->side,*len,max_len);
return ret;
}
/* de-scrambles the raw data from the scanner into the image buffer */
static SANE_Status
descramble_raw(struct scanner *s, struct transfer * tp)
{
SANE_Status ret = SANE_STATUS_GOOD;
unsigned char *p_in, *p_out = tp->image->buffer;
int height = tp->total_bytes / tp->line_stride;
int i, j, k, l;
if (s->model == MODEL_S300)
{
for (i = 0; i < 2; i++) /* page, front/back */
for (j = 0; j < height; j++) /* row (y)*/
for (k = 0; k < tp->plane_width; k++) /* column (x) */
for (l = 0; l < 3; l++) /* color component */
{
p_in = (unsigned char *) tp->raw_data + (j * tp->line_stride) + (l * tp->plane_stride) + k * 3 + i;
*p_out++ = *p_in;
}
}
else /* MODEL_FI60F */
{
for (i = 0; i < height; i++) /* row (y)*/
for (j = 0; j < 3; j++) /* read head */
for (k = 0; k < tp->plane_width; k++) /* column within the read head */
for (l = 0; l < 3; l++) /* color component */
{
p_in = (unsigned char *) tp->raw_data + (i * tp->line_stride) + (l * tp->plane_stride) + k * 3 + j;
*p_out++ = *p_in;
}
}
return ret;
}
/* fills block buffer a little per pass */
static SANE_Status
read_from_scanner(struct scanner *s, struct transfer * tp)
{
SANE_Status ret=SANE_STATUS_GOOD;
size_t bytes = MAX_IMG_PASS;
size_t remainBlock = tp->total_bytes - tp->rx_bytes + 8;
/* determine amount to ask for */
if(bytes > remainBlock){
bytes = remainBlock;
}
if (tp->image == NULL)
{
DBG(5, "internal error: read_from_scanner called with no destination image.\n");
return SANE_STATUS_INVAL;
}
DBG (10, "read_from_scanner: start rB:%lu len:%lu\n",
(unsigned long)remainBlock, (unsigned long)bytes);
if(!bytes){
DBG(10, "read_from_scanner: no bytes!\n");
return SANE_STATUS_INVAL;
}
ret = do_cmd(
s, 0,
NULL, 0,
NULL, 0,
tp->raw_data + tp->rx_bytes, &bytes
);
/* full read or short read */
if (ret == SANE_STATUS_GOOD || (ret == SANE_STATUS_EOF && bytes) ) {
DBG(15,"read_from_scanner: got GOOD/EOF (%lu)\n",(unsigned long)bytes);
if(bytes == remainBlock){
DBG(15,"read_from_scanner: block done, ignoring trailer\n");
bytes -= 8;
tp->done = 1;
}
ret = SANE_STATUS_GOOD;
tp->rx_bytes += bytes;
}
else {
DBG(5, "read_from_scanner: error reading status = %d\n", ret);
}
DBG (10, "read_from_scanner: finish rB:%lu len:%lu\n",
(unsigned long)(tp->total_bytes - tp->rx_bytes), (unsigned long)bytes);
return ret;
}
/* copies block buffer into front or back image buffer */
/* converts pixel data from RGB Color to the output format */
static SANE_Status
copy_block_to_page(struct scanner *s,int side)
{
SANE_Status ret = SANE_STATUS_GOOD;
struct transfer * block = &s->block_xfr;
struct page * page = &s->pages[side];
int height = block->total_bytes / block->line_stride;
int width = block->image->width_pix;
int block_page_stride = block->image->width_bytes * block->image->height;
int page_y_offset = page->bytes_scanned / page->image->width_bytes;
int line_reverse = (side == SIDE_BACK) || (s->model == MODEL_FI60F);
int i,j;
DBG (10, "copy_block_to_page: start\n");
/* loop over all the lines in the block */
for (i = 0; i < height; i++)
{
unsigned char * p_in = block->image->buffer + (side * block_page_stride) + (i * block->image->width_bytes);
unsigned char * p_out = page->image->buffer + ((i + page_y_offset) * page->image->width_bytes);
unsigned char * lineStart = p_out;
/* reverse order for back side or FI-60F scanner */
if (line_reverse)
p_in += (width - 1) * 3;
/* convert all of the pixels in this row */
for (j = 0; j < width; j++)
{
unsigned char r, g, b;
if (s->model == MODEL_S300)
{ r = p_in[1]; g = p_in[2]; b = p_in[0]; }
else /* (s->model == MODEL_FI60F) */
{ r = p_in[0]; g = p_in[1]; b = p_in[2]; }
if (s->mode == MODE_COLOR)
{
*p_out++ = r;
*p_out++ = g;
*p_out++ = b;
}
else if (s->mode == MODE_GRAYSCALE)
{
*p_out++ = (r + g + b) / 3;
}
else if (s->mode == MODE_LINEART)
{
s->dt.buffer[j] = (r + g + b) / 3;
}
if (line_reverse)
p_in -= 3;
else
p_in += 3;
}
/* for MODE_LINEART, binarize the gray line stored in the temp image buffer */
if (s->mode == MODE_LINEART)
binarize_line(s, lineStart, width);
/*add a periodic row because of non-square pixels*/
/*FIXME: only works with 225x200*/
if (s->resolution_x > s->resolution_y && (i + page_y_offset) % 9 == 8)
{
memcpy(lineStart + page->image->width_bytes, lineStart, page->image->width_bytes);
page_y_offset += 1;
page->bytes_scanned += page->image->width_bytes;
}
}
/* update the page counter of bytes scanned */
page->bytes_scanned += page->image->width_bytes * height;
DBG (10, "copy_block_to_page: finish\n");
return ret;
}
/*uses the threshold/threshold_curve to control binarization*/
static SANE_Status
binarize_line(struct scanner *s, unsigned char *lineOut, int width)
{
SANE_Status ret = SANE_STATUS_GOOD;
int j, windowX, sum = 0;
/* ~1mm works best, but the window needs to have odd # of pixels */
windowX = 6 * s->resolution_x / 150;
if (!(windowX % 2)) windowX++;
/*second, prefill the sliding sum*/
for (j = 0; j < windowX; j++)
sum += s->dt.buffer[j];
/* third, walk the dt buffer, update the sliding sum, */
/* determine threshold, output bits */
for (j = 0; j < width; j++)
{
/*output image location*/
int offset = j % 8;
unsigned char mask = 0x80 >> offset;
int thresh = s->threshold;
/* move sum/update threshold only if there is a curve*/
if (s->threshold_curve)
{
int addCol = j + windowX/2;
int dropCol = addCol - windowX;
if (dropCol >= 0 && addCol < width)
{
sum -= s->dt.buffer[dropCol];
sum += s->dt.buffer[addCol];
}
thresh = s->dt_lut[sum/windowX];
}
/*use average to lookup threshold*/
if (s->dt.buffer[j] > thresh)
*lineOut &= ~mask; /* white */
else
*lineOut |= mask; /* black */
if (offset == 7)
lineOut++;
}
return ret;
}
/*
* @@ Section 4 - SANE cleanup functions
*/
/*
* Cancels a scan.
*
* From the SANE spec:
* This function is used to immediately or as quickly as possible
* cancel the currently pending operation of the device represented by
* handle h. This function can be called at any time (as long as
* handle h is a valid handle) but usually affects long-running
* operations only (such as image is acquisition). It is safe to call
* this function asynchronously (e.g., from within a signal handler).
* It is important to note that completion of this operaton does not
* imply that the currently pending operation has been cancelled. It
* only guarantees that cancellation has been initiated. Cancellation
* completes only when the cancelled call returns (typically with a
* status value of SANE_STATUS_CANCELLED). Since the SANE API does
* not require any other operations to be re-entrant, this implies
* that a frontend must not call any other operation until the
* cancelled operation has returned.
*/
void
sane_cancel (SANE_Handle handle)
{
/*FIXME: actually ask the scanner to stop?*/
struct scanner * s = (struct scanner *) handle;
DBG (10, "sane_cancel: start\n");
s->started = 0;
DBG (10, "sane_cancel: finish\n");
}
/*
* Ends use of the scanner.
*
* From the SANE spec:
* This function terminates the association between the device handle
* passed in argument h and the device it represents. If the device is
* presently active, a call to sane_cancel() is performed first. After
* this function returns, handle h must not be used anymore.
*/
void
sane_close (SANE_Handle handle)
{
struct scanner * s = (struct scanner *) handle;
DBG (10, "sane_close: start\n");
/* still connected- drop it */
if(s->fd >= 0){
sane_cancel(handle);
lamp(s, 0);
disconnect_fd(s);
}
DBG (10, "sane_close: finish\n");
}
static SANE_Status
disconnect_fd (struct scanner *s)
{
DBG (10, "disconnect_fd: start\n");
if(s->fd > -1){
DBG (15, "disconnecting usb device\n");
sanei_usb_close (s->fd);
s->fd = -1;
}
DBG (10, "disconnect_fd: finish\n");
return SANE_STATUS_GOOD;
}
static SANE_Status
destroy(struct scanner *s)
{
SANE_Status ret = SANE_STATUS_GOOD;
DBG (10, "destroy: start\n");
teardown_buffers(s);
if(s->sane.name){
free(s->sane.name);
}
if(s->sane.vendor){
free(s->sane.vendor);
}
if(s->sane.model){
free(s->sane.model);
}
free(s);
DBG (10, "destroy: finish\n");
return ret;
}
static SANE_Status
teardown_buffers(struct scanner *s)
{
SANE_Status ret = SANE_STATUS_GOOD;
DBG (10, "teardown_buffers: start\n");
/* temporary cal data */
if(s->coarsecal.buffer){
free(s->coarsecal.buffer);
s->coarsecal.buffer = NULL;
}
if(s->darkcal.buffer){
free(s->darkcal.buffer);
s->darkcal.buffer = NULL;
}
if(s->sendcal.buffer){
free(s->sendcal.buffer);
s->sendcal.buffer = NULL;
}
if(s->cal_image.raw_data){
free(s->cal_image.raw_data);
s->cal_image.raw_data = NULL;
}
if(s->cal_data.raw_data){
free(s->cal_data.raw_data);
s->cal_data.raw_data = NULL;
}
/* image slice */
if(s->block_img.buffer){
free(s->block_img.buffer);
s->block_img.buffer = NULL;
}
if(s->block_xfr.raw_data){
free(s->block_xfr.raw_data);
s->block_xfr.raw_data = NULL;
}
/* dynamic thresh slice */
if(s->dt.buffer){
free(s->dt.buffer);
s->dt.buffer = NULL;
}
/* image buffer to hold frontside data */
if(s->front.buffer){
free(s->front.buffer);
s->front.buffer = NULL;
}
/* image buffer to hold backside data */
if(s->back.buffer){
free(s->back.buffer);
s->back.buffer = NULL;
}
DBG (10, "teardown_buffers: finish\n");
return ret;
}
/*
* Terminates the backend.
*
* From the SANE spec:
* This function must be called to terminate use of a backend. The
* function will first close all device handles that still might be
* open (it is recommended to close device handles explicitly through
* a call to sane_close(), but backends are required to release all
* resources upon a call to this function). After this function
* returns, no function other than sane_init() may be called
* (regardless of the status value returned by sane_exit(). Neglecting
* to call this function may result in some resources not being
* released properly.
*/
void
sane_exit (void)
{
struct scanner *dev, *next;
DBG (10, "sane_exit: start\n");
for (dev = scanner_devList; dev; dev = next) {
next = dev->next;
destroy(dev);
}
if (sane_devArray)
free (sane_devArray);
scanner_devList = NULL;
sane_devArray = NULL;
DBG (10, "sane_exit: finish\n");
}
/*
* @@ Section 5 - misc helper functions
*/
/*
* take a bunch of pointers, send commands to scanner
*/
static SANE_Status
do_cmd(struct scanner *s, int shortTime,
unsigned char * cmdBuff, size_t cmdLen,
unsigned char * outBuff, size_t outLen,
unsigned char * inBuff, size_t * inLen
)
{
/* sanei_usb overwrites the transfer size, so make some local copies */
size_t loc_cmdLen = cmdLen;
size_t loc_outLen = outLen;
size_t loc_inLen = 0;
int cmdTime = USB_COMMAND_TIME;
int outTime = USB_DATA_TIME;
int inTime = USB_DATA_TIME;
int ret = 0;
DBG (10, "do_cmd: start\n");
if(shortTime){
cmdTime /= 20;
outTime /= 20;
inTime /= 20;
}
/* this command has a cmd component, and a place to get it */
if(cmdBuff && cmdLen && cmdTime){
/* change timeout */
sanei_usb_set_timeout(cmdTime);
/* write the command out */
DBG(25, "cmd: writing %ld bytes, timeout %d\n", (long)cmdLen, cmdTime);
hexdump(30, "cmd: >>", cmdBuff, cmdLen);
ret = sanei_usb_write_bulk(s->fd, cmdBuff, &cmdLen);
DBG(25, "cmd: wrote %ld bytes, retVal %d\n", (long)cmdLen, ret);
if(ret == SANE_STATUS_EOF){
DBG(5,"cmd: got EOF, returning IO_ERROR\n");
return SANE_STATUS_IO_ERROR;
}
if(ret != SANE_STATUS_GOOD){
DBG(5,"cmd: return error '%s'\n",sane_strstatus(ret));
return ret;
}
if(loc_cmdLen != cmdLen){
DBG(5,"cmd: wrong size %ld/%ld\n", (long)loc_cmdLen, (long)cmdLen);
return SANE_STATUS_IO_ERROR;
}
}
/* this command has a write component, and a place to get it */
if(outBuff && outLen && outTime){
/* change timeout */
sanei_usb_set_timeout(outTime);
DBG(25, "out: writing %ld bytes, timeout %d\n", (long)outLen, outTime);
hexdump(30, "out: >>", outBuff, outLen);
ret = sanei_usb_write_bulk(s->fd, outBuff, &outLen);
DBG(25, "out: wrote %ld bytes, retVal %d\n", (long)outLen, ret);
if(ret == SANE_STATUS_EOF){
DBG(5,"out: got EOF, returning IO_ERROR\n");
return SANE_STATUS_IO_ERROR;
}
if(ret != SANE_STATUS_GOOD){
DBG(5,"out: return error '%s'\n",sane_strstatus(ret));
return ret;
}
if(loc_outLen != outLen){
DBG(5,"out: wrong size %ld/%ld\n", (long)loc_outLen, (long)outLen);
return SANE_STATUS_IO_ERROR;
}
}
/* this command has a read component, and a place to put it */
if(inBuff && inLen && inTime){
loc_inLen = *inLen;
DBG(25, "in: memset %ld bytes\n", (long)*inLen);
memset(inBuff,0,*inLen);
/* change timeout */
sanei_usb_set_timeout(inTime);
DBG(25, "in: reading %ld bytes, timeout %d\n", (long)*inLen, inTime);
ret = sanei_usb_read_bulk(s->fd, inBuff, inLen);
DBG(25, "in: retVal %d\n", ret);
if(ret == SANE_STATUS_EOF){
DBG(5,"in: got EOF, continuing\n");
}
else if(ret != SANE_STATUS_GOOD){
DBG(5,"in: return error '%s'\n",sane_strstatus(ret));
return ret;
}
DBG(25, "in: read %ld bytes\n", (long)*inLen);
if(*inLen){
hexdump(30, "in: <<", inBuff, *inLen);
}
if(loc_inLen != *inLen){
ret = SANE_STATUS_EOF;
DBG(5,"in: short read %ld/%ld\n", (long)loc_inLen, (long)*inLen);
}
}
DBG (10, "do_cmd: finish\n");
return ret;
}
/**
* Convenience method to determine longest string size in a list.
*/
static size_t
maxStringSize (const SANE_String_Const strings[])
{
size_t size, max_size = 0;
int i;
for (i = 0; strings[i]; ++i) {
size = strlen (strings[i]) + 1;
if (size > max_size)
max_size = size;
}
return max_size;
}
/**
* Prints a hex dump of the given buffer onto the debug output stream.
*/
static void
hexdump (int level, char *comment, unsigned char *p, int l)
{
int i;
char line[128];
char *ptr;
if(DBG_LEVEL < level)
return;
DBG (level, "%s\n", comment);
ptr = line;
for (i = 0; i < l; i++, p++)
{
if ((i % 16) == 0)
{
if (ptr != line)
{
*ptr = '\0';
DBG (level, "%s\n", line);
ptr = line;
}
sprintf (ptr, "%3.3x:", i);
ptr += 4;
}
sprintf (ptr, " %2.2x", *p);
ptr += 3;
}
*ptr = '\0';
DBG (level, "%s\n", line);
}
/**
* An advanced method we don't support but have to define.
*/
SANE_Status
sane_set_io_mode (SANE_Handle h, SANE_Bool non_blocking)
{
DBG (10, "sane_set_io_mode\n");
DBG (15, "%d %p\n", non_blocking, h);
return SANE_STATUS_UNSUPPORTED;
}
/**
* An advanced method we don't support but have to define.
*/
SANE_Status
sane_get_select_fd (SANE_Handle h, SANE_Int *fdp)
{
DBG (10, "sane_get_select_fd\n");
DBG (15, "%p %d\n", h, *fdp);
return SANE_STATUS_UNSUPPORTED;
}
/* s->page_width stores the user setting
* for the paper width in adf. sometimes,
* we need a value that differs from this
* due to using FB
*/
static int
get_page_width(struct scanner *s)
{
/* scanner max for fb */
if(s->source == SOURCE_FLATBED){
return s->max_x;
}
return s->page_width;
}
/* s->page_height stores the user setting
* for the paper height in adf. sometimes,
* we need a value that differs from this
* due to using FB.
*/
static int
get_page_height(struct scanner *s)
{
/* scanner max for fb */
if(s->source == SOURCE_FLATBED){
return s->max_y;
}
return s->page_height;
}