kopia lustrzana https://gitlab.com/sane-project/backends
				
				
				
			
		
			
				
	
	
		
			1657 wiersze
		
	
	
		
			45 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			1657 wiersze
		
	
	
		
			45 KiB
		
	
	
	
		
			C
		
	
	
| /*
 | |
|    (c) 2001,2002 Nathan Rutman  nathan@gordian.com  10/17/01
 | |
| 
 | |
|    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.  
 | |
| */
 | |
| 
 | |
| /*
 | |
|    Communication, calibration, and scanning with the Canon CanoScan FB630U
 | |
|    flatbed scanner under linux.
 | |
|    
 | |
|    Reworked into SANE-compatible format.
 | |
|    
 | |
|    The usb-parallel port interface chip is GL640usb, on the far side of
 | |
|    which is an LM9830 parallel-port scanner-on-a-chip.
 | |
| 
 | |
|    This code has not been tested on anything other than Linux/i386.
 | |
| */
 | |
| 
 | |
| #include <errno.h>
 | |
| #include <fcntl.h>		/* open */
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <unistd.h>		/* usleep */
 | |
| #include <time.h>
 | |
| #include <math.h>               /* exp() */
 | |
| #ifdef HAVE_OS2_H
 | |
| #include <sys/types.h> 		/* mode_t */
 | |
| #endif
 | |
| #include <sys/stat.h>
 | |
| #include "lm9830.h"
 | |
| 
 | |
| #define USB_TYPE_VENDOR                 (0x02 << 5)
 | |
| #define USB_RECIP_DEVICE                0x00
 | |
| #define USB_DIR_OUT                     0
 | |
| #define USB_DIR_IN                      0x80
 | |
| 
 | |
| /* Assign status and verify a good return code */
 | |
| #define CHK(A) {if( (status = A) != SANE_STATUS_GOOD ) { \
 | |
|                  DBG( 1, "Failure on line of %s: %d\n", __FILE__, \
 | |
|                       __LINE__ ); return A; }}
 | |
| 
 | |
| 
 | |
| typedef SANE_Byte byte;
 | |
| 
 | |
| 
 | |
| /*****************************************************
 | |
|             GL640 communication primitives
 | |
|    Provides I/O routines to Genesys Logic GL640USB USB-IEEE1284 parallel
 | |
|    port bridge.  Used in HP3300c, Canon FB630u.
 | |
| ******************************************************/
 | |
| 
 | |
| /* Register codes for the bridge.  These are NOT the registers for the
 | |
|    scanner chip on the other side of the bridge. */
 | |
| typedef enum
 | |
| {
 | |
|   GL640_BULK_SETUP = 0x82,
 | |
|   GL640_EPP_ADDR = 0x83,
 | |
|   GL640_EPP_DATA_READ = 0x84,
 | |
|   GL640_EPP_DATA_WRITE = 0x85,
 | |
|   GL640_SPP_STATUS = 0x86,
 | |
|   GL640_SPP_CONTROL = 0x87,
 | |
|   GL640_SPP_DATA = 0x88,
 | |
|   GL640_GPIO_OE = 0x89,
 | |
|   GL640_GPIO_READ = 0x8a,
 | |
|   GL640_GPIO_WRITE = 0x8b
 | |
| }
 | |
| GL640_Request;
 | |
| 
 | |
| /* Write to the usb-parallel port bridge. */
 | |
| static SANE_Status
 | |
| gl640WriteControl (int fd, GL640_Request req, byte * data, unsigned int size)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   status = sanei_usb_control_msg (fd,
 | |
| 				  /* rqttype */ USB_TYPE_VENDOR |
 | |
| 				  USB_RECIP_DEVICE | USB_DIR_OUT /*0x40? */ ,
 | |
| 				  /* rqt */ (size > 1) ? 0x04 : 0x0C,
 | |
| 				  /* val */ (SANE_Int) req,
 | |
| 				  /* ind */ 0,
 | |
| 				  /* len */ size,
 | |
| 				  /* dat */ data);
 | |
|   if (status != SANE_STATUS_GOOD)
 | |
|     DBG (1, "gl640WriteControl error\n");
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Read from the usb-parallel port bridge. */
 | |
| static SANE_Status
 | |
| gl640ReadControl (int fd, GL640_Request req, byte * data, unsigned int size)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   status = sanei_usb_control_msg (fd,
 | |
| 				  /* rqttype */ USB_TYPE_VENDOR |
 | |
| 				  USB_RECIP_DEVICE | USB_DIR_IN /*0xc0? */ ,
 | |
| 				  /* rqt */ (size > 1) ? 0x04 : 0x0C,
 | |
| 				  /* val */ (SANE_Int) req,
 | |
| 				  /* ind */ 0,
 | |
| 				  /* len */ size,
 | |
| 				  /* dat */ data);
 | |
|   if (status != SANE_STATUS_GOOD)
 | |
|     DBG (1, "gl640ReadControl error\n");
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Wrappers to read or write a single byte to the bridge */
 | |
| static inline SANE_Status
 | |
| gl640WriteReq (int fd, GL640_Request req, byte data)
 | |
| {
 | |
|   return gl640WriteControl (fd, req, &data, 1);
 | |
| }
 | |
| 
 | |
| static inline SANE_Status
 | |
| gl640ReadReq (int fd, GL640_Request req, byte * data)
 | |
| {
 | |
|   return gl640ReadControl (fd, req, data, 1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Write USB bulk data 
 | |
|    setup is an apparently scanner-specific sequence:
 | |
|    {(0=read, 1=write), 0x00, 0x00, 0x00, sizelo, sizehi, 0x00, 0x00}
 | |
|    hp3400: setup[1] = 0x01
 | |
|    fb630u: setup[2] = 0x80
 | |
| */
 | |
| static SANE_Status
 | |
| gl640WriteBulk (int fd, byte * setup, byte * data, size_t size)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   setup[0] = 1;
 | |
|   setup[4] = (size) & 0xFF;
 | |
|   setup[5] = (size >> 8) & 0xFF;
 | |
| 
 | |
|   CHK (gl640WriteControl (fd, GL640_BULK_SETUP, setup, 8));
 | |
| 
 | |
|   status = sanei_usb_write_bulk (fd, data, &size);
 | |
|   if (status != SANE_STATUS_GOOD)
 | |
|     DBG (1, "gl640WriteBulk error\n");
 | |
| 
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Read USB bulk data 
 | |
|    setup is an apparently scanner-specific sequence:
 | |
|    {(0=read, 1=write), 0x00, 0x00, 0x00, sizelo, sizehi, 0x00, 0x00}
 | |
|    fb630u: setup[2] = 0x80
 | |
| */
 | |
| static SANE_Status
 | |
| gl640ReadBulk (int fd, byte * setup, byte * data, size_t size)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   setup[0] = 0;
 | |
|   setup[4] = (size) & 0xFF;
 | |
|   setup[5] = (size >> 8) & 0xFF;
 | |
| 
 | |
|   CHK (gl640WriteControl (fd, GL640_BULK_SETUP, setup, 8));
 | |
| 
 | |
|   status = sanei_usb_read_bulk (fd, data, &size);
 | |
|   if (status != SANE_STATUS_GOOD)
 | |
|     DBG (1, "gl640ReadBulk error\n");
 | |
| 
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*****************************************************
 | |
|             LM9830 communication primitives
 | |
| 	    parallel-port scanner-on-a-chip.
 | |
| ******************************************************/
 | |
| 
 | |
| /* write 1 byte to a LM9830 register address */
 | |
| static SANE_Status
 | |
| write_byte (int fd, byte addr, byte val)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   DBG (14, "write_byte(fd, 0x%02x, 0x%02x);\n", addr, val);
 | |
|   CHK (gl640WriteReq (fd, GL640_EPP_ADDR, addr));
 | |
|   CHK (gl640WriteReq (fd, GL640_EPP_DATA_WRITE, val));
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* read 1 byte from a LM9830 register address */
 | |
| static SANE_Status
 | |
| read_byte (int fd, byte addr, byte * val)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   CHK (gl640WriteReq (fd, GL640_EPP_ADDR, addr));
 | |
|   CHK (gl640ReadReq (fd, GL640_EPP_DATA_READ, val));
 | |
|   DBG (14, "read_byte(fd, 0x%02x, &result); /* got %02x */\n", addr, *val);
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| static byte bulk_setup_data[] = { 0, 0, 0x80, 0, 0, 0, 0, 0 };
 | |
| 
 | |
| /* Bulk write */
 | |
| static SANE_Status
 | |
| write_bulk (int fd, unsigned int addr, void *src, size_t count)
 | |
| {
 | |
|   SANE_Status status;
 | |
| 
 | |
|   DBG (13, "write_bulk(fd, 0x%02x, buf, 0x%04lx);\n", addr, (u_long) count);
 | |
| 
 | |
|   if (!src)
 | |
|     {
 | |
|       DBG (1, "write_bulk: bad src\n");
 | |
|       return SANE_STATUS_INVAL;
 | |
|     }
 | |
| 
 | |
|   /* destination address */
 | |
|   CHK (gl640WriteReq (fd, GL640_EPP_ADDR, addr));
 | |
|   /* write */
 | |
|   CHK (gl640WriteBulk (fd, bulk_setup_data, src, count));
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Bulk read */
 | |
| static SANE_Status
 | |
| read_bulk (int fd, unsigned int addr, void *dst, size_t count)
 | |
| {
 | |
|   SANE_Status status;
 | |
| 
 | |
|   DBG (13, "read_bulk(fd, 0x%02x, buf, 0x%04lx);\n", addr, (u_long) count);
 | |
| 
 | |
|   if (!dst)
 | |
|     {
 | |
|       DBG (1, "read_bulk: bad dest\n");
 | |
|       return SANE_STATUS_INVAL;
 | |
|     }
 | |
| 
 | |
|   /* destination address */
 | |
|   CHK (gl640WriteReq (fd, GL640_EPP_ADDR, addr));
 | |
|   /* read */
 | |
|   CHK (gl640ReadBulk (fd, bulk_setup_data, dst, count));
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*****************************************************
 | |
|             useful macro routines
 | |
| ******************************************************/
 | |
| 
 | |
| /* write a 16-bit int to two sequential registers */
 | |
| static SANE_Status
 | |
| write_word (int fd, unsigned int addr, unsigned int data)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   /* MSB */
 | |
|   CHK (write_byte (fd, addr, (data >> 8) & 0xff));
 | |
|   /* LSB */
 | |
|   CHK (write_byte (fd, addr + 1, data & 0xff));
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* write multiple bytes, one at a time (non-bulk) */
 | |
| static SANE_Status
 | |
| write_many (int fd, unsigned int addr, void *src, size_t count)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   size_t i;
 | |
| 
 | |
|   DBG (14, "multi write %lu\n", (u_long) count);
 | |
|   for (i = 0; i < count; i++)
 | |
|     {
 | |
|       DBG (15, " %04lx:%02x", (u_long) (addr + i), ((byte *) src)[i]);
 | |
|       status = write_byte (fd, addr + i, ((byte *) src)[i]);
 | |
|       if (status != SANE_STATUS_GOOD)
 | |
| 	{
 | |
| 	  DBG (15, "\n");
 | |
| 	  return status;
 | |
| 	}
 | |
|     }
 | |
|   DBG (15, "\n");
 | |
|   return SANE_STATUS_GOOD;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* read multiple bytes, one at a time (non-bulk) */
 | |
| static SANE_Status
 | |
| read_many (int fd, unsigned int addr, void *dst, size_t count)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   size_t i;
 | |
|   byte val;
 | |
| 
 | |
|   DBG (14, "multi read %lu\n", (u_long) count);
 | |
|   for (i = 0; i < count; i++)
 | |
|     {
 | |
|       status = read_byte (fd, addr + i, &val);
 | |
|       ((byte *) dst)[i] = val;
 | |
|       DBG (15, " %04lx:%02x", (u_long) (addr + i), ((byte *) dst)[i]);
 | |
|       /* on err, return number of success */
 | |
|       if (status != SANE_STATUS_GOOD)
 | |
| 	{
 | |
| 	  DBG (15, "\n");
 | |
| 	  return status;
 | |
| 	}
 | |
|     }
 | |
|   DBG (15, "\n");
 | |
|   return SANE_STATUS_GOOD;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Poll addr until result & mask = val */
 | |
| static int
 | |
| read_poll_flag (int fd,
 | |
| 		unsigned int addr, unsigned int mask, unsigned int val)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   byte result = 0;
 | |
|   time_t start_time = time (NULL);
 | |
| 
 | |
|   DBG (12, "read_poll_flag...\n");
 | |
|   do
 | |
|     {
 | |
|       status = read_byte (fd, addr, &result);
 | |
|       if (status != SANE_STATUS_GOOD)
 | |
| 	return -1;
 | |
|       /* Give it a minute */
 | |
|       if ((time (NULL) - start_time) > 60)
 | |
| 	{
 | |
| 	  DBG (1, "read_poll_flag: timed out (%d)\n", result);
 | |
| 	  return -1;
 | |
| 	}
 | |
|       usleep (100000);
 | |
|     }
 | |
|   while ((result & mask) != val);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Keep reading addr until results >= min */
 | |
| static int
 | |
| read_poll_min (int fd, unsigned int addr, unsigned int min)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   byte result;
 | |
|   time_t start_time = time (NULL);
 | |
| 
 | |
|   DBG (12, "waiting...\n");
 | |
|   do
 | |
|     {
 | |
|       status = read_byte (fd, addr, &result);
 | |
|       if (status != SANE_STATUS_GOOD)
 | |
| 	return -1;
 | |
|       /* Give it a minute */
 | |
|       if ((time (NULL) - start_time) > 60)
 | |
| 	{
 | |
| 	  DBG (1, "read_poll_min: timed out (%d < %d)\n", result, min);
 | |
| 	  return -1;
 | |
| 	}
 | |
|       /* no sleep here, or calibration gets unhappy. */
 | |
|     }
 | |
|   while (result < min);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Bulk read "ks" kilobytes + "remainder" bytes of data, to a buffer if the
 | |
|    buffer is valid. */
 | |
| static int
 | |
| read_bulk_size (int fd, int ks, int remainder, byte * dest, int destsize)
 | |
| {
 | |
|   byte *buf;
 | |
|   int bytes = (ks - 1) * 1024 + remainder;
 | |
|   int dropdata = ((dest == 0) || (destsize < bytes));
 | |
| 
 | |
|   if (bytes < 0)
 | |
|     {
 | |
|       DBG (1, "read_bulk_size: invalid size %02x (%d)\n", ks, bytes);
 | |
|       return -1;
 | |
|     }
 | |
|   if (destsize && (destsize < bytes))
 | |
|     {
 | |
|       DBG (3, "read_bulk_size: more data than buffer (%d/%d)\n",
 | |
| 	   destsize, bytes);
 | |
|       bytes = destsize;
 | |
|     }
 | |
| 
 | |
|   if (bytes == 0)
 | |
|     return 0;
 | |
| 
 | |
|   if (dropdata)
 | |
|     {
 | |
|       buf = malloc (bytes);
 | |
|       DBG (3, " ignoring data ");
 | |
|     }
 | |
|   else
 | |
|     buf = dest;
 | |
| 
 | |
|   read_bulk (fd, 0x00, buf, bytes);
 | |
| 
 | |
|   if (dropdata)
 | |
|     free (buf);
 | |
|   return bytes;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*****************************************************
 | |
| 
 | |
|             fb630u calibration and scan
 | |
| 
 | |
| ******************************************************/
 | |
| 
 | |
| /* data structures and constants */
 | |
| 
 | |
| typedef struct CANON_Handle
 | |
| {
 | |
|   int fd;			/* scanner fd */
 | |
|   int x1, x2, y1, y2;		/* in pixels, 600 dpi */
 | |
|   int width, height;		/* at scan resolution */
 | |
|   int resolution;		/* dpi */
 | |
|   char *fname;			/* output file name */
 | |
|   FILE *fp;			/* output file pointer (for reading) */
 | |
|   char *buf, *ptr;		/* data buffer */
 | |
|   unsigned char gain;		/* static analog gain, 0 - 31 */
 | |
|   double gamma;		        /* gamma correction */
 | |
|   int flags;
 | |
| #define FLG_GRAY	0x01	/* grayscale */
 | |
| #define FLG_FORCE_CAL	0x02	/* force calibration */
 | |
| #define FLG_BUF		0x04	/* save scan to buffer instead of file */
 | |
| #define FLG_NO_INTERLEAVE 0x08	/* don't interleave r,g,b pixels; leave them 
 | |
| 				   in row format */
 | |
| #define FLG_PPM_HEADER	0x10	/* include PPM header in scan file */
 | |
| }
 | |
| CANON_Handle;
 | |
| 
 | |
| 
 | |
| /* offset/gain calibration file name */
 | |
| #define CAL_FILE_OGN "/tmp/canon.cal"
 | |
| 
 | |
| /* at 600 dpi */
 | |
| #define CANON_MAX_WIDTH    5100	/* 8.5in */
 | |
| /* this may not be right */
 | |
| #define CANON_MAX_HEIGHT   7000	/* 11.66in */
 | |
| 
 | |
| /* scanline end-of-line data byte, returned after each r,g,b segment,
 | |
|    specific to the FB630u */
 | |
| #define SCANLINE_END	0x0c
 | |
| 
 | |
| 
 | |
| static const byte seq002[] =
 | |
|   { /*r08 */ 0x04, /*300 dpi */ 0x1a, 0x00, 0x0d, 0x4c, 0x2f, 0x00, 0x01,
 | |
| /*r10 */ 0x07, 0x04, 0x05, 0x06, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x25, 0x00,
 | |
| 0x4b, /*r20 */ 0x15, 0xe0, /*data px start */ 0x00, 0x4b, /*data px end */ 0x14, 0x37, 0x15, 0x00 };
 | |
| 
 | |
| static const byte seq003[] =
 | |
|   { 0x02, 0x00, 0x00, /*lights out */ 0x03, 0xff, 0x00, 0x01, 0x03, 0xff,
 | |
| 0x00, 0x01, 0x03, 0xff, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06,
 | |
| 0x1d, 0x00, 0x13, 0x04, 0x1a, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x3c, 0x35, 0x94,
 | |
| 0x00, 0x10, 0x08, 0x3f, 0x2b, 0x91, 0x00, 0x00, 0x01, 0x00, 0x80, 0x00 };
 | |
| 
 | |
| 
 | |
| /* Scanner init, called at calibration and scan time.  Returns 1 if this
 | |
|    was the first time the scanner was plugged in, 0 afterward, and
 | |
|    -1 on error. */
 | |
| static int
 | |
| init (int fd)
 | |
| {
 | |
|   byte result, rv;
 | |
| 
 | |
|   if (gl640WriteReq (fd, GL640_GPIO_OE, 0x71) != SANE_STATUS_GOOD) { 
 | |
|       DBG(1, "Initial write request failed.\n");
 | |
|       return -1;
 | |
|   }
 | |
|   /* Gets 0x04 or 0x05 first run, gets 0x64 subsequent runs. */
 | |
|   if (gl640ReadReq (fd, GL640_GPIO_READ, &rv) != SANE_STATUS_GOOD) {
 | |
|       DBG(1, "Initial read request failed.\n");
 | |
|       return -1;
 | |
|   }
 | |
|   gl640WriteReq (fd, GL640_GPIO_OE, 0x70);
 | |
| 
 | |
|   DBG (2, "init query: %x\n", rv);
 | |
|   if (rv != 0x64)
 | |
|     {
 | |
|       gl640WriteReq (fd, GL640_GPIO_WRITE, 0x00);
 | |
|       gl640WriteReq (fd, GL640_GPIO_WRITE, 0x40);
 | |
|     }
 | |
| 
 | |
|   gl640WriteReq (fd, GL640_SPP_DATA, 0x99);
 | |
|   gl640WriteReq (fd, GL640_SPP_DATA, 0x66);
 | |
|   gl640WriteReq (fd, GL640_SPP_DATA, 0xcc);
 | |
|   gl640WriteReq (fd, GL640_SPP_DATA, 0x33);
 | |
|   /* parallel port setting */
 | |
|   write_byte (fd, PARALLEL_PORT, 0x06);
 | |
|   /* sensor control settings */
 | |
|   write_byte (fd, 0x0b, 0x0d);
 | |
|   write_byte (fd, 0x0c, 0x4c);
 | |
|   write_byte (fd, 0x0d, 0x2f);
 | |
|   read_byte (fd, 0x0b, &result);	/* wants 0d */
 | |
|   read_byte (fd, 0x0c, &result);	/* wants 4c */
 | |
|   read_byte (fd, 0x0d, &result);	/* wants 2f */
 | |
|   /* parallel port noise filter */
 | |
|   write_byte (fd, 0x70, 0x73);
 | |
| 
 | |
|   DBG (2, "init post-reset: %x\n", rv);
 | |
|   /* Returns 1 if this was the first time the scanner was plugged in. */
 | |
|   return (rv != 0x64);
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Turn off the lamps */
 | |
| static void
 | |
| lights_out (int fd)
 | |
| {
 | |
|   write_word (fd, LAMP_R_ON, 0x3fff);
 | |
|   write_word (fd, LAMP_R_OFF, 0x0001);
 | |
|   write_word (fd, LAMP_G_ON, 0x3fff);
 | |
|   write_word (fd, LAMP_G_OFF, 0x0001);
 | |
|   write_word (fd, LAMP_B_ON, 0x3fff);
 | |
|   write_word (fd, LAMP_B_OFF, 0x0001);
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Do the scan and save the resulting image as r,g,b interleaved PPM
 | |
|    file.  */
 | |
| static SANE_Status
 | |
| do_scan (CANON_Handle * s)
 | |
| {
 | |
|   SANE_Status status = SANE_STATUS_GOOD;
 | |
|   int numbytes, datasize, level = 0, line = 0, pixel = 0;
 | |
|   byte *buf, *ptr, *redptr;
 | |
|   FILE *fp;
 | |
| 
 | |
| #define BUFSIZE 0xf000
 | |
|   buf = malloc (BUFSIZE);
 | |
|   if (!buf)
 | |
|     return SANE_STATUS_NO_MEM;
 | |
| 
 | |
|   if (s->flags & FLG_BUF)
 | |
|     {
 | |
|       /* read the whole thing into buf */
 | |
|       if (!s->buf)
 | |
| 	return SANE_STATUS_NO_MEM;
 | |
|       s->ptr = s->buf;
 | |
|       fp = NULL;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       fp = fopen (s->fname, "w");
 | |
|       if (!fp)
 | |
| 	{
 | |
| 	  free (buf);
 | |
| 	  DBG (1, "err:%s when opening %s\n", strerror (errno), s->fname);
 | |
| 	  return SANE_STATUS_IO_ERROR;
 | |
| 	}
 | |
|     }
 | |
|   if (fp && (s->flags & FLG_PPM_HEADER))
 | |
|     /* PPM format header */
 | |
|     fprintf (fp, "P6\n%d %d\n255\n", s->width, s->height);
 | |
| 
 | |
|   /* lights off */
 | |
|   write_byte (s->fd, COMMAND, 0x08);
 | |
|   /* lights on */
 | |
|   write_byte (s->fd, COMMAND, 0x00);
 | |
|   /* begin scan */
 | |
|   write_byte (s->fd, COMMAND, 0x03);
 | |
| 
 | |
|   ptr = redptr = buf;
 | |
|   while (line < s->height)
 | |
|     {
 | |
|       datasize = read_poll_min (s->fd, IMAGE_DATA_AVAIL, 2);
 | |
|       if (datasize < 0)
 | |
| 	{
 | |
| 	  DBG (1, "no data\n");
 | |
| 	  break;
 | |
| 	}
 | |
|       DBG (12, "scan line %d %dk\n", line, datasize - 1);
 | |
|       /* Read may cause scan head to move */
 | |
|       numbytes = read_bulk_size (s->fd, datasize, 0, ptr, BUFSIZE - level);
 | |
|       if (numbytes < 0)
 | |
| 	{
 | |
| 	  status = SANE_STATUS_INVAL;
 | |
| 	  break;
 | |
| 	}
 | |
|       /* Data coming back is "width" bytes Red data followed by 0x0c,
 | |
|          width bytes Green, 0x0c, width bytes Blue, 0x0c, repeat for
 | |
|          "height" lines. */
 | |
|       if (s->flags & FLG_NO_INTERLEAVE)
 | |
| 	{
 | |
| 	  /* number of full lines */
 | |
| 	  line += (numbytes + level) / (s->width * 3);
 | |
| 	  /* remainder (partial line) */
 | |
| 	  level = (numbytes + level) % (s->width * 3);
 | |
| 	  /* but if last line, don't store extra */
 | |
| 	  if (line >= s->height)
 | |
| 	    numbytes -= (line - s->height) * s->width * 3 + level;
 | |
| 	  if (fp)
 | |
| 	    fwrite (buf, 1, numbytes, fp);
 | |
| 	  else
 | |
| 	    {
 | |
| 	      memcpy (s->ptr, buf, numbytes);
 | |
| 	      s->ptr += numbytes;
 | |
| 	    }
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  /* Contorsions to convert data from line-by-line RGB to
 | |
| 	     byte-by-byte RGB, without reading in the whole buffer first.
 | |
| 	     We use the sliding window redptr with the temp buffer buf. */
 | |
| 	  ptr += numbytes;	/* point to the end of data */
 | |
| 	  /* while we have RGB triple data */
 | |
| 	  while (redptr + s->width + s->width <= ptr)
 | |
| 	    {
 | |
| 	      if (*redptr == SCANLINE_END)
 | |
| 		DBG (13, "-%d- ", pixel);
 | |
| 	      if (fp)
 | |
| 		{
 | |
| 		  /* for PPM binary (P6), 3-byte RGB pixel */
 | |
| 		  fwrite (redptr, 1, 1, fp);	/* Red */
 | |
| 		  fwrite (redptr + s->width, 1, 1, fp);	/* Green */
 | |
| 		  fwrite (redptr + s->width + s->width, 1, 1, fp);	/* Blue */
 | |
| 		  /* for PPM ascii (P3)
 | |
| 		     fprintf(fp, "%3d %3d %3d\n",  *redptr, 
 | |
| 		     *(redptr + s->width),
 | |
| 		     *(redptr + s->width + s->width));
 | |
| 		   */
 | |
| 		}
 | |
| 	      else
 | |
| 		{
 | |
| 		  /* R */ *s->ptr = *redptr;
 | |
| 		  s->ptr++;
 | |
| 		  /* G */ *s->ptr = *(redptr + s->width);
 | |
| 		  s->ptr++;
 | |
| 		  /* B */ *s->ptr = *(redptr + s->width + s->width);
 | |
| 		  s->ptr++;
 | |
| 		}
 | |
| 	      redptr++;
 | |
| 	      pixel++;
 | |
| 	      if (pixel && !(pixel % s->width))
 | |
| 		{
 | |
| 		  /* end of a line, move redptr to the next Red section */
 | |
| 		  line++;
 | |
| 		  redptr += s->width + s->width;
 | |
| #if 0
 | |
| 		  /* progress */
 | |
| 		  printf ("%2d%%\r", line * 100 / s->height);
 | |
| 		  fflush (stdout);
 | |
| #endif
 | |
| 		  /* don't record any extra */
 | |
| 		  if (line >= s->height)
 | |
| 		    break;
 | |
| 		}
 | |
| 	    }
 | |
| 	  /* keep the extra around for next time */
 | |
| 	  level = ptr - redptr;
 | |
| 	  if (level < 0)
 | |
| 	    level = 0;
 | |
| 	  memmove (buf, redptr, level);
 | |
| 	  ptr = buf + level;
 | |
| 	  redptr = buf;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   if (fp)
 | |
|     {
 | |
|       fclose (fp);
 | |
|       DBG (6, "created scan file %s\n", s->fname);
 | |
|     }
 | |
|   free (buf);
 | |
|   DBG (6, "%d lines, %d pixels, %d extra bytes\n", line, pixel, level);
 | |
| 
 | |
|   /* motor off */
 | |
|   write_byte (s->fd, COMMAND, 0x00);
 | |
| 
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| wait_for_return (int fd)
 | |
| {
 | |
|   return read_poll_flag (fd, STATUS, STATUS_HOME, STATUS_HOME);
 | |
| }
 | |
| 
 | |
| 
 | |
| static SANE_Status compute_ogn (char *calfilename);
 | |
| 
 | |
| 
 | |
| /* This is the calibration rountine Win2k goes through when the scanner is
 | |
|    first plugged in.
 | |
|    Original usb trace from Win2k with USBSnoopy ("usb sniffer for w2k"
 | |
|    http://benoit.papillault.free.fr/speedtouch/sniff-2000.en.php3)
 | |
|  */
 | |
| static int
 | |
| plugin_cal (CANON_Handle * s)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   unsigned int temp;
 | |
|   byte result;
 | |
|   byte *buf;
 | |
|   int fd = s->fd;
 | |
| 
 | |
|   DBG (6, "Calibrating\n");
 | |
| 
 | |
|   /* reserved? */
 | |
|   read_byte (fd, 0x69, &result);	/* wants 02 */
 | |
| 
 | |
|   /* parallel port setting */
 | |
|   write_byte (fd, PARALLEL_PORT, 0x06);
 | |
| 
 | |
|   write_many (fd, 0x08, (byte *) seq002, sizeof (seq002));
 | |
|   /* addr 0x28 isn't written */
 | |
|   write_many (fd, 0x29, (byte *) seq003, sizeof (seq003));
 | |
|   /* Verification */
 | |
|   buf = malloc (0x400);
 | |
|   read_many (fd, 0x08, buf, sizeof (seq002));
 | |
|   if (memcmp (seq002, buf, sizeof (seq002)))
 | |
|     DBG (1, "seq002 verification error\n");
 | |
|   /* addr 0x28 isn't read */
 | |
|   read_many (fd, 0x29, buf, sizeof (seq003));
 | |
|   if (memcmp (seq003, buf, sizeof (seq003)))
 | |
|     DBG (1, "seq003 verification error\n");
 | |
| 
 | |
|   /* parallel port noise filter */
 | |
|   write_byte (fd, 0x70, 0x73);
 | |
| 
 | |
|   lights_out (fd);
 | |
| 
 | |
|   /* Home motor */
 | |
|   read_byte (fd, STATUS, &result);	/* wants 2f or 2d */
 | |
|   if (!(result & STATUS_HOME) /*0x2d */ )
 | |
|     write_byte (fd, COMMAND, 0x02);
 | |
| 
 | |
|   wait_for_return (fd);
 | |
| 
 | |
|   /* Motor forward */
 | |
|   write_byte (fd, COMMAND, 0x01);
 | |
|   usleep (600000);
 | |
|   read_byte (fd, STATUS, &result);	/* wants 0c or 2c */
 | |
|   read_byte (fd, STATUS, &result);	/* wants 0c */
 | |
|   /* Return home */
 | |
|   write_byte (fd, COMMAND, 0x02);
 | |
| 
 | |
|   /* Gamma tables */
 | |
|   /* Linear gamma */
 | |
|   for (temp = 0; temp < 0x0400; temp++)
 | |
|     buf[temp] = temp / 4;
 | |
|   /* Gamma Red */
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_R | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_WRITE);
 | |
|   write_bulk (fd, DATAPORT, buf, 0x0400);
 | |
|   /* Gamma Green */
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_G | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_WRITE);
 | |
|   write_bulk (fd, DATAPORT, buf, 0x0400);
 | |
|   /* Gamma Blue */
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_B | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_WRITE);
 | |
|   write_bulk (fd, DATAPORT, buf, 0x0400);
 | |
| 
 | |
|   /* Read back gamma tables.  I suppose I should check results... */
 | |
|   /* Gamma Red */
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_R | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_READ);
 | |
|   read_bulk (fd, DATAPORT, buf, 0x0400);
 | |
|   /* Gamma Green */
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_G | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_READ);
 | |
|   read_bulk (fd, DATAPORT, buf, 0x0400);
 | |
|   /* Gamma Blue */
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_B | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_READ);
 | |
|   read_bulk (fd, DATAPORT, buf, 0x0400);
 | |
|   free (buf);
 | |
| 
 | |
|   /* Make sure STATUS_HOME */
 | |
|   read_byte (fd, STATUS, &result);	/* wants 0e */
 | |
|   /* stepper forward */
 | |
|   write_byte (fd, COMMAND, 0x01);
 | |
|   read_byte (fd, STATUS, &result);	/* wants 0c */
 | |
|   /* not sure if this rigid read/write pattern is required */
 | |
|   read_byte (fd, CLOCK_DIV, &result);	/* wants 04 */
 | |
|   write_byte (fd, CLOCK_DIV, 0x04);
 | |
|   read_byte (fd, STEP_SIZE, &result);	/* wants 04 */
 | |
|   write_byte (fd, STEP_SIZE, 0x3f);
 | |
|   read_byte (fd, 0x47, &result);	/* wants 1a */
 | |
|   write_byte (fd, 0x47, 0xff);
 | |
|   read_byte (fd, FAST_STEP, &result);	/* wants 01 */
 | |
|   write_byte (fd, FAST_STEP, 0x01);
 | |
|   read_byte (fd, 0x49, &result);	/* wants 04 */
 | |
|   write_byte (fd, 0x49, 0x04);
 | |
|   read_byte (fd, SKIP_STEPS, &result);	/* wants 00 */
 | |
|   write_byte (fd, SKIP_STEPS, 0x00);
 | |
|   read_byte (fd, 0x4b, &result);	/* wants 00 */
 | |
|   write_byte (fd, 0x4b, 0xc8);
 | |
|   read_byte (fd, BUFFER_LIMIT, &result);	/* wants 57 */
 | |
|   write_byte (fd, BUFFER_LIMIT, 0x04);
 | |
|   read_byte (fd, BUFFER_RESUME, &result);	/* wants 02 */
 | |
|   write_byte (fd, BUFFER_RESUME, 0x02);
 | |
|   read_byte (fd, REVERSE_STEPS, &result);	/* wants 00 */
 | |
|   write_byte (fd, REVERSE_STEPS, 0x00);
 | |
|   write_byte (fd, STEP_PWM, 0x1f);
 | |
| 
 | |
|   /* Reset motor */
 | |
|   write_byte (fd, COMMAND, 0x08);
 | |
|   write_byte (fd, COMMAND, 0x00);
 | |
|   /* Scan */
 | |
|   write_byte (fd, COMMAND, 0x03);
 | |
|   /* Wants 02 or 03, gets a bunch of 0's first */
 | |
|   read_poll_min (fd, IMAGE_DATA_AVAIL, 2);
 | |
|   write_byte (fd, COMMAND, 0x00);
 | |
| 
 | |
|   write_byte (fd, STEP_PWM, 0x3f);
 | |
|   write_byte (fd, CLOCK_DIV, 0x04);
 | |
|   /* 300 dpi */
 | |
|   write_word (fd, STEP_SIZE, 0x041a);
 | |
|   write_word (fd, FAST_STEP, 0x0104);
 | |
|   /* Don't skip the black/white calibration area at the bottom of the
 | |
|      scanner. */
 | |
|   write_word (fd, SKIP_STEPS, 0x0000);
 | |
|   write_byte (fd, BUFFER_LIMIT, 0x57);
 | |
|   write_byte (fd, BUFFER_RESUME, 0x02);
 | |
|   write_byte (fd, REVERSE_STEPS, 0x00);
 | |
|   write_byte (fd, BUFFER_LIMIT, 0x09);
 | |
|   write_byte (fd, STEP_PWM, 0x1f);
 | |
|   read_byte (fd, MICROSTEP, &result);	/* wants 13, active */
 | |
|   write_byte (fd, MICROSTEP, 0x03 /* tristate */ );
 | |
| 
 | |
|   /* Calibration data taken under 3 different lighting conditions */
 | |
|   /* dark */
 | |
|   write_word (fd, LAMP_R_ON, 0x0017);
 | |
|   write_word (fd, LAMP_R_OFF, 0x0100);
 | |
|   write_word (fd, LAMP_G_ON, 0x0017);
 | |
|   write_word (fd, LAMP_G_OFF, 0x0100);
 | |
|   write_word (fd, LAMP_B_ON, 0x0017);
 | |
|   write_word (fd, LAMP_B_OFF, 0x0100);
 | |
|   /* coming in, we've got 300dpi,
 | |
|      data px start : 0x004b
 | |
|      data px end  : 0x1437 for a total of 5100(13ec) 600-dpi pixels, 
 | |
|      (8.5 inches) or 2550 300-dpi pixels (7653 bytes). 
 | |
|      Interestingly, the scan head never moves, no matter how many rows
 | |
|      are read. */
 | |
|   s->width = 2551;
 | |
|   s->height = 1;
 | |
|   s->flags = FLG_BUF | FLG_NO_INTERLEAVE;
 | |
|   s->buf = malloc (s->width * s->height * 3);
 | |
|   /* FIXME do something with this data */
 | |
|   CHK (do_scan (s));
 | |
| 
 | |
|   /* Lighting */
 | |
|   /* medium */
 | |
|   write_word (fd, LAMP_R_ON, 0x0017);
 | |
|   write_word (fd, LAMP_R_OFF, 0x0200);
 | |
|   write_word (fd, LAMP_G_ON, 0x0017);
 | |
|   write_word (fd, LAMP_G_OFF, 0x01d7 /* also 01db */ );
 | |
|   write_word (fd, LAMP_B_ON, 0x0017);
 | |
|   write_word (fd, LAMP_B_OFF, 0x01af /* also 01b2 */ );
 | |
|   /* FIXME do something with this data */
 | |
|   CHK (do_scan (s));
 | |
| 
 | |
|   /* Lighting */
 | |
|   /* bright */
 | |
|   write_word (fd, LAMP_R_ON, 0x0017);
 | |
|   write_word (fd, LAMP_R_OFF, 0x0e8e /* also 1040 */ );
 | |
|   write_word (fd, LAMP_G_ON, 0x0017);
 | |
|   write_word (fd, LAMP_G_OFF, 0x0753 /* also 0718 */ );
 | |
|   write_word (fd, LAMP_B_ON, 0x0017);
 | |
|   write_word (fd, LAMP_B_OFF, 0x03f8 /* also 040d */ );
 | |
|   /* FIXME do something with this data */
 | |
|   CHK (do_scan (s));
 | |
|   free (s->buf);
 | |
|   s->buf = NULL;
 | |
| 
 | |
|   /* The trace gets a little iffy from here on out since the log files
 | |
|      are missing different urb's.  This is kind of a puzzled-out
 | |
|      compilation. */
 | |
| 
 | |
|   write_byte (fd, MICROSTEP, 0x13 /* pins active */ );
 | |
|   write_byte (fd, STEP_PWM, 0x3f);
 | |
|   read_byte (fd, STATUS, &result);	/* wants 0c */
 | |
| 
 | |
|   /* Stepper home */
 | |
|   write_byte (fd, COMMAND, 0x02);
 | |
|   /* Step size */
 | |
|   write_word (fd, STEP_SIZE, 0x041a /* 300 dpi */ );
 | |
|   /* Skip steps */
 | |
|   write_word (fd, SKIP_STEPS, 0x0000);
 | |
|   /* Pause buffer levels */
 | |
|   write_byte (fd, BUFFER_LIMIT, 0x57);
 | |
|   /* Resume buffer levels */
 | |
|   write_byte (fd, BUFFER_RESUME, 0x02);
 | |
| 
 | |
|   wait_for_return (fd);
 | |
|   /* stepper forward small */
 | |
|   write_byte (fd, COMMAND, 0x01);
 | |
|   read_byte (fd, STATUS, &result);	/* wants 0c */
 | |
|   usleep (200000);
 | |
|   write_byte (fd, STEP_PWM, 0x1f);
 | |
| 
 | |
|   /* Read in cal strip at bottom of scanner (to adjust gain/offset
 | |
|      tables.  Note that this isn't the brightest lighting condition.)
 | |
|      At 300 dpi: black rows 0-25; white rows 30-75; beginning
 | |
|      of glass 90.
 | |
|      This produces 574k of data, so save it to a temp file. */
 | |
|   if (!s->fname)
 | |
|     {
 | |
|       DBG (1, "No temp filename!\n");
 | |
|       s->fname = strdup ("/tmp/cal.XXXXXX");
 | |
|       mktemp (s->fname);
 | |
|     }
 | |
|   s->width = 2551;
 | |
|   s->height = 75;
 | |
|   s->flags = FLG_PPM_HEADER | FLG_NO_INTERLEAVE;
 | |
|   CHK (do_scan (s));
 | |
|   compute_ogn (s->fname);
 | |
|   unlink (s->fname);
 | |
| 
 | |
|   write_byte (fd, STEP_PWM, 0x3f);
 | |
|   /* stepper home */
 | |
|   write_byte (fd, COMMAND, 0x02);
 | |
| 
 | |
|   /* discard the remaining data */
 | |
|   read_byte (fd, IMAGE_DATA_AVAIL, &result);	/* wants 42,4c */
 | |
|   if (result > 1)
 | |
|     {
 | |
|       read_bulk_size (fd, result, 0, 0, 0);
 | |
|       DBG (11, "read %dk extra\n", result);
 | |
|     }
 | |
|   read_byte (fd, 0x69, &result);	/* wants 02 */
 | |
|   write_byte (fd, 0x69, 0x0a);
 | |
| 
 | |
|   lights_out (fd);
 | |
|   init (fd);
 | |
| 
 | |
| #if 0
 | |
|   /* Repeatedly send this every 1 second.  Button scan?  FIXME */
 | |
|   gl640ReadReq (fd, GL640_GPIO_READ, &result);	/* wants 00 */
 | |
| #endif
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* The number of regions in the calibration strip (black & white). */
 | |
| #define NREGIONS 2
 | |
| 
 | |
| /* Compute the offset/gain table from the calibration strip.  This is
 | |
|    somewhat more complicated than necessary because I don't hard-code the
 | |
|    strip widths; I try to figure out the regions based on the scan data.
 | |
|    Theoretically, the region-finder should work for any number of distinct
 | |
|    regions (but there are only 2 on this scanner.) 
 | |
|    This produces the CAL_FILE_OGN file, the final offset/gain table. */
 | |
| static SANE_Status
 | |
| compute_ogn (char *calfilename)
 | |
| {
 | |
|   byte *linebuf, *oldline, *newline;
 | |
|   mode_t oldmask;
 | |
|   FILE *fp;
 | |
|   int width, height, nlines = 0, region = -1, i, transition = 1, badcnt;
 | |
|   int pct;
 | |
|   int reglines[NREGIONS];
 | |
|   float *avg;
 | |
|   float max_range[3], tmp1, tmp2;
 | |
| 
 | |
|   fp = fopen (calfilename, "r");
 | |
|   if (!fp)
 | |
|     {
 | |
|       DBG (1, "open %s\n", calfilename);
 | |
|       return SANE_STATUS_EOF;
 | |
|     }
 | |
|   fscanf (fp, "P6 %d %d %*d ", &width, &height);
 | |
|   DBG (12, "cal file %s %dx%d\n", calfilename, width, height);
 | |
|   width = width * 3;		/* 1 byte each of r, g, b */
 | |
|   /* make a buffer holding 2 lines of data */
 | |
|   linebuf = calloc (width * 2, sizeof (linebuf[0]));
 | |
|   /* first line is data read buffer */
 | |
|   newline = linebuf;
 | |
|   /* second line is a temporary holding spot in case the next line read
 | |
|      is the black/white transition, in which case we'll disregard this
 | |
|      one. */
 | |
|   oldline = linebuf + width;
 | |
|   /* column averages per region */
 | |
|   avg = calloc (width * NREGIONS, sizeof (avg[0]));
 | |
| 
 | |
|   while (nlines < height)
 | |
|     {
 | |
|       if (fread (newline, 1, width, fp) != (size_t) width)
 | |
| 	break;
 | |
|       nlines++;
 | |
|       /* Check if new line is majorly different than old.
 | |
|          Criteria is 10 pixels differing by more than 10%. */
 | |
|       badcnt = 0;
 | |
|       for (i = 0; i < width; i++)
 | |
| 	{
 | |
| 	  pct = newline[i] - oldline[i];
 | |
| 	  /* Fix by M.Reinelt <reinelt@eunet.at>
 | |
| 	   * do NOT use 10% (think of a dark area with
 | |
| 	   * oldline=4 and newline=5, which is a change of 20% !!
 | |
| 	   * Use an absolute difference of 10 as criteria
 | |
| 	   */
 | |
| 	  if (pct < -10 || pct > 10)
 | |
| 	    {
 | |
| 	      badcnt++;
 | |
| 	      DBG (16, "pix%d[%d/%d] ", i, newline[i], oldline[i]);
 | |
| 	    }
 | |
| 	}
 | |
|       DBG (13, "line %d changed %d\n", nlines, badcnt);
 | |
|       if ((badcnt > 10) || (nlines == height))
 | |
| 	{
 | |
| 	  /* End of region.  Lines are different or end of data. */
 | |
| 	  transition++;
 | |
| 	  if (transition == 1)
 | |
| 	    DBG (12, "Region %d lines %d-%d\n",
 | |
| 		 region, nlines - reglines[region], nlines - 1);
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  /* Lines are similar, so still in region.  */
 | |
| 	  if (transition)
 | |
| 	    {
 | |
| 	      /* There was just a transition, so this is the start of a
 | |
| 	         new region. */
 | |
| 	      region++;
 | |
| 	      if (region >= NREGIONS)
 | |
| 		/* Too many regions detected.  Err below. */
 | |
| 		break;
 | |
| 	      transition = 0;
 | |
| 	      reglines[region] = 0;
 | |
| 	    }
 | |
| 	  /* Add oldline to the current region's average */
 | |
| 	  for (i = 0; i < width; i++)
 | |
| 	    avg[i + region * width] += oldline[i];
 | |
| 	  reglines[region]++;
 | |
| 	}
 | |
|       /* And newline becomes old */
 | |
|       memcpy (oldline, newline, width);
 | |
|     }
 | |
|   fclose (fp);
 | |
|   free (linebuf);
 | |
|   region++;			/* now call it number of regions instead of index */
 | |
|   DBG (11, "read %d lines as %d regions\n", nlines, region);
 | |
| 
 | |
|   /* Check to see if we screwed up */
 | |
|   if (region != NREGIONS)
 | |
|     {
 | |
|       DBG (1, "Warning: gain/offset compute failed.\n"
 | |
| 	   "Found %d regions instead of %d.\n", region, NREGIONS);
 | |
|       for (i = 0; i < region; i++)
 | |
| 	DBG (1, "   Region %d: %d lines\n",
 | |
| 	     i, (i >= NREGIONS) ? -1 : reglines[i]);
 | |
|       free (avg);
 | |
|       return SANE_STATUS_UNSUPPORTED;
 | |
|     }
 | |
| 
 | |
|   /* Now we've got regions and sums.  Find averages and range. */
 | |
|   max_range[0] = max_range[1] = max_range[2] = 0.0;
 | |
|   for (i = 0; i < width; i++)
 | |
|     {
 | |
|       /* Convert sums to averages */
 | |
|       /* black region */
 | |
|       tmp1 = avg[i] /= reglines[0];
 | |
|       /* white region */
 | |
|       tmp2 = avg[i + width] /= reglines[1];
 | |
|       /* Track largest range for each color.
 | |
|          If image is interleaved, use 'i%3', if not, 'i/(width/3)' */
 | |
|       if ((tmp2 - tmp1) > max_range[i / (width / 3)])
 | |
| 	{
 | |
| 	  max_range[i / (width / 3)] = tmp2 - tmp1;
 | |
| 	  DBG (14, "max %d@%d %f-%f=%f\n",
 | |
| 	       i / (width / 3), i, tmp2, tmp1, tmp2 - tmp1);
 | |
| 	}
 | |
|     }
 | |
|   DBG (13, "max range r %f\n", max_range[0]);
 | |
|   DBG (13, "max range g %f\n", max_range[1]);
 | |
|   DBG (13, "max range b %f\n", max_range[2]);
 | |
| 
 | |
|   /* Set umask to world r/w so other users can overwrite common cal... */
 | |
|   oldmask = umask (0);
 | |
|   fp = fopen (CAL_FILE_OGN, "w");
 | |
|   /* ... and set it back. */
 | |
|   umask (oldmask);
 | |
|   if (!fp)
 | |
|     {
 | |
|       DBG (1, "open " CAL_FILE_OGN);
 | |
|       free (avg);
 | |
|       return SANE_STATUS_IO_ERROR;
 | |
|     }
 | |
| 
 | |
|   /* Finally, compute offset and gain */
 | |
|   for (i = 0; i < width; i++)
 | |
|     {
 | |
|       int gain, offset;
 | |
|       byte ogn[2];
 | |
| 
 | |
|       /* skip line termination flags */
 | |
|       if (!((i + 1) % (width / 3)))
 | |
| 	{
 | |
| 	  DBG (13, "skip scanline EOL %d/%d\n", i, width);
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       /* Gain multiplier: 
 | |
|          255 : 1.5 times brighter
 | |
|          511 : 2 times brighter
 | |
|          1023: 3 times brighter */
 | |
| #if 1
 | |
|       /* Original gain/offset */
 | |
|       gain = 512 * ((max_range[i / (width / 3)] /
 | |
| 		     (avg[i + width] - avg[i])) - 1);
 | |
|       offset = avg[i];
 | |
| #else
 | |
|       /* This doesn't work for some people.  For instance, a negative
 | |
|          offset would be bad.  */
 | |
| 
 | |
|       /* Enhanced offset and gain calculation by M.Reinelt <reinelt@eunet.at>
 | |
|        * These expressions were found by an iterative calibration process, 
 | |
|        * by changing gain and offset values for every pixel until the desired
 | |
|        * values for black and white were reached, and finding an approximation 
 | |
|        * formula.
 | |
|        * Note that offset is linear, but gain isn't!
 | |
|        */
 | |
|       offset = (double)3.53 * avg[i] - 125;
 | |
|       gain = (double)3861.0 * exp(-0.0168 * (avg[i + width] - avg[i]));
 | |
| #endif
 | |
| 
 | |
|       DBG (14, "%d wht=%f blk=%f diff=%f gain=%d offset=%d\n", i,
 | |
| 	   avg[i + width], avg[i], avg[i + width] - avg[i], gain, offset);
 | |
|       /* 10-bit gain, 6-bit offset (subtractor) in two bytes */
 | |
|       ogn[0] = (byte) (((offset << 2) + (gain >> 8)) & 0xFF);
 | |
|       ogn[1] = (byte) (gain & 0xFF);
 | |
|       fwrite (ogn, sizeof (byte), 2, fp);
 | |
|       /* Annoyingly, we seem to use ogn data at 600dpi, while we scanned
 | |
|          at 300, so double our file.  Much easier than doubling at the
 | |
|          read. */
 | |
|       fwrite (ogn, sizeof (byte), 2, fp);
 | |
|     }
 | |
| 
 | |
|   fclose (fp);
 | |
|   free (avg);
 | |
|   return SANE_STATUS_GOOD;
 | |
| }
 | |
| 
 | |
| static int
 | |
| check_ogn_file (void)
 | |
| {
 | |
|   FILE *fp;
 | |
|   fp = fopen (CAL_FILE_OGN, "r");
 | |
|   if (fp)
 | |
|     {
 | |
|       fclose (fp);
 | |
|       return 1;
 | |
|     }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* Load or fake the offset/gain table */
 | |
| static void
 | |
| install_ogn (int fd)
 | |
| {
 | |
|   int temp;
 | |
|   byte *buf;
 | |
|   FILE *fp;
 | |
| 
 | |
|   /* 8.5in at 600dpi = 5104 pixels in scan head
 | |
|      10-bit gain + 6-bit offset = 2 bytes per pixel, so 10208 bytes */
 | |
|   buf = malloc (10208);
 | |
| 
 | |
|   fp = fopen (CAL_FILE_OGN, "r");
 | |
|   if (fp)
 | |
|     {
 | |
|       fread (buf, 2, 5100, fp);
 | |
|       /* screw the last 4 pixels */
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       /* Make up the gain/offset data. */
 | |
| #define GAIN 256		/* 1.5x */
 | |
| #define OFFSET 0
 | |
|       for (temp = 0; temp < 10208; temp += 2)
 | |
| 	{
 | |
| 	  buf[temp] = (byte) ((OFFSET << 2) + (GAIN >> 8));
 | |
| 	  buf[temp + 1] = (byte) (GAIN & 0xFF);
 | |
| 	}
 | |
|     }
 | |
|   /* Gain/offset table (r,g,b) */
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_R | DP_OFFSET);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_WRITE);
 | |
|   write_bulk (fd, DATAPORT, buf, 10208);
 | |
|   if (fp)
 | |
|     fread (buf, 2, 5100, fp);
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_G | DP_OFFSET);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_WRITE);
 | |
|   write_bulk (fd, DATAPORT, buf, 10208);
 | |
|   if (fp)
 | |
|     {
 | |
|       fread (buf, 2, 5100, fp);
 | |
|       fclose (fp);
 | |
|     }
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_B | DP_OFFSET);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_WRITE);
 | |
|   write_bulk (fd, DATAPORT, buf, 10208);
 | |
| 
 | |
|   free (buf);
 | |
|   return;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Scan sequence */
 | |
| /* resolution is 75,150,300,600,1200
 | |
|    scan coordinates in 600-dpi pixels */
 | |
| static SANE_Status
 | |
| scan (CANON_Handle * opt)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   const int left_edge = 0x004b;	/* Just for my scanner, or is this
 | |
| 				   universal?  Calibrate? */
 | |
|   int temp;
 | |
|   int fd = opt->fd;
 | |
|   byte result;
 | |
|   byte *buf;
 | |
| 
 | |
|   /* Check status. (not in w2k driver) */
 | |
|   read_byte (fd, STATUS, &result);	/* wants 2f or 2d */
 | |
|   if (!(result & STATUS_HOME) /*0x2d */ )
 | |
|     return SANE_STATUS_DEVICE_BUSY;
 | |
|   /* or force it to return? 
 | |
|      write_byte(fd, COMMAND, 0x02); 
 | |
|      wait_for_return(fd);
 | |
|    */
 | |
| 
 | |
|   /* reserved? */
 | |
|   read_byte (fd, 0x69, &result);	/* wants 0a */
 | |
| 
 | |
|   read_byte (fd, STATUS, &result);	/* wants 0e */
 | |
|   read_byte (fd, PAPER_SENSOR, &result);	/* wants 2b */
 | |
|   write_byte (fd, PAPER_SENSOR, 0x2b);
 | |
|   /* Color mode:
 | |
|      1-Channel Line Rate Color 0x15.
 | |
|      1-Channel Grayscale 0x14 (and we skip some of these tables) */
 | |
|   write_byte (fd, COLOR_MODE, 0x15);
 | |
| 
 | |
|   /* install the offset/gain table */
 | |
|   install_ogn (fd);
 | |
| 
 | |
|   read_byte (fd, STATUS, &result);	/* wants 0e */
 | |
|   /* move forward to "glass 0" */
 | |
|   write_byte (fd, COMMAND, 0x01);
 | |
|   read_byte (fd, STATUS, &result);	/* wants 0c */
 | |
| 
 | |
|   /* create gamma table */
 | |
|   buf = malloc (0x400);
 | |
|   for (temp = 0; temp < 0x0400; temp++)
 | |
|     /* gamma calculation by M.Reinelt <reinelt@eunet.at> */
 | |
|     buf[temp] = (double) 255.0 * exp(log((temp + 0.5) / 1023.0) / opt->gamma)
 | |
| 	+ 0.5;
 | |
| 
 | |
|   /* Gamma R, write and verify */
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_R | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_WRITE);
 | |
|   write_bulk (fd, DATAPORT, buf, 0x0400);
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_R | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_READ);
 | |
|   read_bulk (fd, DATAPORT, buf, 0x0400);
 | |
|   /* Gamma G */
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_G | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_WRITE);
 | |
|   write_bulk (fd, DATAPORT, buf, 0x0400);
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_G | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_READ);
 | |
|   read_bulk (fd, DATAPORT, buf, 0x0400);
 | |
|   /* Gamma B */
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_B | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_WRITE);
 | |
|   write_bulk (fd, DATAPORT, buf, 0x0400);
 | |
|   write_byte (fd, DATAPORT_TARGET, DP_B | DP_GAMMA);
 | |
|   write_word (fd, DATAPORT_ADDR, DP_READ);
 | |
|   read_bulk (fd, DATAPORT, buf, 0x0400);
 | |
|   free (buf);
 | |
| 
 | |
|   write_byte (fd, CLOCK_DIV, 0x04);
 | |
|   /* Resolution: dpi 75(ie) 100,150(1c) 200,300(1a) 600,1200(18) */
 | |
|   switch (opt->resolution)
 | |
|     {
 | |
|     case 150:
 | |
|       write_byte (fd, 0x09, 0x1c);
 | |
|       break;
 | |
|     case 300:
 | |
|       write_byte (fd, 0x09, 0x1a);
 | |
|       break;
 | |
|     case 600:
 | |
|     case 1200:
 | |
|       /* actually 600 dpi horiz max */
 | |
|       write_byte (fd, 0x09, 0x18);
 | |
|       break;
 | |
|     default:			/* 75 */
 | |
|       write_byte (fd, 0x09, 0x1e);
 | |
|       opt->resolution = 75;
 | |
|     }
 | |
| 
 | |
|   write_word (fd, ACTIVE_PX_START, left_edge);
 | |
|   /* Data pixel start.  Measured at 600dpi regardless of
 | |
|      scan resolution.  0-position is 0x004b */
 | |
|   write_word (fd, DATA_PX_START, left_edge + opt->x1);
 | |
|   /* Data pixel end.  Measured at 600dpi regardless of scan
 | |
|      resolution. */
 | |
|   write_word (fd, DATA_PX_END, left_edge + opt->x2);
 | |
|   /* greyscale has 14,03, different lights */
 | |
|   write_byte (fd, COLOR_MODE, 0x15);
 | |
|   write_byte (fd, 0x29, 0x02);
 | |
|   /* Lights */
 | |
|   write_word (fd, LAMP_R_ON, 0x0017);
 | |
|   /* "Hi-low color" selection from windows driver.  low(1437) hi(1481) */
 | |
|   write_word (fd, LAMP_R_OFF, 0x1437);
 | |
|   write_word (fd, LAMP_G_ON, 0x0017);
 | |
|   write_word (fd, LAMP_G_OFF, 0x094e);
 | |
|   write_word (fd, LAMP_B_ON, 0x0017);
 | |
|   write_word (fd, LAMP_B_OFF, 0x0543);
 | |
| 
 | |
|   /* Analog static offset R,G,B.  Greyscale has 0,0,0 */
 | |
|   write_byte (fd, 0x38, 0x3f);
 | |
|   write_byte (fd, 0x39, 0x3f);
 | |
|   write_byte (fd, 0x3a, 0x3f);
 | |
|   /* Analog static gain R,G,B (normally 0x01) */
 | |
|   write_byte (fd, 0x3b, opt->gain);
 | |
|   write_byte (fd, 0x3c, opt->gain);
 | |
|   write_byte (fd, 0x3d, opt->gain);
 | |
|   /* Digital gain/offset settings.  Greyscale has 0 */
 | |
|   write_byte (fd, 0x3e, 0x1a);
 | |
| 
 | |
|   {
 | |
|     /* Stepper motion setup. */
 | |
|     int stepsize, faststep = 0x0104, reverse = 0x28, phase, pwm = 0x1f;
 | |
|     switch (opt->resolution)
 | |
|       {
 | |
|       case 75:
 | |
| 	stepsize = 0x0106;
 | |
| 	faststep = 0x0106;
 | |
| 	reverse = 0;
 | |
| 	phase = 0x39a8;
 | |
| 	pwm = 0x3f;
 | |
| 	break;
 | |
|       case 150:
 | |
| 	stepsize = 0x020d;
 | |
| 	phase = 0x3198;
 | |
| 	break;
 | |
|       case 300:
 | |
| 	stepsize = 0x041a;
 | |
| 	phase = 0x2184;
 | |
| 	break;
 | |
|       case 600:
 | |
| 	stepsize = 0x0835;
 | |
| 	phase = 0x0074;
 | |
| 	break;
 | |
|       case 1200:
 | |
| 	/* 1200 dpi y only, x is 600 dpi */
 | |
| 	stepsize = 0x106b;
 | |
| 	phase = 0x41ac;
 | |
| 	break;
 | |
|       default:
 | |
| 	DBG (1, "BAD RESOLUTION");
 | |
| 	return SANE_STATUS_UNSUPPORTED;
 | |
|       }
 | |
| 
 | |
|     write_word (fd, STEP_SIZE, stepsize);
 | |
|     write_word (fd, FAST_STEP, faststep);
 | |
|     /* There sounds like a weird step disjoint at the end of skipsteps
 | |
|        at 75dpi, so I think that's why skipsteps=0 at 75dpi in the
 | |
|        Windows driver.  It still works at the normal 0x017a though. */
 | |
|     /* cal strip is 0x17a steps, plus 2 300dpi microsteps per pixel */
 | |
|     write_word (fd, SKIP_STEPS, 0x017a /* cal strip */  + opt->y1 * 2);
 | |
|     /* FIXME could be 0x57, why not? */
 | |
|     write_byte (fd, BUFFER_LIMIT, 0x20);
 | |
|     write_byte (fd, BUFFER_RESUME, 0x02);
 | |
|     write_byte (fd, REVERSE_STEPS, reverse);
 | |
|     /* motor resume phasing */
 | |
|     write_word (fd, 0x52, phase);
 | |
|     write_byte (fd, STEP_PWM, pwm);
 | |
|   }
 | |
| 
 | |
|   read_byte (fd, PAPER_SENSOR, &result);	/* wants 2b */
 | |
|   write_byte (fd, PAPER_SENSOR, 0x0b);
 | |
| 
 | |
|   opt->width = (opt->x2 - opt->x1) * opt->resolution / 600 + 1;
 | |
|   opt->height = (opt->y2 - opt->y1) * opt->resolution / 600;
 | |
|   opt->flags = 0;
 | |
|   DBG (1, "width=%d height=%d dpi=%d\n", opt->width, opt->height,
 | |
|        opt->resolution);
 | |
|   CHK (do_scan (opt));
 | |
| 
 | |
|   read_byte (fd, PAPER_SENSOR, &result);	/* wants 0b */
 | |
|   write_byte (fd, PAPER_SENSOR, 0x2b);
 | |
|   write_byte (fd, STEP_PWM, 0x3f);
 | |
| 
 | |
|   lights_out (fd);
 | |
|   /* home */
 | |
|   write_byte (fd, COMMAND, 0x02);
 | |
| 
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| static SANE_Status
 | |
| CANON_set_scan_parameters (CANON_Handle * scan,
 | |
| 			   const int forceCal,
 | |
| 			   const int gray,
 | |
| 			   const int left,
 | |
| 			   const int top,
 | |
| 			   const int right,
 | |
| 			   const int bottom, 
 | |
| 			   const int res, 
 | |
| 			   const int gain, 
 | |
| 			   const double gamma)
 | |
| {
 | |
|   DBG (2, "CANON_set_scan_parameters:\n");
 | |
|   DBG (2, "cal   = %d\n", forceCal);
 | |
|   DBG (2, "gray  = %d (ignored)\n", gray);
 | |
|   DBG (2, "res   = %d\n", res);
 | |
|   DBG (2, "gain  = %d\n", gain);
 | |
|   DBG (2, "gamma = %f\n", gamma);
 | |
|   DBG (2, "in 600dpi pixels:\n");
 | |
|   DBG (2, "left  = %d, top    = %d\n", left, top);
 | |
|   DBG (2, "right = %d, bottom = %d\n", right, bottom);
 | |
| 
 | |
|   /* Validate the input parameters */
 | |
|   if ((left < 0) || (right > CANON_MAX_WIDTH))
 | |
|     return SANE_STATUS_INVAL;
 | |
| 
 | |
|   if ((top < 0) || (bottom > CANON_MAX_HEIGHT))
 | |
|     return SANE_STATUS_INVAL;
 | |
| 
 | |
|   if (((right - left) < 10) || ((bottom - top) < 10))
 | |
|     return SANE_STATUS_INVAL;
 | |
| 
 | |
|   if ((res != 75) && (res != 150) && (res != 300)
 | |
|       && (res != 600) && (res != 1200))
 | |
|     return SANE_STATUS_INVAL;
 | |
| 
 | |
|   if ((gain < 0) || (gain > 64))
 | |
|     return SANE_STATUS_INVAL;
 | |
| 
 | |
|   if (gamma <= 0.0)
 | |
|     return SANE_STATUS_INVAL;
 | |
| 
 | |
|   /* Store params */
 | |
|   scan->resolution = res;
 | |
|   scan->x1 = left;
 | |
|   scan->x2 = right - /* subtract 1 pixel */ 600 / scan->resolution;
 | |
|   scan->y1 = top;
 | |
|   scan->y2 = bottom;
 | |
|   scan->gain = gain;
 | |
|   scan->gamma = gamma;
 | |
|   scan->flags = forceCal ? FLG_FORCE_CAL : 0;
 | |
| 
 | |
|   return SANE_STATUS_GOOD;
 | |
| }
 | |
| 
 | |
| 
 | |
| static SANE_Status
 | |
| CANON_close_device (CANON_Handle * scan)
 | |
| {
 | |
|   DBG (3, "CANON_close_device:\n");
 | |
|   sanei_usb_close (scan->fd);
 | |
|   return SANE_STATUS_GOOD;
 | |
| }
 | |
| 
 | |
| 
 | |
| static SANE_Status
 | |
| CANON_open_device (CANON_Handle * scan, const char *dev)
 | |
| {
 | |
|   SANE_Word vendor;
 | |
|   SANE_Word product;
 | |
|   SANE_Status res;
 | |
| 
 | |
|   DBG (3, "CANON_open_device: `%s'\n", dev);
 | |
| 
 | |
|   scan->fname = NULL;
 | |
|   scan->fp = NULL;
 | |
|   scan->flags = 0;
 | |
| 
 | |
|   res = sanei_usb_open (dev, &scan->fd);
 | |
|   if (res != SANE_STATUS_GOOD)
 | |
|     {
 | |
|       DBG (1, "CANON_open_device: couldn't open device `%s': %s\n", dev,
 | |
| 	   sane_strstatus (res));
 | |
|       return res;
 | |
|     }
 | |
| 
 | |
| #ifndef NO_AUTODETECT
 | |
|   /* We have opened the device. Check that it is a USB scanner. */
 | |
|   if (sanei_usb_get_vendor_product (scan->fd, &vendor, &product) !=
 | |
|       SANE_STATUS_GOOD)
 | |
|     {
 | |
|       DBG (1, "CANON_open_device: sanei_usb_get_vendor_product failed\n");
 | |
|       /* This is not a USB scanner, or SANE or the OS doesn't support it. */
 | |
|       sanei_usb_close (scan->fd);
 | |
|       scan->fd = -1;
 | |
|       return SANE_STATUS_UNSUPPORTED;
 | |
|     }
 | |
| 
 | |
|   /* Make sure we have a CANON scanner */
 | |
|   if ((vendor != 0x04a9) || (product != 0x2204))
 | |
|     {
 | |
|       DBG (1, "CANON_open_device: incorrect vendor/product (0x%x/0x%x)\n",
 | |
| 	   vendor, product);
 | |
|       sanei_usb_close (scan->fd);
 | |
|       scan->fd = -1;
 | |
|       return SANE_STATUS_UNSUPPORTED;
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|   return SANE_STATUS_GOOD;
 | |
| }
 | |
| 
 | |
| 
 | |
| static const char *
 | |
| CANON_get_device_name (CANON_Handle * scanner)
 | |
| {
 | |
|   scanner = scanner;		/* Eliminate warning about unused parameters */
 | |
|   return "Canoscan FB630U";
 | |
| }
 | |
| 
 | |
| 
 | |
| static SANE_Status
 | |
| CANON_finish_scan (CANON_Handle * scanner)
 | |
| {
 | |
|   DBG (3, "CANON_finish_scan:\n");
 | |
|   if (scanner->fp)
 | |
|     fclose (scanner->fp);
 | |
|   scanner->fp = NULL;
 | |
| 
 | |
|   /* remove temp file */
 | |
|   if (scanner->fname)
 | |
|     {
 | |
|       DBG (4, "removing temp file %s\n", scanner->fname);
 | |
|       unlink (scanner->fname);
 | |
|       free (scanner->fname);
 | |
|     }
 | |
|   scanner->fname = NULL;
 | |
| 
 | |
|   return SANE_STATUS_GOOD;
 | |
| }
 | |
| 
 | |
| 
 | |
| static SANE_Status
 | |
| CANON_start_scan (CANON_Handle * scanner)
 | |
| {
 | |
|   int rv;
 | |
|   SANE_Status status;
 | |
|   DBG (3, "CANON_start_scan called\n");
 | |
| 
 | |
|   /* choose a temp file name for scan data */
 | |
|   scanner->fname = strdup ("/tmp/scan.XXXXXX");
 | |
|   if (!mktemp (scanner->fname))
 | |
|     return SANE_STATUS_IO_ERROR;
 | |
| 
 | |
|   /* calibrate if needed */
 | |
|   rv = init (scanner->fd);
 | |
|   if (rv < 0) {
 | |
|       DBG(1, "Can't talk on USB.\n");
 | |
|       return SANE_STATUS_IO_ERROR;
 | |
|   }
 | |
|   if ((rv == 1)
 | |
|       || !check_ogn_file () 
 | |
|       || (scanner->flags & FLG_FORCE_CAL)) {
 | |
|       plugin_cal (scanner);
 | |
|       wait_for_return (scanner->fd);
 | |
|   }
 | |
|   
 | |
|   /* scan */
 | |
|   if ((status = scan (scanner)) != SANE_STATUS_GOOD)
 | |
|     {
 | |
|       CANON_finish_scan (scanner);
 | |
|       return status;
 | |
|     }
 | |
| 
 | |
|   /* read the temp file back out */
 | |
|   scanner->fp = fopen (scanner->fname, "r");
 | |
|   DBG (4, "reading %s\n", scanner->fname);
 | |
|   if (!scanner->fp)
 | |
|     {
 | |
|       DBG (1, "open %s", scanner->fname);
 | |
|       return SANE_STATUS_IO_ERROR;
 | |
|     }
 | |
|   return SANE_STATUS_GOOD;
 | |
| }
 | |
| 
 | |
| 
 | |
| static SANE_Status
 | |
| CANON_read (CANON_Handle * scanner, SANE_Byte * data,
 | |
| 	    SANE_Int max_length, SANE_Int * length)
 | |
| {
 | |
|   SANE_Status status;
 | |
|   int red_len;
 | |
| 
 | |
|   DBG (5, "CANON_read called\n");
 | |
|   if (!scanner->fp)
 | |
|     return SANE_STATUS_INVAL;
 | |
|   red_len = fread (data, 1, max_length, scanner->fp);
 | |
|   /* return some data */
 | |
|   if (red_len > 0)
 | |
|     {
 | |
|       *length = red_len;
 | |
|       DBG (5, "CANON_read returned (%d/%d)\n", *length, max_length);
 | |
|       return SANE_STATUS_GOOD;
 | |
|     }
 | |
| 
 | |
|   /* EOF or file err */
 | |
|   *length = 0;
 | |
|   if (feof (scanner->fp))
 | |
|     {
 | |
|       DBG (4, "EOF\n");
 | |
|       status = SANE_STATUS_EOF;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       DBG (4, "IO ERR\n");
 | |
|       status = SANE_STATUS_IO_ERROR;
 | |
|     }
 | |
| 
 | |
|   CANON_finish_scan (scanner);
 | |
|   DBG (5, "CANON_read returned (%d/%d)\n", *length, max_length);
 | |
|   return status;
 | |
| }
 |