summaryrefslogtreecommitdiff
path: root/backend/epjitsu.c
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2014-10-06 14:00:40 +0200
committerJörg Frings-Fürst <debian@jff-webhosting.net>2014-10-06 14:00:40 +0200
commit6e9c41a892ed0e0da326e0278b3221ce3f5713b8 (patch)
tree2e301d871bbeeb44aa57ff9cc070fcf3be484487 /backend/epjitsu.c
Initial import of sane-backends version 1.0.24-1.2
Diffstat (limited to 'backend/epjitsu.c')
-rw-r--r--backend/epjitsu.c4330
1 files changed, 4330 insertions, 0 deletions
diff --git a/backend/epjitsu.c b/backend/epjitsu.c
new file mode 100644
index 0000000..3e102da
--- /dev/null
+++ b/backend/epjitsu.c
@@ -0,0 +1,4330 @@
+/* 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;
+}
+