diff options
author | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2014-10-06 14:00:40 +0200 |
---|---|---|
committer | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2014-10-06 14:00:40 +0200 |
commit | 6e9c41a892ed0e0da326e0278b3221ce3f5713b8 (patch) | |
tree | 2e301d871bbeeb44aa57ff9cc070fcf3be484487 /backend/canon_dr.c |
Initial import of sane-backends version 1.0.24-1.2
Diffstat (limited to 'backend/canon_dr.c')
-rw-r--r-- | backend/canon_dr.c | 8133 |
1 files changed, 8133 insertions, 0 deletions
diff --git a/backend/canon_dr.c b/backend/canon_dr.c new file mode 100644 index 0000000..594aab2 --- /dev/null +++ b/backend/canon_dr.c @@ -0,0 +1,8133 @@ +/* sane - Scanner Access Now Easy. + + This file is part of the SANE package, and implements a SANE backend + for various Canon DR-series scanners. + + Copyright (C) 2008-2010 m. allan noah + + Yabarana Corp. www.yabarana.com provided significant funding + EvriChart, Inc. www.evrichart.com provided funding and loaned equipment + Canon, USA. www.usa.canon.com loaned equipment + HPrint hprint.com.br provided funding and testing for DR-2510 support + Stone-IT www.stone-it.com provided funding for DR-2010 and DR-2050 support + + -------------------------------------------------------------------------- + + 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. + + -------------------------------------------------------------------------- + + 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 - calibration functions + Section 6 - sane_close functions + Section 7 - misc functions + Section 8 - image processing functions + + Changes: + v1 2008-10-29, MAN + - initial version + v2 2008-11-04, MAN + - round scanlines to even bytes + - spin RS and usb_clear_halt code into new function + - update various scsi payloads + - calloc out block so it gets set to 0 initially + v3 2008-11-07, MAN + - back window uses id 1 + - add option and functions to read/send page counter + - add rif option + v4 2008-11-11, MAN + - eject document when sane_read() returns EOF + v5 2008-11-25, MAN + - remove EOF ejection code + - add SSM and GSM commands + - add dropout, doublefeed, and jpeg compression options + - disable adf backside + - fix adf duplex + - read two extra lines (ignore errors) at end of image + - only send scan command at beginning of batch + - fix bug in hexdump with 0 length string + - DR-7580 support + v6 2008-11-29, MAN + - fix adf simplex + - rename ssm_duplex to ssm_buffer + - add --buffer option + - reduce inter-page commands when buffering is enabled + - improve sense_handler output + - enable counter option + - drop unused code + v7 2008-11-29, MAN + - jpeg support (size rounding and header overwrite) + - call object_position(load) between pages even if buffering is on + - use request sense info bytes on short scsi reads + - byte swap color BGR to RGB + - round image width down, not up + - round image height down to even # of lines + - always transfer even # of lines per block + - scsi and jpeg don't require reading extra lines to reach EOF + - rename buffer option to buffermode to avoid conflict with scanimage + - send ssm_do and ssm_df during sane_start + - improve sense_handler output + v8 2008-12-07, MAN + - rename read/send_counter to read/send_panel + - enable control panel during init + - add options for all buttons + - call TUR twice in wait_scanner(), even if first succeeds + - disable rif + - enable brightness/contrast/threshold options + v9 2008-12-07, MAN + - add rollerdeskew and stapledetect options + - add rollerdeskew and stapledetect bits to ssm_df() + v10 2008-12-10, MAN + - add all documented request sense codes to sense_handler() + - fix color jpeg (remove unneeded BGR to RGB swapping code) + - add macros for LUT data + v11 2009-01-10, MAN + - send_panel() can disable too + - add cancel() to send d8 command + - call cancel() only after final read from scanner + - stop button reqests cancel + v12 2009-01-21, MAN + - dont export private symbols + v13 2009-03-06, MAN + - new vendor ID for recent machines + - add usb ids for several new machines + v14 2009-03-07, MAN + - remove HARD_SELECT from counter (Legitimate, but API violation) + - attach to CR-series scanners as well + v15 2009-03-15, MAN + - add byte-oriented duplex interlace code + - add RRGGBB color interlace code + - add basic support for DR-2580C + v16 2009-03-20, MAN + - add more unknown setwindow bits + - add support for 16 byte status packets + - clean do_usb_cmd error handling (call reset more often) + - add basic support for DR-2050C, DR-2080C, DR-2510C + v17 2009-03-20, MAN + - set status packet size from config file + v18 2009-03-21, MAN + - rewrite config file parsing to reset options after each scanner + - add config options for vendor, model, version + - dont call inquiry if those 3 options are set + - remove default config file from code + - add initial gray deinterlacing code for DR-2510C + - rename do_usb_reset to do_usb_clear + v19 2009-03-22, MAN + - pad gray deinterlacing area for DR-2510C + - override tl_x and br_x for fixed width scanners + v20 2009-03-23, MAN + - improved macros for inquiry and set window + - shorten inquiry vpd length to match windows driver + - remove status-length config option + - add padded-read config option + - rewrite do_usb_cmd to pad reads and calloc/copy buffers + v21 2009-03-24, MAN + - correct rgb padding macro + - skip send_panel and ssm_df commands for DR-20xx scanners + v22 2009-03-25, MAN + - add deinterlacing code for DR-2510C in duplex and color + v23 2009-03-27, MAN + - rewrite all image data processing code + - handle more image interlacing formats + - re-enable binary mode on some scanners + - limit some machines to full-width scanning + v24 2009-04-02, MAN + - fix DR-2510C duplex deinterlacing code + - rewrite sane_read helpers to read until EOF + - update sane_start for scanners that dont use object_position + - dont call sanei_usb_clear_halt() if device is not open + - increase default buffer size to 4 megs + - set buffermode on by default + - hide modes and resolutions that DR-2510C lies about + - read_panel() logs front-end access to sensors instead of timing + - rewrite do_usb_cmd() to use remainder from RS info + v25 2009-04-12, MAN + - disable SANE_FRAME_JPEG + v26 2009-04-14, MAN (SANE 1.0.20) + - return cmd status for reads on sensors + - allow rs to adjust read length for all bad status responses + v27 2009-05-08, MAN + - bug fix in read_panel() + - initialize vars in do_usb_cmd() + - set buffermode off by default + - clear page counter during init and sane_start() + - eject previous page during init and sane_start() + - improved SSM_BUFF macros + - moved set_window() to after ssm-*() + - add coarse calibration (AFE offset/gain & per-channel exposure) + - add fine calibration (per-cell offset/gain) + - free image and fine cal buffers in sane_close() + - compare page counter of small scanners only in non-buffered mode + - add back-side gray mirroring code for DR-2580C + v28 2009-05-20, MAN + - use average instead of min/max for fine offset and gain + - rewrite supported resolution list as x and y arrays + - merge x and y resolution options into single option + - move scan params into two new structs, s->u and s->s + - sane_get_parameters() just returns values from s->u + - dont call wait_scanner() in object_position() + - dont call ssm_*() from option handler + - refactor sane_start() + - read_from_buffer() can workaround missing res, modes and cropping + - set most DR-2xxx machines to use the read_from_buffer workarounds + - set default threshold to 90 + - add option for button #3 of some machines + - don't eject paper during init + - add DR-2010 quirks + - switch counter to HARD_SELECT, not SOFT + v29 2009-06-01, MAN + - split coarse and fine cal to run independently + - add side option + - reset scan params to user request if calibration fails + - better handling of sane_cancel + - better handling of errors during sane_start and sane_read + v30 2009-06-17, MAN + - add fine cal support for machines with internal buffer (2050/2080) + - support fixed-width machines that require even bytes per scanline + - pad end of scan with gray if scanner stops prematurely + - better handling of errors during calibration + - cleanup canceling debug messages + - remove old cancel() prototype + - small sleep before clearing usb halt condition + v31 2009-06-29, MAN + - reduce default buffer size to 2 megs + v32 2009-07-21, MAN + - crop/resample image data before buffering, not after + - shink image buffers to size of output image, not input + - correct some debug message + - better handling of EOF + - add intermediate param struct to existing user and scan versions + v33 2009-07-23, MAN + - add software brightness/contrast for dumb scanners + - add blocking mode to allow full-page manipulation options to run + - add swdespeck option and support code + - add swdeskew and swcrop options (disabled) + v34 2009-07-28, MAN + - add simplified Hough transform based deskewing code + - add extremity detecting cropping code + - use per-model background color to fill corners after deskew + - request and chop extra scanlines instead of rounding down + - remove padding dumb scanners add to top of front side + - sane_get_params uses intermediate struct instead of user struct + - if scanner stops, clone the last line until the end of buffer + - reset some intermediate params between duplex sides + v35 2010-02-09, MAN (SANE 1.0.21) + - cleanup #includes and copyright + - add SANE_I18N to static strings + - don't fail if scsi buffer is too small + v36 2011-01-03, MAN + - initial support for DR-3080 and DR-5060 + - add code to clamp scan width to an arbitrary byte width boundary + - add code to prevent setting of brightness/threshold/contrast + - don't send dropout color command on non-color scanners + - initial support for DR-7090C + - update credits + v37 2011-01-26, MAN (SANE 1.0.22) + - don't center window when using flatbed + - improve request sense error messages + - enable flatbed for all known models + v38 2011-07-06, MAN + - initial support for DR-5020 + - use ppl_mod instead of Bpl_mod, apply to all modes + - invert logic of read_panel tracking + - add ability to disable read_panel() + - automatically disable read/send_panel if unsupported + v39 2011-11-01, MAN + - DR-2580C pads the backside of duplex scans + v40 2012-11-01, MAN + - initial DR-9050C, DR-7550C, DR-6050C and DR-3010C support + v41 2013-07-31, MAN + - initial P-208 and P-215 support + - bug fix for calibration of scanners with duplex_offset + - allow duplex_offset to be controlled from config file + + 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 "../include/sane/sanei_backend.h" +#include "../include/sane/sanei_scsi.h" +#include "../include/sane/sanei_usb.h" +#include "../include/sane/saneopts.h" +#include "../include/sane/sanei_config.h" + +#include "canon_dr-cmd.h" +#include "canon_dr.h" + +#define DEBUG 1 +#define BUILD 41 + +/* values for SANE_DEBUG_CANON_DR env var: + - errors 5 + - function trace 10 + - function detail 15 + - get/setopt cmds 20 + - scsi/usb trace 25 + - scsi/usb detail 30 + - useless noise 35 +*/ + +/* ------------------------------------------------------------------------- */ +#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_HALFTONE SANE_VALUE_SCAN_MODE_HALFTONE +#define STRING_GRAYSCALE SANE_VALUE_SCAN_MODE_GRAY +#define STRING_COLOR SANE_VALUE_SCAN_MODE_COLOR + +#define STRING_RED SANE_I18N("Red") +#define STRING_GREEN SANE_I18N("Green") +#define STRING_BLUE SANE_I18N("Blue") +#define STRING_EN_RED SANE_I18N("Enhance Red") +#define STRING_EN_GREEN SANE_I18N("Enhance Green") +#define STRING_EN_BLUE SANE_I18N("Enhance Blue") + +#define STRING_NONE SANE_I18N("None") +#define STRING_JPEG SANE_I18N("JPEG") + +/* Also set via config file. */ +static int global_buffer_size; +static int global_buffer_size_default = 2 * 1024 * 1024; +static int global_padded_read; +static int global_padded_read_default = 0; +static int global_duplex_offset; +static int global_duplex_offset_default = 0; +static char global_vendor_name[9]; +static char global_model_name[17]; +static char global_version_name[5]; + +/* + * 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: canon_dr 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_* + * and 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(); + + /* reset globals before reading the file */ + default_globals(); + + fp = sanei_config_open (CANON_DR_CONFIG_FILE); + + if (fp) { + + DBG (15, "sane_get_devices: reading config file %s\n", + CANON_DR_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 ("option", lp, 6) && isspace (lp[6])) { + + lp += 6; + lp = sanei_config_skip_whitespace (lp); + + /* BUFFERSIZE: > 4K */ + if (!strncmp (lp, "buffer-size", 11) && isspace (lp[11])) { + + int buf; + lp += 11; + lp = sanei_config_skip_whitespace (lp); + buf = atoi (lp); + + if (buf < 4096) { + DBG (5, "sane_get_devices: config option \"buffer-size\" " + "(%d) is < 4096, ignoring!\n", buf); + continue; + } + + if (buf > global_buffer_size_default) { + DBG (5, "sane_get_devices: config option \"buffer-size\" " + "(%d) is > %d, scanning problems may result\n", buf, + global_buffer_size_default); + } + + DBG (15, "sane_get_devices: setting \"buffer-size\" to %d\n", + buf); + + global_buffer_size = buf; + } + + /* PADDED READ: we clamp to 0 or 1 */ + else if (!strncmp (lp, "padded-read", 11) && isspace (lp[11])) { + + int buf; + lp += 11; + lp = sanei_config_skip_whitespace (lp); + buf = atoi (lp); + + if (buf < 0) { + DBG (5, "sane_get_devices: config option \"padded-read\" " + "(%d) is < 0, ignoring!\n", buf); + continue; + } + + if (buf > 1) { + DBG (5, "sane_get_devices: config option \"padded-read\" " + "(%d) is > 1, ignoring!\n", buf); + continue; + } + + DBG (15, "sane_get_devices: setting \"padded-read\" to %d\n", + buf); + + global_padded_read = buf; + } + + /* DUPLEXOFFSET: < 1200 */ + else if (!strncmp (lp, "duplex-offset", 13) && isspace (lp[13])) { + + int buf; + lp += 13; + lp = sanei_config_skip_whitespace (lp); + buf = atoi (lp); + + if (buf > 1200) { + DBG (5, "sane_get_devices: config option \"duplex-offset\" " + "(%d) is > 1200, ignoring!\n", buf); + continue; + } + + if (buf < 0) { + DBG (5, "sane_get_devices: config option \"duplex-offset\" " + "(%d) is < 0, ignoring!\n", buf); + continue; + } + + DBG (15, "sane_get_devices: setting \"duplex-offset\" to %d\n", + buf); + + global_duplex_offset = buf; + } + + /* VENDOR: we ingest up to 8 bytes */ + else if (!strncmp (lp, "vendor-name", 11) && isspace (lp[11])) { + + lp += 11; + lp = sanei_config_skip_whitespace (lp); + strncpy(global_vendor_name, lp, 8); + global_vendor_name[8] = 0; + + DBG (15, "sane_get_devices: setting \"vendor-name\" to %s\n", + global_vendor_name); + } + + /* MODEL: we ingest up to 16 bytes */ + else if (!strncmp (lp, "model-name", 10) && isspace (lp[10])) { + + lp += 10; + lp = sanei_config_skip_whitespace (lp); + strncpy(global_model_name, lp, 16); + global_model_name[16] = 0; + + DBG (15, "sane_get_devices: setting \"model-name\" to %s\n", + global_model_name); + } + + /* VERSION: we ingest up to 4 bytes */ + else if (!strncmp (lp, "version-name", 12) && isspace (lp[12])) { + + lp += 12; + lp = sanei_config_skip_whitespace (lp); + strncpy(global_version_name, lp, 4); + global_version_name[4] = 0; + + DBG (15, "sane_get_devices: setting \"version-name\" to %s\n", + global_version_name); + } + + else { + DBG (5, "sane_get_devices: config option \"%s\" unrecognized " + "- ignored.\n", lp); + } + } + 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_usb); + + /* re-default these after reading the usb line */ + default_globals(); + } + else if ((strncmp ("scsi", lp, 4) == 0) && isspace (lp[4])) { + DBG (15, "sane_get_devices: looking for '%s'\n", lp); + sanei_config_attach_matching_devices (lp, attach_one_scsi); + + /* re-default these after reading the scsi line */ + default_globals(); + } + else{ + DBG (5, "sane_get_devices: config line \"%s\" unrecognized - " + "ignored.\n", lp); + } + } + fclose (fp); + } + + else { + DBG (5, "sane_get_devices: missing required config file '%s'!\n", + CANON_DR_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->device_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->device_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; +} + +/* callbacks used by sane_get_devices */ +static SANE_Status +attach_one_scsi (const char *device_name) +{ + return attach_one(device_name,CONNECTION_SCSI); +} + +static SANE_Status +attach_one_usb (const char *device_name) +{ + return attach_one(device_name,CONNECTION_USB); +} + +/* build the scanner struct and link to global list + * unless struct is already loaded, then pretend + */ +static SANE_Status +attach_one (const char *device_name, int connType) +{ + struct scanner *s; + int ret; + + DBG (10, "attach_one: start\n"); + DBG (15, "attach_one: looking for '%s'\n", device_name); + + for (s = scanner_devList; s; s = s->next) { + if (strcmp (s->device_name, device_name) == 0){ + DBG (10, "attach_one: already attached!\n"); + s->missing = 0; + return SANE_STATUS_GOOD; + } + } + + /* build a scanner struct to hold it */ + if ((s = calloc (sizeof (*s), 1)) == NULL) + return SANE_STATUS_NO_MEM; + + /* config file settings */ + s->buffer_size = global_buffer_size; + s->padded_read = global_padded_read; + s->duplex_offset = global_duplex_offset; + + /* copy the device name */ + strcpy (s->device_name, device_name); + + /* connect the fd */ + s->connection = connType; + s->fd = -1; + ret = connect_fd(s); + if(ret != SANE_STATUS_GOOD){ + free (s); + return ret; + } + + /* query the device to load its vendor/model/version, */ + /* if config file doesn't give all three */ + if ( !strlen(global_vendor_name) + || !strlen(global_model_name) + || !strlen(global_version_name) + ){ + ret = init_inquire (s); + if (ret != SANE_STATUS_GOOD) { + disconnect_fd(s); + free (s); + DBG (5, "attach_one: inquiry failed\n"); + return ret; + } + } + + /* override any inquiry settings with those from config file */ + if(strlen(global_vendor_name)) + strcpy(s->vendor_name, global_vendor_name); + if(strlen(global_model_name)) + strcpy(s->model_name, global_model_name); + if(strlen(global_version_name)) + strcpy(s->version_name, global_version_name); + + /* load detailed specs/capabilities from the device */ + /* if a model cannot support inquiry vpd, this function will die */ + ret = init_vpd (s); + if (ret != SANE_STATUS_GOOD) { + disconnect_fd(s); + free (s); + DBG (5, "attach_one: vpd failed\n"); + return ret; + } + + /* clean up the scanner struct based on model */ + /* this is the big piece of model specific code */ + ret = init_model (s); + if (ret != SANE_STATUS_GOOD) { + disconnect_fd(s); + free (s); + DBG (5, "attach_one: model failed\n"); + return ret; + } + + /* enable/read the buttons */ + ret = init_panel (s); + if (ret != SANE_STATUS_GOOD) { + disconnect_fd(s); + free (s); + DBG (5, "attach_one: model failed\n"); + return ret; + } + + /* sets SANE option 'values' to good defaults */ + ret = init_user (s); + if (ret != SANE_STATUS_GOOD) { + disconnect_fd(s); + free (s); + DBG (5, "attach_one: user failed\n"); + return ret; + } + + ret = init_options (s); + if (ret != SANE_STATUS_GOOD) { + disconnect_fd(s); + free (s); + DBG (5, "attach_one: options failed\n"); + return ret; + } + + /* load strings into sane_device struct */ + s->sane.name = s->device_name; + s->sane.vendor = s->vendor_name; + s->sane.model = s->model_name; + s->sane.type = "scanner"; + + /* change name in sane_device struct if scanner has serial number + ret = init_serial (s); + if (ret == SANE_STATUS_GOOD) { + s->sane.name = s->serial_name; + } + else{ + DBG (5, "attach_one: serial number unsupported?\n"); + } + */ + + /* we close the connection, so that another backend can talk to scanner */ + disconnect_fd(s); + + /* store this scanner in global vars */ + 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; + int buffer_size = s->buffer_size; + + DBG (10, "connect_fd: start\n"); + + if(s->fd > -1){ + DBG (5, "connect_fd: already open\n"); + ret = SANE_STATUS_GOOD; + } + else if (s->connection == CONNECTION_USB) { + DBG (15, "connect_fd: opening USB device (%s)\n", s->device_name); + ret = sanei_usb_open (s->device_name, &(s->fd)); + if(!ret){ + ret = sanei_usb_clear_halt(s->fd); + } + } + else { + DBG (15, "connect_fd: opening SCSI device (%s)\n", s->device_name); + ret = sanei_scsi_open_extended (s->device_name, &(s->fd), sense_handler, s, + &s->buffer_size); + if(!ret && buffer_size != s->buffer_size){ + DBG (5, "connect_fd: cannot get requested buffer size (%d/%d)\n", + buffer_size, s->buffer_size); + } + } + + if(ret == SANE_STATUS_GOOD){ + + /* first generation usb scanners can get flaky if not closed + * properly after last use. very first commands sent to device + * must be prepared to correct this- see wait_scanner() */ + ret = wait_scanner(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "connect_fd: could not wait_scanner\n"); + disconnect_fd(s); + } + + } + else{ + DBG (5, "connect_fd: could not open device: %d\n", ret); + } + + DBG (10, "connect_fd: finish\n"); + + return ret; +} + +/* + * This routine will check if a certain device is a Canon scanner + * It also copies interesting data from INQUIRY into the handle structure + */ +static SANE_Status +init_inquire (struct scanner *s) +{ + int i; + SANE_Status ret; + + unsigned char cmd[INQUIRY_len]; + size_t cmdLen = INQUIRY_len; + + unsigned char in[INQUIRY_std_len]; + size_t inLen = INQUIRY_std_len; + + DBG (10, "init_inquire: start\n"); + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, INQUIRY_code); + set_IN_return_size (cmd, inLen); + set_IN_evpd (cmd, 0); + set_IN_page_code (cmd, 0); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + NULL, 0, + in, &inLen + ); + + if (ret != SANE_STATUS_GOOD){ + DBG (10, "init_inquire: failed: %d\n", ret); + return ret; + } + + if (get_IN_periph_devtype (in) != IN_periph_devtype_scanner){ + DBG (5, "The device at '%s' is not a scanner.\n", s->device_name); + return SANE_STATUS_INVAL; + } + + get_IN_vendor (in, s->vendor_name); + get_IN_product (in, s->model_name); + get_IN_version (in, s->version_name); + + s->vendor_name[8] = 0; + s->model_name[16] = 0; + s->version_name[4] = 0; + + /* gobble trailing spaces */ + for (i = 7; s->vendor_name[i] == ' ' && i >= 0; i--) + s->vendor_name[i] = 0; + for (i = 15; s->model_name[i] == ' ' && i >= 0; i--) + s->model_name[i] = 0; + for (i = 3; s->version_name[i] == ' ' && i >= 0; i--) + s->version_name[i] = 0; + + /*check for vendor name*/ + if (strcmp ("CANON", s->vendor_name)) { + DBG (5, "The device at '%s' is reported to be made by '%s'\n", + s->device_name, s->vendor_name); + DBG (5, "This backend only supports Canon products.\n"); + return SANE_STATUS_INVAL; + } + + /*check for model name*/ + if (strncmp ("DR", s->model_name, 2) + && strncmp ("CR", s->model_name, 2) + && strncmp ("P-", s->model_name, 2) + ) { + DBG (5, "The device at '%s' is reported to be a '%s'\n", + s->device_name, s->model_name); + DBG (5, "This backend only supports Canon P-, CR & DR-series products.\n"); + return SANE_STATUS_INVAL; + } + + DBG (15, "init_inquire: Found %s scanner %s version %s at %s\n", + s->vendor_name, s->model_name, s->version_name, s->device_name); + + DBG (10, "init_inquire: finish\n"); + + return SANE_STATUS_GOOD; +} + +/* + * Use INQUIRY VPD to setup more detail about the scanner + */ +static SANE_Status +init_vpd (struct scanner *s) +{ + SANE_Status ret; + + unsigned char cmd[INQUIRY_len]; + size_t cmdLen = INQUIRY_len; + + unsigned char in[INQUIRY_vpd_len]; + size_t inLen = INQUIRY_vpd_len; + + DBG (10, "init_vpd: start\n"); + + /* get EVPD */ + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, INQUIRY_code); + set_IN_return_size (cmd, inLen); + set_IN_evpd (cmd, 1); + set_IN_page_code (cmd, 0xf0); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + NULL, 0, + in, &inLen + ); + + DBG (15, "init_vpd: length=%0x\n",get_IN_page_length (in)); + + /* This scanner supports vital product data. + * Use this data to set dpi-lists etc. */ + if (ret == SANE_STATUS_GOOD || ret == SANE_STATUS_EOF) { + + DBG (15, "standard options\n"); + + s->basic_x_res = get_IN_basic_x_res (in); + DBG (15, " basic x res: %d dpi\n",s->basic_x_res); + + s->basic_y_res = get_IN_basic_y_res (in); + DBG (15, " basic y res: %d dpi\n",s->basic_y_res); + + s->step_x_res = get_IN_step_x_res (in); + DBG (15, " step x res: %d dpi\n", s->step_x_res); + + s->step_y_res = get_IN_step_y_res (in); + DBG (15, " step y res: %d dpi\n", s->step_y_res); + + s->max_x_res = get_IN_max_x_res (in); + DBG (15, " max x res: %d dpi\n", s->max_x_res); + + s->max_y_res = get_IN_max_y_res (in); + DBG (15, " max y res: %d dpi\n", s->max_y_res); + + s->min_x_res = get_IN_min_x_res (in); + DBG (15, " min x res: %d dpi\n", s->min_x_res); + + s->min_y_res = get_IN_min_y_res (in); + DBG (15, " min y res: %d dpi\n", s->min_y_res); + + /* some scanners list B&W resolutions. */ + s->std_res_x[DPI_60] = get_IN_std_res_60 (in); + s->std_res_y[DPI_60] = s->std_res_x[DPI_60]; + DBG (15, " 60 dpi: %d\n", s->std_res_x[DPI_60]); + + s->std_res_x[DPI_75] = get_IN_std_res_75 (in); + s->std_res_y[DPI_75] = s->std_res_x[DPI_75]; + DBG (15, " 75 dpi: %d\n", s->std_res_x[DPI_75]); + + s->std_res_x[DPI_100] = get_IN_std_res_100 (in); + s->std_res_y[DPI_100] = s->std_res_x[DPI_100]; + DBG (15, " 100 dpi: %d\n", s->std_res_x[DPI_100]); + + s->std_res_x[DPI_120] = get_IN_std_res_120 (in); + s->std_res_y[DPI_120] = s->std_res_x[DPI_120]; + DBG (15, " 120 dpi: %d\n", s->std_res_x[DPI_120]); + + s->std_res_x[DPI_150] = get_IN_std_res_150 (in); + s->std_res_y[DPI_150] = s->std_res_x[DPI_150]; + DBG (15, " 150 dpi: %d\n", s->std_res_x[DPI_150]); + + s->std_res_x[DPI_160] = get_IN_std_res_160 (in); + s->std_res_y[DPI_160] = s->std_res_x[DPI_160]; + DBG (15, " 160 dpi: %d\n", s->std_res_x[DPI_160]); + + s->std_res_x[DPI_180] = get_IN_std_res_180 (in); + s->std_res_y[DPI_180] = s->std_res_x[DPI_180]; + DBG (15, " 180 dpi: %d\n", s->std_res_x[DPI_180]); + + s->std_res_x[DPI_200] = get_IN_std_res_200 (in); + s->std_res_y[DPI_200] = s->std_res_x[DPI_200]; + DBG (15, " 200 dpi: %d\n", s->std_res_x[DPI_200]); + + s->std_res_x[DPI_240] = get_IN_std_res_240 (in); + s->std_res_y[DPI_240] = s->std_res_x[DPI_240]; + DBG (15, " 240 dpi: %d\n", s->std_res_x[DPI_240]); + + s->std_res_x[DPI_300] = get_IN_std_res_300 (in); + s->std_res_y[DPI_300] = s->std_res_x[DPI_300]; + DBG (15, " 300 dpi: %d\n", s->std_res_x[DPI_300]); + + s->std_res_x[DPI_320] = get_IN_std_res_320 (in); + s->std_res_y[DPI_320] = s->std_res_x[DPI_320]; + DBG (15, " 320 dpi: %d\n", s->std_res_x[DPI_320]); + + s->std_res_x[DPI_400] = get_IN_std_res_400 (in); + s->std_res_y[DPI_400] = s->std_res_x[DPI_400]; + DBG (15, " 400 dpi: %d\n", s->std_res_x[DPI_400]); + + s->std_res_x[DPI_480] = get_IN_std_res_480 (in); + s->std_res_y[DPI_480] = s->std_res_x[DPI_480]; + DBG (15, " 480 dpi: %d\n", s->std_res_x[DPI_480]); + + s->std_res_x[DPI_600] = get_IN_std_res_600 (in); + s->std_res_y[DPI_600] = s->std_res_x[DPI_600]; + DBG (15, " 600 dpi: %d\n", s->std_res_x[DPI_600]); + + s->std_res_x[DPI_800] = get_IN_std_res_800 (in); + s->std_res_y[DPI_800] = s->std_res_x[DPI_800]; + DBG (15, " 800 dpi: %d\n", s->std_res_x[DPI_800]); + + s->std_res_x[DPI_1200] = get_IN_std_res_1200 (in); + s->std_res_y[DPI_1200] = s->std_res_x[DPI_1200]; + DBG (15, " 1200 dpi: %d\n", s->std_res_x[DPI_1200]); + + /* maximum window width and length are reported in basic units.*/ + s->max_x = get_IN_window_width(in) * 1200 / s->basic_x_res; + DBG(15, " max width: %d (%2.2f in)\n",s->max_x,(float)s->max_x/1200); + + s->max_y = get_IN_window_length(in) * 1200 / s->basic_y_res; + DBG(15, " max length: %d (%2.2f in)\n",s->max_y,(float)s->max_y/1200); + + DBG (15, " AWD: %d\n", get_IN_awd(in)); + DBG (15, " CE Emphasis: %d\n", get_IN_ce_emphasis(in)); + DBG (15, " C Emphasis: %d\n", get_IN_c_emphasis(in)); + DBG (15, " High quality: %d\n", get_IN_high_quality(in)); + + /* known modes FIXME more here? */ + s->can_grayscale = get_IN_multilevel (in); + DBG (15, " grayscale: %d\n", s->can_grayscale); + + s->can_halftone = get_IN_half_tone (in); + DBG (15, " halftone: %d\n", s->can_halftone); + + s->can_monochrome = get_IN_monochrome (in); + DBG (15, " monochrome: %d\n", s->can_monochrome); + + s->can_overflow = get_IN_overflow(in); + DBG (15, " overflow: %d\n", s->can_overflow); + } + /*FIXME no vpd, set some defaults? */ + else{ + DBG (5, "init_vpd: Your scanner does not support VPD?\n"); + DBG (5, "init_vpd: Please contact kitno455 at gmail dot com\n"); + DBG (5, "init_vpd: with details of your scanner model.\n"); + } + + DBG (10, "init_vpd: finish\n"); + + return ret; +} + +/* + * get model specific info that is not in vpd, and correct + * errors in vpd data. struct is already initialized to 0. + */ +static SANE_Status +init_model (struct scanner *s) +{ + + DBG (10, "init_model: start\n"); + + s->reverse_by_mode[MODE_LINEART] = 1; + s->reverse_by_mode[MODE_HALFTONE] = 1; + s->reverse_by_mode[MODE_GRAYSCALE] = 0; + s->reverse_by_mode[MODE_COLOR] = 0; + + s->always_op = 1; + s->has_df = 1; + s->has_btc = 1; + s->has_counter = 1; + s->has_adf = 1; + s->has_duplex = 1; + s->has_buffer = 1; + s->can_read_panel = 1; + s->can_write_panel = 1; + s->has_ssm = 1; + + s->brightness_steps = 255; + s->contrast_steps = 255; + s->threshold_steps = 255; + + s->ppl_mod = 1; + s->bg_color = 0xee; + + /* assume these are same as adf, override below */ + s->valid_x = s->max_x; + s->max_x_fb = s->max_x; + s->max_y_fb = s->max_y; + + /* generic settings missing from vpd */ + if (strstr (s->model_name,"C")){ + s->can_color = 1; + } + + /* specific settings missing from vpd */ + if (strstr (s->model_name,"DR-9080") + || strstr (s->model_name,"DR-7580")){ +#ifdef SANE_FRAME_JPEG + s->has_comp_JPEG = 1; +#endif + s->rgb_format = 2; + } + + else if (strstr (s->model_name,"DR-7090")){ + s->has_flatbed = 1; + } + + else if (strstr (s->model_name,"DR-9050") + || strstr (s->model_name,"DR-7550") + || strstr (s->model_name,"DR-6050")){ + + /*missing*/ + s->std_res_x[DPI_100]=1; + s->std_res_y[DPI_100]=1; + s->std_res_x[DPI_150]=1; + s->std_res_y[DPI_150]=1; + s->std_res_x[DPI_200]=1; + s->std_res_y[DPI_200]=1; + s->std_res_x[DPI_240]=1; + s->std_res_y[DPI_240]=1; + s->std_res_x[DPI_300]=1; + s->std_res_y[DPI_300]=1; + s->std_res_x[DPI_400]=1; + s->std_res_y[DPI_400]=1; + s->std_res_x[DPI_600]=1; + s->std_res_y[DPI_600]=1; + + /*weirdness*/ + s->has_ssm = 0; + s->has_ssm2 = 1; + } + + else if (strstr (s->model_name,"DR-4080") + || strstr (s->model_name,"DR-4580") + || strstr (s->model_name,"DR-7080")){ + s->has_flatbed = 1; + } + + else if (strstr (s->model_name,"DR-2580")){ + s->invert_tly = 1; + s->rgb_format = 1; + s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_RRGGBB; + s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_rRgGbB; + s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_gG; + s->duplex_interlace = DUPLEX_INTERLACE_FBFB; + s->need_ccal = 1; + s->need_fcal = 1; + /*s->duplex_offset = 432; now set in config file*/ + s->duplex_offset_side = SIDE_BACK; + + /*lies*/ + s->can_halftone=0; + s->can_monochrome=0; + } + + else if (strstr (s->model_name,"DR-2510") + || strstr (s->model_name,"DR-2010") + ){ + s->rgb_format = 1; + s->always_op = 0; + s->unknown_byte2 = 0x80; + s->fixed_width = 1; + s->valid_x = 8.5 * 1200; + s->gray_interlace[SIDE_FRONT] = GRAY_INTERLACE_2510; + s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_2510; + s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_2510; + s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_2510; + s->duplex_interlace = DUPLEX_INTERLACE_2510; + /*s->duplex_offset = 400; now set in config file*/ + s->need_ccal = 1; + s->need_fcal = 1; + s->sw_lut = 1; + /*s->invert_tly = 1;*/ + + /*only in Y direction, so we trash them in X*/ + s->std_res_x[DPI_100]=0; + s->std_res_x[DPI_150]=0; + s->std_res_x[DPI_200]=0; + s->std_res_x[DPI_240]=0; + s->std_res_x[DPI_400]=0; + + /*lies*/ + s->can_halftone=0; + s->can_monochrome=0; + } + + /* copied from 2510, possibly incorrect */ + else if (strstr (s->model_name,"DR-3010")){ + s->rgb_format = 1; + s->always_op = 0; + s->unknown_byte2 = 0x80; + s->fixed_width = 1; + s->valid_x = 8.5 * 1200; + s->gray_interlace[SIDE_FRONT] = GRAY_INTERLACE_2510; + s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_2510; + s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_2510; + s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_2510; + s->duplex_interlace = DUPLEX_INTERLACE_2510; + /*s->duplex_offset = 400; now set in config file*/ + s->need_ccal = 1; + s->need_fcal = 1; + s->sw_lut = 1; + s->invert_tly = 1; + + /*only in Y direction, so we trash them in X*/ + s->std_res_x[DPI_100]=0; + s->std_res_x[DPI_150]=0; + s->std_res_x[DPI_200]=0; + s->std_res_x[DPI_240]=0; + s->std_res_x[DPI_400]=0; + + /*lies*/ + s->can_halftone=0; + s->can_monochrome=0; + } + + else if (strstr (s->model_name,"DR-2050") + || strstr (s->model_name,"DR-2080")){ + s->can_write_panel = 0; + s->has_df = 0; + s->fixed_width = 1; + s->even_Bpl = 1; + s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_RRGGBB; + s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_RRGGBB; + s->duplex_interlace = DUPLEX_INTERLACE_FBFB; + s->need_fcal_buffer = 1; + s->bg_color = 0x08; + /*s->duplex_offset = 840; now set in config file*/ + s->sw_lut = 1; + + /*lies*/ + s->can_halftone=0; + s->can_monochrome=0; + } + + else if (strstr (s->model_name,"DR-3080")){ + s->can_write_panel = 0; + s->has_df = 0; + s->has_btc = 0; + } + + else if (strstr (s->model_name,"DR-5060F")){ + s->can_write_panel = 0; + s->has_df = 0; + s->has_btc = 0; + s->ppl_mod = 32; + s->reverse_by_mode[MODE_LINEART] = 0; + s->reverse_by_mode[MODE_HALFTONE] = 0; + } + + else if (strstr (s->model_name,"DR-5020")){ + s->can_read_panel = 0; + s->can_write_panel = 0; + s->has_df = 0; + s->has_btc = 0; + s->ppl_mod = 32; + s->reverse_by_mode[MODE_LINEART] = 0; + s->reverse_by_mode[MODE_HALFTONE] = 0; + } + + else if (strstr (s->model_name, "P-208")) { + s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_RRGGBB; + s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_rRgGbB; + s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_gG; + s->duplex_interlace = DUPLEX_INTERLACE_FBFB; + s->need_ccal = 1; + s->invert_tly = 1; + s->can_color = 1; + s->unknown_byte2 = 0x88; + s->rgb_format = 1; + s->has_ssm_pay_head_len = 1; + s->ppl_mod = 8; + s->ccal_version = 3; + } + + else if (strstr (s->model_name, "P-215")) { + s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_rRgGbB; + s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_RRGGBB; + s->gray_interlace[SIDE_FRONT] = GRAY_INTERLACE_gG; + s->duplex_interlace = DUPLEX_INTERLACE_FBFB; + s->need_ccal = 1; + s->invert_tly = 1; + s->can_color = 1; + s->unknown_byte2 = 0x88; + s->rgb_format = 1; + s->has_ssm_pay_head_len = 1; + s->ppl_mod = 8; + s->ccal_version = 3; + } + + DBG (10, "init_model: finish\n"); + + return SANE_STATUS_GOOD; +} + +/* + * This function enables the buttons and preloads the current panel values + */ +static SANE_Status +init_panel (struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + DBG (10, "init_panel: start\n"); + + ret = read_panel(s,0); + if(ret){ + DBG (5, "init_panel: disabling read_panel\n"); + s->can_read_panel = 0; + ret = SANE_STATUS_GOOD; + } + + s->panel_enable_led = 1; + s->panel_counter = 0; + ret = send_panel(s); + if(ret){ + DBG (5, "init_panel: disabling send_panel\n"); + s->can_write_panel = 0; + ret = SANE_STATUS_GOOD; + } + + DBG (10, "init_panel: finish\n"); + + return ret; +} + +/* + * set good default user values. + * struct is already initialized to 0. + */ +static SANE_Status +init_user (struct scanner *s) +{ + + DBG (10, "init_user: start\n"); + + /* source */ + if(s->has_flatbed) + s->u.source = SOURCE_FLATBED; + else if(s->has_adf) + s->u.source = SOURCE_ADF_FRONT; + + /* scan mode */ + if(s->can_monochrome) + s->u.mode=MODE_LINEART; + else if(s->can_halftone) + s->u.mode=MODE_HALFTONE; + else if(s->can_grayscale) + s->u.mode=MODE_GRAYSCALE; + else if(s->can_color) + s->u.mode=MODE_COLOR; + + /*x and y res*/ + s->u.dpi_x = s->basic_x_res; + s->u.dpi_y = s->basic_x_res; + + /* page width US-Letter */ + s->u.page_x = 8.5 * 1200; + if(s->u.page_x > s->valid_x){ + s->u.page_x = s->valid_x; + } + + /* page height US-Letter */ + s->u.page_y = 11 * 1200; + if(s->u.page_y > s->max_y){ + s->u.page_y = s->max_y; + } + + /* bottom-right x */ + s->u.br_x = s->u.page_x; + + /* bottom-right y */ + s->u.br_y = s->u.page_y; + + s->threshold = 90; + s->compress_arg = 50; + + DBG (10, "init_user: finish\n"); + + return SANE_STATUS_GOOD; +} + +/* + * This function presets the "option" array to blank + */ +static SANE_Status +init_options (struct scanner *s) +{ + int i; + + DBG (10, "init_options: start\n"); + + 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; + } + + /* go ahead and setup the first opt, because + * frontend may call control_option on it + * before calling get_option_descriptor + */ + s->opt[OPT_NUM_OPTS].name = SANE_NAME_NUM_OPTIONS; + s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS; + s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS; + s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT; + s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT; + + DBG (10, "init_options: finish\n"); + + return SANE_STATUS_GOOD; +} + +/* + * 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\n", name); + + for (dev = scanner_devList; dev; dev = dev->next) { + if (strcmp (dev->sane.name, name) == 0 + || strcmp (dev->device_name, name) == 0) { /*always allow sanei devname*/ + 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_STANDARD_GROUP){ + opt->name = SANE_NAME_STANDARD; + opt->title = SANE_TITLE_STANDARD; + opt->desc = SANE_DESC_STANDARD; + opt->type = SANE_TYPE_GROUP; + opt->constraint_type = SANE_CONSTRAINT_NONE; + } + + /* source */ + if(option==OPT_SOURCE){ + i=0; + if(s->has_flatbed){ + s->source_list[i++]=STRING_FLATBED; + } + if(s->has_adf){ + s->source_list[i++]=STRING_ADFFRONT; + + if(s->has_back){ + s->source_list[i++]=STRING_ADFBACK; + } + if(s->has_duplex){ + 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); + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + } + + /* scan mode */ + if(option==OPT_MODE){ + i=0; + if(s->can_monochrome || s->can_grayscale || s->can_color){ + s->mode_list[i++]=STRING_LINEART; + } + if(s->can_halftone){ + s->mode_list[i++]=STRING_HALFTONE; + } + if(s->can_grayscale || s->can_color){ + s->mode_list[i++]=STRING_GRAYSCALE; + } + if(s->can_color){ + 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); + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + } + + /* resolution */ + /* some scanners only support fixed res + * build a list of possible choices */ + /* we actually only look at the y resolution choices, + * and interpolate the image data as required for limited x resolutions */ + if(option==OPT_RES){ + i=0; + if(s->std_res_y[DPI_60] && s->max_y_res >= 60 && s->min_y_res <= 60){ + s->res_list[++i] = 60; + } + if(s->std_res_y[DPI_75] && s->max_y_res >= 75 && s->min_y_res <= 75){ + s->res_list[++i] = 75; + } + if(s->std_res_y[DPI_100] && s->max_y_res >= 100 && s->min_y_res <= 100){ + s->res_list[++i] = 100; + } + if(s->std_res_y[DPI_120] && s->max_y_res >= 120 && s->min_y_res <= 120){ + s->res_list[++i] = 120; + } + if(s->std_res_y[DPI_150] && s->max_y_res >= 150 && s->min_y_res <= 150){ + s->res_list[++i] = 150; + } + if(s->std_res_y[DPI_160] && s->max_y_res >= 160 && s->min_y_res <= 160){ + s->res_list[++i] = 160; + } + if(s->std_res_y[DPI_180] && s->max_y_res >= 180 && s->min_y_res <= 180){ + s->res_list[++i] = 180; + } + if(s->std_res_y[DPI_200] && s->max_y_res >= 200 && s->min_y_res <= 200){ + s->res_list[++i] = 200; + } + if(s->std_res_y[DPI_240] && s->max_y_res >= 240 && s->min_y_res <= 240){ + s->res_list[++i] = 240; + } + if(s->std_res_y[DPI_300] && s->max_y_res >= 300 && s->min_y_res <= 300){ + s->res_list[++i] = 300; + } + if(s->std_res_y[DPI_320] && s->max_y_res >= 320 && s->min_y_res <= 320){ + s->res_list[++i] = 320; + } + if(s->std_res_y[DPI_400] && s->max_y_res >= 400 && s->min_y_res <= 400){ + s->res_list[++i] = 400; + } + if(s->std_res_y[DPI_480] && s->max_y_res >= 480 && s->min_y_res <= 480){ + s->res_list[++i] = 480; + } + if(s->std_res_y[DPI_600] && s->max_y_res >= 600 && s->min_y_res <= 600){ + s->res_list[++i] = 600; + } + if(s->std_res_y[DPI_800] && s->max_y_res >= 800 && s->min_y_res <= 800){ + s->res_list[++i] = 800; + } + if(s->std_res_y[DPI_1200] && s->max_y_res >= 1200 && s->min_y_res <= 1200){ + s->res_list[++i] = 1200; + } + s->res_list[0] = i; + + opt->name = SANE_NAME_SCAN_RESOLUTION; + opt->title = SANE_TITLE_SCAN_RESOLUTION; + opt->desc = SANE_DESC_SCAN_RESOLUTION; + opt->type = SANE_TYPE_INT; + opt->unit = SANE_UNIT_DPI; + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + + if(s->step_y_res){ + s->res_range.min = s->min_y_res; + s->res_range.max = s->max_y_res; + s->res_range.quant = s->step_y_res; + opt->constraint_type = SANE_CONSTRAINT_RANGE; + opt->constraint.range = &s->res_range; + } + else{ + opt->constraint_type = SANE_CONSTRAINT_WORD_LIST; + opt->constraint.word_list = s->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(s->min_x); + s->tl_x_range.max = SCANNER_UNIT_TO_FIXED_MM(get_page_width(s)); + 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; + } + + /* 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(s->min_y); + s->tl_y_range.max = SCANNER_UNIT_TO_FIXED_MM(get_page_height(s)); + 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; + } + + /* 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; + } + + /* 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; + } + + /* 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->valid_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->u.source == SOURCE_FLATBED){ + opt->cap |= SANE_CAP_INACTIVE; + } + } + else{ + 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(s->min_y); + 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 = SANE_DESC_PAGE_HEIGHT; + 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->u.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; + + /* some have hardware brightness (always 0 to 255?) */ + /* some use LUT or GT (-127 to +127)*/ + if (s->brightness_steps){ + s->brightness_range.min=-127; + s->brightness_range.max=127; + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + } + else{ + opt->cap = SANE_CAP_INACTIVE; + } + } + + /* 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; + + /* some have hardware contrast (always 0 to 255?) */ + /* some use LUT or GT (-127 to +127)*/ + if (s->contrast_steps){ + s->contrast_range.min=-127; + s->contrast_range.max=127; + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + } + else { + 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=s->threshold_steps; + s->threshold_range.quant=1; + + if (s->threshold_steps){ + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + if(s->u.mode != MODE_LINEART){ + opt->cap |= SANE_CAP_INACTIVE; + } + } + else { + opt->cap = SANE_CAP_INACTIVE; + } + } + + if(option==OPT_RIF){ + opt->name = "rif"; + opt->title = "RIF"; + opt->desc = "Reverse image format"; + opt->type = SANE_TYPE_BOOL; + opt->unit = SANE_UNIT_NONE; + if (s->has_rif) + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + else + opt->cap = SANE_CAP_INACTIVE; + } + + /* "Advanced" group ------------------------------------------------------ */ + if(option==OPT_ADVANCED_GROUP){ + opt->name = SANE_NAME_ADVANCED; + opt->title = SANE_TITLE_ADVANCED; + opt->desc = SANE_DESC_ADVANCED; + opt->type = SANE_TYPE_GROUP; + opt->constraint_type = SANE_CONSTRAINT_NONE; + } + + /*image compression*/ + if(option==OPT_COMPRESS){ + i=0; + s->compress_list[i++]=STRING_NONE; + + if(s->has_comp_JPEG){ + s->compress_list[i++]=STRING_JPEG; + } + + s->compress_list[i]=NULL; + + opt->name = "compression"; + opt->title = "Compression"; + opt->desc = "Enable compressed data. May crash your front-end program"; + opt->type = SANE_TYPE_STRING; + opt->constraint_type = SANE_CONSTRAINT_STRING_LIST; + opt->constraint.string_list = s->compress_list; + opt->size = maxStringSize (opt->constraint.string_list); + + if (i > 1){ + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + if (s->u.mode != MODE_COLOR && s->u.mode != MODE_GRAYSCALE){ + opt->cap |= SANE_CAP_INACTIVE; + } + } + else + opt->cap = SANE_CAP_INACTIVE; + } + + /*image compression arg*/ + if(option==OPT_COMPRESS_ARG){ + + opt->name = "compression-arg"; + opt->title = "Compression argument"; + opt->desc = "Level of JPEG compression. 1 is small file, 100 is large file."; + opt->type = SANE_TYPE_INT; + opt->unit = SANE_UNIT_NONE; + opt->constraint_type = SANE_CONSTRAINT_RANGE; + opt->constraint.range = &s->compress_arg_range; + s->compress_arg_range.quant=1; + + if(s->has_comp_JPEG){ + s->compress_arg_range.min=0; + s->compress_arg_range.max=100; + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + + if(s->compress != COMP_JPEG){ + opt->cap |= SANE_CAP_INACTIVE; + } + } + else + opt->cap = SANE_CAP_INACTIVE; + } + + /*double feed by length*/ + if(option==OPT_DF_LENGTH){ + opt->name = "df-length"; + opt->title = "DF by length"; + opt->desc = "Detect double feeds by comparing document lengths"; + opt->type = SANE_TYPE_BOOL; + opt->unit = SANE_UNIT_NONE; + opt->constraint_type = SANE_CONSTRAINT_NONE; + + if (1) + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + else + opt->cap = SANE_CAP_INACTIVE; + } + + /*double feed by thickness */ + if(option==OPT_DF_THICKNESS){ + + opt->name = "df-thickness"; + opt->title = "DF by thickness"; + opt->desc = "Detect double feeds using thickness sensor"; + opt->type = SANE_TYPE_BOOL; + opt->unit = SANE_UNIT_NONE; + opt->constraint_type = SANE_CONSTRAINT_NONE; + + if (1){ + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + } + else + opt->cap = SANE_CAP_INACTIVE; + } + + /*deskew by roller*/ + if(option==OPT_ROLLERDESKEW){ + opt->name = "rollerdeskew"; + opt->title = "Roller deskew"; + opt->desc = "Request scanner to correct skewed pages mechanically"; + opt->type = SANE_TYPE_BOOL; + if (1) + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + else + opt->cap = SANE_CAP_INACTIVE; + } + + /*deskew by software*/ + if(option==OPT_SWDESKEW){ + opt->name = "swdeskew"; + opt->title = "Software deskew"; + opt->desc = "Request driver to rotate skewed pages digitally"; + opt->type = SANE_TYPE_BOOL; + if (1) + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + else + opt->cap = SANE_CAP_INACTIVE; + } + + /*software despeckle radius*/ + if(option==OPT_SWDESPECK){ + + opt->name = "swdespeck"; + opt->title = "Software despeckle diameter"; + opt->desc = "Maximum diameter of lone dots to remove from scan"; + opt->type = SANE_TYPE_INT; + opt->unit = SANE_UNIT_NONE; + opt->constraint_type = SANE_CONSTRAINT_RANGE; + opt->constraint.range = &s->swdespeck_range; + s->swdespeck_range.quant=1; + + if(1){ + s->swdespeck_range.min=0; + s->swdespeck_range.max=9; + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + } + else + opt->cap = SANE_CAP_INACTIVE; + } + + /*crop by software*/ + if(option==OPT_SWCROP){ + opt->name = "swcrop"; + opt->title = "Software crop"; + opt->desc = "Request driver to remove border from pages digitally"; + opt->type = SANE_TYPE_BOOL; + if (1) + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + else + opt->cap = SANE_CAP_INACTIVE; + } + + /*staple detection*/ + if(option==OPT_STAPLEDETECT){ + opt->name = "stapledetect"; + opt->title = "Staple detect"; + opt->desc = "Request scanner to halt if stapled pages are detected"; + opt->type = SANE_TYPE_BOOL; + if (1) + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + else + opt->cap = SANE_CAP_INACTIVE; + } + + /*dropout color front*/ + if(option==OPT_DROPOUT_COLOR_F){ + s->do_color_list[0] = STRING_NONE; + s->do_color_list[1] = STRING_RED; + s->do_color_list[2] = STRING_GREEN; + s->do_color_list[3] = STRING_BLUE; + s->do_color_list[4] = STRING_EN_RED; + s->do_color_list[5] = STRING_EN_GREEN; + s->do_color_list[6] = STRING_EN_BLUE; + s->do_color_list[7] = NULL; + + opt->name = "dropout-front"; + opt->title = "Dropout color front"; + opt->desc = "One-pass scanners use only one color during gray or binary scanning, useful for colored paper or ink"; + opt->type = SANE_TYPE_STRING; + opt->constraint_type = SANE_CONSTRAINT_STRING_LIST; + opt->constraint.string_list = s->do_color_list; + opt->size = maxStringSize (opt->constraint.string_list); + + if (1){ + opt->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED; + if(s->u.mode == MODE_COLOR) + opt->cap |= SANE_CAP_INACTIVE; + } + else + opt->cap = SANE_CAP_INACTIVE; + } + + /*dropout color back*/ + if(option==OPT_DROPOUT_COLOR_B){ + s->do_color_list[0] = STRING_NONE; + s->do_color_list[1] = STRING_RED; + s->do_color_list[2] = STRING_GREEN; + s->do_color_list[3] = STRING_BLUE; + s->do_color_list[4] = STRING_EN_RED; + s->do_color_list[5] = STRING_EN_GREEN; + s->do_color_list[6] = STRING_EN_BLUE; + s->do_color_list[7] = NULL; + + opt->name = "dropout-back"; + opt->title = "Dropout color back"; + opt->desc = "One-pass scanners use only one color during gray or binary scanning, useful for colored paper or ink"; + opt->type = SANE_TYPE_STRING; + opt->constraint_type = SANE_CONSTRAINT_STRING_LIST; + opt->constraint.string_list = s->do_color_list; + opt->size = maxStringSize (opt->constraint.string_list); + + if (1){ + opt->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED; + if(s->u.mode == MODE_COLOR) + opt->cap |= SANE_CAP_INACTIVE; + } + else + opt->cap = SANE_CAP_INACTIVE; + } + + /*buffer mode*/ + if(option==OPT_BUFFERMODE){ + opt->name = "buffermode"; + opt->title = "Buffer mode"; + opt->desc = "Request scanner to read pages async into internal memory"; + opt->type = SANE_TYPE_BOOL; + if (s->has_buffer) + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + else + opt->cap = SANE_CAP_INACTIVE; + } + + if(option==OPT_SIDE){ + opt->name = "side"; + opt->title = "Duplex side"; + opt->desc = "Tells which side (0=front, 1=back) of a duplex scan the next call to sane_read will return."; + opt->type = SANE_TYPE_BOOL; + opt->unit = SANE_UNIT_NONE; + opt->size = sizeof(SANE_Word); + opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + opt->constraint_type = SANE_CONSTRAINT_NONE; + } + + /* "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; + } + + if(option==OPT_START){ + opt->name = "start"; + opt->title = "Start/1 button"; + opt->desc = "Big green or small 1 button"; + opt->type = SANE_TYPE_BOOL; + opt->unit = SANE_UNIT_NONE; + opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; + if(!s->can_read_panel) + opt->cap = SANE_CAP_INACTIVE; + } + + if(option==OPT_STOP){ + opt->name = "stop"; + opt->title = "Stop/2 button"; + opt->desc = "Small orange or small 2 button"; + opt->type = SANE_TYPE_BOOL; + opt->unit = SANE_UNIT_NONE; + opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; + if(!s->can_read_panel) + opt->cap = SANE_CAP_INACTIVE; + } + + if(option==OPT_BUTT3){ + opt->name = "button-3"; + opt->title = "3 button"; + opt->desc = "Small 3 button"; + opt->type = SANE_TYPE_BOOL; + opt->unit = SANE_UNIT_NONE; + opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; + if(!s->can_read_panel) + opt->cap = SANE_CAP_INACTIVE; + } + + if(option==OPT_NEWFILE){ + opt->name = "newfile"; + opt->title = "New File button"; + opt->desc = "New File button"; + opt->type = SANE_TYPE_BOOL; + opt->unit = SANE_UNIT_NONE; + opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; + if(!s->can_read_panel) + opt->cap = SANE_CAP_INACTIVE; + } + + if(option==OPT_COUNTONLY){ + opt->name = "countonly"; + opt->title = "Count Only button"; + opt->desc = "Count Only button"; + opt->type = SANE_TYPE_BOOL; + opt->unit = SANE_UNIT_NONE; + opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; + if(!s->can_read_panel) + opt->cap = SANE_CAP_INACTIVE; + } + + if(option==OPT_BYPASSMODE){ + opt->name = "bypassmode"; + opt->title = "Bypass Mode button"; + opt->desc = "Bypass Mode button"; + opt->type = SANE_TYPE_BOOL; + opt->unit = SANE_UNIT_NONE; + opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; + if(!s->can_read_panel) + opt->cap = SANE_CAP_INACTIVE; + } + + if(option==OPT_COUNTER){ + opt->name = "counter"; + opt->title = "Counter"; + opt->desc = "Scan counter"; + opt->type = SANE_TYPE_INT; + opt->unit = SANE_UNIT_NONE; + opt->constraint_type = SANE_CONSTRAINT_RANGE; + opt->constraint.range = &s->counter_range; + s->counter_range.min=0; + s->counter_range.max=500; + s->counter_range.quant=1; + + if (s->can_read_panel && s->has_counter) + 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; + SANE_Status ret = SANE_STATUS_GOOD; + + /* 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->u.source == SOURCE_FLATBED){ + strcpy (val, STRING_FLATBED); + } + else if(s->u.source == SOURCE_ADF_FRONT){ + strcpy (val, STRING_ADFFRONT); + } + else if(s->u.source == SOURCE_ADF_BACK){ + strcpy (val, STRING_ADFBACK); + } + else if(s->u.source == SOURCE_ADF_DUPLEX){ + strcpy (val, STRING_ADFDUPLEX); + } + return SANE_STATUS_GOOD; + + case OPT_MODE: + if(s->u.mode == MODE_LINEART){ + strcpy (val, STRING_LINEART); + } + else if(s->u.mode == MODE_HALFTONE){ + strcpy (val, STRING_HALFTONE); + } + else if(s->u.mode == MODE_GRAYSCALE){ + strcpy (val, STRING_GRAYSCALE); + } + else if(s->u.mode == MODE_COLOR){ + strcpy (val, STRING_COLOR); + } + return SANE_STATUS_GOOD; + + case OPT_RES: + *val_p = s->u.dpi_x; + return SANE_STATUS_GOOD; + + case OPT_TL_X: + *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.tl_x); + return SANE_STATUS_GOOD; + + case OPT_TL_Y: + *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.tl_y); + return SANE_STATUS_GOOD; + + case OPT_BR_X: + *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.br_x); + return SANE_STATUS_GOOD; + + case OPT_BR_Y: + *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.br_y); + return SANE_STATUS_GOOD; + + case OPT_PAGE_WIDTH: + *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.page_x); + return SANE_STATUS_GOOD; + + case OPT_PAGE_HEIGHT: + *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.page_y); + 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_THRESHOLD: + *val_p = s->threshold; + return SANE_STATUS_GOOD; + + case OPT_RIF: + *val_p = s->rif; + return SANE_STATUS_GOOD; + + /* Advanced Group */ + case OPT_COMPRESS: + if(s->compress == COMP_JPEG){ + strcpy (val, STRING_JPEG); + } + else{ + strcpy (val, STRING_NONE); + } + return SANE_STATUS_GOOD; + + case OPT_COMPRESS_ARG: + *val_p = s->compress_arg; + return SANE_STATUS_GOOD; + + case OPT_DF_LENGTH: + *val_p = s->df_length; + return SANE_STATUS_GOOD; + + case OPT_DF_THICKNESS: + *val_p = s->df_thickness; + return SANE_STATUS_GOOD; + + case OPT_ROLLERDESKEW: + *val_p = s->rollerdeskew; + return SANE_STATUS_GOOD; + + case OPT_SWDESKEW: + *val_p = s->swdeskew; + return SANE_STATUS_GOOD; + + case OPT_SWDESPECK: + *val_p = s->swdespeck; + return SANE_STATUS_GOOD; + + case OPT_SWCROP: + *val_p = s->swcrop; + return SANE_STATUS_GOOD; + + case OPT_STAPLEDETECT: + *val_p = s->stapledetect; + return SANE_STATUS_GOOD; + + case OPT_DROPOUT_COLOR_F: + switch (s->dropout_color_f) { + case COLOR_NONE: + strcpy (val, STRING_NONE); + break; + case COLOR_RED: + strcpy (val, STRING_RED); + break; + case COLOR_GREEN: + strcpy (val, STRING_GREEN); + break; + case COLOR_BLUE: + strcpy (val, STRING_BLUE); + break; + case COLOR_EN_RED: + strcpy (val, STRING_EN_RED); + break; + case COLOR_EN_GREEN: + strcpy (val, STRING_EN_GREEN); + break; + case COLOR_EN_BLUE: + strcpy (val, STRING_EN_BLUE); + break; + } + return SANE_STATUS_GOOD; + + case OPT_DROPOUT_COLOR_B: + switch (s->dropout_color_b) { + case COLOR_NONE: + strcpy (val, STRING_NONE); + break; + case COLOR_RED: + strcpy (val, STRING_RED); + break; + case COLOR_GREEN: + strcpy (val, STRING_GREEN); + break; + case COLOR_BLUE: + strcpy (val, STRING_BLUE); + break; + case COLOR_EN_RED: + strcpy (val, STRING_EN_RED); + break; + case COLOR_EN_GREEN: + strcpy (val, STRING_EN_GREEN); + break; + case COLOR_EN_BLUE: + strcpy (val, STRING_EN_BLUE); + break; + } + return SANE_STATUS_GOOD; + + case OPT_BUFFERMODE: + *val_p = s->buffermode; + return SANE_STATUS_GOOD; + + case OPT_SIDE: + *val_p = s->side; + return SANE_STATUS_GOOD; + + /* Sensor Group */ + case OPT_START: + ret = read_panel(s,OPT_START); + *val_p = s->panel_start; + return ret; + + case OPT_STOP: + ret = read_panel(s,OPT_STOP); + *val_p = s->panel_stop; + return ret; + + case OPT_BUTT3: + ret = read_panel(s,OPT_BUTT3); + *val_p = s->panel_butt3; + return ret; + + case OPT_NEWFILE: + ret = read_panel(s,OPT_NEWFILE); + *val_p = s->panel_new_file; + return ret; + + case OPT_COUNTONLY: + ret = read_panel(s,OPT_COUNTONLY); + *val_p = s->panel_count_only; + return ret; + + case OPT_BYPASSMODE: + ret = read_panel(s,OPT_BYPASSMODE); + *val_p = s->panel_bypass_mode; + return ret; + + case OPT_COUNTER: + ret = read_panel(s,OPT_COUNTER); + *val_p = s->panel_counter; + return ret; + + } + } + 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->u.source == tmp) + return SANE_STATUS_GOOD; + + s->u.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_HALFTONE)) { + tmp = MODE_HALFTONE; + } + else if (!strcmp (val, STRING_GRAYSCALE)) { + tmp = MODE_GRAYSCALE; + } + else{ + tmp = MODE_COLOR; + } + + if (tmp == s->u.mode) + return SANE_STATUS_GOOD; + + s->u.mode = tmp; + + *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; + return SANE_STATUS_GOOD; + + case OPT_RES: + + if (s->u.dpi_x == val_c && s->u.dpi_y == val_c) + return SANE_STATUS_GOOD; + + s->u.dpi_x = val_c; + s->u.dpi_y = val_c; + + *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; + return SANE_STATUS_GOOD; + + /* Geometry Group */ + case OPT_TL_X: + if (s->u.tl_x == FIXED_MM_TO_SCANNER_UNIT(val_c)) + return SANE_STATUS_GOOD; + + s->u.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->u.tl_y == FIXED_MM_TO_SCANNER_UNIT(val_c)) + return SANE_STATUS_GOOD; + + s->u.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->u.br_x == FIXED_MM_TO_SCANNER_UNIT(val_c)) + return SANE_STATUS_GOOD; + + s->u.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->u.br_y == FIXED_MM_TO_SCANNER_UNIT(val_c)) + return SANE_STATUS_GOOD; + + s->u.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->u.page_x == FIXED_MM_TO_SCANNER_UNIT(val_c)) + return SANE_STATUS_GOOD; + + s->u.page_x = FIXED_MM_TO_SCANNER_UNIT(val_c); + + *info |= SANE_INFO_RELOAD_OPTIONS; + return SANE_STATUS_GOOD; + + case OPT_PAGE_HEIGHT: + if (s->u.page_y == FIXED_MM_TO_SCANNER_UNIT(val_c)) + return SANE_STATUS_GOOD; + + s->u.page_y = FIXED_MM_TO_SCANNER_UNIT(val_c); + + *info |= SANE_INFO_RELOAD_OPTIONS; + return SANE_STATUS_GOOD; + + /* 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_THRESHOLD: + s->threshold = val_c; + return SANE_STATUS_GOOD; + + case OPT_RIF: + s->rif = val_c; + return SANE_STATUS_GOOD; + + /* Advanced Group */ + case OPT_COMPRESS: + if (!strcmp (val, STRING_JPEG)) { + s->compress = COMP_JPEG; + } + else{ + s->compress = COMP_NONE; + } + return SANE_STATUS_GOOD; + + case OPT_COMPRESS_ARG: + s->compress_arg = val_c; + return SANE_STATUS_GOOD; + + case OPT_DF_LENGTH: + s->df_length = val_c; + return SANE_STATUS_GOOD; + + case OPT_DF_THICKNESS: + s->df_thickness = val_c; + return SANE_STATUS_GOOD; + + case OPT_ROLLERDESKEW: + s->rollerdeskew = val_c; + return SANE_STATUS_GOOD; + + case OPT_SWDESKEW: + s->swdeskew = val_c; + return SANE_STATUS_GOOD; + + case OPT_SWDESPECK: + s->swdespeck = val_c; + return SANE_STATUS_GOOD; + + case OPT_SWCROP: + s->swcrop = val_c; + return SANE_STATUS_GOOD; + + case OPT_STAPLEDETECT: + s->stapledetect = val_c; + return SANE_STATUS_GOOD; + + case OPT_DROPOUT_COLOR_F: + if (!strcmp(val, STRING_NONE)) + s->dropout_color_f = COLOR_NONE; + else if (!strcmp(val, STRING_RED)) + s->dropout_color_f = COLOR_RED; + else if (!strcmp(val, STRING_GREEN)) + s->dropout_color_f = COLOR_GREEN; + else if (!strcmp(val, STRING_BLUE)) + s->dropout_color_f = COLOR_BLUE; + else if (!strcmp(val, STRING_EN_RED)) + s->dropout_color_f = COLOR_EN_RED; + else if (!strcmp(val, STRING_EN_GREEN)) + s->dropout_color_f = COLOR_EN_GREEN; + else if (!strcmp(val, STRING_EN_BLUE)) + s->dropout_color_f = COLOR_EN_BLUE; + return SANE_STATUS_GOOD; + + case OPT_DROPOUT_COLOR_B: + if (!strcmp(val, STRING_NONE)) + s->dropout_color_b = COLOR_NONE; + else if (!strcmp(val, STRING_RED)) + s->dropout_color_b = COLOR_RED; + else if (!strcmp(val, STRING_GREEN)) + s->dropout_color_b = COLOR_GREEN; + else if (!strcmp(val, STRING_BLUE)) + s->dropout_color_b = COLOR_BLUE; + else if (!strcmp(val, STRING_EN_RED)) + s->dropout_color_b = COLOR_EN_RED; + else if (!strcmp(val, STRING_EN_GREEN)) + s->dropout_color_b = COLOR_EN_GREEN; + else if (!strcmp(val, STRING_EN_BLUE)) + s->dropout_color_b = COLOR_EN_BLUE; + return SANE_STATUS_GOOD; + + case OPT_BUFFERMODE: + s->buffermode = val_c; + return SANE_STATUS_GOOD; + + } + } /* else */ + + return SANE_STATUS_INVAL; +} + +static SANE_Status +ssm_buffer (struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + unsigned char cmd[SET_SCAN_MODE_len]; + size_t cmdLen = SET_SCAN_MODE_len; + + unsigned char out[SSM_PAY_len]; + size_t outLen = SSM_PAY_len; + + DBG (10, "ssm_buffer: start\n"); + + if(!s->has_ssm){ + DBG (10, "ssm_buffer: unsupported\n"); + return ret; + } + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, SET_SCAN_MODE_code); + set_SSM_pf(cmd, 1); + set_SSM_pay_len(cmd, outLen); + + memset(out,0,outLen); + if(s->has_ssm_pay_head_len){ + set_SSM_pay_head_len(out, SSM_PAY_HEAD_len); + } + set_SSM_page_code(out, SM_pc_buffer); + set_SSM_page_len(out, SSM_PAGE_len); + + if(s->s.source == SOURCE_ADF_DUPLEX){ + set_SSM_BUFF_duplex(out, 1); + } + else if(s->s.source == SOURCE_FLATBED){ + set_SSM_BUFF_fb(out, 1); + } + if(s->buffermode){ + set_SSM_BUFF_async(out, 1); + } + if(0){ + set_SSM_BUFF_ald(out, 1); + } + if(0){ + set_SSM_BUFF_unk(out,1); + } + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + out, outLen, + NULL, NULL + ); + + DBG (10, "ssm_buffer: finish\n"); + + return ret; +} + +static SANE_Status +ssm_df (struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + unsigned char cmd[SET_SCAN_MODE_len]; + size_t cmdLen = SET_SCAN_MODE_len; + + unsigned char out[SSM_PAY_len]; + size_t outLen = SSM_PAY_len; + + DBG (10, "ssm_df: start\n"); + + if(!s->has_ssm || !s->has_df){ + DBG (10, "ssm_df: unsupported, finishing\n"); + return ret; + } + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, SET_SCAN_MODE_code); + set_SSM_pf(cmd, 1); + set_SSM_pay_len(cmd, outLen); + + memset(out,0,outLen); + if(s->has_ssm_pay_head_len){ + set_SSM_pay_head_len(out, SSM_PAY_HEAD_len); + } + set_SSM_page_code(out, SM_pc_df); + set_SSM_page_len(out, SSM_PAGE_len); + + /* deskew by roller */ + if(s->rollerdeskew){ + set_SSM_DF_deskew_roll(out, 1); + } + + /* staple detection */ + if(s->stapledetect){ + set_SSM_DF_staple(out, 1); + } + + /* thickness */ + if(s->df_thickness){ + set_SSM_DF_thick(out, 1); + } + + /* length */ + if(s->df_length){ + set_SSM_DF_len(out, 1); + } + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + out, outLen, + NULL, NULL + ); + + DBG (10, "ssm_df: finish\n"); + + return ret; +} + +static SANE_Status +ssm_do (struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + unsigned char cmd[SET_SCAN_MODE_len]; + size_t cmdLen = SET_SCAN_MODE_len; + + unsigned char out[SSM_PAY_len]; + size_t outLen = SSM_PAY_len; + + DBG (10, "ssm_do: start\n"); + + if(!s->has_ssm || !s->can_color){ + DBG (10, "ssm_do: unsupported, finishing\n"); + return ret; + } + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, SET_SCAN_MODE_code); + set_SSM_pf(cmd, 1); + set_SSM_pay_len(cmd, outLen); + + memset(out,0,outLen); + if(s->has_ssm_pay_head_len){ + set_SSM_pay_head_len(out, SSM_PAY_HEAD_len); + } + set_SSM_page_code(out, SM_pc_dropout); + set_SSM_page_len(out, SSM_PAGE_len); + + set_SSM_DO_unk1(out, 0x03); + + switch(s->dropout_color_f){ + case COLOR_RED: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_f_do(out,SSM_DO_red); + break; + case COLOR_GREEN: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_f_do(out,SSM_DO_green); + break; + case COLOR_BLUE: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_f_do(out,SSM_DO_blue); + break; + case COLOR_EN_RED: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_f_en(out,SSM_DO_red); + break; + case COLOR_EN_GREEN: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_f_en(out,SSM_DO_green); + break; + case COLOR_EN_BLUE: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_f_en(out,SSM_DO_blue); + break; + } + + switch(s->dropout_color_b){ + case COLOR_RED: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_b_do(out,SSM_DO_red); + break; + case COLOR_GREEN: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_b_do(out,SSM_DO_green); + break; + case COLOR_BLUE: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_b_do(out,SSM_DO_blue); + break; + case COLOR_EN_RED: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_b_en(out,SSM_DO_red); + break; + case COLOR_EN_GREEN: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_b_en(out,SSM_DO_green); + break; + case COLOR_EN_BLUE: + set_SSM_DO_unk2(out, 0x05); + set_SSM_DO_b_en(out,SSM_DO_blue); + break; + } + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + out, outLen, + NULL, NULL + ); + + DBG (10, "ssm_do: finish\n"); + + return ret; +} + +/* used by recent scanners. meaning of payloads unknown */ +static SANE_Status +ssm2 (struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + unsigned char cmd[SET_SCAN_MODE2_len]; + size_t cmdLen = SET_SCAN_MODE2_len; + + unsigned char out[SSM2_PAY_len]; + size_t outLen = SSM2_PAY_len; + + DBG (10, "ssm2:start\n"); + + if(!s->has_ssm2){ + DBG (10, "ssm2: unsupported, finishing\n"); + return ret; + } + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, SET_SCAN_MODE2_code); + /*FIXME: set this correctly */ + set_SSM2_page_code(cmd, 0); + set_SSM2_pay_len(cmd, outLen); + + /*FIXME: set these correctly */ + memset(out,0,outLen); + set_SSM2_unk(out, 0); + set_SSM2_unk2(out, 0); + set_SSM2_unk3(out, 0); + + /* + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + out, outLen, + NULL, NULL + );*/ + + DBG (10, "ssm2: finish\n"); + + return ret; +} + +static SANE_Status +read_panel(struct scanner *s,SANE_Int option) +{ + SANE_Status ret=SANE_STATUS_GOOD; + + unsigned char cmd[READ_len]; + size_t cmdLen = READ_len; + + unsigned char in[R_PANEL_len]; + size_t inLen = R_PANEL_len; + + DBG (10, "read_panel: start %d\n", option); + + if(!s->can_read_panel){ + DBG (10, "read_panel: unsupported, finishing\n"); + return ret; + } + + /* only run this if frontend has read previous value + * or if the caller does not want the data stored */ + if (!option || !s->hw_read[option-OPT_START]) { + + DBG (15, "read_panel: running\n"); + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, READ_code); + set_R_datatype_code (cmd, SR_datatype_panel); + set_R_xfer_length (cmd, inLen); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + NULL, 0, + in, &inLen + ); + + if (ret == SANE_STATUS_GOOD || ret == SANE_STATUS_EOF) { + /*set flags indicating there is data to read*/ + if(option) + memset(s->hw_read,1,sizeof(s->hw_read)); + + s->panel_start = get_R_PANEL_start(in); + s->panel_stop = get_R_PANEL_stop(in); + s->panel_butt3 = get_R_PANEL_butt3(in); + s->panel_new_file = get_R_PANEL_new_file(in); + s->panel_count_only = get_R_PANEL_count_only(in); + s->panel_bypass_mode = get_R_PANEL_bypass_mode(in); + s->panel_enable_led = get_R_PANEL_enable_led(in); + s->panel_counter = get_R_PANEL_counter(in); + ret = SANE_STATUS_GOOD; + } + } + + if(option) + s->hw_read[option-OPT_START] = 0; + + DBG (10, "read_panel: finish %d\n",s->panel_counter); + + return ret; +} + +static SANE_Status +send_panel(struct scanner *s) +{ + SANE_Status ret=SANE_STATUS_GOOD; + + unsigned char cmd[SEND_len]; + size_t cmdLen = SEND_len; + + unsigned char out[S_PANEL_len]; + size_t outLen = S_PANEL_len; + + DBG (10, "send_panel: start\n"); + + if(!s->can_write_panel){ + DBG (10, "send_panel: unsupported, finishing\n"); + return ret; + } + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, SEND_code); + set_S_xfer_datatype (cmd, SR_datatype_panel); + set_S_xfer_length (cmd, outLen); + + memset(out,0,outLen); + set_S_PANEL_enable_led(out,s->panel_enable_led); + set_S_PANEL_counter(out,s->panel_counter); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + out, outLen, + NULL, NULL + ); + + if (ret == SANE_STATUS_EOF) { + ret = SANE_STATUS_GOOD; + } + + DBG (10, "send_panel: finish %d\n", ret); + + 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) +{ + SANE_Status ret = SANE_STATUS_GOOD; + struct scanner *s = (struct scanner *) handle; + + DBG (10, "sane_get_parameters: start\n"); + + if(!s->started){ + ret = update_params(s,0); + if(ret){ + DBG (5, "sane_get_parameters: up error, returning %d\n", ret); + return ret; + } + } + + /* this backend only sends single frame images */ + params->last_frame = 1; + + params->format = s->i.format; + params->lines = s->i.height; + params->depth = s->i.bpp; + if(params->depth == 24) params->depth = 8; + params->pixels_per_line = s->i.width; + params->bytes_per_line = s->i.Bpl; + + DBG(15,"sane_get_parameters: x: max=%d, page=%d, gpw=%d, res=%d\n", + s->valid_x, s->i.page_x, get_page_width(s), s->i.dpi_x); + + DBG(15,"sane_get_parameters: y: max=%d, page=%d, gph=%d, res=%d\n", + s->max_y, s->i.page_y, get_page_height(s), s->i.dpi_y); + + DBG(15,"sane_get_parameters: area: tlx=%d, brx=%d, tly=%d, bry=%d\n", + s->i.tl_x, s->i.br_x, s->i.tl_y, s->i.br_y); + + DBG (15, "sane_get_parameters: params: ppl=%d, Bpl=%d, lines=%d\n", + params->pixels_per_line, params->bytes_per_line, params->lines); + + DBG (15, "sane_get_parameters: params: format=%d, depth=%d, last=%d\n", + params->format, params->depth, params->last_frame); + + DBG (10, "sane_get_parameters: finish\n"); + + return ret; +} + +SANE_Status +update_params(struct scanner *s, int calib) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + DBG (10, "update_params: start\n"); + + s->u.width = (s->u.br_x - s->u.tl_x) * s->u.dpi_x / 1200; + s->u.height = (s->u.br_y - s->u.tl_y) * s->u.dpi_y / 1200; + + if (s->u.mode == MODE_COLOR) { + s->u.format = SANE_FRAME_RGB; + s->u.bpp = 24; + } + else if (s->u.mode == MODE_GRAYSCALE) { + s->u.format = SANE_FRAME_GRAY; + s->u.bpp = 8; + } + else { + s->u.format = SANE_FRAME_GRAY; + s->u.bpp = 1; + + /* round down to byte boundary */ + s->u.width -= s->u.width % 8; + } + + /* round down to pixel boundary for some scanners */ + s->u.width -= s->u.width % s->ppl_mod; + +#ifdef SANE_FRAME_JPEG + /* jpeg requires 8x8 squares */ + if(s->compress == COMP_JPEG && s->u.mode >= MODE_GRAYSCALE){ + s->u.format = SANE_FRAME_JPEG; + s->u.width -= s->u.width % 8; + s->u.height -= s->u.height % 8; + } +#endif + + s->u.Bpl = s->u.width * s->u.bpp / 8; + s->u.valid_Bpl = s->u.Bpl; + s->u.valid_width = s->u.width; + + DBG (15, "update_params: user params: w:%d h:%d m:%d f:%d b:%d\n", + s->u.width, s->u.height, s->u.mode, s->u.format, s->u.bpp); + DBG (15, "update_params: user params: B:%d vB:%d vw:%d\n", + s->u.Bpl, s->u.valid_Bpl, s->u.valid_width); + DBG (15, "update_params: user params: x b:%d t:%d d:%d y b:%d t:%d d:%d\n", + s->u.br_x, s->u.tl_x, s->u.dpi_x, s->u.br_y, s->u.tl_y, s->u.dpi_y); + + /* some scanners are limited in their valid scan params + * make a second version of the params struct, but + * override the user's values with what the scanner can actually do */ + + memcpy(&s->s,&s->u,sizeof(struct img_params)); + + /*********** missing modes (move up to valid one) **************/ + if(s->s.mode == MODE_LINEART && !s->can_monochrome){ + s->s.mode = MODE_GRAYSCALE; + s->s.format = SANE_FRAME_GRAY; + s->s.bpp = 8; + } + if(s->s.mode == MODE_GRAYSCALE && !s->can_grayscale){ + s->s.mode = MODE_COLOR; + s->s.format = SANE_FRAME_RGB; + s->s.bpp = 24; + } + if(s->s.mode == MODE_COLOR && !s->can_color){ + DBG (5, "update_params: no valid mode\n"); + return SANE_STATUS_INVAL; + } + + /********** missing resolutions (move up to valid one) *********/ + if(!s->step_x_res){ + int i; + for(i=0;i<DPI_1200;i++){ + + /* this res is smaller or invalid, skip it */ + if(s->s.dpi_x > dpi_list[i] || !s->std_res_x[i]) + continue; + + /* same & valid res, done */ + if(s->s.dpi_x == dpi_list[i]) + break; + + /* different & valid res, switch */ + s->s.dpi_x = dpi_list[i]; + break; + } + + if(i > DPI_1200){ + DBG (5, "update_params: no dpi\n"); + return SANE_STATUS_INVAL; + } + } + + /*********** weird scan area (increase to valid one) *********/ + if(s->fixed_width){ + s->s.tl_x = 0; + s->s.br_x = s->max_x; + s->s.page_x = s->max_x; + } + + /*recalculate new params*/ + s->s.width = (s->s.br_x - s->s.tl_x) * s->s.dpi_x / 1200; + + /* round down to byte boundary */ + if(s->s.mode < MODE_GRAYSCALE){ + s->s.width -= s->s.width % 8; + } + + /* round down to pixel boundary for some scanners */ + s->s.width -= s->s.width % s->ppl_mod; + + s->s.valid_width = s->s.width; + s->s.valid_Bpl = s->s.valid_width * s->s.bpp / 8; + + /* some machines (DR-2050) require even bytes per scanline */ + /* increase width and Bpl, but not valid_width and valid_Bpl */ + if(s->even_Bpl && (s->s.width % 2)){ + s->s.width++; + } + + s->s.Bpl = s->s.width * s->s.bpp / 8; + + /* figure out how many valid bytes per line (2510 is padded) */ + if(s->color_interlace[SIDE_FRONT] == COLOR_INTERLACE_2510){ + s->s.valid_Bpl = s->s.Bpl*11/12; + s->s.valid_width = s->s.width*11/12; + } + + /* some scanners need longer scans because front/back is offset */ + if(s->u.source == SOURCE_ADF_DUPLEX && s->duplex_offset && !calib) + s->s.height = (s->u.br_y-s->u.tl_y+s->duplex_offset) * s->u.dpi_y / 1200; + + /* round lines up to even number */ + s->s.height += s->s.height % 2; + + DBG (15, "update_params: scan params: w:%d h:%d m:%d f:%d b:%d\n", + s->s.width, s->s.height, s->s.mode, s->s.format, s->s.bpp); + DBG (15, "update_params: scan params: B:%d vB:%d vw:%d\n", + s->s.Bpl, s->s.valid_Bpl, s->s.valid_width); + DBG (15, "update_params: scan params: x b:%d t:%d d:%d y b:%d t:%d d:%d\n", + s->s.br_x, s->s.tl_x, s->s.dpi_x, s->s.br_y, s->s.tl_y, s->s.dpi_y); + + /* make a third (intermediate) version of the params struct, + * currently identical to the user's params. this is what + * we actually will send back to the user (though buffer_xxx + * functions might change these values after this runs) */ + + /* calibration code needs the data just as it comes from the scanner */ + if(calib) + memcpy(&s->i,&s->s,sizeof(struct img_params)); + /* normal scans need the data cleaned for presentation to the user */ + else{ + memcpy(&s->i,&s->u,sizeof(struct img_params)); + /*dumb scanners pad the top of front page in duplex*/ + if(s->i.source == SOURCE_ADF_DUPLEX) + s->i.skip_lines[s->duplex_offset_side] = s->duplex_offset * s->i.dpi_y / 1200; + } + + DBG (15, "update_params: i params: w:%d h:%d m:%d f:%d b:%d\n", + s->i.width, s->i.height, s->i.mode, s->i.format, s->i.bpp); + DBG (15, "update_params: i params: B:%d vB:%d vw:%d\n", + s->i.Bpl, s->i.valid_Bpl, s->i.valid_width); + DBG (15, "update_params: i params: x b:%d t:%d d:%d y b:%d t:%d d:%d\n", + s->i.br_x, s->i.tl_x, s->i.dpi_x, s->i.br_y, s->i.tl_y, s->i.dpi_y); + + DBG (10, "update_params: finish\n"); + return ret; +} + +/* reset image size parameters after buffer_xxx functions changed them */ +SANE_Status +update_i_params(struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + DBG (10, "update_i_params: start\n"); + + s->i.width = s->u.width; + s->i.Bpl = s->u.Bpl; + + DBG (10, "update_i_params: finish\n"); + return ret; +} + +/* + * Called by SANE when a page acquisition operation is to be started. + * commands: set window, object pos, and scan + * + * this will be called between sides of a duplex scan, + * and at the start of each page of an adf batch. + * hence, we spend alot of time playing with s->started, etc. + */ +SANE_Status +sane_start (SANE_Handle handle) +{ + struct scanner *s = handle; + SANE_Status ret = SANE_STATUS_GOOD; + + DBG (10, "sane_start: start\n"); + DBG (15, "started=%d, side=%d, source=%d\n", + s->started, s->side, s->u.source); + + /* undo any prior sane_cancel calls */ + s->cancelled=0; + + /* protect this block from sane_cancel */ + s->reading=1; + + /* not finished with current side, error */ + if (s->started && !s->u.eof[s->side]) { + DBG(5,"sane_start: previous transfer not finished?"); + return SANE_STATUS_INVAL; + } + + /* batch start? inititalize struct and scanner */ + if(!s->started){ + + /* load side marker */ + if(s->u.source == SOURCE_ADF_BACK){ + s->side = SIDE_BACK; + } + else{ + s->side = SIDE_FRONT; + } + + /* eject paper leftover*/ + if(object_position (s, SANE_FALSE)){ + DBG (5, "sane_start: ERROR: cannot eject page\n"); + } + + /* wait for scanner to finish eject */ + ret = wait_scanner (s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot wait scanner\n"); + goto errors; + } + + /* load the brightness/contrast lut with linear slope for calibration */ + ret = load_lut (s->lut, 8, 8, 0, 255, 0, 0); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot load lut\n"); + goto errors; + } + + /* AFE cal */ + if((ret = calibrate_AFE(s))){ + DBG (5, "sane_start: ERROR: cannot cal afe\n"); + goto errors; + } + + /* fine cal */ + if((ret = calibrate_fine(s))){ + DBG (5, "sane_start: ERROR: cannot cal fine\n"); + goto errors; + } + + if((ret = calibrate_fine_buffer(s))){ + DBG (5, "sane_start: ERROR: cannot cal fine from buffer\n"); + goto errors; + } + + /* reset the page counter after calibration */ + s->panel_counter = 0; + s->prev_page = 0; + if(send_panel(s)){ + DBG (5, "sane_start: ERROR: cannot send panel\n"); + } + + /* load our own private copy of scan params */ + ret = update_params(s,0); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot update_params\n"); + goto errors; + } + + /* set window command */ + ret = set_window(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot set window\n"); + goto errors; + } + + /* buffer/duplex/ald command */ + ret = ssm_buffer(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot ssm buffer\n"); + goto errors; + } + + /* dropout color command */ + ret = ssm_do(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot ssm do\n"); + goto errors; + } + + /* double feed detection command */ + ret = ssm_df(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot ssm df\n"); + goto errors; + } + + /* clean scan params for new scan */ + ret = clean_params(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot clean_params\n"); + goto errors; + } + + /* make large buffers to hold the images */ + ret = image_buffers(s,1); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot load buffers\n"); + goto errors; + } + + /* load the brightness/contrast lut with user choices */ + ret = load_lut (s->lut, 8, 8, 0, 255, s->contrast, s->brightness); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot load lut\n"); + goto errors; + } + + /* grab next page */ + ret = object_position (s, SANE_TRUE); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot load page\n"); + goto errors; + } + + /* wait for scanner to finish load */ + ret = wait_scanner (s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot wait scanner\n"); + goto errors; + } + + /* start scanning */ + ret = start_scan (s,0); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot start_scan\n"); + goto errors; + } + + s->started = 1; + } + + /* stuff done for subsequent images */ + else{ + + /* duplex needs to switch sides */ + if(s->s.source == SOURCE_ADF_DUPLEX){ + s->side = !s->side; + } + + /* reset the intermediate params */ + ret = update_i_params(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot update_i_params\n"); + goto errors; + } + + /* set clean defaults with new sheet of paper */ + /* dont reset the transfer vars on backside of duplex page */ + /* otherwise buffered back page will be lost */ + /* ingest paper with adf (no-op for fb) */ + /* dont call object pos or scan on back side of duplex scan */ + if(s->side == SIDE_FRONT || s->s.source == SOURCE_ADF_BACK){ + + /* clean scan params for new scan */ + ret = clean_params(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot clean_params\n"); + goto errors; + } + + /* big scanners and small ones in non-buff mode: OP to detect paper */ + if(s->always_op || !s->buffermode){ + ret = object_position (s, SANE_TRUE); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot load page\n"); + goto errors; + } + + /* user wants unbuffered scans */ + /* send scan command */ + if(!s->buffermode){ + ret = start_scan (s,0); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot start_scan\n"); + goto errors; + } + } + } + + /* small, buffering scanners check for more pages by reading counter */ + else{ + ret = read_panel (s, OPT_COUNTER); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot load page\n"); + goto errors; + } + if(s->prev_page == s->panel_counter){ + DBG (5, "sane_start: same counter (%d) no paper?\n",s->prev_page); + ret = SANE_STATUS_NO_DOCS; + goto errors; + } + DBG (5, "sane_start: diff counter (%d/%d)\n", + s->prev_page,s->panel_counter); + } + } + } + + /* reset jpeg params on each page */ + s->jpeg_stage=JPEG_STAGE_NONE; + s->jpeg_ff_offset=0; + + DBG (15, "started=%d, side=%d, source=%d\n", + s->started, s->side, s->u.source); + + /* certain options require the entire image to + * be collected from the scanner before we can + * tell the user the size of the image. the sane + * API has no way to inform the frontend of this, + * so we block and buffer. yuck */ + if( (s->swdeskew || s->swdespeck || s->swcrop) +#ifdef SANE_FRAME_JPEG + && s->s.format != SANE_FRAME_JPEG +#endif + ){ + + /* get image */ + while(!s->s.eof[s->side] && !ret){ + SANE_Int len = 0; + ret = sane_read((SANE_Handle)s, NULL, 0, &len); + } + + /* check for errors */ + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot buffer image\n"); + goto errors; + } + + DBG (5, "sane_start: OK: done buffering\n"); + + /* finished buffering, adjust image as required */ + if(s->swdeskew){ + buffer_deskew(s,s->side); + } + if(s->swcrop){ + buffer_crop(s,s->side); + } + if(s->swdespeck){ + buffer_despeck(s,s->side); + } + + } + + ret = check_for_cancel(s); + s->reading = 0; + + DBG (10, "sane_start: finish %d\n", ret); + return ret; + + errors: + DBG (10, "sane_start: error %d\n", ret); + s->started = 0; + s->cancelled = 0; + s->reading = 0; + return ret; +} + +/* + * cleans params for new scan + */ +static SANE_Status +clean_params (struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + DBG (10, "clean_params: start\n"); + + s->u.eof[0]=0; + s->u.eof[1]=0; + s->u.bytes_sent[0]=0; + s->u.bytes_sent[1]=0; + s->u.bytes_tot[0]=0; + s->u.bytes_tot[1]=0; + + s->i.eof[0]=0; + s->i.eof[1]=0; + s->i.bytes_sent[0]=0; + s->i.bytes_sent[1]=0; + s->i.bytes_tot[0]=0; + s->i.bytes_tot[1]=0; + + s->s.eof[0]=0; + s->s.eof[1]=0; + s->s.bytes_sent[0]=0; + s->s.bytes_sent[1]=0; + s->s.bytes_tot[0]=0; + s->s.bytes_tot[1]=0; + + /* store the number of front bytes */ + if ( s->u.source != SOURCE_ADF_BACK ) + s->u.bytes_tot[SIDE_FRONT] = s->u.Bpl * s->u.height; + + if ( s->i.source != SOURCE_ADF_BACK ) + s->i.bytes_tot[SIDE_FRONT] = s->i.Bpl * s->i.height; + + if ( s->s.source != SOURCE_ADF_BACK ) + s->s.bytes_tot[SIDE_FRONT] = s->s.Bpl * s->s.height; + + /* store the number of back bytes */ + if ( s->u.source == SOURCE_ADF_DUPLEX || s->u.source == SOURCE_ADF_BACK ) + s->u.bytes_tot[SIDE_BACK] = s->u.Bpl * s->u.height; + + if ( s->i.source == SOURCE_ADF_DUPLEX || s->i.source == SOURCE_ADF_BACK ) + s->i.bytes_tot[SIDE_BACK] = s->i.Bpl * s->i.height; + + if ( s->s.source == SOURCE_ADF_DUPLEX || s->s.source == SOURCE_ADF_BACK ) + s->s.bytes_tot[SIDE_BACK] = s->s.Bpl * s->s.height; + + DBG (10, "clean_params: finish\n"); + + return ret; +} + +/* + * frees/callocs buffers to hold the scan data + */ +static SANE_Status +image_buffers (struct scanner *s, int setup) +{ + SANE_Status ret = SANE_STATUS_GOOD; + int side; + + DBG (10, "image_buffers: start\n"); + + for(side=0;side<2;side++){ + + /* free current buffer */ + if (s->buffers[side]) { + DBG (15, "image_buffers: free buffer %d.\n",side); + free(s->buffers[side]); + s->buffers[side] = NULL; + } + + /* build new buffer if asked */ + if(s->i.bytes_tot[side] && setup){ + s->buffers[side] = calloc (1,s->i.bytes_tot[side]); + if (!s->buffers[side]) { + DBG (5, "image_buffers: Error, no buffer %d.\n",side); + return SANE_STATUS_NO_MEM; + } + } + } + + DBG (10, "image_buffers: finish\n"); + + return ret; +} + +/* + * This routine issues a SCSI SET WINDOW command to the scanner, using the + * values currently in the s->s param structure. + */ +static SANE_Status +set_window (struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + /* The command specifies the number of bytes in the data phase + * the data phase has a header, followed by 1 window desc block + * the header specifies the number of bytes in 1 window desc block + */ + + unsigned char cmd[SET_WINDOW_len]; + size_t cmdLen = SET_WINDOW_len; + + unsigned char out[SW_header_len + SW_desc_len]; + size_t outLen = SW_header_len + SW_desc_len; + + unsigned char * header = out; /*header*/ + unsigned char * desc1 = out + SW_header_len; /*descriptor*/ + + DBG (10, "set_window: start\n"); + + /*build the payload*/ + memset(out,0,outLen); + + /* set window desc size in header */ + set_WPDB_wdblen(header, SW_desc_len); + + /* init the window block */ + if (s->s.source == SOURCE_ADF_BACK) { + set_WD_wid (desc1, WD_wid_back); + } + else{ + set_WD_wid (desc1, WD_wid_front); + } + + set_WD_Xres (desc1, s->s.dpi_x); + set_WD_Yres (desc1, s->s.dpi_y); + + /* some machines need max width */ + if(s->fixed_width){ + set_WD_ULX (desc1, 0); + set_WD_width (desc1, s->max_x); + } + + /* or they align left */ + else if(s->u.source == SOURCE_FLATBED){ + set_WD_ULX (desc1, s->s.tl_x); + set_WD_width (desc1, s->s.width * 1200/s->s.dpi_x); + } + + /* or we have to center the window ourselves */ + else{ + set_WD_ULX (desc1, (s->max_x - s->s.page_x) / 2 + s->s.tl_x); + set_WD_width (desc1, s->s.width * 1200/s->s.dpi_x); + } + + /* some models require that the tly value be inverted? */ + if(s->invert_tly) + set_WD_ULY (desc1, ~s->s.tl_y); + else + set_WD_ULY (desc1, s->s.tl_y); + + set_WD_length (desc1, s->s.height * 1200/s->s.dpi_y); + + if(s->has_btc){ + /*convert our common -127 to +127 range into HW's range + *FIXME: this code assumes hardware range of 0-255 */ + set_WD_brightness (desc1, s->brightness+128); + + set_WD_threshold (desc1, s->threshold); + + /*convert our common -127 to +127 range into HW's range + *FIXME: this code assumes hardware range of 0-255 */ + set_WD_contrast (desc1, s->contrast+128); + } + + set_WD_composition (desc1, s->s.mode); + + if(s->s.bpp == 24) + set_WD_bitsperpixel (desc1, 8); + else + set_WD_bitsperpixel (desc1, s->s.bpp); + + if(s->s.mode == MODE_HALFTONE){ + /*set_WD_ht_type(desc1, s->ht_type); + set_WD_ht_pattern(desc1, s->ht_pattern);*/ + } + + set_WD_rif (desc1, s->rif); + set_WD_rgb(desc1, s->rgb_format); + set_WD_padding(desc1, s->padding); + + /*FIXME: what is this? */ + set_WD_reserved2(desc1, s->unknown_byte2); + + set_WD_compress_type(desc1, COMP_NONE); + set_WD_compress_arg(desc1, 0); + +#ifdef SANE_FRAME_JPEG + /* some scanners support jpeg image compression, for color/gs only */ + if(s->s.format == SANE_FRAME_JPEG){ + set_WD_compress_type(desc1, COMP_JPEG); + set_WD_compress_arg(desc1, s->compress_arg); + } +#endif + + /*build the command*/ + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, SET_WINDOW_code); + set_SW_xferlen(cmd, outLen); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + out, outLen, + NULL, NULL + ); + + if (!ret && s->s.source == SOURCE_ADF_DUPLEX) { + set_WD_wid (desc1, WD_wid_back); + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + out, outLen, + NULL, NULL + ); + } + + DBG (10, "set_window: finish\n"); + + return ret; +} + +/* + * Issues the SCSI OBJECT POSITION command if an ADF is in use. + */ +static SANE_Status +object_position (struct scanner *s, int i_load) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + unsigned char cmd[OBJECT_POSITION_len]; + size_t cmdLen = OBJECT_POSITION_len; + + DBG (10, "object_position: start\n"); + + if (s->u.source == SOURCE_FLATBED) { + DBG (10, "object_position: flatbed no-op\n"); + return SANE_STATUS_GOOD; + } + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, OBJECT_POSITION_code); + + if (i_load) { + DBG (15, "object_position: load\n"); + set_OP_autofeed (cmd, OP_Feed); + } + else { + DBG (15, "object_position: eject\n"); + set_OP_autofeed (cmd, OP_Discharge); + } + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + NULL, 0, + NULL, NULL + ); + if (ret != SANE_STATUS_GOOD) + return ret; + + DBG (10, "object_position: finish\n"); + + return ret; +} + +/* + * Issues SCAN command. + * + * (This doesn't actually read anything, it just tells the scanner + * to start scanning.) + */ +static SANE_Status +start_scan (struct scanner *s, int type) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + unsigned char cmd[SCAN_len]; + size_t cmdLen = SCAN_len; + + unsigned char out[] = {WD_wid_front, WD_wid_back}; + size_t outLen = 2; + + DBG (10, "start_scan: start\n"); + + /* calibration scans use 0xff or 0xfe */ + if(type){ + out[0] = type; + out[1] = type; + } + + if (s->s.source != SOURCE_ADF_DUPLEX) { + outLen--; + if(s->s.source == SOURCE_ADF_BACK) { + out[0] = WD_wid_back; + } + } + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, SCAN_code); + set_SC_xfer_length (cmd, outLen); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + out, outLen, + NULL, NULL + ); + + DBG (10, "start_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; + + DBG (10, "sane_read: start\n"); + + *len=0; + + /* maybe cancelled? */ + if(!s->started){ + DBG (5, "sane_read: not started, call sane_start\n"); + return SANE_STATUS_CANCELLED; + } + + /* sane_start required between sides */ + if(s->u.bytes_sent[s->side] == s->i.bytes_tot[s->side]){ + s->u.eof[s->side] = 1; + DBG (15, "sane_read: returning eof\n"); + return SANE_STATUS_EOF; + } + + s->reading = 1; + + /* double width pnm interlacing */ + if(s->s.source == SOURCE_ADF_DUPLEX + && s->s.format <= SANE_FRAME_RGB + && s->duplex_interlace != DUPLEX_INTERLACE_NONE + ){ + + /* buffer both sides */ + if(!s->s.eof[SIDE_FRONT] || !s->s.eof[SIDE_BACK]){ + ret = read_from_scanner_duplex(s, 0); + if(ret){ + DBG(5,"sane_read: front returning %d\n",ret); + goto errors; + } + /*read last block, update counter*/ + if(s->s.eof[SIDE_FRONT] && s->s.eof[SIDE_BACK]){ + s->prev_page++; + DBG(15,"sane_read: duplex counter %d\n",s->prev_page); + } + } + } + + /* simplex or non-alternating duplex */ + else{ + if(!s->s.eof[s->side]){ + ret = read_from_scanner(s, s->side, 0); + if(ret){ + DBG(5,"sane_read: side %d returning %d\n",s->side,ret); + goto errors; + } + /*read last block, update counter*/ + if(s->s.eof[s->side]){ + s->prev_page++; + DBG(15,"sane_read: side %d counter %d\n",s->side,s->prev_page); + } + } + } + + /* copy a block from buffer to frontend */ + ret = read_from_buffer(s,buf,max_len,len,s->side); + if(ret) + goto errors; + + ret = check_for_cancel(s); + s->reading = 0; + + DBG (10, "sane_read: finish %d\n", ret); + return ret; + + errors: + DBG (10, "sane_read: error %d\n", ret); + s->reading = 0; + s->cancelled = 0; + s->started = 0; + return ret; +} + +static SANE_Status +read_from_scanner(struct scanner *s, int side, int exact) +{ + SANE_Status ret=SANE_STATUS_GOOD; + + unsigned char cmd[READ_len]; + size_t cmdLen = READ_len; + + unsigned char * in; + size_t inLen = 0; + + size_t bytes = s->buffer_size; + size_t remain = s->s.bytes_tot[side] - s->s.bytes_sent[side]; + + DBG (10, "read_from_scanner: start\n"); + + /* all requests must end on line boundary */ + bytes -= (bytes % s->s.Bpl); + + /* some larger scanners require even bytes per block */ + if(bytes % 2){ + bytes -= s->s.Bpl; + } + + /* usually (image) we want to read too much data, and get RS */ + /* sometimes (calib) we want to do an exact read */ + if(exact && bytes > remain){ + bytes = remain; + } + + DBG(15, "read_from_scanner: si:%d to:%d rx:%d re:%lu bu:%d pa:%lu ex:%d\n", + side, s->s.bytes_tot[side], s->s.bytes_sent[side], + (unsigned long)remain, s->buffer_size, (unsigned long)bytes, exact); + + inLen = bytes; + in = malloc(inLen); + if(!in){ + DBG(5, "read_from_scanner: not enough mem for buffer: %d\n",(int)inLen); + return SANE_STATUS_NO_MEM; + } + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, READ_code); + set_R_datatype_code (cmd, SR_datatype_image); + + set_R_xfer_length (cmd, inLen); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + NULL, 0, + in, &inLen + ); + + if (ret == SANE_STATUS_GOOD) { + DBG(15, "read_from_scanner: got GOOD, returning GOOD %lu\n", (unsigned long)inLen); + } + else if (ret == SANE_STATUS_EOF) { + DBG(15, "read_from_scanner: got EOF, finishing %lu\n", (unsigned long)inLen); + } + else if (ret == SANE_STATUS_DEVICE_BUSY) { + DBG(5, "read_from_scanner: got BUSY, returning GOOD\n"); + inLen = 0; + ret = SANE_STATUS_GOOD; + } + else { + DBG(5, "read_from_scanner: error reading data block status = %d\n",ret); + inLen = 0; + } + +#ifdef SANE_FRAME_JPEG + /* this is jpeg data, we need to fix the missing image size */ + if(s->s.format == SANE_FRAME_JPEG){ + + /* look for the SOF header near the beginning */ + if(s->jpeg_stage == JPEG_STAGE_NONE || s->jpeg_ff_offset < 0x0d){ + + size_t i; + + for(i=0;i<inLen;i++){ + + /* about to change stage */ + if(s->jpeg_stage == JPEG_STAGE_NONE && in[i] == 0xff){ + s->jpeg_ff_offset=0; + continue; + } + + s->jpeg_ff_offset++; + + /* last byte was an ff, this byte is SOF */ + if(s->jpeg_ff_offset == 1 && in[i] == 0xc0){ + s->jpeg_stage = JPEG_STAGE_SOF; + continue; + } + + if(s->jpeg_stage == JPEG_STAGE_SOF){ + + /* lines in start of frame, overwrite it */ + if(s->jpeg_ff_offset == 5){ + in[i] = (s->s.height >> 8) & 0xff; + continue; + } + if(s->jpeg_ff_offset == 6){ + in[i] = s->s.height & 0xff; + continue; + } + + /* width in start of frame, overwrite it */ + if(s->jpeg_ff_offset == 7){ + in[i] = (s->s.width >> 8) & 0xff; + continue; + } + if(s->jpeg_ff_offset == 8){ + in[i] = s->s.width & 0xff; + continue; + } + } + } + } + } +#endif + + /*scanner may have sent more data than we asked for, chop it*/ + if(inLen > remain){ + inLen = remain; + } + + /* we've got some data, descramble and store it */ + if(inLen){ + copy_simplex(s,in,inLen,side); + } + + free(in); + + /* we've read all data, but not eof. clear and pretend */ + if(exact && inLen == remain){ + DBG (10, "read_from_scanner: exact read, clearing\n"); + ret = object_position (s,SANE_FALSE); + if(ret){ + return ret; + } + ret = SANE_STATUS_EOF; + } + + if(ret == SANE_STATUS_EOF){ + + switch (s->s.format){ + +#ifdef SANE_FRAME_JPEG + /* this is jpeg data, we need to change the total size */ + case SANE_FRAME_JPEG: + s->s.bytes_tot[side] = s->s.bytes_sent[side]; + s->i.bytes_tot[side] = s->i.bytes_sent[side]; + s->u.bytes_tot[side] = s->i.bytes_sent[side]; + break; +#endif + + /* this is non-jpeg data, fill remainder, change rx'd size */ + default: + + DBG (15, "read_from_scanner: eof: %d %d\n", s->i.bytes_tot[side], s->i.bytes_sent[side]); + + /* clone the last line repeatedly until the end */ + while(s->i.bytes_tot[side] > s->i.bytes_sent[side]){ + memcpy( + s->buffers[side]+s->i.bytes_sent[side]-s->i.Bpl, + s->buffers[side]+s->i.bytes_sent[side], + s->i.Bpl + ); + s->i.bytes_sent[side] += s->i.Bpl; + } + + DBG (15, "read_from_scanner: eof2: %d %d\n", s->i.bytes_tot[side], s->i.bytes_sent[side]); + + /* pretend we got all the data from scanner */ + s->s.bytes_sent[side] = s->s.bytes_tot[side]; + break; + } + + s->i.eof[side] = 1; + s->s.eof[side] = 1; + ret = SANE_STATUS_GOOD; + } + + DBG(15, "read_from_scanner: sto:%d srx:%d sef:%d uto:%d urx:%d uef:%d\n", + s->s.bytes_tot[side], s->s.bytes_sent[side], s->s.eof[side], + s->u.bytes_tot[side], s->u.bytes_sent[side], s->u.eof[side]); + + DBG (10, "read_from_scanner: finish\n"); + + return ret; +} + +/* cheaper scanners interlace duplex scans on a byte basis + * this code requests double width lines from scanner */ +static SANE_Status +read_from_scanner_duplex(struct scanner *s,int exact) +{ + SANE_Status ret=SANE_STATUS_GOOD; + + unsigned char cmd[READ_len]; + size_t cmdLen = READ_len; + + unsigned char * in; + size_t inLen = 0; + + size_t bytes = s->buffer_size; + size_t remain = s->s.bytes_tot[SIDE_FRONT] + s->s.bytes_tot[SIDE_BACK] + - s->s.bytes_sent[SIDE_FRONT] - s->s.bytes_sent[SIDE_BACK]; + + DBG (10, "read_from_scanner_duplex: start\n"); + + /* all requests must end on WIDE line boundary */ + bytes -= (bytes % (s->s.Bpl*2)); + + /* usually (image) we want to read too much data, and get RS */ + /* sometimes (calib) we want to do an exact read */ + if(exact && bytes > remain){ + bytes = remain; + } + + DBG(15, "read_from_scanner_duplex: re:%lu bu:%d pa:%lu ex:%d\n", + (unsigned long)remain, s->buffer_size, (unsigned long)bytes, exact); + + inLen = bytes; + in = malloc(inLen); + if(!in){ + DBG(5, "read_from_scanner_duplex: not enough mem for buffer: %d\n", + (int)inLen); + return SANE_STATUS_NO_MEM; + } + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, READ_code); + set_R_datatype_code (cmd, SR_datatype_image); + + set_R_xfer_length (cmd, inLen); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + NULL, 0, + in, &inLen + ); + + if (ret == SANE_STATUS_GOOD) { + DBG(15, "read_from_scanner_duplex: got GOOD, returning GOOD %lu\n", (unsigned long)inLen); + } + else if (ret == SANE_STATUS_EOF) { + DBG(15, "read_from_scanner_duplex: got EOF, finishing %lu\n", (unsigned long)inLen); + } + else if (ret == SANE_STATUS_DEVICE_BUSY) { + DBG(5, "read_from_scanner_duplex: got BUSY, returning GOOD\n"); + inLen = 0; + ret = SANE_STATUS_GOOD; + } + else { + DBG(5, "read_from_scanner_duplex: error reading data block status = %d\n", + ret); + inLen = 0; + } + + /*scanner may have sent more data than we asked for, chop it*/ + if(inLen > remain){ + inLen = remain; + } + + /* we've got some data, descramble and store it */ + if(inLen){ + copy_duplex(s,in,inLen); + } + + free(in); + + /* we've read all data, but not eof. clear and pretend */ + if(exact && inLen == remain){ + DBG (10, "read_from_scanner_duplex: exact read, clearing\n"); + ret = object_position (s,SANE_FALSE); + if(ret){ + return ret; + } + ret = SANE_STATUS_EOF; + } + + if(ret == SANE_STATUS_EOF){ + + switch (s->s.format){ + +#ifdef SANE_FRAME_JPEG + /* this is jpeg data, we need to change the total size */ + case SANE_FRAME_JPEG: + s->s.bytes_tot[SIDE_FRONT] = s->s.bytes_sent[SIDE_FRONT]; + s->s.bytes_tot[SIDE_BACK] = s->s.bytes_sent[SIDE_BACK]; + s->i.bytes_tot[SIDE_FRONT] = s->i.bytes_sent[SIDE_FRONT]; + s->i.bytes_tot[SIDE_BACK] = s->i.bytes_sent[SIDE_BACK]; + s->u.bytes_tot[SIDE_FRONT] = s->i.bytes_sent[SIDE_FRONT]; + s->u.bytes_tot[SIDE_BACK] = s->i.bytes_sent[SIDE_BACK]; + break; +#endif + + /* this is non-jpeg data, fill remainder, change rx'd size */ + default: + + DBG (15, "read_from_scanner_duplex: eof: %d %d %d %d\n", + s->i.bytes_tot[SIDE_FRONT], s->i.bytes_sent[SIDE_FRONT], + s->i.bytes_tot[SIDE_BACK], s->i.bytes_sent[SIDE_BACK] + ); + + /* clone the last line repeatedly until the end */ + while(s->i.bytes_tot[SIDE_FRONT] > s->i.bytes_sent[SIDE_FRONT]){ + memcpy( + s->buffers[SIDE_FRONT]+s->i.bytes_sent[SIDE_FRONT]-s->i.Bpl, + s->buffers[SIDE_FRONT]+s->i.bytes_sent[SIDE_FRONT], + s->i.Bpl + ); + s->i.bytes_sent[SIDE_FRONT] += s->i.Bpl; + } + + /* clone the last line repeatedly until the end */ + while(s->i.bytes_tot[SIDE_BACK] > s->i.bytes_sent[SIDE_BACK]){ + memcpy( + s->buffers[SIDE_BACK]+s->i.bytes_sent[SIDE_BACK]-s->i.Bpl, + s->buffers[SIDE_BACK]+s->i.bytes_sent[SIDE_BACK], + s->i.Bpl + ); + s->i.bytes_sent[SIDE_BACK] += s->i.Bpl; + } + + DBG (15, "read_from_scanner_duplex: eof2: %d %d %d %d\n", + s->i.bytes_tot[SIDE_FRONT], s->i.bytes_sent[SIDE_FRONT], + s->i.bytes_tot[SIDE_BACK], s->i.bytes_sent[SIDE_BACK] + ); + + /* pretend we got all the data from scanner */ + s->s.bytes_sent[SIDE_FRONT] = s->s.bytes_tot[SIDE_FRONT]; + s->s.bytes_sent[SIDE_BACK] = s->s.bytes_tot[SIDE_BACK]; + break; + } + + s->i.eof[SIDE_FRONT] = 1; + s->i.eof[SIDE_BACK] = 1; + s->s.eof[SIDE_FRONT] = 1; + s->s.eof[SIDE_BACK] = 1; + ret = SANE_STATUS_GOOD; + } + + DBG (10, "read_from_scanner_duplex: finish\n"); + + return ret; +} + +/* these functions copy image data from input buffer to scanner struct + * descrambling it, and putting it in the right side buffer */ +/* NOTE: they assume buffer is scanline aligned */ +static SANE_Status +copy_simplex(struct scanner *s, unsigned char * buf, int len, int side) +{ + SANE_Status ret=SANE_STATUS_GOOD; + int i, j; + int bwidth = s->s.Bpl; + int pwidth = s->s.width; + int t = bwidth/3; + int f = bwidth/4; + int tw = bwidth/12; + + unsigned char * line = NULL; + int line_next = 0; + + /* jpeg data should not pass thru this function, so copy and bail out */ + if(s->s.format > SANE_FRAME_RGB){ + DBG (15, "copy_simplex: jpeg bulk copy\n"); + memcpy(s->buffers[side]+s->i.bytes_sent[side], buf, len); + s->i.bytes_sent[side] += len; + s->s.bytes_sent[side] += len; + return ret; + } + + DBG (15, "copy_simplex: per-line copy\n"); + + line = malloc(bwidth); + if(!line) return SANE_STATUS_NO_MEM; + + /* ingest each line */ + for(i=0; i<len; i+=bwidth){ + + int lineNum = s->s.bytes_sent[side] / bwidth; + + /*increment number of bytes rx'd from scanner*/ + s->s.bytes_sent[side] += bwidth; + + /*have some padding from scanner to drop*/ + if ( lineNum < s->i.skip_lines[side] + || lineNum - s->i.skip_lines[side] >= s->i.height + ){ + continue; + } + + line_next = 0; + + if(s->s.format == SANE_FRAME_GRAY){ + + switch (s->gray_interlace[side]) { + + /* one line has the following format: ggg...GGG + * where the 'capital' letters are the beginning of the line */ + case GRAY_INTERLACE_gG: + DBG (17, "copy_simplex: gray, gG\n"); + for (j=bwidth-1; j>=0; j--){ + line[line_next++] = buf[i+j]; + } + break; + + case GRAY_INTERLACE_2510: + DBG (17, "copy_simplex: gray, 2510\n"); + + /* first read head (third byte of every three) */ + for(j=bwidth-1;j>=0;j-=3){ + line[line_next++] = buf[i+j]; + } + /* second read head (first byte of every three) */ + for(j=bwidth*3/4-3;j>=0;j-=3){ + line[line_next++] = buf[i+j]; + } + /* third read head (second byte of every three) */ + for(j=bwidth-2;j>=0;j-=3){ + line[line_next++] = buf[i+j]; + } + /* padding */ + for(j=0;j<tw;j++){ + line[line_next++] = 0; + } + break; + } + } + + else if (s->s.format == SANE_FRAME_RGB){ + + switch (s->color_interlace[side]) { + + /* scanner returns color data as bgrbgr... */ + case COLOR_INTERLACE_BGR: + DBG (17, "copy_simplex: color, BGR\n"); + for (j=0; j<pwidth; j++){ + line[line_next++] = buf[i+j*3+2]; + line[line_next++] = buf[i+j*3+1]; + line[line_next++] = buf[i+j*3]; + } + break; + + /* one line has the following format: RRR...rrrGGG...gggBBB...bbb */ + case COLOR_INTERLACE_RRGGBB: + DBG (17, "copy_simplex: color, RRGGBB\n"); + for (j=0; j<pwidth; j++){ + line[line_next++] = buf[i+j]; + line[line_next++] = buf[i+pwidth+j]; + line[line_next++] = buf[i+2*pwidth+j]; + } + break; + + /* one line has the following format: rrr...RRRggg...GGGbbb...BBB + * where the 'capital' letters are the beginning of the line */ + case COLOR_INTERLACE_rRgGbB: + DBG (17, "copy_simplex: color, rRgGbB\n"); + for (j=pwidth-1; j>=0; j--){ + line[line_next++] = buf[i+j]; + line[line_next++] = buf[i+pwidth+j]; + line[line_next++] = buf[i+2*pwidth+j]; + } + break; + + case COLOR_INTERLACE_2510: + DBG (17, "copy_simplex: color, 2510\n"); + + /* first read head (third byte of every three) */ + for(j=t-1;j>=0;j-=3){ + line[line_next++] = buf[i+j]; + line[line_next++] = buf[i+t+j]; + line[line_next++] = buf[i+2*t+j]; + } + /* second read head (first byte of every three) */ + for(j=f-3;j>=0;j-=3){ + line[line_next++] = buf[i+j]; + line[line_next++] = buf[i+t+j]; + line[line_next++] = buf[i+2*t+j]; + } + /* third read head (second byte of every three) */ + for(j=t-2;j>=0;j-=3){ + line[line_next++] = buf[i+j]; + line[line_next++] = buf[i+t+j]; + line[line_next++] = buf[i+2*t+j]; + } + /* padding */ + for(j=0;j<tw;j++){ + line[line_next++] = 0; + } + break; + } + } + + /* nothing sent above? just copy one line of the block */ + /* used by uninterlaced gray/color */ + if(!line_next){ + DBG (17, "copy_simplex: default\n"); + memcpy(line+line_next,buf+i,bwidth); + line_next = bwidth; + } + + /* invert image if scanner needs it for this mode */ + if(s->reverse_by_mode[s->s.mode]){ + for(j=0; j<line_next; j++){ + line[j] ^= 0xff; + } + } + + /* apply calibration if we have it */ + if(s->f_offset[side]){ + DBG (17, "copy_simplex: apply offset\n"); + for(j=0; j<s->s.valid_Bpl; j++){ + int curr = line[j] - s->f_offset[side][j]; + if(curr < 0) curr = 0; + line[j] = curr; + } + } + + if(s->f_gain[side]){ + DBG (17, "copy_simplex: apply gain\n"); + for(j=0; j<s->s.valid_Bpl; j++){ + int curr = line[j] * 240/s->f_gain[side][j]; + if(curr > 255) curr = 255; + line[j] = curr; + } + } + + /* apply brightness and contrast if hardware cannot do it */ + if(s->sw_lut && (s->s.mode == MODE_COLOR || s->s.mode == MODE_GRAYSCALE)){ + DBG (17, "copy_simplex: apply brightness/contrast\n"); + for(j=0; j<s->s.valid_Bpl; j++){ + line[j] = s->lut[line[j]]; + } + } + + /*copy the line into the buffer*/ + ret = copy_line(s,line,side); + if(ret){ + break; + } + } + + free(line); + + DBG (10, "copy_simplex: finished\n"); + + return ret; +} + +/* split the data between two buffers, hand them to copy_simplex() + * assumes that the buffer aligns to a double-wide line boundary */ +static SANE_Status +copy_duplex(struct scanner *s, unsigned char * buf, int len) +{ + SANE_Status ret=SANE_STATUS_GOOD; + int i,j; + int bwidth = s->s.Bpl; + int dbwidth = 2*bwidth; + unsigned char * front; + unsigned char * back; + int flen=0, blen=0; + + DBG (10, "copy_duplex: start\n"); + + /*split the input into two simplex output buffers*/ + front = calloc(1,len/2); + if(!front){ + DBG (5, "copy_duplex: no front mem\n"); + return SANE_STATUS_NO_MEM; + } + back = calloc(1,len/2); + if(!back){ + DBG (5, "copy_duplex: no back mem\n"); + free(front); + return SANE_STATUS_NO_MEM; + } + + if(s->duplex_interlace == DUPLEX_INTERLACE_2510){ + + DBG (10, "copy_duplex: 2510\n"); + + for(i=0; i<len; i+=dbwidth){ + + for(j=0;j<dbwidth;j+=6){ + + /* we are actually only partially descrambling, + * copy_simplex() does the rest */ + + /* front */ + /* 2nd head: 2nd byte -> 1st byte */ + /* 3rd head: 4th byte -> 2nd byte */ + /* 1st head: 5th byte -> 3rd byte */ + front[flen++] = buf[i+j+2]; + front[flen++] = buf[i+j+4]; + front[flen++] = buf[i+j+5]; + + /* back */ + /* 2nd head: 3rd byte -> 1st byte */ + /* 3rd head: 0th byte -> 2nd byte */ + /* 1st head: 1st byte -> 3rd byte */ + back[blen++] = buf[i+j+3]; + back[blen++] = buf[i+j]; + back[blen++] = buf[i+j+1]; + } + } + } + + /* no scanners use this? */ + else if(s->duplex_interlace == DUPLEX_INTERLACE_FFBB){ + for(i=0; i<len; i+=dbwidth){ + memcpy(front+flen,buf+i,bwidth); + flen+=bwidth; + memcpy(back+blen,buf+i+bwidth,bwidth); + blen+=bwidth; + } + } + + /*just alternating bytes, FBFBFB*/ + else { + for(i=0; i<len; i+=2){ + front[flen++] = buf[i]; + back[blen++] = buf[i+1]; + } + } + + copy_simplex(s,front,flen,SIDE_FRONT); + copy_simplex(s,back,blen,SIDE_BACK); + + free(front); + free(back); + + DBG (10, "copy_duplex: finished\n"); + + return ret; +} + +/* downsample a single line from scanner's size to user's size */ +/* and copy into final buffer */ +static SANE_Status +copy_line(struct scanner *s, unsigned char * buff, int side) +{ + SANE_Status ret=SANE_STATUS_GOOD; + int spwidth = s->s.width; + int sbwidth = s->s.Bpl; + int ibwidth = s->i.Bpl; + unsigned char * line; + int offset = 0; + int i, j; + + DBG (20, "copy_line: start\n"); + + /* the 'standard' case: non-stupid scan */ + if(s->s.width == s->i.width + && s->s.dpi_x == s->i.dpi_x + && s->s.mode == s->i.mode + ){ + + memcpy(s->buffers[side]+s->i.bytes_sent[side], buff, sbwidth); + s->i.bytes_sent[side] += sbwidth; + + DBG (20, "copy_line: finished smart\n"); + return ret; + } + + /* the 'corner' case: stupid scan */ + + /*setup 24 bit color single line buffer*/ + line = malloc(spwidth*3); + if(!line) return SANE_STATUS_NO_MEM; + + /*load single line color buffer*/ + switch (s->s.mode) { + + case MODE_COLOR: + memcpy(line, buff, sbwidth); + break; + + case MODE_GRAYSCALE: + for(i=0;i<spwidth;i++){ + line[i*3] = line[i*3+1] = line[i*3+2] = buff[i]; + } + break; + + default: + for(i=0;i<sbwidth;i++){ + unsigned char curr = buff[i]; + + line[i*24+0] = line[i*24+1] = line[i*24+2] = ((curr >> 7) & 1) ?0:255; + line[i*24+3] = line[i*24+4] = line[i*24+5] = ((curr >> 6) & 1) ?0:255; + line[i*24+6] = line[i*24+7] = line[i*24+8] = ((curr >> 5) & 1) ?0:255; + line[i*24+9] = line[i*24+10] = line[i*24+11] = ((curr >> 4) & 1) ?0:255; + line[i*24+12] = line[i*24+13] = line[i*24+14] =((curr >> 3) & 1) ?0:255; + line[i*24+15] = line[i*24+16] = line[i*24+17] =((curr >> 2) & 1) ?0:255; + line[i*24+18] = line[i*24+19] = line[i*24+20] =((curr >> 1) & 1) ?0:255; + line[i*24+21] = line[i*24+22] = line[i*24+23] =((curr >> 0) & 1) ?0:255; + } + break; + } + + /* scan is higher res than user wanted, scale it */ + /*FIXME: interpolate instead */ + if(s->i.dpi_x != s->s.dpi_x){ + for(i=0;i<spwidth;i++){ + int source = i * s->s.dpi_x/s->i.dpi_x * 3; + + if(source+2 >= spwidth*3) + break; + + line[i*3] = line[source]; + line[i*3+1] = line[source+1]; + line[i*3+2] = line[source+2]; + } + } + + /* scan is wider than user wanted, skip some pixels on left side */ + if(s->i.width != s->s.width){ + offset = ((s->valid_x-s->i.page_x) / 2 + s->i.tl_x) * s->i.dpi_x/1200*3; + } + + /* change mode, store line in buffer */ + switch (s->i.mode) { + + case MODE_COLOR: + memcpy(s->buffers[side]+s->i.bytes_sent[side], line+offset, ibwidth); + s->i.bytes_sent[side] += ibwidth; + break; + + case MODE_GRAYSCALE: + for(i=0;i<ibwidth;i++){ + int source = (offset+i)*3; + s->buffers[side][s->i.bytes_sent[side]++] + = ((int)line[source] + line[source+1] + line[source+2])/3; + } + break; + + default: + /*loop over output bytes*/ + for(i=0;i<ibwidth;i++){ + + unsigned char curr = 0; + int thresh = s->threshold*3; + + /*loop over output bits*/ + for(j=0;j<8;j++){ + int source = (offset+i)*24 + j*3; + if( (line[source] + line[source+1] + line[source+2]) < thresh ){ + curr |= 1 << (7-j); + } + } + + s->buffers[side][s->i.bytes_sent[side]++] = curr; + } + break; + } + + free(line); + + DBG (20, "copy_line: finish stupid\n"); + + return ret; +} + +static SANE_Status +read_from_buffer(struct scanner *s, SANE_Byte * buf, SANE_Int max_len, + SANE_Int * len, int side) +{ + SANE_Status ret=SANE_STATUS_GOOD; + int bytes = max_len; + int remain = s->i.bytes_sent[side] - s->u.bytes_sent[side]; + + DBG (10, "read_from_buffer: start\n"); + + /* figure out the max amount to transfer */ + if(bytes > remain) + bytes = remain; + + *len = bytes; + + /*FIXME this needs to timeout eventually */ + if(!bytes){ + DBG(5,"read_from_buffer: nothing to do\n"); + return SANE_STATUS_GOOD; + } + + DBG(15, "read_from_buffer: si:%d to:%d tx:%d bu:%d pa:%d\n", side, + s->i.bytes_tot[side], s->u.bytes_sent[side], max_len, bytes); + + /* copy to caller */ + memcpy(buf,s->buffers[side]+s->u.bytes_sent[side],bytes); + s->u.bytes_sent[side] += bytes; + + DBG (10, "read_from_buffer: finished\n"); + + return ret; +} + +/* + * @@ Section 5 - calibration functions + */ + +#if 0 +static SANE_Status +foo_AFE(struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + unsigned char cmd[] = { + 0x3b, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00 + }; + size_t cmdLen = 12; + + unsigned char in[4]; + size_t inLen = 4; + + DBG (10, "foo_AFE: start\n"); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + NULL, 0, + in, &inLen + ); + if (ret != SANE_STATUS_GOOD) + return ret; + + DBG (10, "foo_AFE: finish\n"); + + return ret; +} +#endif + +/* + * makes several scans, adjusts coarse calibration + */ +static SANE_Status +calibrate_AFE (struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + int i, j, k; + int min, max; + int lines = 8; + + /*buffer these for later*/ + int old_tl_y = s->u.tl_y; + int old_br_y = s->u.br_y; + int old_mode = s->u.mode; + int old_source = s->u.source; + + DBG (10, "calibrate_AFE: start\n"); + + if(!s->need_ccal){ + DBG (10, "calibrate_AFE: not required\n"); + return ret; + } + + /* always cal with a short scan in duplex color */ + s->u.tl_y = 0; + s->u.br_y = lines * 1200 / s->u.dpi_y; + s->u.mode = MODE_COLOR; + s->u.source = SOURCE_ADF_DUPLEX; + + /* load our own private copy of scan params */ + ret = update_params(s,1); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot update_params\n"); + goto cleanup; + } + + if(s->c_res == s->s.dpi_x && s->c_mode == s->s.mode){ + DBG (10, "calibrate_AFE: already done\n"); + goto cleanup; + } + + /* clean scan params for new scan */ + ret = clean_params(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot clean_params\n"); + goto cleanup; + } + + /* make buffers to hold the images */ + ret = image_buffers(s,1); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot load buffers\n"); + goto cleanup; + } + + /*blast the existing fine cal data so reading code wont apply it*/ + ret = offset_buffers(s,0); + ret = gain_buffers(s,0); + + /* need to tell it we want duplex */ + ret = ssm_buffer(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot ssm buffer\n"); + goto cleanup; + } + + /* set window command */ + ret = set_window(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot set window\n"); + goto cleanup; + } + + /* first pass (black offset), lamp off, no offset/gain/exposure */ + DBG (15, "calibrate_AFE: offset\n"); + + /* blast all the existing coarse cal data */ + for(i=0;i<2;i++){ + s->c_gain[i] = 1; + s->c_offset[i] = 1; + for(j=0;j<3;j++){ + s->c_exposure[i][j] = 0; + } + } + + ret = write_AFE(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot write afe\n"); + goto cleanup; + } + + ret = calibration_scan(s,0xff); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot make offset cal scan\n"); + goto cleanup; + } + + for(i=0;i<2;i++){ + min = 255; + for(j=0; j<s->s.valid_Bpl; j++){ + if(s->buffers[i][j] < min) + min = s->buffers[i][j]; + } + s->c_offset[i] = min*3-2; + DBG (15, "calibrate_AFE: offset %d %d %02x\n", i, min, s->c_offset[i]); + } + + /*handle second pass (per channel exposure), lamp on, overexposed*/ + DBG (15, "calibrate_AFE: exposure\n"); + for(i=0;i<2;i++){ + for(j=0; j<3; j++){ + s->c_exposure[i][j] = 0x320; + } + } + + ret = write_AFE(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot write afe\n"); + goto cleanup; + } + + ret = calibration_scan(s,0xfe); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot make exposure cal scan\n"); + goto cleanup; + } + + for(i=0;i<2;i++){ /*sides*/ + for(j=0;j<3;j++){ /*channels*/ + max = 0; + for(k=j; k<s->s.valid_Bpl; k+=3){ /*bytes*/ + if(s->buffers[i][k] > max) + max = s->buffers[i][k]; + } + + /*generally we reduce the exposure (smaller number) */ + if(old_mode == MODE_COLOR) + s->c_exposure[i][j] = s->c_exposure[i][j] * 102/max; + else + s->c_exposure[i][j] = s->c_exposure[i][j] * 64/max; + + DBG (15, "calibrate_AFE: exp %d %d %d %02x\n", i, j, max, + s->c_exposure[i][j]); + } + } + + /*handle third pass (gain), lamp on with current offset/exposure */ + DBG (15, "calibrate_AFE: gain\n"); + + ret = write_AFE(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot write afe\n"); + goto cleanup; + } + + ret = calibration_scan(s,0xfe); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot make gain cal scan\n"); + goto cleanup; + } + + for(i=0;i<2;i++){ + max = 0; + for(j=0; j<s->s.valid_Bpl; j++){ + if(s->buffers[i][j] > max) + max = s->buffers[i][j]; + } + + if(old_mode == MODE_COLOR) + s->c_gain[i] = (250-max)*4/5; + else + s->c_gain[i] = (125-max)*4/5; + + if(s->c_gain[i] < 1) + s->c_gain[i] = 1; + + DBG (15, "calibrate_AFE: gain %d %d %02x\n", i, max, s->c_gain[i]); + } + + /*handle fourth pass (offset again), lamp off*/ +#if 0 + DBG (15, "calibrate_AFE: offset2\n"); + + ret = write_AFE(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot write afe\n"); + goto cleanup; + } + + ret = calibration_scan(s,0xff); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot make offset2 cal scan\n"); + goto cleanup; + } + + for(i=0;i<2;i++){ + min = 255; + for(j=0; j<s->s.valid_Bpl; j++){ + if(s->buffers[i][j] < min) + min = s->buffers[i][j]; + } + /*s->c_offset[i] += min*3-2;*/ + DBG (15, "calibrate_AFE: offset2 %d %d %02x\n", i, min, s->c_offset[i]); + } +#endif + + /*send final afe params to scanner*/ + ret = write_AFE(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_AFE: ERROR: cannot write afe\n"); + goto cleanup; + } + + /* log current cal type */ + s->c_res = s->s.dpi_x; + s->c_mode = s->s.mode; + + cleanup: + + /* recover user settings */ + s->u.tl_y = old_tl_y; + s->u.br_y = old_br_y; + s->u.mode = old_mode; + s->u.source = old_source; + + DBG (10, "calibrate_AFE: finish %d\n",ret); + + return ret; +} + + +/* alternative version- extracts data from scanner memory */ +static SANE_Status +calibrate_fine_buffer (struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + int i, j, k; + + unsigned char cmd[READ_len]; + size_t cmdLen = READ_len; + + unsigned char * in = NULL; + size_t inLen = 0, reqLen = 0; + + /*buffer these for later*/ + int old_tl_y = s->u.tl_y; + int old_br_y = s->u.br_y; + int old_source = s->u.source; + + DBG (10, "calibrate_fine_buffer: start\n"); + + if(!s->need_fcal_buffer){ + DBG (10, "calibrate_fine_buffer: not required\n"); + return ret; + } + + /* pretend we are doing a 1 line scan in duplex */ + s->u.tl_y = 0; + s->u.br_y = 1200 / s->u.dpi_y; + s->u.source = SOURCE_ADF_DUPLEX; + + /* load our own private copy of scan params */ + ret = update_params(s,1); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine_buffer: ERROR: cannot update_params\n"); + goto cleanup; + } + + if(s->f_res == s->s.dpi_x && s->f_mode == s->s.mode){ + DBG (10, "calibrate_fine_buffer: already done\n"); + goto cleanup; + } + + /* clean scan params for new scan */ + ret = clean_params(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine_buffer: ERROR: cannot clean_params\n"); + goto cleanup; + } + + /*calibration buffers in scanner are single color channel, but duplex*/ + reqLen = s->s.width*2; + + in = malloc(reqLen); + if (!in) { + DBG (5, "calibrate_fine_buffer: ERROR: cannot malloc in\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + /*fine offset*/ + ret = offset_buffers(s,1); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine_buffer: ERROR: cannot load offset buffers\n"); + goto cleanup; + } + + DBG (5, "calibrate_fine_buffer: %d %x\n", s->s.dpi_x/10, s->s.dpi_x/10); + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, READ_code); + set_R_datatype_code (cmd, SR_datatype_fineoffset); + set_R_xfer_lid (cmd, s->s.dpi_x/10); + set_R_xfer_length (cmd, reqLen); + + inLen = reqLen; + + hexdump(15, "cmd:", cmd, cmdLen); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + NULL, 0, + in, &inLen + ); + if (ret != SANE_STATUS_GOOD) + goto cleanup; + + for(i=0;i<2;i++){ + + /*color mode, expand offset across all three channels? */ + if(s->s.format == SANE_FRAME_RGB){ + for(j=0; j<s->s.valid_width; j++){ + + /*red*/ + s->f_offset[i][j*3] = in[j*2+i]; + if(s->f_offset[i][j*3] < 1) + s->f_offset[i][j*3] = 1; + + /*green and blue, same as red*/ + s->f_offset[i][j*3+1] = s->f_offset[i][j*3+2] = s->f_offset[i][j*3]; + } + } + + /*gray mode, copy*/ + else{ + for(j=0; j<s->s.valid_width; j++){ + + s->f_offset[i][j] = in[j*2+i]; + if(s->f_offset[i][j] < 1) + s->f_offset[i][j] = 1; + } + } + + hexdump(15, "off:", s->f_offset[i], s->s.valid_Bpl); + } + + /*fine gain*/ + ret = gain_buffers(s,1); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine_buffer: ERROR: cannot load gain buffers\n"); + goto cleanup; + } + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, READ_code); + set_R_datatype_code (cmd, SR_datatype_finegain); + set_R_xfer_lid (cmd, s->s.dpi_x/10); + set_R_xfer_length (cmd, reqLen); + + /*color gain split into three buffers, grab them and merge*/ + if(s->s.format == SANE_FRAME_RGB){ + + int codes[] = {R_FINE_uid_red,R_FINE_uid_green,R_FINE_uid_blue}; + + for(k=0;k<3;k++){ + + set_R_xfer_uid (cmd, codes[k]); + inLen = reqLen; + + hexdump(15, "cmd:", cmd, cmdLen); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + NULL, 0, + in, &inLen + ); + if (ret != SANE_STATUS_GOOD) + goto cleanup; + + for(i=0;i<2;i++){ + for(j=0; j<s->s.valid_width; j++){ + + s->f_gain[i][j*3+k] = in[j*2+i]*3/4; + + if(s->f_gain[i][j*3+k] < 1) + s->f_gain[i][j*3+k] = 1; + } + } + } + } + + /*gray gain, copy*/ + else{ + + set_R_xfer_uid (cmd, R_FINE_uid_gray); + inLen = reqLen; + + hexdump(15, "cmd:", cmd, cmdLen); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + NULL, 0, + in, &inLen + ); + if (ret != SANE_STATUS_GOOD) + goto cleanup; + + for(i=0;i<2;i++){ + for(j=0; j<s->s.valid_width; j++){ + + s->f_gain[i][j] = in[j*2+i]*3/4; + + if(s->f_gain[i][j] < 1) + s->f_gain[i][j] = 1; + } + } + } + + for(i=0;i<2;i++){ + hexdump(15, "gain:", s->f_gain[i], s->s.valid_Bpl); + } + + /* log current cal type */ + s->f_res = s->s.dpi_x; + s->f_mode = s->s.mode; + + cleanup: + + if(in){ + free(in); + } + + /* recover user settings */ + s->u.tl_y = old_tl_y; + s->u.br_y = old_br_y; + s->u.source = old_source; + + DBG (10, "calibrate_fine_buffer: finish %d\n",ret); + + return ret; +} + +/* + * makes several scans, adjusts fine calibration + */ +static SANE_Status +calibrate_fine (struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + int i, j, k; + int min, max; + int lines = 8; + + /*buffer these for later*/ + int old_tl_y = s->u.tl_y; + int old_br_y = s->u.br_y; + int old_source = s->u.source; + + DBG (10, "calibrate_fine: start\n"); + + if(!s->need_fcal){ + DBG (10, "calibrate_fine: not required\n"); + return ret; + } + + /* always cal with a short scan in duplex */ + s->u.tl_y = 0; + s->u.br_y = lines * 1200 / s->u.dpi_y; + s->u.source = SOURCE_ADF_DUPLEX; + + /* load our own private copy of scan params */ + ret = update_params(s,1); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine: ERROR: cannot update_params\n"); + goto cleanup; + } + + if(s->f_res == s->s.dpi_x && s->f_mode == s->s.mode){ + DBG (10, "calibrate_fine: already done\n"); + goto cleanup; + } + + /* clean scan params for new scan */ + ret = clean_params(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibration_fine: ERROR: cannot clean_params\n"); + goto cleanup; + } + + /* make buffers to hold the images */ + ret = image_buffers(s,1); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine: ERROR: cannot load buffers\n"); + goto cleanup; + } + + /*blast the existing fine cal data so reading code wont apply it*/ + ret = offset_buffers(s,0); + ret = gain_buffers(s,0); + + /* need to tell it we want duplex */ + ret = ssm_buffer(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine: ERROR: cannot ssm buffer\n"); + goto cleanup; + } + + /* set window command */ + ret = set_window(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine: ERROR: cannot set window\n"); + goto cleanup; + } + + /*handle fifth pass (fine offset), lamp off*/ + DBG (15, "calibrate_fine: offset\n"); + ret = calibration_scan(s,0xff); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine: ERROR: cannot make offset cal scan\n"); + goto cleanup; + } + + ret = offset_buffers(s,1); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine: ERROR: cannot load offset buffers\n"); + goto cleanup; + } + + for(i=0;i<2;i++){ + for(j=0; j<s->s.valid_Bpl; j++){ + min = 0; + for(k=j;k<lines*s->s.Bpl;k+=s->s.Bpl){ + min += s->buffers[i][k]; + } + s->f_offset[i][j] = min/lines; + } + hexdump(15, "off:", s->f_offset[i], s->s.valid_Bpl); + } + + /*handle sixth pass (fine gain), lamp on*/ + DBG (15, "calibrate_fine: gain\n"); + ret = calibration_scan(s,0xfe); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine: ERROR: cannot make gain cal scan\n"); + goto cleanup; + } + + ret = gain_buffers(s,1); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibrate_fine: ERROR: cannot load gain buffers\n"); + goto cleanup; + } + + for(i=0;i<2;i++){ + for(j=0; j<s->s.valid_Bpl; j++){ + max = 0; + for(k=j;k<lines*s->s.Bpl;k+=s->s.Bpl){ + max += s->buffers[i][k]; + } + s->f_gain[i][j] = max/lines; + + if(s->f_gain[i][j] < 1) + s->f_gain[i][j] = 1; + } + hexdump(15, "gain:", s->f_gain[i], s->s.valid_Bpl); + } + + /* log current cal type */ + s->f_res = s->s.dpi_x; + s->f_mode = s->s.mode; + + cleanup: + + /* recover user settings */ + s->u.tl_y = old_tl_y; + s->u.br_y = old_br_y; + s->u.source = old_source; + + DBG (10, "calibrate_fine: finish %d\n",ret); + + return ret; +} + +/* + * sends AFE params, and ingests entire duplex image into buffers + */ +static SANE_Status +calibration_scan (struct scanner *s, int scan) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + DBG (10, "calibration_scan: start\n"); + + /* clean scan params for new scan */ + ret = clean_params(s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibration_scan: ERROR: cannot clean_params\n"); + return ret; + } + + /* start scanning */ + ret = start_scan (s,scan); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "calibration_scan: ERROR: cannot start_scan\n"); + return ret; + } + + while(!s->s.eof[SIDE_FRONT] && !s->s.eof[SIDE_BACK]){ + ret = read_from_scanner_duplex(s,1); + } + + DBG (10, "calibration_scan: finished\n"); + + return ret; +} + +/* + * sends AFE and exposure params + */ +static SANE_Status +write_AFE(struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + unsigned char cmd[COR_CAL_len]; + size_t cmdLen = COR_CAL_len; + + /*use the longest payload for buffer*/ + unsigned char pay[CC3_pay_len]; + size_t payLen = CC3_pay_len; + + DBG (10, "write_AFE: start\n"); + + /* newer scanners use a longer cc payload */ + if(s->ccal_version == 3){ + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, COR_CAL_code); + set_CC_version(cmd,CC3_pay_ver); + set_CC_xferlen(cmd,payLen); + + memset(pay,0,payLen); + + set_CC3_gain_f_r(pay,s->c_gain[SIDE_FRONT]); + set_CC3_gain_f_g(pay,s->c_gain[SIDE_FRONT]); + set_CC3_gain_f_b(pay,s->c_gain[SIDE_FRONT]); + + set_CC3_off_f_r(pay,s->c_offset[SIDE_FRONT]); + set_CC3_off_f_g(pay,s->c_offset[SIDE_FRONT]); + set_CC3_off_f_b(pay,s->c_offset[SIDE_FRONT]); + + set_CC3_exp_f_r(pay,s->c_exposure[SIDE_FRONT][CHAN_RED]); + set_CC3_exp_f_g(pay,s->c_exposure[SIDE_FRONT][CHAN_GREEN]); + set_CC3_exp_f_b(pay,s->c_exposure[SIDE_FRONT][CHAN_BLUE]); + + set_CC3_gain_b_r(pay,s->c_gain[SIDE_BACK]); + set_CC3_gain_b_g(pay,s->c_gain[SIDE_BACK]); + set_CC3_gain_b_b(pay,s->c_gain[SIDE_BACK]); + + set_CC3_off_b_r(pay,s->c_offset[SIDE_BACK]); + set_CC3_off_b_g(pay,s->c_offset[SIDE_BACK]); + set_CC3_off_b_b(pay,s->c_offset[SIDE_BACK]); + + set_CC3_exp_b_r(pay,s->c_exposure[SIDE_BACK][CHAN_RED]); + set_CC3_exp_b_g(pay,s->c_exposure[SIDE_BACK][CHAN_GREEN]); + set_CC3_exp_b_b(pay,s->c_exposure[SIDE_BACK][CHAN_BLUE]); + } + + else{ + payLen = CC_pay_len; + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, COR_CAL_code); + set_CC_version(cmd,CC_pay_ver); + set_CC_xferlen(cmd,payLen); + + memset(pay,0,payLen); + set_CC_f_gain(pay,s->c_gain[SIDE_FRONT]); + set_CC_unk1(pay,1); + set_CC_f_offset(pay,s->c_offset[SIDE_FRONT]); + set_CC_unk2(pay,1); + set_CC_exp_f_r1(pay,s->c_exposure[SIDE_FRONT][CHAN_RED]); + set_CC_exp_f_g1(pay,s->c_exposure[SIDE_FRONT][CHAN_GREEN]); + set_CC_exp_f_b1(pay,s->c_exposure[SIDE_FRONT][CHAN_BLUE]); + set_CC_exp_f_r2(pay,s->c_exposure[SIDE_FRONT][CHAN_RED]); + set_CC_exp_f_g2(pay,s->c_exposure[SIDE_FRONT][CHAN_GREEN]); + set_CC_exp_f_b2(pay,s->c_exposure[SIDE_FRONT][CHAN_BLUE]); + + set_CC_b_gain(pay,s->c_gain[SIDE_BACK]); + set_CC_b_offset(pay,s->c_offset[SIDE_BACK]); + set_CC_exp_b_r1(pay,s->c_exposure[SIDE_BACK][CHAN_RED]); + set_CC_exp_b_g1(pay,s->c_exposure[SIDE_BACK][CHAN_GREEN]); + set_CC_exp_b_b1(pay,s->c_exposure[SIDE_BACK][CHAN_BLUE]); + set_CC_exp_b_r2(pay,s->c_exposure[SIDE_BACK][CHAN_RED]); + set_CC_exp_b_g2(pay,s->c_exposure[SIDE_BACK][CHAN_GREEN]); + set_CC_exp_b_b2(pay,s->c_exposure[SIDE_BACK][CHAN_BLUE]); + } + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + pay, payLen, + NULL, NULL + ); + if (ret != SANE_STATUS_GOOD) + return ret; + + DBG (10, "write_AFE: finish\n"); + + return ret; +} + +/* + * frees/callocs buffers to hold the fine cal offset data + */ +static SANE_Status +offset_buffers (struct scanner *s, int setup) +{ + SANE_Status ret = SANE_STATUS_GOOD; + int side; + + DBG (10, "offset_buffers: start\n"); + + for(side=0;side<2;side++){ + + if (s->f_offset[side]) { + DBG (15, "offset_buffers: free f_offset %d.\n",side); + free(s->f_offset[side]); + s->f_offset[side] = NULL; + } + + if(setup){ + s->f_offset[side] = calloc (1,s->s.Bpl); + if (!s->f_offset[side]) { + DBG (5, "offset_buffers: error, no f_offset %d.\n",side); + return SANE_STATUS_NO_MEM; + } + } + } + + DBG (10, "offset_buffers: finish\n"); + + return ret; +} + +/* + * frees/callocs buffers to hold the fine cal gain data + */ +static SANE_Status +gain_buffers (struct scanner *s, int setup) +{ + SANE_Status ret = SANE_STATUS_GOOD; + int side; + + DBG (10, "gain_buffers: start\n"); + + for(side=0;side<2;side++){ + + if (s->f_gain[side]) { + DBG (15, "gain_buffers: free f_gain %d.\n",side); + free(s->f_gain[side]); + s->f_gain[side] = NULL; + } + + if(setup){ + s->f_gain[side] = calloc (1,s->s.Bpl); + if (!s->f_gain[side]) { + DBG (5, "gain_buffers: error, no f_gain %d.\n",side); + return SANE_STATUS_NO_MEM; + } + } + } + + DBG (10, "gain_buffers: finish\n"); + + return ret; +} + +/* + * @@ Section 6 - SANE cleanup functions + */ +/* + * Cancels a scan. + * + * It has been said on the mailing list that sane_cancel is a bit of a + * misnomer because it is routinely called to signal the end of a + * batch - quoting David Mosberger-Tang: + * + * > In other words, the idea is to have sane_start() be called, and + * > collect as many images as the frontend wants (which could in turn + * > consist of multiple frames each as indicated by frame-type) and + * > when the frontend is done, it should call sane_cancel(). + * > Sometimes it's better to think of sane_cancel() as "sane_stop()" + * > but that name would have had some misleading connotations as + * > well, that's why we stuck with "cancel". + * + * The current consensus regarding duplex and ADF scans seems to be + * the following call sequence: sane_start; sane_read (repeat until + * EOF); sane_start; sane_read... and then call sane_cancel if the + * batch is at an end. I.e. do not call sane_cancel during the run but + * as soon as you get a SANE_STATUS_NO_DOCS. + * + * 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 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) +{ + struct scanner * s = (struct scanner *) handle; + + DBG (10, "sane_cancel: start\n"); + s->cancelled = 1; + + /* if there is no other running function to check, we do it */ + if(!s->reading) + check_for_cancel(s); + + DBG (10, "sane_cancel: finish\n"); +} + +/* checks started and cancelled flags in scanner struct, + * sends cancel command to scanner if required. don't call + * this function asyncronously, wait for pending operation */ +static SANE_Status +check_for_cancel(struct scanner *s) +{ + SANE_Status ret=SANE_STATUS_GOOD; + + DBG (10, "check_for_cancel: start\n"); + + if(s->started && s->cancelled){ + unsigned char cmd[CANCEL_len]; + size_t cmdLen = CANCEL_len; + + DBG (15, "check_for_cancel: cancelling\n"); + + /* cancel scan */ + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, CANCEL_code); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + NULL, 0, + NULL, NULL + ); + if(ret){ + DBG (5, "check_for_cancel: ignoring bad cancel: %d\n",ret); + } + + ret = object_position(s,SANE_FALSE); + if(ret){ + DBG (5, "check_for_cancel: ignoring bad eject: %d\n",ret); + } + + s->started = 0; + s->cancelled = 0; + ret = SANE_STATUS_CANCELLED; + } + else if(s->cancelled){ + DBG (15, "check_for_cancel: already cancelled\n"); + s->cancelled = 0; + ret = SANE_STATUS_CANCELLED; + } + + DBG (10, "check_for_cancel: finish %d\n",ret); + return ret; +} + +/* + * 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"); + disconnect_fd(s); + image_buffers(s,0); + offset_buffers(s,0); + gain_buffers(s,0); + DBG (10, "sane_close: finish\n"); +} + +static SANE_Status +disconnect_fd (struct scanner *s) +{ + DBG (10, "disconnect_fd: start\n"); + + if(s->fd > -1){ + if (s->connection == CONNECTION_USB) { + DBG (15, "disconnecting usb device\n"); + sanei_usb_close (s->fd); + } + else if (s->connection == CONNECTION_SCSI) { + DBG (15, "disconnecting scsi device\n"); + sanei_scsi_close (s->fd); + } + s->fd = -1; + } + + DBG (10, "disconnect_fd: finish\n"); + + return SANE_STATUS_GOOD; +} + +/* + * 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) { + disconnect_fd(dev); + next = dev->next; + free (dev); + } + + if (sane_devArray) + free (sane_devArray); + + scanner_devList = NULL; + sane_devArray = NULL; + + DBG (10, "sane_exit: finish\n"); +} + + +/* + * @@ Section 7 - misc helper functions + */ +static void +default_globals(void) +{ + global_buffer_size = global_buffer_size_default; + global_padded_read = global_padded_read_default; + global_duplex_offset = global_duplex_offset_default; + global_vendor_name[0] = 0; + global_model_name[0] = 0; + global_version_name[0] = 0; +} + +/* + * Called by the SANE SCSI core and our usb code on device errors + * parses the request sense return data buffer, + * decides the best SANE_Status for the problem, produces debug msgs, + * and copies the sense buffer into the scanner struct + */ +static SANE_Status +sense_handler (int fd, unsigned char * sensed_data, void *arg) +{ + struct scanner *s = arg; + unsigned int sense = get_RS_sense_key (sensed_data); + unsigned int asc = get_RS_ASC (sensed_data); + unsigned int ascq = get_RS_ASCQ (sensed_data); + unsigned int eom = get_RS_EOM (sensed_data); + unsigned int ili = get_RS_ILI (sensed_data); + unsigned int info = get_RS_information (sensed_data); + + DBG (5, "sense_handler: start\n"); + + /* kill compiler warning */ + fd = fd; + + /* copy the rs return data into the scanner struct + so that the caller can use it if he wants + memcpy(&s->rs_buffer,sensed_data,RS_return_size); + */ + + DBG (5, "Sense=%#02x, ASC=%#02x, ASCQ=%#02x, EOM=%d, ILI=%d, info=%#08x\n", sense, asc, ascq, eom, ili, info); + + switch (sense) { + case 0: + if (ili == 1) { + s->rs_info = info; + DBG (5, "No sense: EOM remainder:%d\n",info); + return SANE_STATUS_EOF; + } + DBG (5, "No sense: unknown asc/ascq\n"); + return SANE_STATUS_GOOD; + + case 1: + if (asc == 0x37 && ascq == 0x00) { + DBG (5, "Recovered error: parameter rounded\n"); + return SANE_STATUS_GOOD; + } + DBG (5, "Recovered error: unknown asc/ascq\n"); + return SANE_STATUS_GOOD; + + case 2: + if (asc == 0x04 && ascq == 0x01) { + DBG (5, "Not ready: previous command unfinished\n"); + return SANE_STATUS_DEVICE_BUSY; + } + DBG (5, "Not ready: unknown asc/ascq\n"); + return SANE_STATUS_DEVICE_BUSY; + + case 3: + if (asc == 0x36 && ascq == 0x00) { + DBG (5, "Medium error: no cartridge\n"); + return SANE_STATUS_IO_ERROR; + } + if (asc == 0x3a && ascq == 0x00) { + DBG (5, "Medium error: hopper empty\n"); + return SANE_STATUS_NO_DOCS; + } + if (asc == 0x80 && ascq == 0x00) { + DBG (5, "Medium error: paper jam\n"); + return SANE_STATUS_JAMMED; + } + if (asc == 0x80 && ascq == 0x01) { + DBG (5, "Medium error: cover open\n"); + return SANE_STATUS_COVER_OPEN; + } + if (asc == 0x81 && ascq == 0x01) { + DBG (5, "Medium error: double feed\n"); + return SANE_STATUS_JAMMED; + } + if (asc == 0x81 && ascq == 0x02) { + DBG (5, "Medium error: skew detected\n"); + return SANE_STATUS_JAMMED; + } + if (asc == 0x81 && ascq == 0x04) { + DBG (5, "Medium error: staple detected\n"); + return SANE_STATUS_JAMMED; + } + DBG (5, "Medium error: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + + case 4: + if (asc == 0x60 && ascq == 0x00) { + DBG (5, "Hardware error: lamp error\n"); + return SANE_STATUS_IO_ERROR; + } + if (asc == 0x80 && ascq == 0x01) { + DBG (5, "Hardware error: CPU check error\n"); + return SANE_STATUS_IO_ERROR; + } + if (asc == 0x80 && ascq == 0x02) { + DBG (5, "Hardware error: RAM check error\n"); + return SANE_STATUS_IO_ERROR; + } + if (asc == 0x80 && ascq == 0x03) { + DBG (5, "Hardware error: ROM check error\n"); + return SANE_STATUS_IO_ERROR; + } + if (asc == 0x80 && ascq == 0x04) { + DBG (5, "Hardware error: hardware check error\n"); + return SANE_STATUS_IO_ERROR; + } + DBG (5, "Hardware error: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + + case 5: + if (asc == 0x1a && ascq == 0x00) { + DBG (5, "Illegal request: Parameter list error\n"); + return SANE_STATUS_INVAL; + } + if (asc == 0x20 && ascq == 0x00) { + DBG (5, "Illegal request: invalid command\n"); + return SANE_STATUS_INVAL; + } + if (asc == 0x24 && ascq == 0x00) { + DBG (5, "Illegal request: invalid CDB field\n"); + return SANE_STATUS_INVAL; + } + if (asc == 0x25 && ascq == 0x00) { + DBG (5, "Illegal request: unsupported logical unit\n"); + return SANE_STATUS_UNSUPPORTED; + } + if (asc == 0x26 && ascq == 0x00) { + DBG (5, "Illegal request: invalid field in parm list\n"); + return SANE_STATUS_INVAL; + } + if (asc == 0x2c && ascq == 0x00) { + DBG (5, "Illegal request: command sequence error\n"); + return SANE_STATUS_INVAL; + } + if (asc == 0x2c && ascq == 0x01) { + DBG (5, "Illegal request: too many windows\n"); + return SANE_STATUS_INVAL; + } + if (asc == 0x3a && ascq == 0x00) { + DBG (5, "Illegal request: no paper\n"); + return SANE_STATUS_NO_DOCS; + } + if (asc == 0x3d && ascq == 0x00) { + DBG (5, "Illegal request: invalid IDENTIFY\n"); + return SANE_STATUS_INVAL; + } + if (asc == 0x55 && ascq == 0x00) { + DBG (5, "Illegal request: scanner out of memory\n"); + return SANE_STATUS_NO_MEM; + } + DBG (5, "Illegal request: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + break; + + case 6: + if (asc == 0x29 && ascq == 0x00) { + DBG (5, "Unit attention: device reset\n"); + return SANE_STATUS_GOOD; + } + if (asc == 0x2a && ascq == 0x00) { + DBG (5, "Unit attention: param changed by 2nd initiator\n"); + return SANE_STATUS_GOOD; + } + DBG (5, "Unit attention: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + break; + + case 7: + DBG (5, "Data protect: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + + case 8: + DBG (5, "Blank check: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + + case 9: + DBG (5, "Vendor defined: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + + case 0xa: + DBG (5, "Copy aborted: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + + case 0xb: + if (asc == 0x00 && ascq == 0x00) { + DBG (5, "Aborted command: no sense/cancelled\n"); + return SANE_STATUS_CANCELLED; + } + if (asc == 0x45 && ascq == 0x00) { + DBG (5, "Aborted command: reselect failure\n"); + return SANE_STATUS_IO_ERROR; + } + if (asc == 0x47 && ascq == 0x00) { + DBG (5, "Aborted command: SCSI parity error\n"); + return SANE_STATUS_IO_ERROR; + } + if (asc == 0x48 && ascq == 0x00) { + DBG (5, "Aborted command: initiator error message\n"); + return SANE_STATUS_IO_ERROR; + } + if (asc == 0x49 && ascq == 0x00) { + DBG (5, "Aborted command: invalid message\n"); + return SANE_STATUS_IO_ERROR; + } + if (asc == 0x80 && ascq == 0x00) { + DBG (5, "Aborted command: timeout\n"); + return SANE_STATUS_IO_ERROR; + } + DBG (5, "Aborted command: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + break; + + case 0xc: + DBG (5, "Equal: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + + case 0xd: + DBG (5, "Volume overflow: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + + case 0xe: + if (asc == 0x3b && ascq == 0x0d) { + DBG (5, "Miscompare: too many docs\n"); + return SANE_STATUS_IO_ERROR; + } + if (asc == 0x3b && ascq == 0x0e) { + DBG (5, "Miscompare: too few docs\n"); + return SANE_STATUS_IO_ERROR; + } + DBG (5, "Miscompare: unknown asc/ascq\n"); + return SANE_STATUS_IO_ERROR; + + default: + DBG (5, "Unknown Sense Code\n"); + return SANE_STATUS_IO_ERROR; + } + + DBG (5, "sense_handler: should never happen!\n"); + + return SANE_STATUS_IO_ERROR; +} + +/* + * take a bunch of pointers, send commands to scanner + */ +static SANE_Status +do_cmd(struct scanner *s, int runRS, int shortTime, + unsigned char * cmdBuff, size_t cmdLen, + unsigned char * outBuff, size_t outLen, + unsigned char * inBuff, size_t * inLen +) +{ + if (s->connection == CONNECTION_SCSI) { + return do_scsi_cmd(s, runRS, shortTime, + cmdBuff, cmdLen, + outBuff, outLen, + inBuff, inLen + ); + } + if (s->connection == CONNECTION_USB) { + return do_usb_cmd(s, runRS, shortTime, + cmdBuff, cmdLen, + outBuff, outLen, + inBuff, inLen + ); + } + return SANE_STATUS_INVAL; +} + +static SANE_Status +do_scsi_cmd(struct scanner *s, int runRS, int shortTime, + unsigned char * cmdBuff, size_t cmdLen, + unsigned char * outBuff, size_t outLen, + unsigned char * inBuff, size_t * inLen +) +{ + int ret; + size_t actLen = 0; + + /*shut up compiler*/ + runRS=runRS; + shortTime=shortTime; + + DBG(10, "do_scsi_cmd: start\n"); + + DBG(25, "cmd: writing %d bytes\n", (int)cmdLen); + hexdump(30, "cmd: >>", cmdBuff, cmdLen); + + if(outBuff && outLen){ + DBG(25, "out: writing %d bytes\n", (int)outLen); + hexdump(30, "out: >>", outBuff, outLen); + } + if (inBuff && inLen){ + DBG(25, "in: reading %d bytes\n", (int)*inLen); + memset(inBuff,0,*inLen); + actLen = *inLen; + } + + ret = sanei_scsi_cmd2(s->fd, cmdBuff, cmdLen, outBuff, outLen, inBuff, inLen); + + if(ret != SANE_STATUS_GOOD && ret != SANE_STATUS_EOF){ + DBG(5,"do_scsi_cmd: return '%s'\n",sane_strstatus(ret)); + return ret; + } + + if (inBuff && inLen){ + if(ret == SANE_STATUS_EOF){ + DBG(25, "in: short read, remainder %lu bytes\n", (u_long)s->rs_info); + *inLen -= s->rs_info; + } + hexdump(30, "in: <<", inBuff, *inLen); + DBG(25, "in: read %d bytes\n", (int)*inLen); + } + + DBG(10, "do_scsi_cmd: finish\n"); + + return ret; +} + +static SANE_Status +do_usb_cmd(struct scanner *s, int runRS, int shortTime, + unsigned char * cmdBuff, size_t cmdLen, + unsigned char * outBuff, size_t outLen, + unsigned char * inBuff, size_t * inLen +) +{ + size_t cmdOffset = 0; + size_t cmdLength = 0; + size_t cmdActual = 0; + unsigned char * cmdBuffer = NULL; + int cmdTimeout = 0; + + size_t outOffset = 0; + size_t outLength = 0; + size_t outActual = 0; + unsigned char * outBuffer = NULL; + int outTimeout = 0; + + size_t inOffset = 0; + size_t inLength = 0; + size_t inActual = 0; + unsigned char * inBuffer = NULL; + int inTimeout = 0; + + size_t statOffset = 0; + size_t statLength = 0; + size_t statActual = 0; + unsigned char * statBuffer = NULL; + int statTimeout = 0; + + int ret = 0; + int ret2 = 0; + + DBG (10, "do_usb_cmd: start\n"); + + /****************************************************************/ + /* the command stage */ + { + cmdOffset = USB_HEADER_LEN; + cmdLength = cmdOffset+USB_COMMAND_LEN; + cmdActual = cmdLength; + cmdTimeout = USB_COMMAND_TIME; + + /* change timeout */ + if(shortTime) + cmdTimeout/=60; + sanei_usb_set_timeout(cmdTimeout); + + /* build buffer */ + cmdBuffer = calloc(cmdLength,1); + if(!cmdBuffer){ + DBG(5,"cmd: no mem\n"); + return SANE_STATUS_NO_MEM; + } + + /* build a USB packet around the SCSI command */ + cmdBuffer[3] = cmdLength-4; + cmdBuffer[5] = 1; + cmdBuffer[6] = 0x90; + memcpy(cmdBuffer+cmdOffset,cmdBuff,cmdLen); + + /* write the command out */ + DBG(25, "cmd: writing %d bytes, timeout %d\n", (int)cmdLength, cmdTimeout); + hexdump(30, "cmd: >>", cmdBuffer, cmdLength); + ret = sanei_usb_write_bulk(s->fd, cmdBuffer, &cmdActual); + DBG(25, "cmd: wrote %d bytes, retVal %d\n", (int)cmdActual, ret); + + if(cmdLength != cmdActual){ + DBG(5,"cmd: wrong size %d/%d\n", (int)cmdLength, (int)cmdActual); + free(cmdBuffer); + return SANE_STATUS_IO_ERROR; + } + if(ret != SANE_STATUS_GOOD){ + DBG(5,"cmd: write error '%s'\n",sane_strstatus(ret)); + free(cmdBuffer); + return ret; + } + free(cmdBuffer); + } + + /****************************************************************/ + /* the output stage */ + if(outBuff && outLen){ + + outOffset = USB_HEADER_LEN; + outLength = outOffset+outLen; + outActual = outLength; + outTimeout = USB_DATA_TIME; + + /* change timeout */ + if(shortTime) + outTimeout/=60; + sanei_usb_set_timeout(outTimeout); + + /* build outBuffer */ + outBuffer = calloc(outLength,1); + if(!outBuffer){ + DBG(5,"out: no mem\n"); + return SANE_STATUS_NO_MEM; + } + + /* build a USB packet around the SCSI command */ + outBuffer[3] = outLength-4; + outBuffer[5] = 2; + outBuffer[6] = 0xb0; + memcpy(outBuffer+outOffset,outBuff,outLen); + + /* write the command out */ + DBG(25, "out: writing %d bytes, timeout %d\n", (int)outLength, outTimeout); + hexdump(30, "out: >>", outBuffer, outLength); + ret = sanei_usb_write_bulk(s->fd, outBuffer, &outActual); + DBG(25, "out: wrote %d bytes, retVal %d\n", (int)outActual, ret); + + if(outLength != outActual){ + DBG(5,"out: wrong size %d/%d\n", (int)outLength, (int)outActual); + free(outBuffer); + return SANE_STATUS_IO_ERROR; + } + if(ret != SANE_STATUS_GOOD){ + DBG(5,"out: write error '%s'\n",sane_strstatus(ret)); + free(outBuffer); + return ret; + } + free(outBuffer); + } + + /****************************************************************/ + /* the input stage */ + if(inBuff && inLen){ + + inOffset = 0; + if(s->padded_read) + inOffset = USB_HEADER_LEN; + + inLength = inOffset+*inLen; + inActual = inLength; + + /*blast caller's copy in case we error out*/ + *inLen = 0; + + inTimeout = USB_DATA_TIME; + + /* change timeout */ + if(shortTime) + inTimeout/=60; + sanei_usb_set_timeout(inTimeout); + + /* build inBuffer */ + inBuffer = calloc(inLength,1); + if(!inBuffer){ + DBG(5,"in: no mem\n"); + return SANE_STATUS_NO_MEM; + } + + DBG(25, "in: reading %d bytes, timeout %d\n", (int)inLength, inTimeout); + ret = sanei_usb_read_bulk(s->fd, inBuffer, &inActual); + DBG(25, "in: read %d bytes, retval %d\n", (int)inActual, ret); + hexdump(30, "in: <<", inBuffer, inActual); + + if(!inActual){ + DBG(5,"in: got no data, clearing\n"); + free(inBuffer); + return do_usb_clear(s,1,runRS); + } + if(inActual < inOffset){ + DBG(5,"in: read shorter than inOffset\n"); + free(inBuffer); + return SANE_STATUS_IO_ERROR; + } + if(ret != SANE_STATUS_GOOD){ + DBG(5,"in: return error '%s'\n",sane_strstatus(ret)); + free(inBuffer); + return ret; + } + + /* note that inBuffer is not copied and freed here...*/ + } + + /****************************************************************/ + /* the status stage */ + statOffset = 0; + if(s->padded_read) + statOffset = USB_HEADER_LEN; + + statLength = statOffset+USB_STATUS_LEN; + statActual = statLength; + statTimeout = USB_STATUS_TIME; + + /* change timeout */ + if(shortTime) + statTimeout/=60; + sanei_usb_set_timeout(statTimeout); + + /* build statBuffer */ + statBuffer = calloc(statLength,1); + if(!statBuffer){ + DBG(5,"stat: no mem\n"); + if(inBuffer) free(inBuffer); + return SANE_STATUS_NO_MEM; + } + + DBG(25, "stat: reading %d bytes, timeout %d\n", (int)statLength, statTimeout); + ret2 = sanei_usb_read_bulk(s->fd, statBuffer, &statActual); + DBG(25, "stat: read %d bytes, retval %d\n", (int)statActual, ret2); + hexdump(30, "stat: <<", statBuffer, statActual); + + /*weird status*/ + if(ret2 != SANE_STATUS_GOOD){ + DBG(5,"stat: clearing error '%s'\n",sane_strstatus(ret2)); + ret2 = do_usb_clear(s,1,runRS); + } + /*short read*/ + else if(statLength != statActual){ + DBG(5,"stat: clearing short %d/%d\n",(int)statLength,(int)statActual); + ret2 = do_usb_clear(s,1,runRS); + } + /*inspect the last byte of the status response*/ + else if(statBuffer[statLength-1]){ + DBG(5,"stat: status %d\n",statBuffer[statLength-1]); + ret2 = do_usb_clear(s,0,runRS); + } + free(statBuffer); + + /* if status said EOF, adjust input with remainder count */ + if(ret2 == SANE_STATUS_EOF && inBuffer){ + + /* EOF is ok */ + ret2 = SANE_STATUS_GOOD; + + if(inActual <= inLength - s->rs_info){ + DBG(5,"in: we read <= RS, ignoring RS: %d <= %d (%d-%d)\n", + (int)inActual,(int)(inLength-s->rs_info),(int)inLength,(int)s->rs_info); + } + else if(s->rs_info){ + DBG(5,"in: we read > RS, using RS: %d to %d (%d-%d)\n", + (int)inActual,(int)(inLength-s->rs_info),(int)inLength,(int)s->rs_info); + inActual = inLength - s->rs_info; + } + } + + /* bail out on bad RS status */ + if(ret2){ + if(inBuffer) free(inBuffer); + DBG(5,"stat: bad RS status, %d\n", ret2); + return ret2; + } + + /* now that we have read status, deal with input buffer */ + if(inBuffer){ + if(inLength != inActual){ + ret = SANE_STATUS_EOF; + DBG(5,"in: short read, %d/%d\n", (int)inLength,(int)inActual); + } + + /* ignore the USB packet around the SCSI command */ + *inLen = inActual - inOffset; + memcpy(inBuff,inBuffer+inOffset,*inLen); + + free(inBuffer); + } + + DBG (10, "do_usb_cmd: finish\n"); + + return ret; +} + +static SANE_Status +do_usb_clear(struct scanner *s, int clear, int runRS) +{ + SANE_Status ret, ret2; + + DBG (10, "do_usb_clear: start\n"); + + usleep(100000); + + if(clear){ + DBG (15, "do_usb_clear: clear halt\n"); + ret = sanei_usb_clear_halt(s->fd); + if(ret != SANE_STATUS_GOOD){ + DBG(5,"do_usb_clear: cant clear halt, returning %d\n", ret); + return ret; + } + } + + /* caller is interested in having RS run on errors */ + if(runRS){ + + unsigned char rs_cmd[REQUEST_SENSE_len]; + size_t rs_cmdLen = REQUEST_SENSE_len; + + unsigned char rs_in[RS_return_size]; + size_t rs_inLen = RS_return_size; + + memset(rs_cmd,0,rs_cmdLen); + set_SCSI_opcode(rs_cmd, REQUEST_SENSE_code); + set_RS_return_size(rs_cmd, rs_inLen); + + DBG(25,"rs sub call >>\n"); + ret2 = do_cmd( + s,0,0, + rs_cmd, rs_cmdLen, + NULL,0, + rs_in, &rs_inLen + ); + DBG(25,"rs sub call <<\n"); + + if(ret2 == SANE_STATUS_EOF){ + DBG(5,"rs: got EOF, returning IO_ERROR\n"); + return SANE_STATUS_IO_ERROR; + } + if(ret2 != SANE_STATUS_GOOD){ + DBG(5,"rs: return error '%s'\n",sane_strstatus(ret2)); + return ret2; + } + + /* parse the rs data */ + ret2 = sense_handler( 0, rs_in, (void *)s ); + + DBG (10, "do_usb_clear: finish after RS\n"); + return ret2; + } + + DBG (10, "do_usb_clear: finish with io error\n"); + + return SANE_STATUS_IO_ERROR; +} + +static SANE_Status +wait_scanner(struct scanner *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + unsigned char cmd[TEST_UNIT_READY_len]; + size_t cmdLen = TEST_UNIT_READY_len; + + DBG (10, "wait_scanner: start\n"); + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd,TEST_UNIT_READY_code); + + ret = do_cmd ( + s, 0, 1, + cmd, cmdLen, + NULL, 0, + NULL, NULL + ); + + if (ret != SANE_STATUS_GOOD) { + DBG(5,"WARNING: Brain-dead scanner. Hitting with stick\n"); + ret = do_cmd ( + s, 0, 1, + cmd, cmdLen, + NULL, 0, + NULL, NULL + ); + } + if (ret != SANE_STATUS_GOOD) { + DBG(5,"WARNING: Brain-dead scanner. Hitting with stick again\n"); + ret = do_cmd ( + s, 0, 1, + cmd, cmdLen, + NULL, 0, + NULL, NULL + ); + } + + if (ret != SANE_STATUS_GOOD) { + DBG (5, "wait_scanner: error '%s'\n", sane_strstatus (ret)); + } + + DBG (10, "wait_scanner: finish\n"); + + return ret; +} + +/* s->u.page_x stores the user setting + * for the paper width in adf. sometimes, + * we need a value that differs from this + * due to using FB or overscan. + */ +static int +get_page_width(struct scanner *s) +{ + int width = s->u.page_x; + + /* scanner max for fb */ + if(s->u.source == SOURCE_FLATBED){ + return s->max_x_fb; + } + + /* cant overscan larger than scanner max */ + if(width > s->valid_x){ + return s->valid_x; + } + + /* overscan adds a margin to both sides */ + return width; +} + +/* s->u.page_y stores the user setting + * for the paper height in adf. sometimes, + * we need a value that differs from this + * due to using FB or overscan. + */ +static int +get_page_height(struct scanner *s) +{ + int height = s->u.page_y; + + /* scanner max for fb */ + if(s->u.source == SOURCE_FLATBED){ + return s->max_y_fb; + } + + /* cant overscan larger than scanner max */ + if(height > s->max_y){ + return s->max_y; + } + + /* overscan adds a margin to both sides */ + return height; +} + + +/** + * 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[70]; /* 'xxx: xx xx ... xx xx abc */ + char *hex = line+4; + char *bin = line+53; + + if(DBG_LEVEL < level) + return; + + line[0] = 0; + + DBG (level, "%s\n", comment); + + for (i = 0; i < l; i++, p++) { + + /* at start of line */ + if ((i % 16) == 0) { + + /* not at start of first line, print current, reset */ + if (i) { + DBG (level, "%s\n", line); + } + + memset(line,0x20,69); + line[69] = 0; + hex = line + 4; + bin = line + 53; + + sprintf (line, "%3.3x:", i); + } + + /* the hex section */ + sprintf (hex, " %2.2x", *p); + hex += 3; + *hex = ' '; + + /* the char section */ + if(*p >= 0x20 && *p <= 0x7e){ + *bin=*p; + } + else{ + *bin='.'; + } + bin++; + } + + /* print last (partial) line */ + 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; +} + +/* + * @@ Section 8 - Image processing functions + */ + +/* Look in image for likely upper and left paper edges, then rotate + * image so that upper left corner of paper is upper left of image. + * FIXME: should we do this before we binarize instead of after? */ +static SANE_Status +buffer_deskew(struct scanner *s, int side) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + int pwidth = s->i.width; + int width = s->i.Bpl; + int height = s->i.height; + + double TSlope = 0; + int TXInter = 0; + int TYInter = 0; + double TSlopeHalf = 0; + int TOffsetHalf = 0; + + double LSlope = 0; + int LXInter = 0; + int LYInter = 0; + double LSlopeHalf = 0; + int LOffsetHalf = 0; + + int rotateX = 0; + int rotateY = 0; + + int * topBuf = NULL, * botBuf = NULL; + + DBG (10, "buffer_deskew: start\n"); + + /* get buffers for edge detection */ + topBuf = getTransitionsY(s,side,1); + if(!topBuf){ + DBG (5, "buffer_deskew: cant gTY\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + if(0){ + int i; + for(i=0;i<width;i++){ + if(topBuf[i] >=0 && topBuf[i] < height) + s->buffers[side][topBuf[i]*width+i] = 0; + } + } + + botBuf = getTransitionsY(s,side,0); + if(!botBuf){ + DBG (5, "buffer_deskew: cant gTY\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + /* find best top line */ + ret = getEdgeIterate (pwidth, height, s->i.dpi_y, topBuf, + &TSlope, &TXInter, &TYInter); + if(ret){ + DBG(5,"buffer_deskew: gEI error: %d",ret); + goto cleanup; + } + DBG(15,"top: %04.04f %d %d\n",TSlope,TXInter,TYInter); + + /* slope is too shallow, don't want to divide by 0 */ + if(fabs(TSlope) < 0.0001){ + DBG(15,"buffer_deskew: slope too shallow: %0.08f\n",TSlope); + goto cleanup; + } + + /* find best left line, perpendicular to top line */ + LSlope = (double)-1/TSlope; + ret = getEdgeSlope (pwidth, height, topBuf, botBuf, LSlope, + &LXInter, &LYInter); + if(ret){ + DBG(5,"buffer_deskew: gES error: %d",ret); + goto cleanup; + } + DBG(15,"buffer_deskew: left: %04.04f %d %d\n",LSlope,LXInter,LYInter); + + /* find point about which to rotate */ + TSlopeHalf = tan(atan(TSlope)/2); + TOffsetHalf = LYInter; + DBG(15,"buffer_deskew: top half: %04.04f %d\n",TSlopeHalf,TOffsetHalf); + + LSlopeHalf = tan((atan(LSlope) + ((LSlope < 0)?-M_PI_2:M_PI_2))/2); + LOffsetHalf = - LSlopeHalf * TXInter; + DBG(15,"buffer_deskew: left half: %04.04f %d\n",LSlopeHalf,LOffsetHalf); + + rotateX = (LOffsetHalf-TOffsetHalf) / (TSlopeHalf-LSlopeHalf); + rotateY = TSlopeHalf * rotateX + TOffsetHalf; + DBG(15,"buffer_deskew: rotate: %d %d\n",rotateX,rotateY); + + ret = rotateOnCenter (s, side, rotateX, rotateY, TSlope); + if(ret){ + DBG(5,"buffer_deskew: gES error: %d",ret); + goto cleanup; + } + + cleanup: + if(topBuf) + free(topBuf); + if(botBuf) + free(botBuf); + + DBG (10, "buffer_deskew: finish\n"); + return ret; +} + +/* Look in image for likely left/right/bottom paper edges, then crop + * image to match. Does not attempt to rotate the image. + * FIXME: should we do this before we binarize instead of after? */ +static SANE_Status +buffer_crop(struct scanner *s, int side) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + int bwidth = s->i.Bpl; + int width = s->i.width; + int height = s->i.height; + + int top = 0; + int bot = 0; + int left = width; + int right = 0; + + int * topBuf = NULL, * botBuf = NULL; + int * leftBuf = NULL, * rightBuf = NULL; + int leftCount = 0, rightCount = 0, botCount = 0; + int i; + + DBG (10, "buffer_crop: start\n"); + + /* get buffers to find sides and bottom */ + topBuf = getTransitionsY(s,side,1); + if(!topBuf){ + DBG (5, "buffer_crop: no topBuf\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + botBuf = getTransitionsY(s,side,0); + if(!botBuf){ + DBG (5, "buffer_crop: no botBuf\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + leftBuf = getTransitionsX(s,side,1); + if(!leftBuf){ + DBG (5, "buffer_crop: no leftBuf\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + rightBuf = getTransitionsX(s,side,0); + if(!rightBuf){ + DBG (5, "buffer_crop: no rightBuf\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + /* loop thru top and bottom lists, look for l and r extremes */ + for(i=0; i<width; i++){ + if(botBuf[i] > topBuf[i]){ + if(left > i){ + left = i; + } + + leftCount++; + if(leftCount > 3){ + break; + } + } + else{ + leftCount = 0; + left = width; + } + } + + for(i=width-1; i>=0; i--){ + if(botBuf[i] > topBuf[i]){ + if(right < i){ + right = i; + } + + rightCount++; + if(rightCount > 3){ + break; + } + } + else{ + rightCount = 0; + right = -1; + } + } + + /* loop thru left and right lists, look for bottom extreme */ + for(i=height-1; i>=0; i--){ + if(rightBuf[i] > leftBuf[i]){ + if(bot < i){ + bot = i; + } + + botCount++; + if(botCount > 3){ + break; + } + } + else{ + botCount = 0; + bot = -1; + } + } + + DBG (15, "buffer_crop: t:%d b:%d l:%d r:%d\n",top,bot,left,right); + + /* now crop the image */ + /*FIXME: crop duplex backside at same time?*/ + if(left < right && top < bot){ + + int pixels = 0; + int bytes = 0; + unsigned char * line = NULL; + + /*convert left and right to bytes, figure new byte and pixel width */ + switch (s->i.mode) { + + case MODE_COLOR: + pixels = right-left; + bytes = pixels * 3; + left *= 3; + right *= 3; + break; + + case MODE_GRAYSCALE: + pixels = right-left; + bytes = right-left; + break; + + case MODE_LINEART: + case MODE_HALFTONE: + left /= 8; + right = (right+7)/8; + bytes = right-left; + pixels = bytes * 8; + break; + } + + DBG (15, "buffer_crop: l:%d r:%d p:%d b:%d\n",left,right,pixels,bytes); + + line = malloc(bytes); + if(!line){ + DBG (5, "buffer_crop: no line\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + s->i.bytes_sent[side] = 0; + + for(i=top; i<bot; i++){ + memcpy(line, s->buffers[side] + i*bwidth + left, bytes); + memcpy(s->buffers[side] + s->i.bytes_sent[side], line, bytes); + s->i.bytes_sent[side] += bytes; + } + + s->i.bytes_tot[side] = s->i.bytes_sent[side]; + s->i.width = pixels; + s->i.height = bot-top; + s->i.Bpl = bytes; + + free(line); + } + + cleanup: + if(topBuf) + free(topBuf); + if(botBuf) + free(botBuf); + if(leftBuf) + free(leftBuf); + if(rightBuf) + free(rightBuf); + + DBG (10, "buffer_crop: finish\n"); + return ret; +} + +/* Look in image for disconnected 'spots' of the requested size. + * Replace the spots with the average color of the surrounding pixels. + * FIXME: should we do this before we binarize instead of after? */ +static SANE_Status +buffer_despeck(struct scanner *s, int side) +{ + SANE_Status ret = SANE_STATUS_GOOD; + int i,j,k,l,n; + int w = s->i.Bpl; + int pw = s->i.width; + int h = s->i.height; + int t = w*h; + int d = s->swdespeck; + + DBG (10, "buffer_despeck: start\n"); + + switch (s->i.mode){ + + case MODE_COLOR: + for(i=w; i<t-w-(w*d); i+=w){ + for(j=1; j<pw-1-d; j++){ + + int thresh = 255*3; + int outer[] = {0,0,0}; + int hits = 0; + + /*loop over rows and columns in window */ + for(k=0; k<d; k++){ + for(l=0; l<d; l++){ + int tmp = 0; + + for(n=0; n<3; n++){ + tmp += s->buffers[side][i + j*3 + k*w + l*3 + n]; + } + + if(tmp < thresh) + thresh = tmp; + } + } + + thresh = (thresh + 255*3 + 255*3)/3; + + /*loop over rows and columns around window */ + for(k=-1; k<d+1; k++){ + for(l=-1; l<d+1; l++){ + + int tmp[3]; + + /* dont count pixels in the window */ + if(k != -1 && k != d && l != -1 && l != d) + continue; + + for(n=0; n<3; n++){ + tmp[n] = s->buffers[side][i + j*3 + k*w + l*3 + n]; + outer[n] += tmp[n]; + } + if(tmp[0]+tmp[1]+tmp[2] < thresh){ + hits++; + break; + } + } + } + + for(n=0; n<3; n++){ + outer[n] /= (4*d + 4); + } + + /*no hits, overwrite with avg surrounding color*/ + if(!hits){ + for(k=0; k<d; k++){ + for(l=0; l<d; l++){ + for(n=0; n<3; n++){ + s->buffers[side][i + j*3 + k*w + l*3 + n] = outer[n]; + } + } + } + } + + } + } + break; + + case MODE_GRAYSCALE: + for(i=w; i<t-w-(w*d); i+=w){ + for(j=1; j<w-1-d; j++){ + + int thresh = 255; + int outer = 0; + int hits = 0; + + for(k=0; k<d; k++){ + for(l=0; l<d; l++){ + if(s->buffers[side][i + j + k*w + l] < thresh) + thresh = s->buffers[side][i + j + k*w + l]; + } + } + + thresh = (thresh + 255 + 255)/3; + + /*loop over rows and columns around window */ + for(k=-1; k<d+1; k++){ + for(l=-1; l<d+1; l++){ + + int tmp = 0; + + /* dont count pixels in the window */ + if(k != -1 && k != d && l != -1 && l != d) + continue; + + tmp = s->buffers[side][i + j + k*w + l]; + + if(tmp < thresh){ + hits++; + break; + } + + outer += tmp; + } + } + + outer /= (4*d + 4); + + /*no hits, overwrite with avg surrounding color*/ + if(!hits){ + for(k=0; k<d; k++){ + for(l=0; l<d; l++){ + s->buffers[side][i + j + k*w + l] = outer; + } + } + } + + } + } + break; + + case MODE_LINEART: + case MODE_HALFTONE: + for(i=w; i<t-w-(w*d); i+=w){ + for(j=1; j<pw-1-d; j++){ + + int curr = 0; + int hits = 0; + + for(k=0; k<d; k++){ + for(l=0; l<d; l++){ + curr += s->buffers[side][i + k*w + (j+l)/8] >> (7-(j+l)%8) & 1; + } + } + + if(!curr) + continue; + + /*loop over rows and columns around window */ + for(k=-1; k<d+1; k++){ + for(l=-1; l<d+1; l++){ + + /* dont count pixels in the window */ + if(k != -1 && k != d && l != -1 && l != d) + continue; + + hits += s->buffers[side][i + k*w + (j+l)/8] >> (7-(j+l)%8) & 1; + + if(hits) + break; + } + } + + /*no hits, overwrite with white*/ + if(!hits){ + for(k=0; k<d; k++){ + for(l=0; l<d; l++){ + s->buffers[side][i + k*w + (j+l)/8] &= ~(1 << (7-(j+l)%8)); + } + } + } + + } + } + break; + + default: + break; + } + + DBG (10, "buffer_despeck: finish\n"); + return ret; +} + +/* Loop thru the image width and look for first color change in each column. + * Return a malloc'd array. Caller is responsible for freeing. */ +int * +getTransitionsY (struct scanner *s, int side, int top) +{ + int * buff; + + int i, j, k; + int near, far; + int winLen = 9; + + int width = s->i.width; + int height = s->i.height; + int depth = 1; + + /* defaults for bottom-up */ + int firstLine = height-1; + int lastLine = -1; + int direction = -1; + + DBG (10, "getTransitionsY: start\n"); + + buff = calloc(width,sizeof(int)); + if(!buff){ + DBG (5, "getTransitionsY: no buff\n"); + return NULL; + } + + /* override for top-down */ + if(top){ + firstLine = 0; + lastLine = height; + direction = 1; + } + + /* load the buff array with y value for first color change from edge + * gray/color uses a different algo from binary/halftone */ + switch (s->i.mode) { + + case MODE_COLOR: + depth = 3; + + case MODE_GRAYSCALE: + + for(i=0; i<width; i++){ + buff[i] = lastLine; + + /* load the near and far windows with repeated copy of first pixel */ + near = 0; + for(k=0; k<depth; k++){ + near += s->buffers[side][(firstLine*width+i) * depth + k]; + } + near *= winLen; + far = near; + + /* move windows, check delta */ + for(j=firstLine+direction; j!=lastLine; j+=direction){ + + int farLine = j-winLen*2*direction; + int nearLine = j-winLen*direction; + + if(farLine < 0 || farLine >= height){ + farLine = firstLine; + } + if(nearLine < 0 || nearLine >= height){ + nearLine = firstLine; + } + + for(k=0; k<depth; k++){ + far -= s->buffers[side][(farLine*width+i)*depth+k]; + far += s->buffers[side][(nearLine*width+i)*depth+k]; + + near -= s->buffers[side][(nearLine*width+i)*depth+k]; + near += s->buffers[side][(j*width+i)*depth+k]; + } + + if(abs(near - far) > winLen*depth*9){ + buff[i] = j; + break; + } + } + } + break; + + case MODE_LINEART: + case MODE_HALFTONE: + for(i=0; i<width; i++){ + buff[i] = lastLine; + + /* load the near window with first pixel */ + near = s->buffers[side][(firstLine*width+i)/8] >> (7-(i%8)) & 1; + + /* move */ + for(j=firstLine+direction; j!=lastLine; j+=direction){ + if((s->buffers[side][(j*width+i)/8] >> (7-(i%8)) & 1) != near){ + buff[i] = j; + break; + } + } + } + break; + + } + + /* blast any stragglers with no neighbors within .5 inch */ + for(i=0;i<width-7;i++){ + int sum = 0; + for(j=1;j<=7;j++){ + if(abs(buff[i+j] - buff[i]) < s->i.dpi_y/2) + sum++; + } + if(sum < 2) + buff[i] = lastLine; + } + + DBG (10, "getTransitionsY: finish\n"); + + return buff; +} + +/* Loop thru the image height and look for first color change in each row. + * Return a malloc'd array. Caller is responsible for freeing. */ +int * +getTransitionsX (struct scanner *s, int side, int left) +{ + int * buff; + + int i, j, k; + int near, far; + int winLen = 9; + + int bwidth = s->i.Bpl; + int width = s->i.width; + int height = s->i.height; + int depth = 1; + + /* defaults for right-first */ + int firstCol = width-1; + int lastCol = -1; + int direction = -1; + + DBG (10, "getTransitionsX: start\n"); + + buff = calloc(height,sizeof(int)); + if(!buff){ + DBG (5, "getTransitionsY: no buff\n"); + return NULL; + } + + /* override for left-first*/ + if(left){ + firstCol = 0; + lastCol = width; + direction = 1; + } + + /* load the buff array with x value for first color change from edge + * gray/color uses a different algo from binary/halftone */ + switch (s->i.mode) { + + case MODE_COLOR: + depth = 3; + + case MODE_GRAYSCALE: + + for(i=0; i<height; i++){ + buff[i] = lastCol; + + /* load the near and far windows with repeated copy of first pixel */ + near = 0; + for(k=0; k<depth; k++){ + near += s->buffers[side][i*bwidth + k]; + } + near *= winLen; + far = near; + + /* move windows, check delta */ + for(j=firstCol+direction; j!=lastCol; j+=direction){ + + int farCol = j-winLen*2*direction; + int nearCol = j-winLen*direction; + + if(farCol < 0 || farCol >= width){ + farCol = firstCol; + } + if(nearCol < 0 || nearCol >= width){ + nearCol = firstCol; + } + + for(k=0; k<depth; k++){ + far -= s->buffers[side][i*bwidth + farCol*depth + k]; + far += s->buffers[side][i*bwidth + nearCol*depth + k]; + + near -= s->buffers[side][i*bwidth + nearCol*depth + k]; + near += s->buffers[side][i*bwidth + j*depth + k]; + } + + if(abs(near - far) > winLen*depth*9){ + buff[i] = j; + break; + } + } + } + break; + + case MODE_LINEART: + case MODE_HALFTONE: + for(i=0; i<height; i++){ + buff[i] = lastCol; + + /* load the near window with first pixel */ + near = s->buffers[side][i*bwidth + firstCol/8] >> (7-(firstCol%8)) & 1; + + /* move */ + for(j=firstCol+direction; j!=lastCol; j+=direction){ + if((s->buffers[side][i*bwidth + j/8] >> (7-(j%8)) & 1) != near){ + buff[i] = j; + break; + } + } + } + break; + + } + + /* blast any stragglers with no neighbors within .5 inch */ + for(i=0;i<height-7;i++){ + int sum = 0; + for(j=1;j<=7;j++){ + if(abs(buff[i+j] - buff[i]) < s->i.dpi_x/2) + sum++; + } + if(sum < 2) + buff[i] = lastCol; + } + + DBG (10, "getTransitionsX: finish\n"); + + return buff; +} + +/* Loop thru a getTransitions array, and use a simplified Hough transform + * to divide likely edges into a 2-d array of bins. Then weight each + * bin based on its angle and offset. Return the 'best' bin. */ +static SANE_Status +getLine (int height, int width, int * buff, + int slopes, double minSlope, double maxSlope, + int offsets, int minOffset, int maxOffset, + double * finSlope, int * finOffset, int * finDensity) +{ + SANE_Status ret = 0; + + int ** lines = NULL; + int i, j; + int rise, run; + double slope; + int offset; + int sIndex, oIndex; + int hWidth = width/2; + + double * slopeCenter = NULL; + int * slopeScale = NULL; + double * offsetCenter = NULL; + int * offsetScale = NULL; + + int maxDensity = 1; + double absMaxSlope = fabs(maxSlope); + double absMinSlope = fabs(minSlope); + int absMaxOffset = abs(maxOffset); + int absMinOffset = abs(minOffset); + + DBG(10,"getLine: start %+0.4f %+0.4f %d %d\n", + minSlope,maxSlope,minOffset,maxOffset); + + /*silence compiler*/ + height = height; + + if(absMaxSlope < absMinSlope) + absMaxSlope = absMinSlope; + + if(absMaxOffset < absMinOffset) + absMaxOffset = absMinOffset; + + /* build an array of pretty-print values for slope */ + slopeCenter = calloc(slopes,sizeof(double)); + if(!slopeCenter){ + DBG(5,"getLine: cant load slopeCenter\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + /* build an array of scaling factors for slope */ + slopeScale = calloc(slopes,sizeof(int)); + if(!slopeScale){ + DBG(5,"getLine: cant load slopeScale\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + for(j=0;j<slopes;j++){ + + /* find central value of this 'bucket' */ + slopeCenter[j] = ( + (double)j*(maxSlope-minSlope)/slopes+minSlope + + (double)(j+1)*(maxSlope-minSlope)/slopes+minSlope + )/2; + + /* scale value from the requested range into an inverted 100-1 range + * input close to 0 makes output close to 100 */ + slopeScale[j] = 101 - fabs(slopeCenter[j])*100/absMaxSlope; + } + + /* build an array of pretty-print values for offset */ + offsetCenter = calloc(offsets,sizeof(double)); + if(!offsetCenter){ + DBG(5,"getLine: cant load offsetCenter\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + /* build an array of scaling factors for offset */ + offsetScale = calloc(offsets,sizeof(int)); + if(!offsetScale){ + DBG(5,"getLine: cant load offsetScale\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + for(j=0;j<offsets;j++){ + + /* find central value of this 'bucket'*/ + offsetCenter[j] = ( + (double)j/offsets*(maxOffset-minOffset)+minOffset + + (double)(j+1)/offsets*(maxOffset-minOffset)+minOffset + )/2; + + /* scale value from the requested range into an inverted 100-1 range + * input close to 0 makes output close to 100 */ + offsetScale[j] = 101 - fabs(offsetCenter[j])*100/absMaxOffset; + } + + /* build 2-d array of 'density', divided into slope and offset ranges */ + lines = calloc(slopes, sizeof(int *)); + if(!lines){ + DBG(5,"getLine: cant load lines\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + for(i=0;i<slopes;i++){ + if(!(lines[i] = calloc(offsets, sizeof(int)))){ + DBG(5,"getLine: cant load lines %d\n",i); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + } + + for(i=0;i<width;i++){ + for(j=i+1;j<width && j<i+width/3;j++){ + + /*FIXME: check for invalid (min/max) values?*/ + rise = buff[j] - buff[i]; + run = j-i; + + slope = (double)rise/run; + if(slope >= maxSlope || slope < minSlope) + continue; + + /* offset in center of width, not y intercept! */ + offset = slope * hWidth + buff[i] - slope * i; + if(offset >= maxOffset || offset < minOffset) + continue; + + sIndex = (slope - minSlope) * slopes/(maxSlope-minSlope); + if(sIndex >= slopes) + continue; + + oIndex = (offset - minOffset) * offsets/(maxOffset-minOffset); + if(oIndex >= offsets) + continue; + + lines[sIndex][oIndex]++; + } + } + + /* go thru array, and find most dense line (highest number) */ + for(i=0;i<slopes;i++){ + for(j=0;j<offsets;j++){ + if(lines[i][j] > maxDensity) + maxDensity = lines[i][j]; + } + } + + DBG(15,"getLine: maxDensity %d\n",maxDensity); + + *finSlope = 0; + *finOffset = 0; + *finDensity = 0; + + /* go thru array, and scale densities to % of maximum, plus adjust for + * prefered (smaller absolute value) slope and offset */ + for(i=0;i<slopes;i++){ + for(j=0;j<offsets;j++){ + lines[i][j] = lines[i][j] * slopeScale[i] * offsetScale[j] / maxDensity; + if(lines[i][j] > *finDensity){ + *finDensity = lines[i][j]; + *finSlope = slopeCenter[i]; + *finOffset = offsetCenter[j]; + } + } + } + + if(0){ + DBG(15,"offsetCenter: "); + for(j=0;j<offsets;j++){ + DBG(15," %+04.0f",offsetCenter[j]); + } + DBG(15,"\n"); + + DBG(15,"offsetScale: "); + for(j=0;j<offsets;j++){ + DBG(15," %04d",offsetScale[j]); + } + DBG(15,"\n"); + + for(i=0;i<slopes;i++){ + DBG(15,"slope: %02d %+02.2f %03d:",i,slopeCenter[i],slopeScale[i]); + for(j=0;j<offsets;j++){ + DBG(15,"% 5d",lines[i][j]/100); + } + DBG(15,"\n"); + } + } + + /* dont forget to cleanup */ + cleanup: + for(i=0;i<10;i++){ + if(lines[i]) + free(lines[i]); + } + if(lines) + free(lines); + if(slopeCenter) + free(slopeCenter); + if(slopeScale) + free(slopeScale); + if(offsetCenter) + free(offsetCenter); + if(offsetScale) + free(offsetScale); + + DBG(10,"getLine: finish\n"); + + return ret; +} + +/* Repeatedly find the best range of slope and offset via Hough transform. + * Shift the ranges thru 4 different positions to avoid splitting data + * across multiple bins (false positive). Home-in on the most likely upper + * line of the paper inside the image. Return the 'best' line. */ +SANE_Status +getEdgeIterate (int width, int height, int resolution, +int * buff, double * finSlope, int * finXInter, int * finYInter) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + int slopes = 11; + int offsets = 11; + double maxSlope = 1; + double minSlope = -1; + int maxOffset = resolution/6; + int minOffset = -resolution/6; + + double topSlope = 0; + int topOffset = 0; + int topDensity = 0; + + int i,j; + int pass = 0; + + DBG(10,"getEdgeIterate: start\n"); + + while(pass++ < 7){ + double sStep = (maxSlope-minSlope)/slopes; + int oStep = (maxOffset-minOffset)/offsets; + + double slope = 0; + int offset = 0; + int density = 0; + int go = 0; + + topSlope = 0; + topOffset = 0; + topDensity = 0; + + /* find lines 4 times with slightly moved params, + * to bypass binning errors, highest density wins */ + for(i=0;i<2;i++){ + double sStep2 = sStep*i/2; + for(j=0;j<2;j++){ + int oStep2 = oStep*j/2; + ret = getLine(height,width,buff,slopes,minSlope+sStep2,maxSlope+sStep2,offsets,minOffset+oStep2,maxOffset+oStep2,&slope,&offset,&density); + if(ret){ + DBG(5,"getEdgeIterate: getLine error %d\n",ret); + return ret; + } + DBG(15,"getEdgeIterate: %d %d %+0.4f %d %d\n",i,j,slope,offset,density); + + if(density > topDensity){ + topSlope = slope; + topOffset = offset; + topDensity = density; + } + } + } + + DBG(15,"getEdgeIterate: ok %+0.4f %d %d\n",topSlope,topOffset,topDensity); + + /* did not find anything promising on first pass, + * give up instead of fixating on some small, pointless feature */ + if(pass == 1 && topDensity < width/5){ + DBG(5,"getEdgeIterate: density too small %d %d\n",topDensity,width); + topOffset = 0; + topSlope = 0; + break; + } + + /* if slope can zoom in some more, do so. */ + if(sStep >= 0.0001){ + minSlope = topSlope - sStep; + maxSlope = topSlope + sStep; + go = 1; + } + + /* if offset can zoom in some more, do so. */ + if(oStep){ + minOffset = topOffset - oStep; + maxOffset = topOffset + oStep; + go = 1; + } + + /* cannot zoom in more, bail out */ + if(!go){ + break; + } + + DBG(15,"getEdgeIterate: zoom: %+0.4f %+0.4f %d %d\n", + minSlope,maxSlope,minOffset,maxOffset); + } + + /* topOffset is in the center of the image, + * convert to x and y intercept */ + if(topSlope != 0){ + *finYInter = topOffset - topSlope * width/2; + *finXInter = *finYInter / -topSlope; + *finSlope = topSlope; + } + else{ + *finYInter = 0; + *finXInter = 0; + *finSlope = 0; + } + + DBG(10,"getEdgeIterate: finish\n"); + + return 0; +} + +/* find the left side of paper by moving a line + * perpendicular to top slope across the image + * the 'left-most' point on the paper is the + * one with the smallest X intercept + * return x and y intercepts */ +SANE_Status +getEdgeSlope (int width, int height, int * top, int * bot, + double slope, int * finXInter, int * finYInter) +{ + + int i; + int topXInter, topYInter; + int botXInter, botYInter; + int leftCount; + + DBG(10,"getEdgeSlope: start\n"); + + topXInter = width; + topYInter = 0; + leftCount = 0; + + for(i=0;i<width;i++){ + + if(top[i] < height){ + int tyi = top[i] - (slope * i); + int txi = tyi/-slope; + + if(topXInter > txi){ + topXInter = txi; + topYInter = tyi; + } + + leftCount++; + if(leftCount > 5){ + break; + } + } + else{ + topXInter = width; + topYInter = 0; + leftCount = 0; + } + } + + botXInter = width; + botYInter = 0; + leftCount = 0; + + for(i=0;i<width;i++){ + + if(bot[i] > -1){ + + int byi = bot[i] - (slope * i); + int bxi = byi/-slope; + + if(botXInter > bxi){ + botXInter = bxi; + botYInter = byi; + } + + leftCount++; + if(leftCount > 5){ + break; + } + } + else{ + botXInter = width; + botYInter = 0; + leftCount = 0; + } + } + + if(botXInter < topXInter){ + *finXInter = botXInter; + *finYInter = botYInter; + } + else{ + *finXInter = topXInter; + *finYInter = topYInter; + } + + DBG(10,"getEdgeSlope: finish\n"); + + return 0; +} + +/* function to do a simple rotation by a given slope, around + * a given point. The point can be outside of image to get + * proper edge alignment. Unused areas filled with bg color + * FIXME: Do in-place rotation to save memory */ +SANE_Status +rotateOnCenter (struct scanner *s, int side, + int centerX, int centerY, double slope) +{ + double slopeRad = -atan(slope); + double slopeSin = sin(slopeRad); + double slopeCos = cos(slopeRad); + + int bwidth = s->i.Bpl; + int pwidth = s->i.width; + int height = s->i.height; + int depth = 1; + int bg_color = s->lut[s->bg_color]; + + unsigned char * outbuf; + int i, j, k; + + DBG(10,"rotateOnCenter: start: %d %d\n",centerX,centerY); + + outbuf = malloc(s->i.bytes_tot[side]); + if(!outbuf){ + DBG(15,"rotateOnCenter: no outbuf\n"); + return SANE_STATUS_NO_MEM; + } + + switch (s->i.mode){ + + case MODE_COLOR: + depth = 3; + + case MODE_GRAYSCALE: + memset(outbuf,bg_color,s->i.bytes_tot[side]); + + for (i=0; i<height; i++) { + int shiftY = centerY - i; + + for (j=0; j<pwidth; j++) { + int shiftX = centerX - j; + int sourceX, sourceY; + + sourceX = centerX - (int)(shiftX * slopeCos + shiftY * slopeSin); + if (sourceX < 0 || sourceX >= pwidth) + continue; + + sourceY = centerY + (int)(-shiftY * slopeCos + shiftX * slopeSin); + if (sourceY < 0 || sourceY >= height) + continue; + + for (k=0; k<depth; k++) { + outbuf[i*bwidth+j*depth+k] + = s->buffers[side][sourceY*bwidth+sourceX*depth+k]; + } + } + } + break; + + case MODE_LINEART: + case MODE_HALFTONE: + memset(outbuf,(bg_color<s->threshold)?0xff:0x00,s->i.bytes_tot[side]); + + for (i=0; i<height; i++) { + int shiftY = centerY - i; + + for (j=0; j<pwidth; j++) { + int shiftX = centerX - j; + int sourceX, sourceY; + + sourceX = centerX - (int)(shiftX * slopeCos + shiftY * slopeSin); + if (sourceX < 0 || sourceX >= pwidth) + continue; + + sourceY = centerY + (int)(-shiftY * slopeCos + shiftX * slopeSin); + if (sourceY < 0 || sourceY >= height) + continue; + + /* wipe out old bit */ + outbuf[i*bwidth + j/8] &= ~(1 << (7-(j%8))); + + /* fill in new bit */ + outbuf[i*bwidth + j/8] |= + ((s->buffers[side][sourceY*bwidth + sourceX/8] + >> (7-(sourceX%8))) & 1) << (7-(j%8)); + } + } + break; + } + + memcpy(s->buffers[side],outbuf,s->i.bytes_tot[side]); + + free(outbuf); + + DBG(10,"rotateOnCenter: finish\n"); + + return 0; +} + +/* 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 + + pos zero neg + . xxxxxxxx . xx . + . x . x . + out x . x . x + . . x . x + ............ xx.......... 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 %d %d\n", slope, offset); + + /* slope is converted to rise per unit run: + * first [-127,127] to [-.999,.999] + * then to [-PI/4,PI/4] then [0,PI/2] + * 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/128 * M_PI_4 + M_PI_4) * 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; +} + |