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