/* epson.c - SANE library for Epson flatbed (and film) scanners. based on Kazuhiro Sasayama previous Work on epson.[ch] file from the SANE package. original code taken from sane-0.71 Copyright (C) 1997 Hypercore Software Design, Ltd. modifications (see detail below) --------------------------------- Copyright (C) 1998 Christian Bucher Copyright (C) 1998 Kling & Hautzinger GmbH Copyright (C) 1999 Clive Stubbings This file is part of the SANE package. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. As a special exception, the authors of SANE give permission for additional uses of the libraries contained in this release of SANE. The exception is that, if you link a SANE library with other files to produce an executable, this does not by itself cause the resulting executable to be covered by the GNU General Public License. Your use of that executable is in no way restricted on account of linking the SANE library code into it. This exception does not, however, invalidate any other reasons why the executable file might be covered by the GNU General Public License. If you submit changes to SANE to the maintainers to be included in a subsequent release, you agree by submitting the changes that those changes may be distributed with this exception intact. If you write modifications of your own for SANE, it is your choice whether to permit this exception to apply to your modifications. If you do not wish that, delete this exception notice. */ /* Style: K&R, 4 column hard tabs */ /* Changes March-Sept 1999, Clive Stubbings, http://www.vjet.demon.co.uk/scanner Support of Filmscan 200 scanner Support for multiple devices Lots of extra comments.... Oct 1999, Dave Hill dave _at_ minnie.demon.co.uk Better Support of GT7000 using Clive's Filmscan mods. 12/6/02 - better debug levels < 10 user stuff > 10 options and details > 20 i/o routines, status, internal sane stuff etc > 30 low level io status (scsi io routines) */ #ifdef _AIX # include /* MUST come first for AIX! */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "epson.h" #define BACKEND_NAME epson #include #include #define EPSON_CONFIG_FILE "epson.conf" #define DEFAULT_DEVNAME "/dev/scanner" #ifndef PATH_MAX # define PATH_MAX 1024 #endif #define TEST_UNIT_READY_COMMAND 0x00 #define READ_6_COMMAND 0x08 #define WRITE_6_COMMAND 0x0a #define INQUIRY_COMMAND 0x12 #define TYPE_PROCESSOR 0x03 #define walloc(x) ( x *) malloc( sizeof( x) ) #define walloca(x) ( x *) alloca( sizeof( x) ) /* * Epson ESC-I scanner control codes */ #define STX 0x02 #define ACK 0x06 #define NAK 0x15 #define CAN 0x18 #define ESC 0x1B #define EPSON_LEVEL_A1 0 #define EPSON_LEVEL_A2 1 #define EPSON_LEVEL_B1 2 #define EPSON_LEVEL_B2 3 #define EPSON_LEVEL_B3 4 #define EPSON_LEVEL_B4 5 #define EPSON_LEVEL_B5 6 #define EPSON_LEVEL_B6 7 #define EPSON_LEVEL_B7 8 /* GT7000 */ #define EPSON_LEVEL_F5 9 /* Default for filmscan */ #define EPSON_LEVEL_DEFAULT EPSON_LEVEL_B3 /* For flatbeds only */ typedef struct { char *level; int I:1 /* request identity */ ,F:1 /* request status */ ,S:1 /* request condition */ ,C:1 /* set color mode */ ,G:1 /* start scanning */ ,D:1 /* set data format */ ,R:1 /* set resolution */ ,H:1 /* set zoom */ ,A:1 /* set scan area */ ,L:1 /* set bright */ ,Z:1 /* set gamma */ ,B:1 /* set halftoning */ ,M:1 /* set color correction */ ,AT:1 /* initialize scanner */ ,g:1 /* set speed */ ,d:1, /* set lcount */ N:1, /* set filmtype */ Q:1, /* set sharpness */ K:1, /* set orientation */ T:1, /* Exposure Time */ m:1, /* Download colour correction */ z:1 /* Download gamma table */ ; } EpsonCmdRec; EpsonCmdRec epson_cmd[] = { /* request identity | request status | | request condition | | | set color mode | | | | start scanning | | | | | set data format | | | | | | set resolution set filmtype | | | | | | | set zoom | set sharpness | | | | | | | | set scan area | | set orientation | | | | | | | | | set brightness | | | exposure Time | | | | | | | | | | set gamma | | | | download colour corr'n | | | | | | | | | | | set halftoning | | | | | download gamma table | | | | | | | | | | | | set color correction | | | | | | | | | | | | | | | | initialize scanner| | | | | | | | | | | | | | | | | set speed | | | | | | | | | | | | | | | | | | | set lcount | | | | | | | | | | | | | | | | | | | | | | | | | */ {"A1", 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, {"A2", 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0}, {"B1", 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, {"B2", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0}, {"B3", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0}, {"B4", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0}, {"B5", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0}, {"B6", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0}, {"B7", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0}, {"F5", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} }; /* * This enum and the DefaultCmdTable must match.. */ enum { RESET= 0, SET_MODE, SET_FILMTYPE, SET_SPEED, SET_RESOLUTION, SET_GAMMA, SET_SHARPNESS, SET_ZOOM, SET_AREA, SET_DOWNLOAD_COLOUR, SET_DOWNLOAD_GAMMA, SET_ORIENTATION, SET_DEPTH, SET_HALFTONE, SET_COLOUR, SET_LCOUNT, SET_BRIGHTNESS, SET_BAY, SET_EXPOSURETIME, CMD_TABLE_SIZE }; /* * */ CmdEntry DefaultCmdTable[] = { { RESET, '@', "Reset/initialise",0, 0 }, { SET_MODE, 'C', "Set mode", 0, 1 }, { SET_FILMTYPE, 'N', "Set filmtype", 0, 1 }, { SET_SPEED, 'g', "Set speed", 0, 1 }, { SET_RESOLUTION, 'R', "Set resolution", 0, 4 }, { SET_GAMMA, 'Z', "Set gamma", 0, 1 }, { SET_SHARPNESS, 'Q', "Set sharpness", 0, 1 }, { SET_ZOOM, 'H', "Set zoom", 0, 2 }, { SET_AREA, 'A', "Set area", 0, 8 }, { SET_DOWNLOAD_COLOUR, 'm', "Download colour correction", 0, 9 }, { SET_DOWNLOAD_GAMMA, 'z', "Download gamma", 0, 257 }, { SET_ORIENTATION, 'K', "Set orientation", 0, 1 }, { SET_DEPTH, 'D', "Set depth", 0, 1 }, { SET_HALFTONE, 'B', "Set halftone", 0, 1 }, { SET_COLOUR, 'M', "Set colour corr.",0, 1 }, { SET_LCOUNT, 'd', "Set lcount", 0, 1 }, { SET_BRIGHTNESS, 'L', "Set brightness", 0, 1 }, { SET_BAY, 'P', "Set bay", 0, 2 }, { SET_EXPOSURETIME, 'T', "Set exposure-time",0, 4 } }; static unsigned char neg_tval[4] = {2, 0x8e, 0x86, 0x92}; static Epson_Scanner *handles = NULL; static Epson_Device *devices = NULL; static int num_devices = 0; /* * Structs for input data commands */ #pragma pack(1) typedef struct { u_char code; u_char status; u_short count; u_char buf[1]; } EpsonHdrRec, *EpsonHdr; typedef struct { u_char code; u_char status; u_short count; u_char type; u_char level; u_char buf[1]; } EpsonIdentRec, *EpsonIdent; #pragma pack() /* * FILMTYPE OPTIONS - Command 'N' */ static const SANE_String_Const filmtype_list[] = { "Positive Film", "Negative Film", "Positive Slide", "Negative Slide", NULL}; static int filmtype_values[] = { 0, 1, 2, 3 }; #define OPT_FILMTYPE_NEGATIVE 0x01 #define OPT_FILMTYPE_SLIDE 0x02 /* * Mode changes all sorts of stuff.. These are values that are set based * on the current mode.. mode takes values 0-2 (binary, mono, colour) */ struct mode_param { int color; int mode_flags; int dropout_mask; int depth; }; static const struct mode_param mode_params[] = { {0, 0x00, 0x30, 1}, /* Binary, 1 bit/pixel */ {0, 0x00, 0x30, 8}, /* Mono, grey scale (8bpp) */ {1, 0x02, 0x00, 8}, /* Colour */ }; static const SANE_String_Const mode_list[] = { "Binary", "Gray", "Color", NULL }; static const SANE_String_Const bay_list[] = { "Bay 1", "Bay 2", "Bay 3", "Bay 4", "Bay 5", "Bay 6", NULL }; static int dropout_params[] = { 0x00, 0x10, 0x20, 0x30, }; static const SANE_String_Const dropout_list[] = { "None", "Red", "Green", "Blue", NULL }; static int halftone_params[] = { 0x01, 0x00, 0x10, 0x20, 0x80, 0x90, 0xa0, 0xb0 }; static const SANE_String_Const halftone_list[] = { "None", "Halftone A", "Halftone B", "Halftone C", NULL }; static const SANE_String_Const halftone_list_4[] = { "None", "Halftone A", "Halftone B", "Halftone C", "Dither A", "Dither B", "Dither C", "Dither D", NULL }; static int brightness_params[] = {3, 2, 1, 0, -1, -2, -3}; static const SANE_String_Const brightness_list[] = { "Very light" ,"Lighter" ,"Light" ,"Normal" ,"Dark" ,"Darker" ,"Very dark" ,NULL }; /* * Sharpness - implemented on the FS200 */ static int sharpness_params[] = {2, 1, 0, -1, -2}; static const SANE_String_Const sharpness_list[] = { "Very Sharp", "Sharpened", "Normal", "Softened", "Very Soft", NULL }; /* * Gamma - implemented on the FS200 */ static int gamma_params[] = {1, 2, 0, 16, 32, 3}; static const SANE_String_Const gamma_list[] = { "CRT A (linear)", "CRT B (normal)", "Printer A", "Printer B", "Printer C", "Automatic (default)", NULL }; #define OPT_GAMMA_USER 5 #define GAMMAVAL 0.37 /* 2.6 ish for display */ /* #define GAMMAVAL 0.45 *//* 2.2 ish - the correct number really */ /* * For the FS200 the only acceptable values are: 0, 1, 16, 32, 64, 128 * They select the type of 3x3 colour matrix to apply to the data * during the scan */ static int colour_params[] = {128,0,32,64,16, 1}; static const SANE_String_Const colour_list[] = { "CRT","dot matrix","thermal transfer","ink jet","green/yellow","Automatic (default)", NULL }; #define OPT_COLOUR_USER 5 unsigned char colour_table[9] = {32,0,0, 0,32,0, 0,0,32}; unsigned char slide_colour_table[9] = {0x2b, 0x07, 0x8d, 0x84, 0x1f, 0x83, 0x86, 0x85, 0x31}; /* * Lookuptable for slide exposure time vs 'T' exposure value to use full * dynamic range of scanner */ unsigned char pos_exposure_correction[256] = { 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 154, 154, 154, 154, 154, 154, 153, 153, 153, 153, 153, 153, 152, 152, 152, 152, 152, 152, 151, 151, 151, 151, 151, 151, 150, 150, 150, 150, 150, 150, 149, 149, 149, 149, 149, 149, 148, 148, 148, 148, 148, 148, 147, 147, 147, 147, 147, 147, 146, 146, 146, 146, 146, 145, 145, 145, 145, 145, 145, 144, 144, 144, 144, 144, 144, 143, 143, 143, 143, 143, 143, 142, 142, 142, 142, 142, 142, 141, 141, 141, 141, 141, 141, 140, 140, 140, 140, 140, 140, 139, 139, 139, 139, 139, 139, 138, 138, 138, 138, 138, 138, 138, 138, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 133, 133, 133, 133, 133, 133, 133, 133, 133, 133, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }; #if 0 unsigned char neg_exposure_correction[256] = { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 132, 133, 133, 133, 133, 133, 133, 133, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 139, 139, 139, 139, 140, 140, 140, 140, 140, 140, 141, 141, 141, 141, 141, 141, 142, 142, 142, 142, 142, 142, 143, 143, 143, 143, 143, 143, 144, 144, 144, 144, 144, 144, 145, 145, 145, 145, 145, 145, 146, 146, 146, 146, 146, 147, 147, 147, 147, 147, 147, 148, 148, 148, 148, 148, 148, 149, 149, 149, 149, 149, 149, 150, 150, 150, 150, 150, 150, 151, 151, 151, 151, 151, 151, 152, 152, 152, 152, 152, 152, 153, 153, 153, 153, 153, 153, 154, 154, 154, 154, 154, 154, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, }; #endif SANE_Range zoom_range = {50, 200, 0}; /*SANE_Range zoom_range = {50, 200};*/ /* * Select image orientation ('K' command) */ static int orientation_params[] = {0,1,2,3}; static const SANE_String_Const orientation_list_f[] = { "Normal", "L<->R swapped", "Inverted", "Both", NULL }; /* For flatbed GT7000 */ static const SANE_String_Const orientation_list_b[] = { "Normal", "L<->R swapped", NULL }; /* * Internal decls */ static char *nstring(char *str, int len); static int scsi_write(int fd, const void *buf, size_t buf_size, SANE_Status * status); static int send(Epson_Scanner * s, const void *buf, size_t buf_size, SANE_Status * status); static int scsi_read(int fd, void *buf, size_t buf_size, SANE_Status * status); static int receive(Epson_Scanner * s, void *buf, size_t buf_size, SANE_Status * status); static SANE_Status inquiry(int fd, int page_code, void *buf, size_t * buf_size); static SANE_Status expect_ack(Epson_Scanner * s); static SANE_Status epsonopen(const char *dev_name, Epson_Scanner *s); static void epsonclose (Epson_Scanner * s); static void setgammatable(unsigned char *gammatable, int min, int max, float gammaval); /* * S E T U P C O M M A N D S -- Set up the scanner command table */ static void setupcommands(Epson_Device *hw, int type, int level) { CmdEntry *cmdtab; EpsonCmdRec *ref; int n; int matched; cmdtab = hw->cmd2; switch (type) { case 'B': ref = &epson_cmd[EPSON_LEVEL_DEFAULT]; break; case 'F': ref = &epson_cmd[EPSON_LEVEL_F5]; break; default: return; } matched = 0; for (n = 0; n < NELEMS(epson_cmd); n++) { if ((type == epson_cmd[n].level[0]) && (level == epson_cmd[n].level[1])) { ref = &epson_cmd[n]; matched = 1; break; } } if (! matched) DBG (1, "Unknown type %c or level %c, using %s\n", type, level, ref->level); hw->type = type; hw->level = ref->level[1] - '0'; DBG (2, "hw->level 0x%02x\n", hw->level); if (ref->AT) cmdtab[RESET].enabled = 1; if (ref->A) cmdtab[SET_AREA].enabled = 1; if (ref->B) cmdtab[SET_HALFTONE].enabled = 1; if (ref->C) cmdtab[SET_MODE].enabled = 1; if (ref->D) cmdtab[SET_DEPTH].enabled = 1; if (ref->H) cmdtab[SET_ZOOM].enabled = 1; if (ref->K) cmdtab[SET_ORIENTATION].enabled = 1; if (ref->L) cmdtab[SET_BRIGHTNESS].enabled = 1; if (ref->M) cmdtab[SET_COLOUR].enabled = 1; if (ref->N) cmdtab[SET_FILMTYPE].enabled = 1; if (ref->Q) cmdtab[SET_SHARPNESS].enabled = 1; if (ref->R) cmdtab[SET_RESOLUTION].enabled = 1; if (ref->Z) cmdtab[SET_GAMMA].enabled = 1; if (ref->d) cmdtab[SET_LCOUNT].enabled = 1; if (ref->g) cmdtab[SET_SPEED].enabled = 1; if (ref->T) cmdtab[SET_EXPOSURETIME].enabled = 1; if (ref->z) cmdtab[SET_DOWNLOAD_GAMMA].enabled = 1; if (ref->m) cmdtab[SET_DOWNLOAD_COLOUR].enabled = 1; if (hw->type == 'F') { cmdtab[SET_BAY].enabled = 1; } } /* * S I M P L E C O M M A N D -- Send an command with 1 or 2 * bytes of data * * Send: * Expect: * Send: [] * Expect: * * If the command is not enabled for this scanner configuration, just * return a 'good' status and do nothing. */ static SANE_Status simplecommand(int command, Epson_Scanner *s, int value, int value2) { SANE_Status status; unsigned char data[2]; CmdEntry *cmd; assert(command == s->hw->cmd2[command].command); cmd = &s->hw->cmd2[command]; if (cmd->enabled != 1) { DBG(2, "Command %s not enabled - ignoring.\n", cmd->name); return SANE_STATUS_GOOD; } DBG(2, "Command: %s \'%c\' %d %d\n", cmd->name, cmd->code, value, value2); data[0] = ESC; data[1] = cmd->code; send (s, data, 2, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) { DBG(2, "Command: %s \'%c\' - issue failed\n", cmd->name, cmd->code); return status; } data[0] = value; data[1] = value2; send (s, data, cmd->length, &status); if ((status = expect_ack (s)) != SANE_STATUS_GOOD) DBG(2, "Command: %s \'%c\' %d %d - data failed\n", cmd->name, cmd->code, value, value2); return status; } /* * I N P U T C O M M A N D -- Execute an input command, grab response and return in EpsonHdr struct * * Returns SANE_Status in *status * Returns NULL if failed or a malloc()'d EpsonHdr struct that must be * free()'d by caller */ static EpsonHdr inputcommand (Epson_Scanner * s, const u_char * cmd, size_t cmd_size, SANE_Status * status) { EpsonHdr head; u_char *buf; if ((head = walloc (EpsonHdrRec)) == NULL) { *status = SANE_STATUS_NO_MEM; return (EpsonHdr) 0; } send (s, cmd, cmd_size, status); if (*status != SANE_STATUS_GOOD) { DBG(16,"Send failed\n"); free(head); return (EpsonHdr) 0; } buf = (u_char *) head; if (s->hw->is_scsi) receive (s, buf, 4, status); else receive (s, buf, 1, status); if (SANE_STATUS_GOOD != *status) { free(head); return (EpsonHdr) 0; } buf++; DBG (14, "code %02x\n", (int) head->code); switch (head->code) { default: if (head->code == 0) DBG (1, "Incompatible printer port (probably bi/directional)\n"); else if (cmd[cmd_size - 1] == head->code) DBG (1, "Incompatible printer port (probably not bi/directional)\n"); DBG (2, "Illegal response of scanner for command: %02x\n", head->code); break; case NAK: DBG(3,"inputcommand - nak'd\n"); case ACK: head->count = 0; break; case STX: if (!s->hw->is_scsi) { receive (s, buf, 3, status); } if (SANE_STATUS_GOOD != *status) { free(head); return (EpsonHdr) 0; } DBG (14, "status %02x\n", (int) head->status); DBG (14, "count %d\n", (int) head->count); /* Only read rest of buffer if it exists! */ if (head->count > 0) { if (NULL == (head = realloc (head, sizeof (EpsonHdrRec) + head->count))) { *status = SANE_STATUS_NO_MEM; free(head); return (EpsonHdr) 0; } buf = head->buf; receive (s, buf, head->count, status); if (SANE_STATUS_GOOD != *status) { free(head); return (EpsonHdr) 0; } /* buf += head->count; */ /* sanei_hexdmp( head, sizeof( EpsonHdrRec) + head->count, "epson"); */ } break; } return head; } /* * S E T _ R E S O L U T I O N -- */ static SANE_Status set_resolution (Epson_Scanner * s, int xres, int yres) { SANE_Status status; unsigned char data[2], params[4]; CmdEntry *cmd; assert(s->hw->cmd2[SET_RESOLUTION].command == SET_RESOLUTION); cmd = &s->hw->cmd2[SET_RESOLUTION]; if (cmd->enabled != 1) return SANE_STATUS_GOOD; DBG(2, "Command: %s \'%c\' %d %d\n", cmd->name, cmd->code, xres, yres); data[0] = ESC; data[1] = cmd->code; send (s, data, 2, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) { DBG(2, "Command: %s \'%c\' - issue failed\n", cmd->name, cmd->code); return status; } params[0] = xres; params[1] = xres >> 8; params[2] = yres; params[3] = yres >> 8; send (s, params, 4, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) DBG(2, "Command: %s \'%c\' %d %d - data failed\n", cmd->name, cmd->code, xres, yres); return status; } /* * S E T _ A R E A -- */ static SANE_Status set_area (Epson_Scanner * s, int x, int y, int width, int height) { SANE_Status status; unsigned char data[2], params[8]; CmdEntry *cmd; assert(s->hw->cmd2[SET_AREA].command == SET_AREA); cmd = &s->hw->cmd2[SET_AREA]; if (cmd->enabled != 1) return SANE_STATUS_GOOD; DBG(2, "Command: %s \'%c\' %d %d %dx%d \n", cmd->name, cmd->code, x, y, width, height); data[0] = ESC; data[1] = cmd->code; send (s, data, 2, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) { DBG(2, "Command: %s \'%c\' - issue failed\n", cmd->name, cmd->code); return status; } params[0] = x; params[1] = x >> 8; params[2] = y; params[3] = y >> 8; params[4] = width; params[5] = width >> 8; params[6] = height; params[7] = height >> 8; send (s, params, 8, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) DBG(2, "Command: %s \'%c\' %d %d %dx%d - data failed\n", cmd->name, cmd->code, x, y, width, height); return status; } /* * S E T _ E X P O S U R E T I M E -- * */ static SANE_Status set_exposuretime(Epson_Scanner * s) { SANE_Status status; unsigned char data[2]; CmdEntry *cmd; int bay; unsigned char *tvalptr; static unsigned char default_tval[4] = {2, 0x80, 0x80, 0x80}; assert(s->hw->cmd2[SET_EXPOSURETIME].command == SET_EXPOSURETIME); cmd = &s->hw->cmd2[SET_EXPOSURETIME]; if (cmd->enabled != 1) return SANE_STATUS_GOOD; bay = s->val[OPT_BAY]; assert(bay < EPSON_MAX_BAY); if (filmtype_values[s->val[OPT_FILMTYPE]] & OPT_FILMTYPE_NEGATIVE) { tvalptr = neg_tval; } else if (s->bay[bay].calibrated) { tvalptr = s->bay[bay].tvaltable; } else tvalptr = default_tval; DBG(2, "Command: %s \'%c\' %d,%02x,%02x,%02x\n", cmd->name, cmd->code, tvalptr[0], tvalptr[1], tvalptr[2], tvalptr[3]); data[0] = ESC; data[1] = cmd->code; send (s, data, 2, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) { DBG(2, "Command: %s \'%c\' - issue failed\n", cmd->name, cmd->code); return status; } send (s, tvalptr, cmd->length, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) DBG(2, "Command: %s \'%c\' - data failed\n", cmd->name, cmd->code); return(status); } /* * S E T _ U S E R G A M M A -- * */ static SANE_Status set_usergamma(Epson_Scanner * s) { SANE_Status status; unsigned char data[2], params[257]; CmdEntry *cmd; int i, bay; assert(s->hw->cmd2[SET_DOWNLOAD_GAMMA].command == SET_DOWNLOAD_GAMMA); cmd = &s->hw->cmd2[SET_DOWNLOAD_GAMMA]; if (cmd->enabled != 1) return SANE_STATUS_GOOD; /* IF FILMSCAN */ bay = s->val[OPT_BAY]; /* If the bay is uncalibrated, set user gamma to linear... */ if (!s->bay[bay].calibrated) { if (filmtype_values[s->val[OPT_FILMTYPE]] & OPT_FILMTYPE_NEGATIVE) { for (i=0; i<256; i++) { s->bay[bay].gammatableR[255 - i] = i; s->bay[bay].gammatableG[255 - i] = i; s->bay[bay].gammatableB[255 - i] = i; } } else { for (i=0; i<256; i++) { s->bay[bay].gammatableR[i] = i; s->bay[bay].gammatableG[i] = i; s->bay[bay].gammatableB[i] = i; } } } DBG(2, "Command: %s \'%c\'\n", cmd->name, cmd->code); for (i=0; i < 3; i++) { data[0] = ESC; data[1] = cmd->code; send (s, data, 2, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) { DBG(2, "Command: %s \'%c\' - issue failed\n", cmd->name, cmd->code); return status; } switch (i) { case 0: params[0] = 'R'; memcpy(params+1, s->bay[bay].gammatableR, 256); break; case 1: params[0] = 'G'; memcpy(params+1, s->bay[bay].gammatableG, 256); break; case 2: params[0] = 'B'; memcpy(params+1, s->bay[bay].gammatableB, 256); break; } send (s, params, cmd->length, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) DBG(2, "Command: %s \'%c\' - data failed\n", cmd->name, cmd->code); } return status; } /* * S E T _ U S E R C O L O U R * */ static SANE_Status set_usercolour(Epson_Scanner * s, unsigned char *colourtable) { SANE_Status status; unsigned char data[2]; CmdEntry *cmd; assert(s->hw->cmd2[SET_DOWNLOAD_COLOUR].command == SET_DOWNLOAD_COLOUR); cmd = &s->hw->cmd2[SET_DOWNLOAD_COLOUR]; if (cmd->enabled != 1) return SANE_STATUS_GOOD; DBG(2, "Command: %s \'%c\'\n", cmd->name, cmd->code); data[0] = ESC; data[1] = cmd->code; send (s, data, 2, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) { DBG(2, "Command: %s \'%c\' - issue failed\n", cmd->name, cmd->code); return status; } send (s, colourtable, 9, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) DBG(2, "Command: %s \'%c\' - data failed\n", cmd->name, cmd->code); return status; } /* * R E S E T -- */ static SANE_Status reset(Epson_Scanner * s) { SANE_Status status; unsigned char data[2]; CmdEntry *cmd; assert(s->hw->cmd2[RESET].command == RESET); cmd = &s->hw->cmd2[RESET]; if (cmd->enabled != 1) return SANE_STATUS_GOOD; DBG(2, "Command: %s \'%c\'\n", cmd->name, cmd->code); data[0] = ESC; data[1] = cmd->code; send (s, data, 2, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) { DBG(2, "Command: %s \'%c\' - issue failed\n", cmd->name, cmd->code); } return status; } /* * E J E C T -- */ static SANE_Status eject(Epson_Scanner * s) { SANE_Status status; unsigned char data[2]; EpsonHdr scanner_status; int i; if ((status = epsonopen(s->hw->sane.name, s)) != SANE_STATUS_GOOD) return status; scanner_status = (EpsonHdr) inputcommand (s, "\033F", 2, &status); if (status == SANE_STATUS_GOOD) { int i; unsigned char *ptr; DBG(4, "esc-F status: %x %d\n", scanner_status->status, scanner_status->count); for (ptr = (char *) scanner_status, i=4+scanner_status->count; i; i--, ptr++) { DBG(4," %02x",*ptr); if (i%10 == 0) DBG(4,"\n"); } DBG(4,"\n"); } if (scanner_status) free(scanner_status); data[0] = 12; send (s, data, 1, &status); if ((status = expect_ack(s)) != SANE_STATUS_GOOD) { DBG(2, "Command: eject - issue failed\n"); } for (i=0; i<6; i++) s->bay[i].calibrated = 0; epsonclose (s); return status; } /* * C A L I B R A T E -- * */ static SANE_Status calibrate(Epson_Scanner * s) { unsigned char *image, *iptr, *gammatableR, *gammatableG, *gammatableB, *tvaltable; int phase, isize, readsize, i, tval, red, green, blue; int bay, filmtype; int saved_resolution, saved_gammamode, saved_colourmode; int red_min, blue_min, green_min, min; int red_max, blue_max, green_max, max; float luma; int luma_min, luma_max; saved_resolution = s->val[OPT_RESOLUTION]; s->val[OPT_RESOLUTION] = s->hw->dpi_range.min; saved_gammamode = s->val[OPT_GAMMA]; s->val[OPT_GAMMA] = OPT_GAMMA_USER; saved_colourmode = s->val[OPT_COLOUR]; s->val[OPT_COLOUR] = OPT_COLOUR_USER; bay = s->val[OPT_BAY]; assert(bay < EPSON_MAX_BAY); filmtype = filmtype_values[s->val[OPT_FILMTYPE]]; gammatableR = s->bay[bay].gammatableR; gammatableG = s->bay[bay].gammatableG; gammatableB = s->bay[bay].gammatableB; tvaltable = s->bay[bay].tvaltable; tvaltable[0] = 2; /* * Set linear gamma table while we work out a better one.. */ if (filmtype & OPT_FILMTYPE_NEGATIVE) { for (i=0; i<256; i++) { gammatableR[255 - i] = i; gammatableG[255 - i] = i; gammatableB[255 - i] = i; } } else { for (i=0; i<256; i++) { gammatableR[i] = i; gammatableG[i] = i; gammatableB[i] = i; } } s->bay[bay].calibrated = 1; /* indicate calibration so user tables are downloaded */ DBG(1,"Calibrate\n"); image = NULL; tval = 0x80; if (filmtype & OPT_FILMTYPE_NEGATIVE) phase = 1; /* Only do gamma calcs for negs */ else phase = 0; /* Do dynamic range stretch for slides. */ for ( ; phase <2; phase ++) { /* * Define INVESTIGATE to force it to try a range of tval values, rather than * doing a table lookup.. */ /* #define INVESTIGATE */ #ifdef INVESTIGATE for ( ; tval < 0xa0; tval +=2) { #endif tvaltable[1] = tvaltable[2] = tvaltable[3] = tval; if (filmtype & OPT_FILMTYPE_NEGATIVE) { tvaltable[1] = neg_tval[1]; tvaltable[2] = neg_tval[2]; tvaltable[3] = neg_tval[3]; } DBG(1,"Calibrate: %x\n",tval); sane_start(s); isize = s->params.pixels_per_line * s->params.lines * 3; if (!image) image = (unsigned char *) malloc(isize); iptr = image; while (sane_read(s, iptr, isize - (iptr - image), &readsize) == SANE_STATUS_GOOD) { DBG(1,"Calibrate: read %d, %d to go\n", readsize, isize - (iptr - image)); iptr += readsize; } iptr = image; red = green = blue = 0; red_max = green_max = blue_max = 0; red_min = green_min = blue_min = 255; luma_min = 255; luma_max = 0; for (i=0; i red_max) red_max = *iptr; if (*iptr < red_min) red_min = *iptr; luma = (float)*iptr * 0.299; red += *iptr++; if (*iptr > green_max) green_max = *iptr; if (*iptr < green_min) green_min = *iptr; luma += (float)*iptr * 0.587; green += *iptr++; if (*iptr > blue_max) blue_max = *iptr; if (*iptr < blue_min) blue_min = *iptr; luma += (float)*iptr * 0.114; blue += *iptr++; if (luma > luma_max) luma_max = luma; if (luma < luma_min) luma_min = luma; } DBG(1,"Calibrate: average %3d %3d %3d\n", red/(isize/3), green/(isize/3), blue/(isize/3)); DBG(1,"Calibrate: min %3d %3d %3d\n", red_min, green_min, blue_min); DBG(1,"Calibrate: max %3d %3d %3d\n", red_max, green_max, blue_max); DBG(1,"Calibrate: LUMA: min %d max %d\n\n", luma_min, luma_max); max = red_max; if (green_max > max) max = green_max; if (blue_max > max) max = blue_max; min = red_min; if (green_min < min) min = green_min; if (blue_min < min) min = blue_min; #ifdef INVESTIGATE if (phase != 0) break; if (max >= 255) { if (tval > 0x80) tval -= 1; tvaltable[1] = tvaltable[2] = tvaltable[3] = tval; break; } } #else if (phase == 0) { tval = pos_exposure_correction[max]; tvaltable[1] = tvaltable[2] = tvaltable[3] = tval; } #endif if (phase == 1) { DBG(2,"Calibrate: Setting gamma table..\n"); /* * If Neg film/slide set gamma curves (RGB) and manage the * 'inverted' data */ if (filmtype_values[s->val[OPT_FILMTYPE]] & OPT_FILMTYPE_NEGATIVE) { /* * We set 255-min/max because the gamma correction will be applied * to the (pre) inverted image in the scanner, where the active * range of the image needs to be measured from 255 down * * -GAMMAVAL gets setgammatable() to generate an inverting gamma.. * so we don't have to invert the image over here.. */ setgammatable(gammatableR, 255-max, 255-min, -GAMMAVAL); setgammatable(gammatableG, 255-max, 255-min, -GAMMAVAL); setgammatable(gammatableB, 255-max, 255-min, -GAMMAVAL); } /* * Pos film/slide - simple gamma */ else { setgammatable(gammatableR, min, max, GAMMAVAL); setgammatable(gammatableG, min, max, GAMMAVAL); setgammatable(gammatableB, min, max, GAMMAVAL); } } } if (image) free(image); /* * Restore all the real settings.. */ s->val[OPT_RESOLUTION] = saved_resolution; s->val[OPT_GAMMA] = saved_gammamode; s->val[OPT_COLOUR] = saved_colourmode; return(SANE_STATUS_GOOD); } /* * E P S O N O P E N -- */ static SANE_Status epsonopen(const char *dev_name, Epson_Scanner *s) { SANE_Status status; DBG(2, "epsonopen: Open %s %s\n", s->hw->is_scsi ? "scsi" : "pio", dev_name); if (s->hw->is_scsi) { if ((status = sanei_scsi_open (dev_name, &s->fd, NULL, NULL)) != SANE_STATUS_GOOD) { DBG (1, "epsonopen: open failed: %s\n", sane_strstatus(status)); } } else { if ((status = sanei_pio_open (dev_name, &s->fd)) != SANE_STATUS_GOOD) { DBG (1, "epsonopen: %s: can't open %s as a parallel-port device\n", sane_strstatus (status), dev_name); } } return(status); } /* * E P S O N C L O S E -- */ static void epsonclose (Epson_Scanner * s) { DBG(2, "epsonclose: Close device\n"); assert(s->fd != -1); if (s->hw->is_scsi) sanei_scsi_close (s->fd); else sanei_pio_close (s->fd); s->fd = -1; return; } /* * A T T A C H -- * */ static SANE_Status attach (const char *dev_name, Epson_Device * *devp) { SANE_Status status; Epson_Device epson_dev, *dev; Epson_Scanner s; EpsonIdent ident; unsigned char buf[36], *ptr; size_t buf_size; char *str, *end; int max_x, max_y, n, k; if (devp) *devp = 0; /* * make sure we have not already see this device */ for (dev = devices; dev; dev = dev->next) { if (strcmp (dev->sane.name, dev_name) == 0) { if (devp) *devp = dev; DBG(1, "attach: duplicate device %s\n", dev_name); return SANE_STATUS_GOOD; } } DBG(1, "attach: opening %s\n", dev_name); memset(&epson_dev, 0, sizeof(epson_dev)); memset(&s, 0, sizeof(s)); s.hw = &epson_dev; /* epson_dev.cmd = &epson_cmd[EPSON_LEVEL_DEFAULT]; */ epson_dev.cmd2 = malloc(sizeof(CmdEntry) * CMD_TABLE_SIZE); memcpy(epson_dev.cmd2, DefaultCmdTable, sizeof(CmdEntry) * CMD_TABLE_SIZE); strtol(dev_name, &end, 0); if ((end == dev_name) || *end) { epson_dev.is_scsi = 1; DBG (3, "attach: is_scsi is %s\n", epson_dev.is_scsi?"true":"false"); } else { epson_dev.is_scsi = 0; } if ((status = epsonopen(dev_name, &s)) != SANE_STATUS_GOOD) { return status; } if (epson_dev.is_scsi) { DBG (3, "attach: sending INQUIRY\n"); buf_size = sizeof buf; /* * Do basic check that we have an EPSON scanner before we do anything * more dangerous.. ;-) */ if ((status = inquiry (s.fd, 0, buf, &buf_size)) != SANE_STATUS_GOOD) { DBG (1, "attach: inquiry failed: %s\n", sane_strstatus (status)); epsonclose (&s); return status; } if (buf[0] == TYPE_PROCESSOR && (strncmp(buf + 8, "EPSON", 5) == 0)) { if ((strncmp (buf + 16, "SCANNER ", 8) == 0) || (strncmp (buf + 14, "SCANNER ", 8) == 0)) { DBG (3, "attach: conventional scanner\n"); epson_dev.sane.type = "Flatbed Scanner"; } else if (strncmp (buf + 16, "FilmScan 200", 12) == 0) { DBG (3, "attach: Filmscan\n"); epson_dev.sane.type = "Film Scanner"; } else { DBG (1, "attach: device doesn't look like an Epson scanner\n"); DBG (2, "attach: device is unknown product: \"%s\"\n",nstring(buf+16,16)); epsonclose (&s); return SANE_STATUS_INVAL; } str = nstring(buf+16,16); epson_dev.sane.model = malloc(strlen(str)+1); strcpy((char *)epson_dev.sane.model, str); } else { DBG (1, "attach: device doesn't look like an Epson scanner\n"); DBG (2, "attach: inquiry returned Processor: %s Vendor: \"%s\"\n",(buf[0] == TYPE_PROCESSOR)?"Yes":"No",nstring(buf+8,8)); epsonclose (&s); return SANE_STATUS_INVAL; } } else { epson_dev.sane.type = "Scanner"; } /* * We now believe we really have an epson scanner there to poke at.. so * lets investigate it a bit more.. */ ident = (EpsonIdent) inputcommand (&s, "\033I", 2, &status); if (ident == NULL) { DBG (0, "ident failed\n"); epsonclose(&s); if (epson_dev.sane.model) free((char *)epson_dev.sane.model); return status; } DBG (2, "type %3c 0x%02x\n", ident->type, ident->type); DBG (2, "level %3c 0x%02x\n", ident->level, ident->level); /* * Ident * ===== * Type B - Flat bed scanners GT5000/5500 * Type F - FilmScaners FS200 */ ptr = ident->buf; setupcommands(&epson_dev, ident->type, ident->level); /* * Since it takes 3 bytes to store 1 resolution vector, the max possible count * for resolution entries is count/3 - it makes a safe guess for calloc size ;-) */ epson_dev.res_list_size = 0; epson_dev.res_list = (SANE_Int *) calloc (ident->count/3, sizeof (SANE_Int)); if (epson_dev.res_list == NULL) { DBG (0, "attach: resolution list calloc(%d) failed - no mem\n", ident->count/3); exit (0); } epson_dev.dpi_range.quant = 0; epson_dev.dpi_range.min = 0x7fff; epson_dev.dpi_range.max = 0; max_x = max_y = 0; for (n = ident->count; n; n -= k, ptr += k) { switch (*ptr) { case 'R': { int val = ptr[2] << 8 | ptr[1]; epson_dev.res_list[epson_dev.res_list_size] = (SANE_Int) val; epson_dev.res_list_size++; DBG (3, "resolution (dpi): %d\n", val); if (val < epson_dev.dpi_range.min) epson_dev.dpi_range.min = val; if (val > epson_dev.dpi_range.max) epson_dev.dpi_range.max = val; k = 3; continue; } case 'A': { max_x = ptr[2] << 8 | ptr[1]; max_y = ptr[4] << 8 | ptr[3]; DBG (3, "maximum scan area: %dx%d\n", max_x, max_y); k = 5; continue; } default: break; } break; } if (epson_dev.dpi_range.min == 0x7fff) epson_dev.dpi_range.min = 0; DBG(2, "min dpi: %d, max dpi: %d\n",epson_dev.dpi_range.min,epson_dev.dpi_range.max); if (max_x) { epson_dev.x_range.min = 0; epson_dev.x_range.max = SANE_FIX (max_x * 25.4 / epson_dev.dpi_range.max); epson_dev.x_range.quant = 0; } if (max_y) { epson_dev.y_range.min = 0; epson_dev.y_range.max = SANE_FIX (max_y * 25.4 / epson_dev.dpi_range.max); epson_dev.y_range.quant = 0; } free(ident); /* * Set up remaining SANE identification info */ epson_dev.sane.name = malloc(strlen(dev_name) + 1); strcpy ((char *)epson_dev.sane.name, dev_name); epson_dev.sane.vendor = "Epson"; /* * Now make a real copy of the data to keep.... */ if ((dev = malloc (sizeof (*dev))) == NULL) { return SANE_STATUS_NO_MEM; if (epson_dev.sane.model) free((char *)epson_dev.sane.model); } memcpy(dev, &epson_dev, sizeof(epson_dev)); num_devices++; dev->next = devices; devices = dev; if (devp) *devp = dev; { EpsonHdr scanner_status; scanner_status = (EpsonHdr) inputcommand (&s, "\033F", 2, &status); if (status == SANE_STATUS_GOOD) { int i; unsigned char *ptr; DBG(4, "esc-F status: %x %d\n", scanner_status->status, scanner_status->count); for (ptr = (char *) scanner_status, i=4+scanner_status->count; i; i--, ptr++) { DBG(4," %02x",*ptr); if (i%10 == 0) DBG(4,"\n"); } DBG(4,"\n"); } free(scanner_status); scanner_status = (EpsonHdr) inputcommand (&s, "\033f", 2, &status); if (status == SANE_STATUS_GOOD) { int i; unsigned char *ptr; DBG(4, "esc-f status: %x %d\n", scanner_status->status, scanner_status->count); for (ptr = (char *) scanner_status, i=4+scanner_status->count; i; i--, ptr++) { DBG(4," %02x",*ptr); if (i%10 == 0) DBG(4,"\n"); } DBG(4,"\n"); } free(scanner_status); scanner_status = (EpsonHdr) inputcommand (&s, "\033S", 2, &status); if (status == SANE_STATUS_GOOD) { int i; unsigned char *ptr; DBG(1, "esc-S status: %x %d\n", scanner_status->status, scanner_status->count); for (ptr = (char *) scanner_status, i=4+scanner_status->count; i; i--, ptr++) { DBG(4," %02x",*ptr); if (i%10 == 0) DBG(4,"\n"); } DBG(4,"\n"); } free(scanner_status); } epsonclose(&s); return SANE_STATUS_GOOD; } /* * A T T A C H _ O N E -- * */ static SANE_Status attach_one (const char *dev) { attach (dev, 0); return SANE_STATUS_GOOD; } /********************************************************************/ /* * S A N E _ I N I T * */ SANE_Status sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize) { char line[PATH_MAX], *lptr; int len; FILE *fp; char *env; int e1, e2, e3; DBG_INIT(); #if defined PACKAGE && defined VERSION DBG (2, "sane_init: " PACKAGE " " VERSION "\n"); #endif if (version_code != NULL) *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, 0); /* * Look for a config file */ if ((fp = sanei_config_open (EPSON_CONFIG_FILE))) { while (fgets (line, sizeof (line), fp)) { if (line[0] == '#') /* ignore line comments */ continue; lptr = line; len = strlen (line); if (line[len - 1] == '\n') line[--len] = '\0'; while (*lptr && isspace(*lptr)) lptr++; if (!*lptr) continue; /* ignore empty lines */ DBG (1, "sane_init \"%s\"\n", lptr); /* * In case our config line needs processing. eg its like: * * scsi VENDOR MODEL TYPE BUS CHANNEL ID LUN * * then sort it out then call 'attach one' */ sanei_config_attach_matching_devices (lptr, attach_one); } fclose (fp); /* * default to DEFAULT_DEVNAME instead of insisting on config file */ } else { sanei_config_attach_matching_devices (DEFAULT_DEVNAME, attach_one); } /* * Look to see if there is a negative exposure correction in the env.. */ if ((env = getenv("EPSON_NEGATIVE_CORRECTION"))) { DBG(2, "Got environ neg correction \"%s\"\n", env); sscanf(env, "%d %d %d", &e1, &e2, &e3); neg_tval[1] = e1; neg_tval[2] = e2; neg_tval[3] = e3; } return SANE_STATUS_GOOD; } /* * S A N E _ E X I T -- * */ void sane_exit (void) { Epson_Device *dev, *next; DBG(1, "sane_exit:\n"); /* * Clean up open handles... */ while (handles) sane_close(handles); handles = NULL; /* * Clean up devices */ for (dev = devices; dev; dev = next) { next = dev->next; if (dev->sane.model) free ((char *) dev->sane.model); if (dev->sane.name) free ((char *) dev->sane.name); if (dev->res_list) free(dev->res_list); free (dev); } devices = NULL; num_devices = 0; } /* * S A N E _ G E T _ D E V I C E S -- * */ SANE_Status sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only) { static const SANE_Device **devlist = 0; Epson_Device *dev; int i; if (devlist) free (devlist); devlist = malloc ((num_devices + 1) * sizeof (devlist[0])); if (!devlist) return SANE_STATUS_NO_MEM; i = 0; for (dev = devices; i < num_devices; dev = dev->next) devlist[i++] = &dev->sane; devlist[i++] = 0; *device_list = devlist; return SANE_STATUS_GOOD; } /* * I N I T _ O P T I O N S -- * */ static SANE_Status init_options (Epson_Scanner * s) { int i; DBG(10, "init_options\n"); for (i = 0; i < NUM_OPTIONS; ++i) { s->opt[i].size = sizeof (SANE_Word); s->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; } 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; s->val[OPT_NUM_OPTS] = NUM_OPTIONS; /* "Scan Mode" group: */ s->opt[OPT_MODE_GROUP].title = "Scan Mode"; s->opt[OPT_MODE_GROUP].desc = ""; s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP; s->opt[OPT_MODE_GROUP].cap = 0; /* filmtype */ s->opt[OPT_FILMTYPE].name = "filmtype"; s->opt[OPT_FILMTYPE].title = "Film Type"; s->opt[OPT_FILMTYPE].desc = "Selects the filmtype."; s->opt[OPT_FILMTYPE].type = SANE_TYPE_STRING; s->opt[OPT_FILMTYPE].size = 32; s->opt[OPT_FILMTYPE].cap |= SANE_CAP_ADVANCED; s->opt[OPT_FILMTYPE].constraint_type = SANE_CONSTRAINT_STRING_LIST; s->opt[OPT_FILMTYPE].constraint.string_list = filmtype_list; s->val[OPT_FILMTYPE] = OPT_FILMTYPE_NEGATIVE; /* Normal */ if (!s->hw->cmd2[SET_FILMTYPE].enabled) s->opt[OPT_FILMTYPE].cap |= SANE_CAP_INACTIVE; /* eject */ s->opt[OPT_EJECT].name = "eject"; s->opt[OPT_EJECT].title = "Eject film tray"; s->opt[OPT_EJECT].desc = "Ejects the film tray"; s->opt[OPT_EJECT].type = SANE_TYPE_BUTTON; s->opt[OPT_EJECT].cap |= SANE_CAP_ADVANCED; if (s->hw->type != 'F') s->opt[OPT_EJECT].cap |= SANE_CAP_INACTIVE; /* calibrate */ s->opt[OPT_CALIBRATE].name = "calibrate"; s->opt[OPT_CALIBRATE].title = "calibrate"; s->opt[OPT_CALIBRATE].desc = "calibrate"; s->opt[OPT_CALIBRATE].type = SANE_TYPE_BUTTON; s->opt[OPT_CALIBRATE].cap |= SANE_CAP_ADVANCED; if (s->hw->type != 'F') s->opt[OPT_CALIBRATE].cap |= SANE_CAP_INACTIVE; /* select bay */ s->opt[OPT_BAY].name = "Bay"; s->opt[OPT_BAY].title = "Select bay to scan"; s->opt[OPT_BAY].desc = "select bay to scan"; s->opt[OPT_BAY].type = SANE_TYPE_STRING; s->opt[OPT_BAY].size = 32; s->opt[OPT_BAY].constraint_type = SANE_CONSTRAINT_STRING_LIST; s->opt[OPT_BAY].constraint.string_list = bay_list; s->val[OPT_BAY] = 0; /* Bay 1 */ if (!s->hw->cmd2[SET_BAY].enabled) s->opt[OPT_BAY].cap |= SANE_CAP_INACTIVE; /* scan mode */ s->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE; s->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE; s->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE; s->opt[OPT_MODE].type = SANE_TYPE_STRING; s->opt[OPT_MODE].size = 32; s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; s->opt[OPT_MODE].constraint.string_list = mode_list; s->val[OPT_MODE] = 0; /* Binary */ /* halftone */ s->opt[OPT_HALFTONE].name = SANE_NAME_HALFTONE; s->opt[OPT_HALFTONE].title = SANE_TITLE_HALFTONE; s->opt[OPT_HALFTONE].desc = "Selects the halftone."; s->opt[OPT_HALFTONE].type = SANE_TYPE_STRING; s->opt[OPT_HALFTONE].size = 32; s->opt[OPT_HALFTONE].constraint_type = SANE_CONSTRAINT_STRING_LIST; if (s->hw->level >= 4) s->opt[OPT_HALFTONE].constraint.string_list = halftone_list_4; else s->opt[OPT_HALFTONE].constraint.string_list = halftone_list; s->val[OPT_HALFTONE] = 1; /* Halftone A */ if ((!s->hw->cmd2[SET_HALFTONE].enabled) || (mode_params[s->val[OPT_MODE]].depth != 1)) s->opt[OPT_HALFTONE].cap |= SANE_CAP_INACTIVE; /* dropout */ s->opt[OPT_DROPOUT].name = "dropout"; s->opt[OPT_DROPOUT].title = "Dropout"; s->opt[OPT_DROPOUT].desc = "Selects the dropout."; s->opt[OPT_DROPOUT].type = SANE_TYPE_STRING; s->opt[OPT_DROPOUT].size = 32; s->opt[OPT_DROPOUT].cap |= SANE_CAP_ADVANCED; s->opt[OPT_DROPOUT].constraint_type = SANE_CONSTRAINT_STRING_LIST; s->opt[OPT_DROPOUT].constraint.string_list = dropout_list; s->val[OPT_DROPOUT] = 0; /* None */ if ((s->hw->type == 'F') || (mode_params[s->val[OPT_MODE]].color)) s->opt[OPT_DROPOUT].cap |= SANE_CAP_INACTIVE; /* brightness */ if (!s->hw->cmd2[SET_BRIGHTNESS].enabled) s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE; s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS; s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS; s->opt[OPT_BRIGHTNESS].desc = "Selects the brightness."; s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_STRING; s->opt[OPT_BRIGHTNESS].size = 32; s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_ADVANCED; s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_STRING_LIST; s->opt[OPT_BRIGHTNESS].constraint.string_list = brightness_list; s->val[OPT_BRIGHTNESS] = 3; /* Normal */ /* sharpness */ if (!s->hw->cmd2[SET_SHARPNESS].enabled) s->opt[OPT_SHARPNESS].cap |= SANE_CAP_INACTIVE; s->opt[OPT_SHARPNESS].name = "Sharpness"; s->opt[OPT_SHARPNESS].title = "Sharpness"; s->opt[OPT_SHARPNESS].desc = "Selects the sharpness."; s->opt[OPT_SHARPNESS].type = SANE_TYPE_STRING; s->opt[OPT_SHARPNESS].size = 32; s->opt[OPT_SHARPNESS].cap |= SANE_CAP_ADVANCED; s->opt[OPT_SHARPNESS].constraint_type = SANE_CONSTRAINT_STRING_LIST; s->opt[OPT_SHARPNESS].constraint.string_list = sharpness_list; s->val[OPT_SHARPNESS] = 2; /* Normal */ /* resolution */ s->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION; s->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION; s->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION; s->opt[OPT_RESOLUTION].type = SANE_TYPE_INT; s->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI; s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE; s->opt[OPT_RESOLUTION].constraint.range = &s->hw->dpi_range; s->val[OPT_RESOLUTION] = s->hw->dpi_range.min; /* gamma */ if (s->hw->type != 'F') s->opt[OPT_GAMMA].cap |= SANE_CAP_INACTIVE; s->opt[OPT_GAMMA].name = "gamma"; s->opt[OPT_GAMMA].title = "Gamma"; s->opt[OPT_GAMMA].desc = "Adjust scanner gamma correction"; s->opt[OPT_GAMMA].type = SANE_TYPE_STRING; s->opt[OPT_GAMMA].size = 32; s->opt[OPT_GAMMA].cap |= SANE_CAP_ADVANCED; s->opt[OPT_GAMMA].constraint_type = SANE_CONSTRAINT_STRING_LIST; s->opt[OPT_GAMMA].constraint.string_list = gamma_list; s->val[OPT_GAMMA] = OPT_GAMMA_USER; /* Automatic */ /* colour */ if (s->hw->type != 'F') s->opt[OPT_COLOUR].cap |= SANE_CAP_INACTIVE; s->opt[OPT_COLOUR].name = "colour"; s->opt[OPT_COLOUR].title = "colour"; s->opt[OPT_COLOUR].desc = "colour"; s->opt[OPT_COLOUR].type = SANE_TYPE_STRING; s->opt[OPT_COLOUR].size = 32; s->opt[OPT_COLOUR].cap |= SANE_CAP_ADVANCED; s->opt[OPT_COLOUR].constraint_type = SANE_CONSTRAINT_STRING_LIST; s->opt[OPT_COLOUR].constraint.string_list = colour_list; s->val[OPT_COLOUR] = OPT_COLOUR_USER; /* Automatic */ /* zoom */ if (!s->hw->cmd2[SET_ZOOM].enabled) s->opt[OPT_ZOOM].cap |= SANE_CAP_INACTIVE; s->opt[OPT_ZOOM].name = "zoom"; s->opt[OPT_ZOOM].title = "Zoom"; s->opt[OPT_ZOOM].desc = "Zoom part of the image"; s->opt[OPT_ZOOM].type = SANE_TYPE_INT; s->opt[OPT_ZOOM].unit = SANE_UNIT_PERCENT; s->opt[OPT_ZOOM].constraint_type = SANE_CONSTRAINT_RANGE; s->opt[OPT_ZOOM].constraint.range = &zoom_range; s->val[OPT_ZOOM] = 100; /* "Geometry" group: */ s->opt[OPT_GEOMETRY_GROUP].title = "Geometry"; s->opt[OPT_GEOMETRY_GROUP].desc = ""; s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP; s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED; /* top-left x */ s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X; s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X; s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X; s->opt[OPT_TL_X].type = SANE_TYPE_FIXED; s->opt[OPT_TL_X].unit = SANE_UNIT_MM; s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE; s->opt[OPT_TL_X].constraint.range = &s->hw->x_range; s->val[OPT_TL_X] = 0; /* top-left y */ s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y; s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y; s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y; s->opt[OPT_TL_Y].type = SANE_TYPE_FIXED; s->opt[OPT_TL_Y].unit = SANE_UNIT_MM; s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE; s->opt[OPT_TL_Y].constraint.range = &s->hw->y_range; s->val[OPT_TL_Y] = 0; /* bottom-right x */ s->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X; s->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X; s->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X; s->opt[OPT_BR_X].type = SANE_TYPE_FIXED; s->opt[OPT_BR_X].unit = SANE_UNIT_MM; s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE; s->opt[OPT_BR_X].constraint.range = &s->hw->x_range; s->val[OPT_BR_X] = s->hw->x_range.max; /* bottom-right y */ s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y; s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y; s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y; s->opt[OPT_BR_Y].type = SANE_TYPE_FIXED; s->opt[OPT_BR_Y].unit = SANE_UNIT_MM; s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE; s->opt[OPT_BR_Y].constraint.range = &s->hw->y_range; s->val[OPT_BR_Y] = s->hw->y_range.max; /* orientation */ if (!s->hw->cmd2[SET_ORIENTATION].enabled) s->opt[OPT_ORIENTATION].cap |= SANE_CAP_INACTIVE; s->opt[OPT_ORIENTATION].name = "Orientation"; s->opt[OPT_ORIENTATION].title = "Orientation"; s->opt[OPT_ORIENTATION].desc = "Selects the orientation."; s->opt[OPT_ORIENTATION].type = SANE_TYPE_STRING; s->opt[OPT_ORIENTATION].size = 32; s->opt[OPT_ORIENTATION].cap |= SANE_CAP_ADVANCED; s->opt[OPT_ORIENTATION].constraint_type = SANE_CONSTRAINT_STRING_LIST; if (s->hw->type == 'F') s->opt[OPT_ORIENTATION].constraint.string_list = orientation_list_f; else s->opt[OPT_ORIENTATION].constraint.string_list = orientation_list_b; s->val[OPT_ORIENTATION] = 0; /* Normal */ return SANE_STATUS_GOOD; } /* * S A N E _ O P E N -- * */ SANE_Status sane_open (SANE_String_Const devicename, SANE_Handle * handle) { Epson_Device *dev; Epson_Scanner *s; SANE_Status status; if (*devicename) { for (dev = devices; dev; dev = dev->next) if (strcmp (dev->sane.name, devicename) == 0) break; if (!dev) { status = attach (devicename, &dev); if (status != SANE_STATUS_GOOD) return status; } } else /* empty devicename -> use first device */ dev = devices; if (!dev) return SANE_STATUS_INVAL; if ((s = calloc (1, sizeof (Epson_Scanner))) == NULL) return SANE_STATUS_NO_MEM; s->fd = -1; s->hw = dev; s->needinit = 1; init_options(s); /* * insert newly opened handle into list of open handles: */ s->next = handles; handles = s; *handle = (SANE_Handle) s; return SANE_STATUS_GOOD; } /* * S A N E _ C L O S E -- * */ void sane_close (SANE_Handle handle) { Epson_Scanner *s, *prev; /* * find and remove handle from list of open handles: */ prev = 0; for (s = handles; s; s = s->next) { if (s == handle) break; prev = s; } if (s) { DBG(1,"sane_close %s\n", s->hw->sane.name); if (prev) prev->next = s->next; else handles = s->next; if (s->buf) free (s->buf); if (s->fd != -1) epsonclose(s); free(handle); } else DBG(1,"sane_close: bad handle!\n"); } /* * S A N E _ G E T _ O P T I O N _ D E S C R I P T O R -- * * Return pointer to the option descriptor for option 'option' of scanner 'handle' * */ const SANE_Option_Descriptor * sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) { Epson_Scanner *s = (Epson_Scanner *) handle; DBG(25, "sane_get_option_descriptor %d\n", option); if (option < 0 || option >= NUM_OPTIONS) return NULL; return s->opt + option; } /* */ static const SANE_String_Const * search_string_list (const SANE_String_Const * list, SANE_String value) { while (*list != NULL && strcmp (value, *list) != 0) ++list; if (*list == NULL) return NULL; return list; } /* * S A N E _ C O N T R O L _ O P T I O N -- * */ SANE_Status sane_control_option (SANE_Handle handle, SANE_Int option, SANE_Action action, void *value, SANE_Int * info) { Epson_Scanner *s = (Epson_Scanner *) handle; SANE_Status status; const SANE_String_Const *optptr; int optval; DBG(25, "sane_control_option, action %d, option %d\n", action, option); if (option < 0 || option >= NUM_OPTIONS) return SANE_STATUS_INVAL; if (info != NULL) *info = 0; switch (action) { /* * Get the current value of an option */ case SANE_ACTION_GET_VALUE: switch (option) { /* * Simple SANE_TYPE_INT options * (SANE_CONSTRAINT_RANGE, SANE_CONSTRAINT_WORD_LIST) */ case OPT_NUM_OPTS: case OPT_RESOLUTION: case OPT_TL_X: case OPT_TL_Y: case OPT_BR_X: case OPT_BR_Y: case OPT_ZOOM: *(SANE_Word *) value = s->val[option]; break; /* * Simple SANE_TYPE_STRING options * (SANE_CONSTRAINT_STRING_LIST) */ case OPT_MODE: case OPT_BAY: case OPT_HALFTONE: case OPT_DROPOUT: case OPT_BRIGHTNESS: case OPT_SHARPNESS: case OPT_FILMTYPE: case OPT_GAMMA: case OPT_ORIENTATION: case OPT_COLOUR: strcpy ((char *) value, s->opt[option].constraint.string_list[s->val[option]]); break; /* * Anything else - reject */ default: return SANE_STATUS_INVAL; } break; /* * Set an option to supplied value */ case SANE_ACTION_SET_VALUE: /* * First lets make sure the value we want make sense */ status = sanei_constrain_value (s->opt + option, value, info); if (status != SANE_STATUS_GOOD) return status; /* * If its a string list then do a lookup to get the index into * the list (we were passed a pointer to the string value) * quit if we can't find it.. */ optptr = NULL; optval = 0; if (s->opt[option].constraint_type == SANE_CONSTRAINT_STRING_LIST) { optptr = search_string_list (s->opt[option].constraint.string_list, (char *) value); if (optptr == NULL) return SANE_STATUS_INVAL; optval = optptr - s->opt[option].constraint.string_list; DBG(1, "Opt %s, val %d\n", *optptr, optval); } /* * Deal with each option in turn, doing any special processing * as we go */ switch (option) { case OPT_EJECT: eject(s); break; case OPT_CALIBRATE: calibrate(s); break; /* * Resolution - look for nearest matching resolution */ case OPT_RESOLUTION: { int n, old; int min_d = s->hw->res_list[s->hw->res_list_size - 1]; int v = *(SANE_Word *) value; int best = v; old = s->val[option]; for (n = 0; n < s->hw->res_list_size; n++) { int d = abs (v - s->hw->res_list[n]); if (d < min_d) { min_d = d; best = s->hw->res_list[n]; } } s->val[option] = (SANE_Word) best; if (min_d != 0) { DBG(2, "resolution was %d requested %d set to %d\n",old,v,best); if (info) *info |= SANE_INFO_INEXACT; } break; } case OPT_TL_X: case OPT_TL_Y: case OPT_BR_X: case OPT_BR_Y: if (info != NULL) *info |= SANE_INFO_RELOAD_PARAMS; /* fall through */ case OPT_ZOOM: s->val[option] = *(SANE_Word *) value; break; /* * Mode - setting mode enables or disables a number of * other options... */ case OPT_MODE: if ((s->hw->type == 'F') || (mode_params[optval].depth != 1)) s->opt[OPT_HALFTONE].cap |= SANE_CAP_INACTIVE; else s->opt[OPT_HALFTONE].cap &= ~SANE_CAP_INACTIVE; if ((s->hw->type == 'F') || mode_params[optval].color) s->opt[OPT_DROPOUT].cap |= SANE_CAP_INACTIVE; else s->opt[OPT_DROPOUT].cap &= ~SANE_CAP_INACTIVE; if (info) { *info |= SANE_INFO_RELOAD_OPTIONS; *info |= SANE_INFO_RELOAD_PARAMS; } /* fall through */ case OPT_HALFTONE: case OPT_DROPOUT: case OPT_BRIGHTNESS: case OPT_FILMTYPE: case OPT_SHARPNESS: case OPT_GAMMA: case OPT_COLOUR: case OPT_BAY: case OPT_ORIENTATION: if (!(s->opt[option].cap & SANE_CAP_INACTIVE)) s->val[option] = optval; break; default: return SANE_STATUS_INVAL; } break; default: return SANE_STATUS_INVAL; } return SANE_STATUS_GOOD; } /* * S A N E _ G E T _ P A R A M E T E R S -- */ SANE_Status sane_get_parameters (SANE_Handle handle, SANE_Parameters * params) { Epson_Scanner *s = (Epson_Scanner *) handle; int ndpi; DBG(25, "sane_get_parameters()\n"); memset (&s->params, 0, sizeof (SANE_Parameters)); ndpi = s->val[OPT_RESOLUTION]; s->params.pixels_per_line = (SANE_UNFIX(s->val[OPT_BR_X] - s->val[OPT_TL_X]) * s->val[OPT_ZOOM]) /100 / 25.4 * ndpi; s->params.lines = (SANE_UNFIX(s->val[OPT_BR_Y] - s->val[OPT_TL_Y]) * s->val[OPT_ZOOM]) / 100 / 25.4 * ndpi; /* pixels_per_line seems to be 8 * n. */ s->params.pixels_per_line = s->params.pixels_per_line & ~7; s->params.last_frame = SANE_TRUE; s->params.depth = mode_params[s->val[OPT_MODE]].depth; if (mode_params[s->val[OPT_MODE]].color) { s->params.format = SANE_FRAME_RGB; s->params.bytes_per_line = 3 * s->params.pixels_per_line; } else { s->params.format = SANE_FRAME_GRAY; s->params.bytes_per_line = s->params.pixels_per_line * s->params.depth / 8; } if (params != NULL) *params = s->params; return SANE_STATUS_GOOD; } /* * Debug stuff */ static int red_min, red_max; static int green_min, green_max; static int blue_min, blue_max; /* * S A N E _ S T A R T -- Setup and start a scan * * Note, no scan data is read here - thats done in subsequent calls to * sane_read() * */ SANE_Status sane_start (SANE_Handle handle) { Epson_Scanner *s = (Epson_Scanner *) handle; SANE_Status status; const struct mode_param *mparam; int ndpi; int left, top; int lcount; red_min = 255; red_max = 0; green_min = 255; green_max = 0; blue_min = 255; blue_max = 0; /* * First ensure all the s->params values are updated.. */ status = sane_get_parameters (handle, NULL); if (status != SANE_STATUS_GOOD) return status; /* * Open the physical device. */ if ((status = epsonopen(s->hw->sane.name, s)) != SANE_STATUS_GOOD) return status; /* * INIT * * Send an init to the scanner if needed. * It wastes a lot of time on the filmscan moving the slide around if * you send a reset every scan.. */ if (s->needinit) { reset(s); if (s->hw->type == 'F') s->needinit = 0; } /* FILMTYPE */ if ((status = simplecommand(SET_FILMTYPE, s, filmtype_values[s->val[OPT_FILMTYPE]],0)) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_filmtype failed: %s\n", sane_strstatus(status)); return status; } /* #define LINEMODE */ /* MODE */ mparam = mode_params + s->val[OPT_MODE]; /* * For scanners > level 5 and when in colour mode set mode to * be 0x13 otherwise take base bits from mode_params based on bitdepth * and add in dropout mask bits */ #ifdef LINEMODE status = simplecommand(SET_MODE, s, 0x12, 0); #else if (s->hw->level >= 5 && mparam->mode_flags == 0x02) { status = simplecommand(SET_MODE, s, 0x13, 0); } else status = simplecommand(SET_MODE, s, mparam->mode_flags | (mparam->dropout_mask & dropout_params[s->val[OPT_DROPOUT]]),0); #endif if (status != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_mode failed: %s\n", sane_strstatus (status)); return status; } /* DEPTH (number of bits/pixel) */ if ((status = simplecommand(SET_DEPTH, s, s->params.depth, 0)) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_depth failed: %s\n", sane_strstatus (status)); return status; } /* EXPOSURE TIME */ if ((status = set_exposuretime(s)) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_exposuretime failed: %s\n", sane_strstatus (status)); return status; } /* GAMMA */ if ((status = set_usergamma(s)) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_usegamma failed: %s\n", sane_strstatus (status)); return status; } if (s->opt[OPT_GAMMA].cap & SANE_CAP_INACTIVE) status = simplecommand(SET_GAMMA, s, s->params.depth == 1 ? 1 : 2, 0); else status = simplecommand(SET_GAMMA, s, gamma_params[s->val[OPT_GAMMA]], 0); if (status != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_gamma failed: %s\n", sane_strstatus (status)); return status; } /* COLOUR */ if (filmtype_values[s->val[OPT_FILMTYPE]] & OPT_FILMTYPE_NEGATIVE) status = set_usercolour(s, colour_table); else status = set_usercolour(s, slide_colour_table); if (status != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_usercolour failed: %s\n", sane_strstatus (status)); return status; } if (s->opt[OPT_COLOUR].cap & SANE_CAP_INACTIVE) status = simplecommand(SET_COLOUR, s, 0x80, 0); else status = simplecommand(SET_COLOUR, s, colour_params[s->val[OPT_COLOUR]], 0); if (status != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_colour failed: %s\n", sane_strstatus (status)); return status; } /* HALFTONE */ if ((status = simplecommand(SET_HALFTONE, s, halftone_params[s->val[OPT_HALFTONE]], 0)) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_halftone failed: %s\n", sane_strstatus (status)); return status; } /* BRIGHTNESS */ status = simplecommand(SET_BRIGHTNESS, s, brightness_params[s->val[OPT_BRIGHTNESS]], 0); if (status != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_brightness failed: %s\n", sane_strstatus (status)); return status; } /* SHARPNESS */ if ((status = simplecommand(SET_SHARPNESS, s, sharpness_params[s->val[OPT_SHARPNESS]], 0)) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_sharpness failed: %s\n", sane_strstatus(status)); return status; } /* ZOOM */ if ((status = simplecommand(SET_ZOOM, s, s->val[OPT_ZOOM], s->val[OPT_ZOOM])) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_zoom failed: %s\n", sane_strstatus(status)); return status; } /* ORIENTATION */ if ((status = simplecommand(SET_ORIENTATION, s, orientation_params[s->val[OPT_ORIENTATION]], 0)) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_orientation failed: %s\n", sane_strstatus(status)); return status; } /* SPEED */ if ((status = simplecommand(SET_SPEED, s, mode_params[s->val[OPT_MODE]].depth == 1 ? 1 : 0, 0)) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_speed failed: %s\n", sane_strstatus (status)); return status; } /* RESOLUTION */ ndpi = s->val[OPT_RESOLUTION]; if ((status = set_resolution (s, ndpi, ndpi)) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_resolution failed: %s\n", sane_strstatus (status)); return status; } /* SCAN AREA * * Need to adjust for zoom and resolution. Calculate the (left, top) position * as integers based on the SANE_Fixed point TL_X,TL_Y values. * The width and height values have already been calculated in * sane_get_parameters() and stored in * s->params as the frontend needed them to calc memory requirements etc.. */ left = (SANE_UNFIX(s->val[OPT_TL_X]) * s->val[OPT_ZOOM]) / 100 / 25.4 * ndpi + 0.5; top = (SANE_UNFIX(s->val[OPT_TL_Y]) * s->val[OPT_ZOOM]) / 100 / 25.4 * ndpi + 0.5; status = set_area (s, left, top, s->params.pixels_per_line, s->params.lines); if (status != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_area failed: %s\n", sane_strstatus(status)); return status; } /* LCOUNT * * It is possible to get some scanners to block lines up and send them * in chunks. Take care under Linux. The maximum size request for the * /dev/sg? devices is set in the driver. Default is 4096. It can be * overriden by SG_BIG_BUFF (probably in /usr/include/scsi/sg.h) but * the kernel (or module) needs to have been compiled with it. * man sane-scsi for more information */ s->block = SANE_FALSE; lcount = 1; /* * For level 5 scanners, or level 4 when not colour we can use lcount * I don't understand why set_lcount is enabled for A1-3 and B1-3 * scanners in epson_cmd[] above... */ if (s->hw->level >= 5 || (s->hw->level == 4 && !mode_params[s->val[OPT_MODE]].color)) { s->block = SANE_TRUE; lcount = sanei_scsi_max_request_size / s->params.bytes_per_line; #ifdef LINEMODE lcount = (lcount /3) * 3; if (!lcount) lcount =1; #endif #if 0 /* * This is a useful bodge to test to see if you think you may * have a BIG_BUFF problem.. Don't use 4096 - this includes * command overhead etc.. */ if (s->params.bytes_per_line > 4000) lcount = 1; else lcount = 4000 / s->params.bytes_per_line; #endif /* Lcount is stored in a single unsigned byte... */ if (lcount > 255) lcount = 255; if (lcount == 0) return SANE_STATUS_NO_MEM; if ((status = simplecommand(SET_LCOUNT, s, lcount, 0)) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_lcount failed: %s\n", sane_strstatus (status)); return status; } } /* BAY */ if ((status = simplecommand(SET_BAY, s, s->val[OPT_BAY]+1,s->val[OPT_BAY]+1)) != SANE_STATUS_GOOD) { DBG (1, "sane_start: set_bay failed: %s\n", sane_strstatus(status)); return status; } /* START SCAN */ DBG(2, "sane_start:\n"); send (s, "\033G", 2, &status); if (status != SANE_STATUS_GOOD) { DBG (1, "sane_start: start failed: %s\n", sane_strstatus (status)); return status; } /* * Set up the buffers ready to grab the data. The actual data will * be read in a series of calls from the frontend to sane_read().. */ s->eof = SANE_FALSE; s->buf = realloc (s->buf, lcount * s->params.bytes_per_line); s->ptr = s->end = s->buf; s->canceling = SANE_FALSE; return SANE_STATUS_GOOD; } /* * S A N E _ R E A D * */ SANE_Status sane_read (SANE_Handle handle, SANE_Byte * data, SANE_Int max_length, SANE_Int * length) { Epson_Scanner *s = (Epson_Scanner *) handle; SANE_Status status; SANE_Byte *dptr; unsigned char result[6]; /* *ptr; */ size_t len, buf_len; int totalread, i; DBG (25, "sane_read: beginn\n"); /* * Check to see if there is data stashed in our buffers that the app * hasn't had yet. If not read in another buffer load. */ if (s->ptr == s->end) { /* * Seen end.. clean up buffers and close the device */ if (s->eof) { free (s->buf); s->buf = NULL; DBG(25, "sane_read: scan complete\n"); if ((s->hw->type == 'F') && (filmtype_values[s->val[OPT_FILMTYPE]] & OPT_FILMTYPE_NEGATIVE)) { reset(s); s->needinit = 0; } epsonclose (s); *length = 0; return SANE_STATUS_EOF; } /* * Otherwise there is more to do! */ DBG (25, "sane_read: begin scan\n"); /* * Work out how much data is coming in this transfer */ len = s->block ? 6 : 4; receive (s, result, len, &status); buf_len = result[3] << 8 | result[2]; if (s->block) buf_len *= (result[5] << 8 | result[4]); DBG(25,"sane_read: %d %d len: %d\n",result[0],result[1],buf_len); /* * Length of zero means there is no more to come * Ack it and return an INVAL error */ if (buf_len == 0) { if (!(result[1] && 0x20)) /* Area end flag - don't send ack.. */ send (s, "\006", 1, &status); DBG(2, "sane_read: zero buffer, ack status: %s, block mode %d, flags 0x%x\n",sane_strstatus(status), s->block, (unsigned)result[1]); { EpsonHdr scanner_status; scanner_status = (EpsonHdr) inputcommand (s, "\033F", 2, &status); if (status == SANE_STATUS_GOOD) { int i; unsigned char *ptr; DBG(4, "esc-F status: %x %d\n", scanner_status->status, scanner_status->count); for (ptr = (char *) scanner_status, i=4+scanner_status->count; i; i--, ptr++) { DBG(4," %02x",*ptr); if (i%10 == 0) DBG(4,"\n"); } DBG(4,"\n"); } free(scanner_status); scanner_status = (EpsonHdr) inputcommand (s, "\033f", 2, &status); if (status == SANE_STATUS_GOOD) { int i; unsigned char *ptr; DBG(4, "esc-f status: %x %d\n", scanner_status->status, scanner_status->count); for (ptr = (char *) scanner_status, i=4+scanner_status->count; i; i--, ptr++) { DBG(4," %02x",*ptr); if (i%10 == 0) DBG(4,"\n"); } DBG(4,"\n"); } free(scanner_status); } return SANE_STATUS_INVAL; } /* * If no 'block' mode and in RGB mode we will need to grab 3 separate blocks of data * and send an ACK for each */ if (!s->block && SANE_FRAME_RGB == s->params.format) { receive (s, s->buf + s->params.pixels_per_line, buf_len, &status); send (s, "\006", 1, &status); len = s->block ? 6 : 4; receive (s, result, len, &status); buf_len = result[3] << 8 | result[2]; if (s->block) buf_len *= (result[5] << 8 | result[4]); DBG (25, "sane_read: buf len2 = %lu\n", (u_long) buf_len); receive (s, s->buf, buf_len, &status); send (s, "\006", 1, &status); len = s->block ? 6 : 4; receive (s, result, len, &status); buf_len = result[3] << 8 | result[2]; if (s->block) buf_len *= (result[5] << 8 | result[4]); DBG (25, "sane_read: buf len3 = %lu\n", (u_long) buf_len); receive (s, s->buf + 2 * s->params.pixels_per_line, buf_len, &status); totalread = 3 * s->params.pixels_per_line; } else { /* Otherwise its easy.. just grab one lot.. */ receive (s, s->buf, buf_len, &status); totalread = buf_len;; } #if 0 /* No longer needed - don in gamma correction 24/2/2000 */ /* * If we are working with a film scanner and have a * negative image we need to invert it.. */ if ((status == SANE_STATUS_GOOD) && (s->hw->type == 'F') && (filmtype_values[s->val[OPT_FILMTYPE]] & OPT_FILMTYPE_NEGATIVE)) { ptr = s->buf; if (s->params.depth == 8) { DBG(3, "Inverting grey/colour image (%d bytes)\n", totalread); for (i=0; iparams.depth == 1) { DBG(2, "Inverting binary image\n"); for (i=0; ieof = SANE_TRUE; DBG (25, "sane_read: scan complete\n"); } else { if (s->canceling) { send (s, "\030", 1, &status); /* Send 'CAN' */ expect_ack (s); free (s->buf); s->buf = NULL; DBG (25, "sane_read: scan cancel\n"); epsonclose (s); *length = 0; return SANE_STATUS_CANCELLED; } else { send (s, "\006", 1, &status); DBG (25, "sane_read: scan more\n"); } } s->end = s->buf + buf_len; s->ptr = s->buf; } else DBG(25, "sane_read: data left in old buffer\n"); /* * Copy the read data to the user buffer, making any rearrangements * necessary. Copy up to the amount asked for (max_length) but no more.. * Leave any remnamts in our buffer for next time. */ if (!s->block && s->params.format == SANE_FRAME_RGB) { max_length /= 3; if (max_length > s->end - s->ptr) max_length = s->end - s->ptr; *length = 3 * max_length; while (max_length-- != 0) { if (*data > red_max) red_max = *data; if (*data < red_min) red_min = *data; *data++ = s->ptr[0]; *data++ = s->ptr[s->params.pixels_per_line]; *data++ = s->ptr[2 * s->params.pixels_per_line]; ++s->ptr; } } else { if (max_length > (s->end - s->ptr)) max_length = s->end - s->ptr; *length = max_length; /* The amount we actually will copy */ /* One bit deep image */ if (s->params.depth == 1) { while (max_length-- != 0) *data++ = ~*s->ptr++; } else { /* Eight bit deep image */ #ifdef LINEMODE int i, j; DBG(1,"COPYING.. %d %d %d\n", max_length, max_length / (3 * s->params.pixels_per_line), s->params.pixels_per_line); *length = (max_length / (3 * s->params.pixels_per_line)) * (3 * s->params.pixels_per_line); for (j=0; j< max_length / (3 * s->params.pixels_per_line); j++) { for (i=0; iparams.pixels_per_line; i++) { *data++ = s->ptr[0]; *data++ = s->ptr[s->params.pixels_per_line]; *data++ = s->ptr[2 * s->params.pixels_per_line]; ++s->ptr; } s->ptr += 2 * s->params.pixels_per_line; } #else memcpy (data, s->ptr, max_length); /* Stash range of values coming from scanner */ if (mode_params[s->val[OPT_MODE]].color) { for (i=0, dptr = data; i red_max) red_max = *dptr; if (*dptr < red_min) red_min = *dptr; dptr++; if (*dptr > green_max) green_max = *dptr; if (*dptr < green_min) green_min = *dptr; dptr++; if (*dptr > blue_max) blue_max = *dptr; if (*dptr < blue_min) blue_min = *dptr; dptr++; } } s->ptr += max_length; #endif } } DBG (25, "sane_read: end\n"); if (s->eof) { printf("SCAN END: RED %3d %3d\n", red_min, red_max); printf("SCAN END: GRN %3d %3d\n", green_min, green_max); printf("SCAN END: BLU %3d %3d\n", blue_min, blue_max); } return SANE_STATUS_GOOD; } void sane_cancel (SANE_Handle handle) { Epson_Scanner *s = (Epson_Scanner *) handle; if (s->buf != NULL) s->canceling = SANE_TRUE; } SANE_Status sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking) { return SANE_STATUS_UNSUPPORTED; } SANE_Status sane_get_select_fd (SANE_Handle handle, SANE_Int * fd) { return SANE_STATUS_UNSUPPORTED; } /********************************************************************** * LOW LEVEL INTERNAL STUFF */ /* * N S T R I N G -- Extract 'N' chars from a string and terminate * * Result left in internal static buffer */ static char * nstring(char *str, int len) { static char buf[100]; if (len >= sizeof(buf)) len = sizeof(buf) - 1; strncpy(buf, str, len); *(buf+len) = '\0'; return(buf); } /* * S E T G A M M A T A B L E -- * */ static void setgammatable(unsigned char *gammatable, int min, int max, float gammaval) { int i, rev; unsigned char *gammaptr; rev = 0; if (gammaval < 0.0) { rev = 1; gammaval = -gammaval; } gammaptr = gammatable; for (i=0; i<256; i++) { if (i < min) *gammaptr = 0; else if (i > max) *gammaptr = 255; else { *gammaptr = (int) (pow( (float)(i-min) / (float)(max + 1 - min), gammaval) * 256); } if (rev) *gammaptr = 255 - *gammaptr; gammaptr++; } #if 0 int i, inc; unsigned char *gammaptr; if (gammaval < 0.0) { inc = -1; gammaval = -gammaval; gammaptr = gammatable+255; } else { inc = 1; gammaptr = gammatable; } for (i=0; i<256; i++) { if (i < min) *gammaptr = 0; else if (i > max) *gammaptr = 255; else { *gammaptr = (int) (pow( (float)(i-min) / (float)(max + 1 - min), gammaval) * 256); } gammaptr += inc; } #endif #if 1 { int t; printf("GAMMA: "); for (t=0; t<=255; t++) printf("%03d, ",gammatable[t]); printf("\n"); } #endif } static int scsi_write (int fd, const void *buf, size_t buf_size, SANE_Status * status) { unsigned char *cmd; cmd = alloca (6 + buf_size); memset (cmd, 0, 6); cmd[0] = WRITE_6_COMMAND; cmd[2] = buf_size >> 16; cmd[3] = buf_size >> 8; cmd[4] = buf_size; memcpy (cmd + 6, buf, buf_size); if (SANE_STATUS_GOOD == (*status = sanei_scsi_cmd (fd, cmd, 6 + buf_size, NULL, NULL))) return buf_size; return 0; } static int send (Epson_Scanner * s, const void *buf, size_t buf_size, SANE_Status * status) { DBG(28, "send buf, size = %lu\n", (u_long) buf_size); /* { size_t k; const char * s = buf; for( k = 0; k < buf_size; k++) { DBG(28, "buf[%u] %02x %c\n", k, s[ k], isprint( s[ k]) ? s[ k] : '.'); } } */ if (s->hw->is_scsi) { return scsi_write (s->fd, buf, buf_size, status); } else { int n; if (buf_size == (n = sanei_pio_write (s->fd, buf, buf_size))) *status = SANE_STATUS_GOOD; else *status = SANE_STATUS_INVAL; return n; } /* never reached */ } static int scsi_read (int fd, void *buf, size_t buf_size, SANE_Status * status) { unsigned char cmd[6]; memset (cmd, 0, 6); cmd[0] = READ_6_COMMAND; cmd[2] = buf_size >> 16; cmd[3] = buf_size >> 8; cmd[4] = buf_size; DBG(31, "scsi_read: requesting %d bytes\n", buf_size); assert(buf_size > 0); if (SANE_STATUS_GOOD == (*status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), buf, &buf_size))) return buf_size; return 0; } static int receive (Epson_Scanner * s, void *buf, size_t buf_size, SANE_Status * status) { int n; if (s->hw->is_scsi) { n = scsi_read (s->fd, buf, buf_size, status); if ((n == 0) && (*status == SANE_STATUS_NO_MEM)) { DBG(10, "receive: no mem error\n"); } else if (n==0) DBG(28, "receive: status %d\n", *status); } else { if (buf_size == (n = sanei_pio_read (s->fd, buf, buf_size))) *status = SANE_STATUS_GOOD; else *status = SANE_STATUS_INVAL; } DBG(28, "receive buf, expected = %lu, got = %d\n", (u_long) buf_size, n); /* { int k; const char * s = buf; z for( k = 0; k < n; k++) { DBG(28, "buf[%u] %02x %c\n", k, s[ k], isprint( s[ k]) ? s[ k] : '.'); } } */ return n; } static SANE_Status inquiry (int fd, int page_code, void *buf, size_t * buf_size) { unsigned char cmd[6]; int status; memset (cmd, 0, 6); cmd[0] = INQUIRY_COMMAND; cmd[2] = page_code; cmd[4] = *buf_size > 255 ? 255 : *buf_size; status = sanei_scsi_cmd (fd, cmd, sizeof cmd, buf, buf_size); return status; } static SANE_Status expect_ack (Epson_Scanner * s) { unsigned char result[1]; size_t len; SANE_Status status; len = sizeof result; receive (s, result, len, &status); if (status != SANE_STATUS_GOOD) return status; if (result[0] != ACK) { /* DBG(8, "Expect ack got %d\n",result[0]); */ return SANE_STATUS_INVAL; } return SANE_STATUS_GOOD; }