diff options
Diffstat (limited to 'backend/avision.c')
-rw-r--r-- | backend/avision.c | 8626 |
1 files changed, 8626 insertions, 0 deletions
diff --git a/backend/avision.c b/backend/avision.c new file mode 100644 index 0000000..31b0a58 --- /dev/null +++ b/backend/avision.c @@ -0,0 +1,8626 @@ +/******************************************************************************* + * SANE - Scanner Access Now Easy. + + avision.c + + 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. + + ***************************************************************************** + + This backend is based upon the Tamarack backend and adapted to the Avision + scanners by René Rebe and Meino Cramer. + + This file implements a SANE backend for the Avision SCSI Scanners (like the + AV 630 / 620 (CS) ...) and some Avision (OEM) USB scanners (like the HP 53xx, + 74xx, Minolta FS-V1 ...) or Fujitsu ScanPartner with the AVISION SCSI-2/3 + or USB command set. + + Copyright 1999, 2000, 2001 by + "René Rebe" <rene@exactcode.de> + "Meino Christian Cramer" <mccramer@s.netic.de> + + Copyright 2002 by + "René Rebe" <rene@exactcode.de> + "Jose Paulo Moitinho de Almeida" <moitinho@civil.ist.utl.pt> + + Copyright 2003, 2004, 2005, 2006, 2007 by + "René Rebe" <rene@exactcode.de> + + Copyright 2010, 2011 by + "Mike Kelly" <mike@piratehaven.org> + + Additional Contributers: + "Gunter Wagner" + (some fixes and the transparency option) + "Martin Jelínek" <mates@sirrah.troja.mff.cuni.cz> + nice attach debug output + "Marcin Siennicki" <m.siennicki@cloos.pl> + found some typos and contributed fixes for the HP 7400 + "Frank Zago" <fzago@greshamstorage.com> + Mitsubishi IDs and report + Avision INC + example code to handle calibration and C5 ASIC specifics + "Franz Bakan" <fbakan@gmx.net> + OS/2 threading support + "Falk Rohsiepe" + Spelling and whitespace as well as HP5370 quirks + + Many additional special thanks to: + Avision INC for providing protocol documentation. + Avision INC for sponsoring an AV 8000S with ADF. + Avision Europe and BHS Binkert for sponsoring several more scanners. + Archivista GmbH, Switzerland, for sponsoring several features + Roberto Di Cosmo who sponsored a HP 5370 scanner. + Oliver Neukum who sponsored a HP 5300 USB scanner. + Matthias Wiedemann for lending his HP 7450C for some weeks. + Compusoft, C.A. Caracas / Venezuela for sponsoring a + HP 7450 scanner and so enhanced ADF support. + Chris Komatsu for the nice ADF scanning observation. + + All the many other beta-tester and debug-log sender! + + Thanks to all the people and companies above. Without you + the Avision backend would not be in the shape it is today! ;-) + +********************************************************************************/ + +/* SANE-FLOW-DIAGRAMM (from umax.c) + * + * - sane_init() : initialize backend, attach scanners(devicename,0) + * . - sane_get_devices() : query list of scanner-devices + * . - sane_open() : open a particular scanner-device and attach_scanner(devicename,&dev) + * . . - sane_set_io_mode : set blocking-mode + * . . - sane_get_select_fd : get scanner-fd + * . . - sane_get_option_descriptor() : get option information + * . . - sane_control_option() : change option values + * . . + * . . - sane_start() : start image acquisition + * . . - sane_get_parameters() : returns actual scan-parameters + * . . - sane_read() : read image-data (from pipe) + * + * in ADF mode this is done often: + * . . - sane_start() : start image acquisition + * . . - sane_get_parameters() : returns actual scan-parameters + * . . - sane_read() : read image-data (from pipe) + * + * . . - sane_cancel() : cancel operation, kill reader_process + * + * . - sane_close() : close opened scanner-device, do_cancel, free buffer and handle + * - sane_exit() : terminate use of backend, free devicename and device-structure + */ + +#include "../include/sane/config.h" + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/time.h> + +#include <math.h> + +#define BACKEND_NAME avision +#define BACKEND_BUILD 296 /* avision backend BUILD version */ + +#include "../include/sane/sane.h" +#include "../include/sane/sanei.h" +#include "../include/sane/saneopts.h" +#include "../include/sane/sanei_thread.h" +#include "../include/sane/sanei_scsi.h" +#include "../include/sane/sanei_usb.h" +#include "../include/sane/sanei_config.h" +#include "../include/sane/sanei_backend.h" + +#include <avision.h> + +/* For timeval... */ +#ifdef DEBUG +#include <sys/time.h> +#endif + +/* Attention: The comments must stay as they are - they are automatically parsed + to generate the SANE avision.desc file, as well as HTML online content! */ + +/* Attention2: This device table is part of the source code and as such + licensed under the terms of the license as listed above (GPL2+). By + using this data you obviously create derived work! -ReneR */ + +static Avision_HWEntry Avision_Device_List [] = + { + { "AVISION", "AV100CS", + 0, 0, + "Avision", "AV100CS", + 0,0}, + /* status="untested" */ + + { "AVISION", "AV100IIICS", + 0, 0, + "Avision", "AV100IIICS", + 0,0}, + /* status="untested" */ + + { "AVISION", "AV100S", + 0, 0, + "Avision", "AV100S", + 0,0}, + /* status="untested" */ + + { NULL, NULL, + 0x0638, 0x0A27, + "Avision", "AV120", + AV_INT_STATUS,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A3C, + "Avision", "AV121", + AV_INT_BUTTON | AV_DOES_KEEP_WINDOW | AV_DOES_KEEP_GAMMA,0}, + /* comment="sheetfed scanner" */ + /* status="good" */ + + { NULL, NULL, + 0x0638, 0x0A33, + "Avision", "AV122", + AV_INT_BUTTON | AV_2ND_LINE_INTERLACED | AV_NO_REAR | AV_SOFT_SCALE | AV_DOES_KEEP_WINDOW | AV_DOES_KEEP_GAMMA | AV_REAR_OFFSET,0}, + /* comment="sheetfed duplex scanner" */ + /* status="good" */ + + { NULL, NULL, + 0x0638, 0x0A93, + "Avision", "AV122 C2", + AV_INT_BUTTON | AV_2ND_LINE_INTERLACED | AV_NO_REAR | AV_SOFT_SCALE | AV_DOES_NOT_KEEP_WINDOW | AV_DOES_KEEP_GAMMA | AV_REAR_OFFSET,0}, + /* comment="sheetfed duplex scanner" */ + /* status="good" */ + + { NULL, NULL, + 0x0638, 0x0A24, + "Avision", "AV210", + AV_INT_BUTTON | AV_ACCEL_TABLE,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A25, + "Avision", "AV210", + AV_INT_BUTTON | AV_ACCEL_TABLE | AV_NO_64BYTE_ALIGN,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A3A, + "Avision", "AV210C2", + AV_INT_BUTTON | AV_GRAY_MODES,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A2F, + "Avision", "AV210C2-G", + AV_INT_BUTTON | AV_GRAY_MODES,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x1A35, + "Avision", "AV210D2+", + AV_INT_BUTTON, AV_USE_GRAY_FILTER}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A23, + "Avision", "AV220", + AV_INT_BUTTON | AV_GRAY_MODES,0}, + /* comment="duplex! sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A2A, + "Avision", "AV220C2", + AV_INT_BUTTON | AV_CANCEL_BUTTON,0}, + /* comment="duplex! sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A2B, + "Avision", "AV220D2", + AV_INT_BUTTON | AV_CANCEL_BUTTON,0}, + /* comment="duplex! sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A2C, + "Avision", "AV220+", + AV_INT_BUTTON | AV_CANCEL_BUTTON,0}, + /* comment="duplex! sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A2D, + "Avision", "AV220C2-G", + AV_INT_BUTTON | AV_CANCEL_BUTTON,0}, + /* comment="duplex! sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A2E, + "Avision", "AV220C2-B", + AV_INT_BUTTON | AV_CANCEL_BUTTON,0}, + /* comment="duplex! sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A94, + "Avision", "AV220-G", + AV_INT_BUTTON | AV_2ND_LINE_INTERLACED, AV_FIRMWARE}, + /* comment="duplex! sheetfed scanner" */ + /* status="complete" */ + + { "AVISION", "AV240SC", + 0, 0, + "Avision", "AV240SC", + 0,0}, + /* status="untested" */ + + { "AVISION", "AV260CS", + 0, 0, + "Avision", "AV260CS", + 0,0}, + /* status="untested" */ + + { "AVISION", "AV360CS", + 0, 0, + "Avision", "AV360CS", + 0,0}, + /* status="untested" */ + + { "AVISION", "AV363CS", + 0, 0, + "Avision", "AV363CS", + 0,0}, + /* status="untested" */ + + { "AVISION", "AV420CS", + 0, 0, + "Avision", "AV420CS", + 0,0}, + /* status="untested" */ + + { "AVISION", "AV6120", + 0, 0, + "Avision", "AV6120", + 0,0}, + /* status="untested" */ + + { NULL, "AV610", + 0x0638, 0x0a18, + "Avision", "AV610", + AV_GRAY_CALIB_BLUE | AV_ACCEL_TABLE | AV_NO_64BYTE_ALIGN | AV_INT_BUTTON, 0}, + /* status="good" */ + + { NULL, NULL, + 0x0638, 0x0a18, + "Avision", "AV600U Plus", + /* If this unit requires the AV_INT_STATUS flag, then we'll need to alter the code to deal with two different devices with the same USB id (AV610 above) */ + AV_GRAY_CALIB_BLUE | AV_ACCEL_TABLE | AV_NO_64BYTE_ALIGN | /* AV_INT_STATUS | */ AV_INT_BUTTON,0}, + /* status="good" */ + + { NULL, NULL, + 0x0638, 0x0a5e, + "Avision", "AV610C2", + AV_NO_BACKGROUND | AV_INT_BUTTON,0}, /* cancel button -> sense abort! */ + /* status="good" */ + + { NULL, NULL, + 0x0638, 0x0a41, + "Avision", "AM3000 Series", + 0,0}, + /* comment="MFD" */ + /* status="basic" */ + + { NULL, NULL, + 0x0638, 0x0a16, + "Avision", "DS610CU Scancopier", + AV_INT_STATUS,0}, + /* comment="1 pass, 600 dpi, A4" */ + /* status="good" */ + + { "AVISION", "AV620CS", + 0, 0, + "Avision", "AV620CS", + 0,0}, + /* comment="1 pass, 600 dpi" */ + /* status="complete" */ + + { "AVISION", "AV620CS Plus", + 0, 0, + "Avision", "AV620CS Plus", + 0,0}, + /* comment="1 pass, 1200 dpi" */ + /* status="complete" */ + + { "AVISION", "AV630CS", + 0, 0, + "Avision", "AV630CS", + 0,0}, + /* comment="1 pass, 1200 dpi" */ + /* status="complete" */ + + { "AVISION", "AV630CSL", + 0, 0, + "Avision", "AV630CSL", + 0,0}, + /* comment="1 pass, 1200 dpi" */ + /* status="untested" */ + + { "AVISION", "AV6240", + 0, 0, + "Avision", "AV6240", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A13, + "Avision", "AV600U", + AV_MULTI_CALIB_CMD | AV_ADF_BGR_ORDER_INVERT | AV_SOFT_SCALE | AV_INT_STATUS | AV_NO_BUTTON,0}, + /* comment="1 pass, 600 dpi" */ + /* status="good" */ + + { "AVISION", "AV660S", + 0, 0, + "Avision", "AV660S", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="untested" */ + + { "AVISION", "AV680S", + 0, 0, + "Avision", "AV680S", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="untested" */ + + { "AVISION", "AV690U", + 0, 0, + "Avision", "AV690U", + 0,0}, + /* comment="1 pass, 2400 dpi" */ + /* status="untested" */ + + { "AVISION", "AV800S", + 0, 0, + "Avision", "AV800S", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="untested" */ + + { "AVISION", "AV810C", + 0, 0, + "Avision", "AV810C", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="untested" */ + + { "AVISION", "AV820", + 0, 0, + "Avision", "AV820", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="untested" */ + + { "AVISION", "AV820C", + 0, 0, + "Avision", "AV820C", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="complete" */ + + { "AVISION", "AV820C Plus", + 0, 0, + "Avision", "AV820C Plus", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="complete" */ + + { "AVISION", "AV830C", + 0, 0, + "Avision", "AV830C", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="complete" */ + + { "AVISION", "AV830C Plus", + 0, 0, + "Avision", "AV830C Plus", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="untested" */ + + { "AVISION", "AV880", + 0, 0, + "Avision", "AV880", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="untested" */ + + { "AVISION", "AV880C", + 0, 0, + "Avision", "AV880C", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="untested" */ + + { "AVISION", "AV3200C", + 0, 0, + "Avision", "AV3200C", + AV_NON_INTERLACED_DUPLEX_300 | AV_FASTER_WITH_FILTER,0}, + /* comment="1 pass, ??? dpi" */ + /* status="complete" */ + + { "AVISION", "AV3200SU", + 0x0638, 0x0A4E, + "Avision", "AV3200SU", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="complete" */ + + { "AVISION", "AV3730SU", + 0x0638, 0x0A4F, + "Avision", "AV3730SU", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="complete" */ + + { "AVISION", "AV3750SU", + 0x0638, 0x0A65, + "Avision", "AV3750SU", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="complete" */ + + { "AVISION", "AV3800C", + 0, 0, + "Avision", "AV3800C", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="complete" */ + + { "AVISION", "AV3850SU", + 0x0638, 0x0a66, + "Avision", "AV3850SU", + 0,0}, + /* comment="1 pass, ??? dpi" */ + /* status="complete" */ + + { "AVISION", "FB6000E", + 0, 0, + "Avision", "FB6000E", + AV_NON_INTERLACED_DUPLEX_300,0}, + /* comment="1 pass, 1200 dpi, A3 - duplex! - zero edge!" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0a82, + "Avision", "FB6080E", + AV_NON_INTERLACED_DUPLEX_300,0}, + /* comment="1 pass, 1200 dpi, A3 - duplex! - zero edge!" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0a84, + "Avision", "FB2080E", + 0,0}, + /* comment="1 pass, 600 dpi, zero-edge" ASIC 7 */ + /* status="basic" */ + + { "AVISION", "AV8000S", + 0, 0, + "Avision", "AV8000S", + AV_DOES_NOT_KEEP_WINDOW,0}, + /* comment="1 pass, 1200 dpi, A3" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0a4d, + "Avision", "AV8050U", + AV_NON_INTERLACED_DUPLEX_300 | AV_DOES_NOT_KEEP_GAMMA,0}, + /* comment="1 pass, 1200 dpi, A3 - duplex!" */ + /* status="complete" */ + + { "AVISION", "AV8300", + 0x0638, 0x0A40, + "Avision", "AV8300", + AV_NON_INTERLACED_DUPLEX_300 | AV_DOES_NOT_KEEP_GAMMA,0}, + /* comment="1 pass, 1200 dpi, A3 - duplex!" */ + /* status="complete" */ + + { "AVISION", "AV8350", + 0x0638, 0x0A68, + "Avision", "AV8350", + AV_NON_INTERLACED_DUPLEX_300 | AV_DOES_NOT_KEEP_GAMMA,0}, + /* comment="1 pass, 1200 dpi, A3 - duplex!" */ + /* status="complete" */ + + { NULL, NULL, + 0x0638, 0x0A61, + "Avision", "IT8300", + AV_NON_INTERLACED_DUPLEX_300 | AV_ACCEL_TABLE,0}, + /* comment="1 pass, 1200 dpi, A3 - duplex!, LCD screen, paper sensors" */ + /* status="good" */ + + { NULL, NULL, + 0x0638, 0x0AA1, + "Avision", "@V2500", + 0,0}, + /* comment="" */ + /* status="untested" */ + + { NULL, NULL, + 0x0638, 0x0A45, + "Avision", "@V5100", + 0,0}, + /* comment="1 pass, 1200 dpi, A3 - duplex!, LCD screen, paper sensors" */ + /* status="good" */ + + { "AVISION", "AVA3", + 0, 0, + "Avision", "AVA3", + AV_FORCE_A3,0}, + /* comment="1 pass, 600 dpi, A3" */ + /* status="basic" */ + + /* and possibly more avisions ;-) */ + + { "HP", "ScanJet 5300C", + 0x03f0, 0x0701, + "Hewlett-Packard", "ScanJet 5300C", + AV_INT_STATUS,0}, + /* comment="1 pass, 2400 dpi - some FW revisions have x-axis image scaling problems over 1200 dpi" */ + /* status="complete" */ + + { "HP", "ScanJet 5370C", + 0x03f0, 0x0701, + "Hewlett-Packard", "ScanJet 5370C", + AV_MULTI_CALIB_CMD | AV_INT_STATUS, AV_FIRMWARE }, + /* comment="1 pass, 2400 dpi - some FW revisions have x-axis image scaling problems over 1200 dpi" */ + /* status="good" */ + + { "hp", "scanjet 7400c", + 0x03f0, 0x0801, + "Hewlett-Packard", "ScanJet 7400c", + AV_LIGHT_CHECK_BOGUS | AV_NO_64BYTE_ALIGN | AV_INT_STATUS,0}, + /* comment="1 pass, 2400 dpi - dual USB/SCSI interface" */ + /* status="good" */ + +#ifdef FAKE_ENTRIES_FOR_DESC_GENERATION + { "hp", "scanjet 7450c", + 0x03f0, 0x0801, + "Hewlett-Packard", "ScanJet 7450c", + AV_NO_64BYTE_ALIGN | AV_INT_STATUS,0}, + /* comment="1 pass, 2400 dpi - dual USB/SCSI interface" */ + /* status="good" */ + + { "hp", "scanjet 7490c", + 0x03f0, 0x0801, + "Hewlett-Packard", "ScanJet 7490c", + AV_NO_64BYTE_ALIGN | AV_INT_STATUS,0}, + /* comment="1 pass, 1200 dpi - dual USB/SCSI interface" */ + /* status="good" */ + +#endif + { "HP", "C9930A", + 0x03f0, 0x0b01, + "Hewlett-Packard", "ScanJet 8200", + 0, AV_ADF_FLIPPING_DUPLEX | AV_FIRMWARE }, + /* comment="1 pass, 4800 (?) dpi - USB 2.0" */ + /* status="good" */ + +#ifdef FAKE_ENTRIES_FOR_DESC_GENERATION + { "HP", "C9930A", + 0x03f0, 0x0b01, + "Hewlett-Packard", "ScanJet 8250", + 0, AV_ADF_FLIPPING_DUPLEX | AV_FIRMWARE }, + /* comment="1 pass, 4800 (?) dpi - USB 2.0" */ + /* status="good" */ +#endif + + { "HP", "C9930A", + 0x03f0, 0x3905, + "Hewlett-Packard", "ScanJet 8270", + 0, AV_ADF_FLIPPING_DUPLEX | AV_FIRMWARE }, + /* comment="1 pass, 4800 (?) dpi - USB 2.0" */ + /* status="good" */ + +#ifdef FAKE_ENTRIES_FOR_DESC_GENERATION + { "HP", "C9930A", + 0x03f0, 0x0b01, + "Hewlett-Packard", "ScanJet 8290", + 0, AV_ADF_FLIPPING_DUPLEX | AV_FIRMWARE }, + /* comment="1 pass, 4800 (?) dpi - USB 2.0 and SCSI - only SCSI tested so far" */ + /* status="good" */ + +#endif + { "HP", "C9930A", + 0x03f0, 0x3805, + "Hewlett-Packard", "ScanJet 8300", + 0,0}, + /* comment="1 pass, 4800 (?) dpi - USB 2.0" */ + /* status="good" */ + +#ifdef FAKE_ENTRIES_FOR_DESC_GENERATION + { "HP", "C9930A", + 0x03f0, 0x3805, + "Hewlett-Packard", "ScanJet 8350", + 0,0}, + /* comment="1 pass, 4800 (?) dpi - USB 2.0" */ + /* status="good" */ + + { "HP", "C9930A", + 0x03f0, 0x3805, + "Hewlett-Packard", "ScanJet 8390", + 0,0}, + /* comment="1 pass, 4800 (?) dpi - USB 2.0" */ + /* status="good" */ + +#endif + { "Minolta", "#2882", + 0, 0, + "Minolta", "Dimage Scan Dual I", + AV_FORCE_FILM | AV_NO_START_SCAN,0}, /* not AV_FILMSCANNER (no frame control) */ + /* status="basic" */ + + { "Minolta", "#2887", + 0, 0, + "Minolta", "Scan Multi Pro", + AV_FORCE_FILM | AV_NO_START_SCAN,0}, /* AV_FILMSCANNER (frame control)? */ + /* status="untested" */ + + { "MINOLTA", "FS-V1", + 0x0638, 0x026a, + "Minolta", "Dimage Scan Dual II", + AV_FILMSCANNER | AV_ONE_CALIB_CMD | AV_12_BIT_MODE,0}, + /* comment="1 pass, film-scanner" */ + /* status="good" */ + + { "MINOLTA", "Elite II", + 0x0686, 0x4004, + "Minolta", "Elite II", + AV_FILMSCANNER | AV_ONE_CALIB_CMD,0}, + /* comment="1 pass, film-scanner" */ + /* status="untested" */ + + { "MINOLTA", "FS-V3", + 0x0686, 0x400d, + "Minolta", "Dimage Scan Dual III", + AV_FILMSCANNER | AV_ONE_CALIB_CMD | AV_ACCEL_TABLE ,0}, + /* comment="1 pass, film-scanner" */ + /* status="good" */ + + { "MINOLTA", "FS-V4", + 0x0686, 0x400e, + "Minolta", "Dimage Scan Elite 5400", + AV_FILMSCANNER | AV_ONE_CALIB_CMD | /*AV_ACCEL_TABLE |*/ AV_NO_START_SCAN,0}, + /* comment="1 pass, film-scanner" */ + /* status="good" */ + + { "QMS", "SC-110", + 0x0638, 0x0a15, + "Minolta-QMS", "SC-110", + 0,0}, + /* comment="" */ + /* status="untested" */ + + { "QMS", "SC-215", + 0x0638, 0x0a16, + "Minolta-QMS", "SC-215", + 0,0}, + /* comment="" */ + /* status="good" */ + + { "MITSBISH", "MCA-ADFC", + 0, 0, + "Mitsubishi", "MCA-ADFC", + 0,0}, + /* status="untested" */ + + { "MITSBISH", "MCA-S1200C", + 0, 0, + "Mitsubishi", "S1200C", + 0,0}, + /* status="untested" */ + + { "MITSBISH", "MCA-S600C", + 0, 0, + "Mitsubishi", "S600C", + 0,0}, + /* status="untested" */ + + { "MITSBISH", "SS600", + 0, 0, + "Mitsubishi", "SS600", + 0,0}, + /* status="good" */ + + /* The next are all untested ... */ + + { "FCPA", "ScanPartner", + 0, 0, + "Fujitsu", "ScanPartner", + AV_FUJITSU,0}, + /* status="untested" */ + + { "FCPA", "ScanPartner 10", + 0, 0, + "Fujitsu", "ScanPartner 10", + AV_FUJITSU,0}, + /* status="untested" */ + + { "FCPA", "ScanPartner 10C", + 0, 0, + "Fujitsu", "ScanPartner 10C", + AV_FUJITSU,0}, + /* status="untested" */ + + { "FCPA", "ScanPartner 15C", + 0, 0, + "Fujitsu", "ScanPartner 15C", + AV_FUJITSU,0}, + /* status="untested" */ + + { "FCPA", "ScanPartner 300C", + 0, 0, + "Fujitsu", "ScanPartner 300C", + 0,0}, + /* status="untested" */ + + { "FCPA", "ScanPartner 600C", + 0, 0, + "Fujitsu", "ScanPartner 600C", + 0,0}, + /* status="untested" */ + + { "FCPA", "ScanPartner 620C", + 0, 0, + "Fujitsu", "ScanPartner 620C", + AV_LIGHT_CHECK_BOGUS,0}, + /* status="good" */ + + { "FCPA", "ScanPartner Jr", + 0, 0, + "Fujitsu", "ScanPartner Jr", + 0,0}, + /* status="untested" */ + + { "FCPA", "ScanStation", + 0, 0, + "Fujitsu", "ScanStation", + 0,0}, + /* status="untested" */ + + { NULL, NULL, + 0x04c5, 0x1029, + "Fujitsu", "fi-4010CU", + 0,0}, + /* status="untested" */ + + { NULL, NULL, + 0x04c5, 0x10ef, + "Fujitsu", "fi-5015C", + 0,0}, + /* status="good" */ + + { NULL, NULL, + 0x040a, 0x6001, + "Kodak", "i30", + AV_INT_BUTTON | AV_GRAY_MODES,0}, + /* status="untested" */ + + { NULL, NULL, + 0x040a, 0x6002, + "Kodak", "i40", + AV_INT_BUTTON | AV_GRAY_MODES,0}, + /* status="basic" */ + + { NULL, NULL, + 0x040a, 0x6003, + "Kodak", "i50", + AV_INT_BUTTON,0}, + /* status="untested" */ + +#ifdef FAKE_ENTRIES_FOR_DESC_GENERATION + { NULL, NULL, + 0x040a, 0x6003, + "Kodak", "i55", + AV_INT_BUTTON,0}, + /* status="untested" */ +#endif + + { NULL, NULL, + 0x040a, 0x6004, + "Kodak", "i60", + AV_INT_BUTTON,0}, + /* status="untested" */ + +#ifdef FAKE_ENTRIES_FOR_DESC_GENERATION + { NULL, NULL, + 0x040a, 0x6004, + "Kodak", "i65", + AV_INT_BUTTON,0}, + /* status="untested" */ +#endif + + { NULL, NULL, + 0x040a, 0x6005, + "Kodak", "i80", + AV_INT_BUTTON,0}, + /* status="good" */ + + { "iVina", "1200U", + 0x0638, 0x0268, + "iVina", "1200U", + 0,0}, + /* status="untested" */ + + { NULL, NULL, + 0x04a7, 0x0424, + "Visioneer", "Strobe XP 450", + AV_INT_BUTTON | AV_ACCEL_TABLE,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x0491, + "Visioneer", "Strobe XP 450-G", + AV_INT_BUTTON | AV_ACCEL_TABLE,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x0479, + "Visioneer", "Strobe XP 470", + AV_INT_BUTTON | AV_ACCEL_TABLE,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x048F, + "Visioneer", "Strobe XP 470-G", + AV_INT_BUTTON | AV_ACCEL_TABLE,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x0420, + "Visioneer", "9320", + 0,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x0421, + "Visioneer", "9450", + AV_MULTI_CALIB_CMD | AV_ADF_BGR_ORDER_INVERT | AV_NO_BUTTON, AV_NO_TUNE_SCAN_LENGTH}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x047A, + "Visioneer", "9450-G", + 0,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x0422, + "Visioneer", "9550", + 0,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x0390, + "Visioneer", "9650", + 0,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x047B, + "Visioneer", "9650-G", + 0,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x0423, + "Visioneer", "9750", + AV_INT_BUTTON,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x0493, + "Visioneer", "9750-G", + AV_INT_BUTTON,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x0497, + "Visioneer", "Patriot 430", + AV_INT_BUTTON | AV_2ND_LINE_INTERLACED | AV_NO_REAR | AV_SOFT_SCALE | AV_DOES_KEEP_WINDOW | AV_DOES_KEEP_GAMMA | AV_REAR_OFFSET,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + +#ifdef FAKE_ENTRIES_FOR_DESC_GENERATION + { NULL, NULL, + 0x04a7, 0x048F, + "Visioneer", "Patriot 470", + AV_INT_BUTTON,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ +#endif + + { NULL, NULL, + 0x04a7, 0x0498, + "Visioneer", "Patriot 680", + AV_INT_BUTTON,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x0499, + "Visioneer", "Patriot 780", + AV_INT_BUTTON,0}, + /* comment="sheetfed scanner" */ + /* status="complete" */ + + { NULL, NULL, + 0x04a7, 0x049C, + "Xerox", "DocuMate150", + AV_INT_BUTTON | AV_SOFT_SCALE | AV_DOES_KEEP_WINDOW | AV_DOES_KEEP_GAMMA | AV_BACKGROUND_QUIRK,0}, + /* status="good" */ + + { NULL, NULL, + 0x04a7, 0x0477, + "Xerox", "DocuMate152", + AV_INT_BUTTON | AV_2ND_LINE_INTERLACED | AV_NO_REAR | AV_SOFT_SCALE | AV_DOES_KEEP_WINDOW | AV_DOES_KEEP_GAMMA | AV_REAR_OFFSET | AV_BACKGROUND_QUIRK,0}, + /* status="good" */ + + { NULL, NULL, + 0x04a7, 0x049D, + "Xerox", "DocuMate162", + AV_INT_BUTTON | AV_2ND_LINE_INTERLACED | AV_NO_REAR | AV_SOFT_SCALE | AV_DOES_KEEP_WINDOW | AV_DOES_KEEP_GAMMA | AV_REAR_OFFSET | AV_BACKGROUND_QUIRK,0}, + /* status="good" */ + + { NULL, NULL, + 0x04a7, 0x0448, + "Xerox", "DocuMate250", + AV_INT_BUTTON,0}, + /* status="good" */ + + { NULL, NULL, + 0x04a7, 0x0490, + "Xerox", "DocuMate250-G", + AV_INT_BUTTON,0}, + /* status="good" */ + + { NULL, NULL, + 0x04a7, 0x0449, + "Xerox", "DocuMate252", + AV_INT_BUTTON,0}, + /* status="good" */ + + { NULL, NULL, + 0x04a7, 0x048C, + "Xerox", "DocuMate252-G", + AV_INT_BUTTON,0}, + /* status="good" */ + + { NULL, NULL, + 0x04a7, 0x0476, + "Xerox", "DocuMate232", + AV_INT_BUTTON,0}, + /* status="good" */ + + { NULL, NULL, + 0x04a7, 0x044c, + "Xerox", "DocuMate262", + AV_INT_BUTTON,0}, + /* status="good" */ + + { NULL, NULL, + 0x04a7, 0x048D, + "Xerox", "DocuMate262-G", + AV_INT_BUTTON,0}, + /* status="good" */ + + { NULL, NULL, + 0x04a7, 0x04a7, + "Xerox", "DocuMate262i", + AV_INT_BUTTON,0}, + /* status="good" */ + + { NULL, NULL, + 0x04a7, 0x0475, + "Xerox", "DocuMate272", + AV_INT_BUTTON,0}, + /* status="untested" */ + + { NULL, NULL, + 0x04a7, 0x048E, + "Xerox", "DocuMate272-G", + AV_INT_BUTTON,0}, + /* status="untested" */ + + { NULL, NULL, + 0x04a7, 0x0446, + "Xerox", "DocuMate510", + AV_INT_BUTTON,0}, + /* status="untested" */ + + { NULL, NULL, + 0x04a7, 0x0495, + "Xerox", "DocuMate512", + AV_INT_BUTTON,0}, + /* status="untested" */ + + { NULL, NULL, + 0x04a7, 0x047c, + "Xerox", "DocuMate510-G", + AV_INT_BUTTON,0}, + /* status="untested" */ + + { NULL, NULL, + 0x04a7, 0x0447, + "Xerox", "DocuMate520", + AV_INT_BUTTON,0}, + /* status="untested" */ + + { NULL, NULL, + 0x04a7, 0x0492, + "Xerox", "DocuMate520-G", + AV_INT_BUTTON,0}, + /* status="untested" */ + +#ifdef FAKE_ENTRIES_FOR_DESC_GENERATION + { NULL, NULL, + 0x04a7, 0x0498, + "Xerox", "DocuMate632", + AV_INT_BUTTON,0}, + /* status="untested" */ +#endif + + { NULL, NULL, + 0x04a7, 0x0478, + "Xerox", "DocuMate752", + AV_INT_BUTTON,0}, + /* status="untested" */ + + { NULL, NULL, + 0x04a7, 0x049A, + "Xerox", "DocuMate752", + AV_INT_BUTTON,0}, + /* status="untested" */ + +#ifdef FAKE_ENTRIES_FOR_DESC_GENERATION + { NULL, NULL, + 0x0638, 0x0a16, + "OKI", "S700 Scancopier", + 0,0}, + /* comment="1 pass, 600 dpi, A4" */ + /* status="good" */ +#endif + + { "B+H", "2000F", + 0, 0, + "Bell+Howell", "2000F", + 0,0}, + /* comment="1 pass, ??? dpi, A4" */ + /* status="basic" */ + + { NULL, NULL, + 0x0482, 0x0335, + "Kyocera", "FS-1016MFP", + 0,0}, + /* comment="1 pass, ??? dpi, A4" */ + /* status="untested" */ + + /* More IDs from the Avision dll: + ArtiScan ProA3 + FB1065 + FB1265 + PHI860S + PSDC SCSI + SCSI Scan 19200 + V6240 */ + + /* Possibly: +Lexmark 4600 MFP Option MFP Options +Lexmark 4600 MFP Option (C772n) MFP Options +Lexmark X215 +Lexmark Optra Image X242 +Lexmark X443 +Lexmark 3100 +Lexmark 3200 +Lexmark X340 MFP Multifunction +Lexmark X342n MFP Multifunction +Lexmark X522 +Lexmark X630 +Lexmark X632E +Lexmark X642e MFP Multifunction +Lexmark X644e MFP Multifunction +Lexmark X646dte MFP Multifunction +Lexmark X646e MFP Multifunction +Lexmark X646ef MFP Multifunction +Lexmark X772e Multifunction +Lexmark X850e MFP Multifunction +Lexmark X852e MFP Multifunction +Lexmark X854e MFP Multifunction +Lexmark X4500 MFP + */ + + /* last entry detection */ + { NULL, NULL, + 0, 0, + NULL, NULL, + 0,0} + }; + +#if 0 + struct timeval tv; + #define TIMING(txt) gettimeofday (&tv, NULL); \ + DBG (4, "%lu: " txt "\n", tv.tv_sec * 1000000 + tv.tv_usec) +#else + #define TIMING(txt) +#endif + +/* used when scanner returns invalid range fields ... */ +#define A4_X_RANGE 8.5 /* or 8.25 ? */ +#define A4_Y_RANGE 11.8 +#define A3_X_RANGE 11.8 +#define A3_Y_RANGE 16.5 /* or 17 ? */ +#define FILM_X_RANGE 1.0 /* really ? */ +#define FILM_Y_RANGE 1.0 +#define SHEETFEED_Y_RANGE 14.0 + +#define AVISION_CONFIG_FILE "avision.conf" + +#define STD_INQUIRY_SIZE 0x24 +#define AVISION_INQUIRY_SIZE_V1 0x60 +#define AVISION_INQUIRY_SIZE_V2 0x88 +#define AVISION_INQUIRY_SIZE_MAX AVISION_INQUIRY_SIZE_V2 + + +#define AVISION_BASE_RES 300 + +/* calibration (shading) defines */ + +#define INVALID_WHITE_SHADING 0x0000 +#define DEFAULT_WHITE_SHADING 0xFFF0 + +#define MAX_WHITE_SHADING 0xFFFF +/* originally the WHITE_MAP_RANGE was 0x4000 - but this always + * resulted in slightly too dark images - thus I have chosen + * 0x4FFF ... */ +#define WHITE_MAP_RANGE 0x4FFF + +#define INVALID_DARK_SHADING 0xFFFF +#define DEFAULT_DARK_SHADING 0x0000 + +#define read_constrains(s,var) {\ + if (s->hw->hw->feature_type & AV_NO_64BYTE_ALIGN) {\ + if (var % 64 == 0) var /= 2;\ + if (var % 64 == 0) var += 2;\ + }\ +}\ + +static int num_devices; +static Avision_Device* first_dev; +static Avision_Scanner* first_handle; +static const SANE_Device** devlist = 0; + +/* this is a bit hacky to get extra information in the attach callback */ +static Avision_HWEntry* attaching_hw = 0; + +/* disable the usage of a custom gamma-table */ +static SANE_Bool disable_gamma_table = SANE_FALSE; + +/* disable the calibration */ +static SANE_Bool disable_calibration = SANE_FALSE; +static SANE_Bool force_calibration = SANE_FALSE; + +/* force scanable areas to ISO(DIN) A4/A3 */ +static SANE_Bool force_a4 = SANE_FALSE; +static SANE_Bool force_a3 = SANE_FALSE; + +/* hardware resolutions to interpolate from */ +static const int hw_res_list_c5[] = + { + /* tested on AV600U */ + 75, 150, 300, 600, 1200, 2400, 4800, /* ... */ 0 + }; +static const int hw_res_list_generic[] = + { + 50, /* slower than 150 on the AV122/DM152, left for USB 1 host's preview */ + 75, /* slower than 150 on the AV122/DM152, left for USB 1 host's */ + 150, 200, 300, + /* 400,*/ /* AV122 simplex y-scaling and duplex interlacing corrupt */ + 600, 1200, 2400, 4800, + /* ... */ + 0 + }; + +static SANE_Bool static_calib_list[3] = + { + SANE_FALSE, SANE_FALSE, SANE_FALSE + }; + +static const SANE_Range u8_range = + { + 0, /* minimum */ + 255, /* maximum */ + 0 /* quantization */ + }; + +static const SANE_Range percentage_range = + { + SANE_FIX (-100), /* minimum */ + SANE_FIX (100), /* maximum */ + SANE_FIX (1) /* quantization */ + }; + +static const SANE_Range abs_percentage_range = + { + SANE_FIX (0), /* minimum */ + SANE_FIX (100), /* maximum */ + SANE_FIX (1) /* quantization */ + }; + +static const SANE_Range exposure_range = + { + 0, /* minimum */ + 1000, /* maximum */ + 1 /* quantization */ + }; + +static const SANE_Range overscan_range = + { + SANE_FIX (0), /* minimum */ + SANE_FIX (4), /* maximum */ /* 4mm, measured on AV122, AV220C2, i40 */ + 0 /* quantization */ + }; + +/* The 0x32 is a random guess based on USB logs. Might need a + per-device value in the future - 0x32 was tested on the AV122, + DM152, AV220. */ +static const SANE_Range background_range = + { + 0, /* minimum */ + 0x32, /* maximum */ + 0 /* quantization */ + }; + +static const uint8_t test_unit_ready[] = + { + AVISION_SCSI_TEST_UNIT_READY, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + +static const uint8_t get_status[] = + { + AVISION_SCSI_GET_DATA_STATUS, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0c, 0x00 + }; + +static size_t +max_string_size (const SANE_String_Const strings[]) +{ + size_t size, max_size = 0; + int i; + + DBG (3, "max_string_size:\n"); + + for (i = 0; strings[i]; ++ i) { + size = strlen (strings[i]) + 1; + if (size > max_size) + max_size = size; + } + return max_size; +} + +static SANE_Status +constrain_value (Avision_Scanner* s, SANE_Int option, void* value, + SANE_Int* info) +{ + DBG (3, "constrain_value:\n"); + return sanei_constrain_value (s->opt + option, value, info); +} + +static void debug_print_raw (int dbg_level, char* info, const uint8_t* data, + size_t count) +{ + size_t i; + + DBG (dbg_level, "%s", info); + for (i = 0; i < count; ++ i) { + DBG (dbg_level, " [%lu] %1d%1d%1d%1d%1d%1d%1d%1db %3oo %3dd %2xx\n", + (u_long) i, + BIT(data[i],7), BIT(data[i],6), BIT(data[i],5), BIT(data[i],4), + BIT(data[i],3), BIT(data[i],2), BIT(data[i],1), BIT(data[i],0), + data[i], data[i], data[i]); + } +} + +static void debug_print_hex_raw (int dbg_level, char* info, const uint8_t* data, + size_t count) +{ + int address = 0; + char text [16*3+1]; + + DBG (dbg_level, "%s", info); + while (count) { + char* t = text; + int i = 0; + while (i < 16 && count) { + t += sprintf (t, "%02x ", *data++); + count--; i++; + } + *--t = 0; + + DBG (dbg_level, " [%08x] %s\n", address, text); + address += 16; + } +} + +static void debug_print_nvram_data (int dbg_level, char* func, + nvram_data* nvram) +{ + DBG (dbg_level, "%s: pad scans: %d\n", + func, get_quad(nvram->pad_scans)); + DBG (dbg_level, "%s: ADF simplex scans: %d\n", + func, get_quad(nvram->adf_simplex_scans)); + DBG (dbg_level, "%s: ADF duplex scans: %d\n", + func, get_quad(nvram->adf_duplex_scans)); + DBG (dbg_level, "%s: flatbed scans: %d\n", + func, get_quad(nvram->flatbed_scans)); + + DBG (dbg_level, "%s: flatbed leading edge: %d\n", + func, (int16_t)get_double(nvram->flatbed_leading_edge)); + DBG (dbg_level, "%s: flatbed side edge: %d\n", + func, (int16_t)get_double(nvram->flatbed_side_edge)); + DBG (dbg_level, "%s: ADF leading edge: %d\n", + func, (int16_t)get_double(nvram->adf_leading_edge)); + DBG (dbg_level, "%s: ADF side edge: %d\n", + func, (int16_t)get_double(nvram->adf_side_edge)); + DBG (dbg_level, "%s: ADF rear leading edge: %d\n", + func, (int16_t)get_double(nvram->adf_rear_leading_edge)); + DBG (dbg_level, "%s: ADF rear side edge: %d\n", + func, (int16_t)get_double(nvram->adf_rear_side_edge)); + + DBG (dbg_level, "%s: born month: %d\n", + func, get_double(nvram->born_month)); + DBG (dbg_level, "%s: born day: %d\n", + func, get_double(nvram->born_day)); + DBG (dbg_level, "%s: born year: %d\n", + func, get_double(nvram->born_year)); + + DBG (dbg_level, "%s: first scan month: %d\n", + func, get_double(nvram->first_scan_month)); + DBG (dbg_level, "%s: first scan day: %d\n", + func, get_double(nvram->first_scan_day)); + DBG (dbg_level, "%s: first scan year: %d\n", + func, get_double(nvram->first_scan_year)); + + + DBG (dbg_level, "%s: vert. magnification: %d\n", + func, get_double(nvram->vertical_magnification)); + DBG (dbg_level, "%s: horiz. magnification: %d\n", + func, get_double(nvram->horizontal_magnification)); + + DBG (dbg_level, "%s: CCD type: %d\n", + func, nvram->ccd_type); + DBG (dbg_level, "%s: scan speed: %d\n", + func, nvram->scan_speed); + + DBG (dbg_level, "%s: serial: '%.24s'\n", /* 24 chars max */ + func, nvram->serial); + + DBG (dbg_level, "%s: power saving time: %d\n", + func, get_double(nvram->power_saving_time)); + + DBG (dbg_level, "%s: auto feed: %d\n", + func, nvram->auto_feed); + + DBG (dbg_level, "%s: roller count: %d\n", + func, get_quad(nvram->roller_count)); + DBG (dbg_level, "%s: multifeed count: %d\n", + func, get_quad(nvram->multifeed_count)); + DBG (dbg_level, "%s: jam count: %d\n", + func, get_quad(nvram->jam_count)); + + DBG (dbg_level, "%s: identify info: '%.16s'\n", /* 16 chars max */ + func, nvram->identify_info); + DBG (dbg_level, "%s: formal_name: '%.16s'\n", /* 16 chars max */ + func, nvram->formal_name); +} + +static void debug_print_avdimen (int dbg_level, char* func, + Avision_Dimensions* avdimen) +{ + DBG (dbg_level, "%s: hw_xres: %d, hw_yres: %d, line_difference: %d\n", + func, avdimen->hw_xres, avdimen->hw_yres, avdimen->line_difference); + + DBG (dbg_level, "%s: tlx: %ld, tly: %ld, brx: %ld, bry: %ld\n", + func, avdimen->tlx, avdimen->tly, + avdimen->brx, avdimen->bry); + + DBG (dbg_level, "%s: hw_pixel_per_line: %d, hw_lines: %d, hw_bytes_per_line: %d\n", + func, avdimen->hw_pixels_per_line, avdimen->hw_lines, avdimen->hw_bytes_per_line); + + DBG (dbg_level, "%s: xres: %d, yres: %d\n", + func, avdimen->xres, avdimen->yres); +} + +static void debug_print_params (int dbg_level, char* func, SANE_Parameters* params) +{ + DBG (dbg_level, "%s: pixel_per_line: %d, lines: %d\n", + func, params->pixels_per_line, params->lines); + + DBG (dbg_level, "%s: depth: %d, bytes_per_line: %d\n", + func, params->depth, params->bytes_per_line); +} + +static void debug_print_calib_format (int dbg_level, char* func, + uint8_t* result) +{ + debug_print_raw (dbg_level + 2, "debug_print_calib_format:\n", result, 32); + + DBG (dbg_level, "%s: [0-1] pixels per line: %d\n", + func, get_double ( &(result[0]) )); + DBG (dbg_level, "%s: [2] bytes per channel: %d\n", func, result[2]); + DBG (dbg_level, "%s: [3] line count: %d\n", func, result[3]); + + DBG (dbg_level, "%s: [4] FLAG:%s%s%s\n", + func, + result[4] == 1?" MUST_DO_CALIBRATION":"", + result[4] == 2?" SCAN_IMAGE_DOES_CALIBRATION":"", + result[4] == 3?" NEEDS_NO_CALIBRATION":""); + + DBG (dbg_level, "%s: [5] Ability1:%s%s%s%s%s%s%s%s\n", + func, + BIT(result[5],7)?" NONE_PACKED":" PACKED", + BIT(result[5],6)?" INTERPOLATED":"", + BIT(result[5],5)?" SEND_REVERSED":"", + BIT(result[5],4)?" PACKED_DATA":"", + BIT(result[5],3)?" COLOR_CALIB":"", + BIT(result[5],2)?" DARK_CALIB":"", + BIT(result[5],1)?" NEEDS_WHITE_BLACK_SHADING_DATA":"", + BIT(result[5],0)?" NEEDS_CALIB_TABLE_CHANNEL_BY_CHANNEL":""); + + DBG (dbg_level, "%s: [6] R gain: %d\n", func, result[6]); + DBG (dbg_level, "%s: [7] G gain: %d\n", func, result[7]); + DBG (dbg_level, "%s: [8] B gain: %d\n", func, result[8]); + + DBG (dbg_level, "%s: [9-10] R shading target: %x\n", + func, get_double ( &(result[9]) ) ); + DBG (dbg_level, "%s: [11-12] G shading target: %x\n", + func, get_double ( &(result[11]) ) ); + DBG (dbg_level, "%s: [13-14] B shading target: %x\n", + func, get_double ( &(result[13]) ) ); + + DBG (dbg_level, "%s: [15-16] R dark shading target: %x\n", + func, get_double ( &(result[15]) ) ); + DBG (dbg_level, "%s: [17-18] G dark shading target: %x\n", + func, get_double ( &(result[17]) ) ); + DBG (dbg_level, "%s: [19-20] B dark shading target: %x\n", + func, get_double ( &(result[19]) ) ); + + DBG (dbg_level, "%s: [21] true-gray gain: %d\n", func, result[21]); + DBG (dbg_level, "%s: [22-23] true-gray shading target: %x\n", + func, get_double ( &(result[22]) ) ); + + DBG (dbg_level, "%s: [24-25] true-gray dark shading target: %x\n", + func, get_double ( &(result[24]) ) ); +} + +static void debug_print_accel_info (int dbg_level, char* func, + uint8_t* result) +{ + debug_print_raw (dbg_level + 2, "debug_print_accel_info:\n", result, 24); + + DBG (dbg_level, "%s: [0-1] acceleration step count: %d\n", + func, get_double ( &(result[0]) )); + DBG (dbg_level, "%s: [2-3] stable step count: %d\n", + func, get_double ( &(result[2]) )); + DBG (dbg_level, "%s: [4-7] table units: %d\n", + func, get_quad ( &(result[4]) )); + DBG (dbg_level, "%s: [8-11] base units: %d\n", + func, get_quad ( &(result[8]) )); + DBG (dbg_level, "%s: [12-13] start speed: %d\n", + func, get_double ( &(result[12]) )); + DBG (dbg_level, "%s: [14-15] target speed: %d\n", + func, get_double ( &(result[14]) )); + DBG (dbg_level, "%s: [16] ability:%s%s\n", + func, + BIT(result[16],0)?" TWO_BYTES_PER_ELEM":" SINGLE_BYTE_PER_ELEM", + BIT(result[16],1)?" LOW_HIGH_ORDER":" HIGH_LOW_ORDER"); + DBG (dbg_level, "%s: [17] table count: %d\n", func, result[17]); + +} + +static void debug_print_window_descriptor (int dbg_level, char* func, + command_set_window_window* window) +{ + debug_print_raw (dbg_level + 1, "window_data_header: \n", + (uint8_t*)(&window->header), + sizeof(window->header)); + + debug_print_raw (dbg_level + 1, "window_descriptor: \n", + (uint8_t*)(&window->descriptor), + sizeof(*window) - + sizeof(window->header)); + + DBG (dbg_level, "%s: [0] window_id: %d\n", func, + window->descriptor.winid); + DBG (dbg_level, "%s: [2-3] x-axis res: %d\n", func, + get_double (window->descriptor.xres)); + DBG (dbg_level, "%s: [4-5] y-axis res: %d\n", func, + get_double (window->descriptor.yres)); + DBG (dbg_level, "%s: [6-9] x-axis upper left: %d\n", + func, get_quad (window->descriptor.ulx)); + DBG (dbg_level, "%s: [10-13] y-axis upper left: %d\n", + func, get_quad (window->descriptor.uly)); + DBG (dbg_level, "%s: [14-17] window width: %d\n", func, + get_quad (window->descriptor.width)); + DBG (dbg_level, "%s: [18-21] window length: %d\n", func, + get_quad (window->descriptor.length)); + DBG (dbg_level, "%s: [22] brightness: %d\n", func, + window->descriptor.brightness); + DBG (dbg_level, "%s: [23] threshold: %d\n", func, + window->descriptor.threshold); + DBG (dbg_level, "%s: [24] contrast: %d\n", func, + window->descriptor.contrast); + DBG (dbg_level, "%s: [25] image composition: %x\n", func, + window->descriptor.image_comp); + DBG (dbg_level, "%s: [26] bits per channel: %d\n", func, + window->descriptor.bpc); + DBG (dbg_level, "%s: [27-28] halftone pattern: %x\n", func, + get_double (window->descriptor.halftone)); + DBG (dbg_level, "%s: [29] padding_and_bitset: %x\n", func, + window->descriptor.padding_and_bitset); + DBG (dbg_level, "%s: [30-31] bit ordering: %x\n", func, + get_double (window->descriptor.bitordering)); + DBG (dbg_level, "%s: [32] compression type: %x\n", func, + window->descriptor.compr_type); + DBG (dbg_level, "%s: [33] compression argument: %x\n", func, + window->descriptor.compr_arg); + DBG (dbg_level, "%s: [34-35] paper length: %x\n", func, + get_double (window->descriptor.paper_length) ); + DBG (dbg_level, "%s: [40] vendor id: %x\n", func, + window->descriptor.vendor_specific); + DBG (dbg_level, "%s: [41] param length: %d\n", func, + window->descriptor.paralen); + DBG (dbg_level, "%s: [42] bitset1: %x\n", func, + window->avision.bitset1); + DBG (dbg_level, "%s: [43] highlight: %d\n", func, + window->avision.highlight); + DBG (dbg_level, "%s: [44] shadow: %d\n", func, + window->avision.shadow); + DBG (dbg_level, "%s: [45-46] line-width: %d\n", func, + get_double (window->avision.line_width)); + DBG (dbg_level, "%s: [47-48] line-count: %d\n", func, + get_double (window->avision.line_count)); + DBG (dbg_level, "%s: [49] bitset2: %x\n", func, + window->avision.type.normal.bitset2); + DBG (dbg_level, "%s: [50] ir exposure time: %x\n", + func, window->avision.type.normal.ir_exposure_time); + + DBG (dbg_level, "%s: [51-52] r exposure: %x\n", func, + get_double (window->avision.type.normal.r_exposure_time)); + DBG (dbg_level, "%s: [53-54] g exposure: %x\n", func, + get_double (window->avision.type.normal.g_exposure_time)); + DBG (dbg_level, "%s: [55-56] b exposure: %x\n", func, + get_double (window->avision.type.normal.b_exposure_time)); + + DBG (dbg_level, "%s: [57] bitset3: %x\n", func, + window->avision.type.normal.bitset3); + DBG (dbg_level, "%s: [58] auto focus: %d\n", func, + window->avision.type.normal.auto_focus); + DBG (dbg_level, "%s: [59] line-width (MSB): %d\n", + func, window->avision.type.normal.line_width_msb); + DBG (dbg_level, "%s: [60] line-count (MSB): %d\n", + func, window->avision.type.normal.line_count_msb); + DBG (dbg_level, "%s: [61] background lines: %d\n", + func, window->avision.type.normal.background_lines); +} + +static int write_pnm_header (FILE* f, color_mode m, int depth, int width, int height) +{ + int maxval = (1 << depth) - 1; + const char* hdr_str = NULL; + /* construct PNM header */ + + switch (m) { + case AV_THRESHOLDED: + case AV_DITHERED: + hdr_str = "P4\n%d %d\n"; + break; + case AV_GRAYSCALE: + case AV_GRAYSCALE12: + case AV_GRAYSCALE16: + hdr_str = "P5\n%d %d\n%d\n"; + break; + case AV_TRUECOLOR: + case AV_TRUECOLOR12: + case AV_TRUECOLOR16: + hdr_str = "P6\n%d %d\n%d\n"; + break; + case AV_COLOR_MODE_LAST: + ; /* silence compiler warning */ + } + + return fprintf (f, hdr_str, width, height, maxval); +} + +static SANE_Status +sense_handler (int fd, u_char* sense, void* arg) +{ + SANE_Status status = SANE_STATUS_IO_ERROR; /* default case */ + + char* text; + char textbuf[64]; + + uint8_t error_code = sense[0] & 0x7f; + uint8_t sense_key = sense[2] & 0xf; + uint8_t additional_sense = sense[7]; + + fd = fd; /* silence gcc */ + arg = arg; /* silence gcc */ + + DBG (3, "sense_handler:\n"); + + switch (error_code) + { + case 0x70: + text = "standard sense"; + break; + case 0x7f: + text = "Avision-specific sense"; + break; + default: + text = "unknown sense"; + } + + debug_print_raw (1, "sense_handler: data:\n", sense, 8 + additional_sense); + + /* request valid? */ + if (! (sense[0] & (1<<7))) { + DBG (1, "sense_handler: sense not valid ...\n"); + return status; + } + + switch (sense_key) + { + case 0x00: + status = SANE_STATUS_GOOD; + text = "ok ?!?"; + break; + case 0x02: + text = "NOT READY"; + break; + case 0x03: + text = "MEDIUM ERROR (mostly ADF)"; + status = SANE_STATUS_JAMMED; + break; + case 0x04: + text = "HARDWARE ERROR"; + break; + case 0x05: + text = "ILLEGAL REQUEST"; + break; + case 0x06: + text = "UNIT ATTENTION"; + break; + case 0x09: + text = "VENDOR SPECIFIC"; + break; + case 0x0b: + text = "ABORTED COMMAND"; + status = SANE_STATUS_CANCELLED; /* AV610C2 cancel button */ + break; + default: + sprintf (textbuf, "got unknown sense code 0x%02x", (int)sense_key); + text = textbuf; + } + + DBG (1, "sense_handler: sense code: %s\n", text); + + if (sense[2] & (1<<6)) + DBG (1, "sense_handler: end of scan\n"); + else + DBG (1, "sense_handler: scan has not yet been completed\n"); + + if (sense[2] & (1<<5)) + DBG (1, "sense_handler: incorrect logical length\n"); + else + DBG (1, "sense_handler: correct logical length\n"); + + { + uint8_t asc = sense[12]; + uint8_t ascq = sense[13]; + +#define ADDITIONAL_SENSE(asc,ascq,txt) \ + case ( (asc << 8) + ascq): text = txt; break + + switch ( (asc << 8) + ascq ) + { + /* normal */ + ADDITIONAL_SENSE (0x00,0x00, "No additional sense information"); + ADDITIONAL_SENSE (0x00,0x06, "I/O process terminated"); + ADDITIONAL_SENSE (0x15,0x01, "Mechanical positioning error"); + + ADDITIONAL_SENSE (0x15,0x02, "Flatbed Home Sensor Error (OKI only"); + ADDITIONAL_SENSE (0x15,0x03, "ADF Home Sensor Error (OKI only)"); + ADDITIONAL_SENSE (0x15,0x04, "Lock Error (OKI only)"); + + ADDITIONAL_SENSE (0x1a,0x00, "parameter list length error"); + + ADDITIONAL_SENSE (0x20,0x00, "Invalid command"); + ADDITIONAL_SENSE (0x24,0x00, "Invalid field in CDB"); + ADDITIONAL_SENSE (0x25,0x00, "Logical unit not supported"); + ADDITIONAL_SENSE (0x26,0x00, "Invalid field in parameter list"); + ADDITIONAL_SENSE (0x26,0x01, "parameter not supported"); + ADDITIONAL_SENSE (0x26,0x02, "parameter value invalid"); + ADDITIONAL_SENSE (0x29,0x00, "Power-on, reset or bus device reset occurred"); + ADDITIONAL_SENSE (0x2c,0x02, "Invalid combination of window specified"); + ADDITIONAL_SENSE (0x2f,0x00, "Command cleared by another initiator"); + + ADDITIONAL_SENSE (0x3D,0x00, "Invalid Bit in Identify Message"); + + ADDITIONAL_SENSE (0x43,0x00, "Message error"); + ADDITIONAL_SENSE (0x44,0x00, "Internal target failure"); + ADDITIONAL_SENSE (0x44,0x01, "Flatbed DRAM Error(OKI only)"); + ADDITIONAL_SENSE (0x44,0x02, "ADF DRAM Error(OKI only)"); + ADDITIONAL_SENSE (0x44,0x03, "Write NVRAM Error"); + ADDITIONAL_SENSE (0x47,0x00, "SCSI parity error"); + ADDITIONAL_SENSE (0x49,0x00, "Invalid message error"); + + ADDITIONAL_SENSE (0x60,0x00, "Lamp failure"); + ADDITIONAL_SENSE (0x60,0x01, "Flatbed Lamp error (Oki only)"); + ADDITIONAL_SENSE (0x60,0x02, "ADF lamp error (Oki only)"); + ADDITIONAL_SENSE (0x62,0x00, "Scan head positioning error"); + + ADDITIONAL_SENSE (0x80,0x01, "ADF paper jam"; status = SANE_STATUS_JAMMED); + ADDITIONAL_SENSE (0x80,0x02, "ADF cover open"; status = SANE_STATUS_COVER_OPEN); + ADDITIONAL_SENSE (0x80,0x03, "ADF chute empty"; status = SANE_STATUS_NO_DOCS); + ADDITIONAL_SENSE (0x80,0x04, "ADF paper end"; status = SANE_STATUS_EOF); + ADDITIONAL_SENSE (0x80,0x05, "Multi-feed (AV220,Kodak)"); + ADDITIONAL_SENSE (0x80,0x06, "ADF prefeeding (OKI only)"); + ADDITIONAL_SENSE (0x80,0x07, "Flatbed cover open (OKI only)"; status = SANE_STATUS_COVER_OPEN); + ADDITIONAL_SENSE (0x80,0x08, "FW module doesn't match with scanner"); + ADDITIONAL_SENSE (0x80,0x09, "Papers fed from multiple trays (DM272)"); + ADDITIONAL_SENSE (0x80,0x0A, "ADF Paper Start"); + ADDITIONAL_SENSE (0x80,0x0B, "Multiple ADF paper End and Start"); + ADDITIONAL_SENSE (0x80,0x0C, "Multiple ADF paper End"); + + /* film scanner */ + ADDITIONAL_SENSE (0x81,0x00, "ADF/MFP front door open"; status = SANE_STATUS_COVER_OPEN); + ADDITIONAL_SENSE (0x81,0x01, "ADF holder cartridge open"; status = SANE_STATUS_COVER_OPEN); + ADDITIONAL_SENSE (0x81,0x02, "ADF no film inside"; status = SANE_STATUS_NO_DOCS); + ADDITIONAL_SENSE (0x81,0x03, "ADF initial load fail"); + ADDITIONAL_SENSE (0x81,0x04, "ADF film end"; status = SANE_STATUS_NO_DOCS); + ADDITIONAL_SENSE (0x81,0x05, "ADF forward feed error"); + ADDITIONAL_SENSE (0x81,0x06, "ADF rewind error"); + ADDITIONAL_SENSE (0x81,0x07, "ADF set unload"); + ADDITIONAL_SENSE (0x81,0x08, "ADF adapter error"); + + ADDITIONAL_SENSE (0xA0,0x01, "Filter Positioning Error"); + + ADDITIONAL_SENSE (0x90,0x00, "Scanner busy (FW busy)"); + + default: + sprintf (textbuf, "Unknown sense code asc: 0x%02x, ascq: 0x%02x", + (int)asc, (int)ascq); + text = textbuf; + } + +#undef ADDITIONAL_SENSE + + DBG (1, "sense_handler: sense code: %s\n", text); + + /* sense code specific for invalid request + * it is possible to get a detailed error location here ;-)*/ + if (sense_key == 0x05) { + if (sense[15] & (1<<7) ) + { + if (sense[15] & (1<<6) ) + DBG (1, "sense_handler: error in command parameter\n"); + else + DBG (1, "sense_handler: error in data parameter\n"); + + DBG (1, "sense_handler: error in parameter byte: %d, %x\n", + get_double(&(sense[16])), get_double(&(sense[16]))); + + /* bit pointer valid ?*/ + if (sense[15] & (1<<3) ) + DBG (1, "sense_handler: error in command parameter\n"); + else + DBG (1, "sense_handler: bit pointer invalid\n"); + } + } + } + + return status; +} + +/* + * Avision scsi/usb multiplexers - to keep the code clean: + */ + +static SANE_Status +avision_usb_status (Avision_Connection* av_con, int retry, int timeout) +{ + SANE_Status status = 0; + uint8_t usb_status[1] = {0}; + size_t count = 0; + int t_retry = retry; + +#define valid_status(status,a) (status == SANE_STATUS_GOOD ? a : 0) + + DBG (4, "avision_usb_status: timeout %d, %d retries\n", timeout, retry); +#ifndef HAVE_SANEI_USB_SET_TIMEOUT +#error "You must update include/sane/sanei_usb.h and sanei/sanei_usb.c accordingly!" +#endif + sanei_usb_set_timeout (timeout); + + /* 1st try bulk transfers - they are more lightweight ... */ + for (; + count == 0 && + (av_con->usb_status == AVISION_USB_BULK_STATUS || + av_con->usb_status == AVISION_USB_UNTESTED_STATUS) && + retry > 0; + --retry) + { + count = sizeof (usb_status); + + DBG (5, "==> (bulk read) going down ...\n"); + status = sanei_usb_read_bulk (av_con->usb_dn, usb_status, + &count); + DBG (5, "<== (bulk read) got: %ld, status: %d\n", + (u_long)count, valid_status(status, usb_status[0])); + + if (count > 0) { + av_con->usb_status = AVISION_USB_BULK_STATUS; + } + } + + /* reset retry count ... */ + retry = t_retry; + + /* 2nd try interrupt status read - if not yet disabled */ + for (; + count == 0 && + (av_con->usb_status == AVISION_USB_INT_STATUS || + av_con->usb_status == AVISION_USB_UNTESTED_STATUS) && + retry > 0; + --retry) + { + count = sizeof (usb_status); + + DBG (5, "==> (interrupt read) going down ...\n"); + status = sanei_usb_read_int (av_con->usb_dn, usb_status, + &count); + DBG (5, "<== (interrupt read) got: %ld, status: %d\n", + (u_long)count, valid_status(status, usb_status[0])); + + if (count > 0) + av_con->usb_status = AVISION_USB_INT_STATUS; + } + + if (status != SANE_STATUS_GOOD) + return status; + + if (count == 0) + return SANE_STATUS_IO_ERROR; + + /* 0 = ok, 2 => request sense, 8 ==> busy, else error */ + switch (usb_status[0]) + { + case AVISION_USB_GOOD: + return SANE_STATUS_GOOD; + case AVISION_USB_REQUEST_SENSE: + DBG (2, "avision_usb_status: Needs to request sense!\n"); + return SANE_STATUS_INVAL; + case AVISION_USB_BUSY: + DBG (2, "avision_usb_status: Busy!\n"); + return SANE_STATUS_DEVICE_BUSY; + default: + DBG (1, "avision_usb_status: Unknown!\n"); + return SANE_STATUS_INVAL; + } +} + +static SANE_Status avision_open (const char* device_name, + Avision_Connection* av_con, + SANEI_SCSI_Sense_Handler sense_handler, + void *sense_arg) +{ + if (av_con->connection_type == AV_SCSI) { + return sanei_scsi_open (device_name, &(av_con->scsi_fd), + sense_handler, sense_arg); + } + else { + SANE_Status status; + status = sanei_usb_open (device_name, &(av_con->usb_dn)); + return status; + } +} + +static SANE_Status avision_open_extended (const char* device_name, + Avision_Connection* av_con, + SANEI_SCSI_Sense_Handler sense_handler, + void *sense_arg, int *buffersize) +{ + if (av_con->connection_type == AV_SCSI) { + return sanei_scsi_open_extended (device_name, &(av_con->scsi_fd), + sense_handler, sense_arg, buffersize); + } + else { + SANE_Status status; + status = sanei_usb_open (device_name, &(av_con->usb_dn)); + return status; + } +} + +static void avision_close (Avision_Connection* av_con) +{ + if (av_con->connection_type == AV_SCSI) { + sanei_scsi_close (av_con->scsi_fd); + av_con->scsi_fd = -1; + } + else { + sanei_usb_close (av_con->usb_dn); + av_con->usb_dn = -1; + } +} + +static SANE_Bool avision_is_open (Avision_Connection* av_con) +{ + if (av_con->connection_type == AV_SCSI) { + return av_con->scsi_fd >= 0; + } + else { + return av_con->usb_dn >= 0; + } +} + +static SANE_Status avision_cmd (Avision_Connection* av_con, + const void* cmd, size_t cmd_size, + const void* src, size_t src_size, + void* dst, size_t* dst_size) +{ + if (av_con->connection_type == AV_SCSI) { + return sanei_scsi_cmd2 (av_con->scsi_fd, cmd, cmd_size, + src, src_size, dst, dst_size); + } + else { + SANE_Status status = SANE_STATUS_GOOD; + + size_t i, count, out_count; + /* some commands on some devices need a rather long time to respond */ +#define STD_TIMEOUT 30000 +#define STD_STATUS_TIMEOUT 10000 + int retry = 4; + int write_timeout = STD_TIMEOUT; + int read_timeout = STD_TIMEOUT; + int status_timeout = STD_STATUS_TIMEOUT; + + /* simply to allow nicer code below */ + const uint8_t* m_cmd = (const uint8_t*)cmd; + const uint8_t* m_src = (const uint8_t*)src; + uint8_t* m_dst = (uint8_t*)dst; + + /* may I vote for the possibility to use C99 ... */ +#define min_usb_size 10 +#define max_usb_size 256 * 1024 /* or 0x10000, used by AV Windows driver during background raster read, ... ? */ + + /* 1st send command data - at least 10 Bytes for USB scanners */ + uint8_t enlarged_cmd [min_usb_size]; + if (cmd_size < min_usb_size) { + DBG (1, "filling command to have a length of 10, was: %lu\n", (u_long) cmd_size); + memcpy (enlarged_cmd, m_cmd, cmd_size); + memset (enlarged_cmd + cmd_size, 0, min_usb_size - cmd_size); + m_cmd = enlarged_cmd; + cmd_size = min_usb_size; + } + + /* per command class timeout tweaks */ + switch (m_cmd[0]) { + case AVISION_SCSI_INQUIRY: + read_timeout = 1000; /* quickly timeout on initial detection */ + status_timeout = 1000; + break; + case AVISION_SCSI_TEST_UNIT_READY: + read_timeout = 15000; /* quickly timeout on initial detection */ + status_timeout = 15000; + break; + } + + DBG (7, "Timeouts: write: %d, read: %d, status: %d\n", + write_timeout, read_timeout, status_timeout); + +write_usb_cmd: + if (--retry == 0) { + DBG (1, "Max retry count reached: I/O error\n"); + return SANE_STATUS_IO_ERROR; + } + + count = cmd_size; + + sanei_usb_set_timeout (write_timeout); + DBG (8, "try to write cmd, count: %lu.\n", (u_long) count); + status = sanei_usb_write_bulk (av_con->usb_dn, m_cmd, &count); + + DBG (8, "wrote %lu bytes\n", (u_long) count); + if (status != SANE_STATUS_GOOD || count != cmd_size) { + DBG (3, "=== Got error %d trying to write, wrote: %ld. ===\n", + status, (long)count); + + if (status != SANE_STATUS_GOOD) /* == SANE_STATUS_EOF) */ { + DBG (3, "try to read status to clear the FIFO\n"); + status = avision_usb_status (av_con, 1, 500); + if (status != SANE_STATUS_GOOD) { + DBG (3, "=== Got error %d trying to read status. ===\n", status); + return SANE_STATUS_IO_ERROR; + } + else + goto write_usb_cmd; + } else { + DBG (3, "Retrying to send command\n"); + goto write_usb_cmd; + } + + return SANE_STATUS_IO_ERROR; + } + + /* 2nd send command data (if any) */ + for (i = 0; i < src_size; ) { + + count = src_size - i; + /* if (count > max_usb_size) + count = max_usb_size; */ + + DBG (8, "try to write src, count: %lu.\n", (u_long) count); + sanei_usb_set_timeout (write_timeout); + status = sanei_usb_write_bulk (av_con->usb_dn, &(m_src[i]), &count); + + DBG (8, "wrote %lu bytes\n", (u_long) count); + if (status == SANE_STATUS_GOOD) { + i += count; + } + else { + goto write_usb_cmd; + } + } + + /* 3rd: read the resulting data (payload) (if any) */ + if (status == SANE_STATUS_GOOD && dst != NULL && *dst_size > 0) { + out_count = 0; + sanei_usb_set_timeout (read_timeout); + while (out_count < *dst_size) { + count = (*dst_size - out_count); + + DBG (8, "try to read %lu bytes\n", (u_long) count); + status = sanei_usb_read_bulk(av_con->usb_dn, &(m_dst[out_count]), + &count); + DBG (8, "read %lu bytes\n", (u_long) count); + + if (count == 1 && (*dst_size - out_count > 1)) { + DBG (1, "Got 1 byte - status? (%d) Resending.\n", m_dst[out_count]); + goto write_usb_cmd; + } + else if (count > 0) { + out_count += count; + } + else { + DBG (1, "No data arrived.\n"); + goto write_usb_cmd; + } + } + } + + /* last: read the device status via a pseudo interrupt transfer + * this is needed - otherwise the scanner will hang ... */ + sanei_usb_set_timeout (status_timeout); + status = avision_usb_status (av_con, /*retry*/ 1, status_timeout); + /* next i/o hardening attempt - and yes this gets ugly ... */ + if (status != SANE_STATUS_GOOD && status != SANE_STATUS_INVAL) + goto write_usb_cmd; + + if (status == SANE_STATUS_INVAL) { + struct { + command_header header; + uint8_t pad[4]; + } sense_cmd; + + uint8_t sense_buffer[22]; + + DBG (3, "Error during status read!\n"); + DBG (3, "=== Try to request sense ===\n"); + + /* we can not call avision_cmd recursively - we might ending in + an endless recursion requesting sense for failing request + sense transfers ...*/ + + memset (&sense_cmd, 0, sizeof (sense_cmd) ); + memset (&sense_buffer, 0, sizeof (sense_buffer) ); + sense_cmd.header.opc = AVISION_SCSI_REQUEST_SENSE; + sense_cmd.header.len = sizeof (sense_buffer); + + count = sizeof(sense_cmd); + + DBG (8, "try to write %lu bytes\n", (u_long) count); + sanei_usb_set_timeout (write_timeout); + status = sanei_usb_write_bulk (av_con->usb_dn, + (uint8_t*) &sense_cmd, &count); + DBG (8, "wrote %lu bytes\n", (u_long) count); + + if (status != SANE_STATUS_GOOD) { + DBG (3, "=== Got error %d trying to request sense! ===\n", status); + } + else { + count = sizeof (sense_buffer); + + DBG (8, "try to read %lu bytes sense data\n", (u_long) count); + sanei_usb_set_timeout (read_timeout); + status = sanei_usb_read_bulk(av_con->usb_dn, sense_buffer, &count); + DBG (8, "read %lu bytes sense data\n", (u_long) count); + + /* we need to read out the status from the scanner i/o buffer */ + status = avision_usb_status (av_con, 1, status_timeout); + + /* some scanner return NEED_SENSE even after reading it */ + if (status != SANE_STATUS_GOOD && status != SANE_STATUS_INVAL) + DBG (3, "=== Got error %d trying to read sense! ===\n", status); + else { + /* read complete -> call our sense handler */ + status = sense_handler (-1, sense_buffer, 0); + } + } /* end read sense data */ + } /* end request sense */ + return status; + } /* end cmd usb */ +} + +/* A bubble sort for the calibration. It only sorts the first third + * and returns an average of the top 2/3 values. The input data is + * 16bit big endian and the count is the count of the words - not + * bytes! */ + +static uint16_t +bubble_sort (uint8_t* sort_data, size_t count) +{ + size_t i, j, limit, k; + double sum = 0.0; + + limit = count / 3; + + for (i = 0; i < limit; ++i) + { + uint16_t ti = 0; + uint16_t tj = 0; + + for (j = (i + 1); j < count; ++j) + { + ti = get_double ((sort_data + i*2)); + tj = get_double ((sort_data + j*2)); + + if (ti > tj) { + set_double ((sort_data + i*2), tj); + set_double ((sort_data + j*2), ti); + } + } + } + + for (k = 0, i = limit; i < count; ++i) { + sum += get_double ((sort_data + i*2)); + ++ k; + } + + /* DBG (7, "bubble_sort: %d values for average\n", k); */ + + if (k > 0) /* if avg to compute */ + return (uint16_t) (sum / k); + else + return (uint16_t) (sum); /* always zero? */ +} + +static SANE_Status +add_color_mode (Avision_Device* dev, color_mode mode, SANE_String name) +{ + int i; + DBG (3, "add_color_mode: %d %s\n", mode, name); + + for (i = 0; i < AV_COLOR_MODE_LAST; ++i) + { + if (dev->color_list [i] == 0) { + dev->color_list [i] = strdup (name); + dev->color_list_num [i] = mode; + return SANE_STATUS_GOOD; + } else if (strcmp (dev->color_list [i], name) == 0) { + /* already in list */ + return SANE_STATUS_GOOD; + } + } + + DBG (3, "add_color_mode: failed\n"); + return SANE_STATUS_NO_MEM; +} + +static int +last_color_mode (Avision_Device* dev) +{ + int i = 1; + + while (dev->color_list [i] != 0 && i < AV_COLOR_MODE_LAST) + ++i; + + /* we are off by one */ + --i; + + return i; +} + +static color_mode +match_color_mode (Avision_Device* dev, SANE_String name) +{ + int i; + DBG (3, "match_color_mode:\n"); + + for (i = 0; i < AV_COLOR_MODE_LAST; ++i) + { + if (dev->color_list [i] != 0 && strcmp (dev->color_list [i], name) == 0) { + DBG (3, "match_color_mode: found at %d mode: %d\n", + i, dev->color_list_num [i]); + return dev->color_list_num [i]; + } + } + + DBG (3, "match_color_mode: source mode invalid\n"); + return AV_GRAYSCALE; +} + +static SANE_Bool +color_mode_is_shaded (color_mode mode) +{ + return mode >= AV_GRAYSCALE; +} + +static SANE_Bool +color_mode_is_color (color_mode mode) +{ + return mode >= AV_TRUECOLOR; +} + +static SANE_Bool +is_adf_scan (Avision_Scanner* s) +{ + return s->hw->scanner_type == AV_SHEETFEED || (s->hw->scanner_type == AV_FLATBED && s->source_mode_dim == AV_ADF_DIM); + +} + +static SANE_Status +add_source_mode (Avision_Device* dev, source_mode mode, SANE_String name) +{ + int i; + + for (i = 0; i < AV_SOURCE_MODE_LAST; ++i) + { + if (dev->source_list [i] == 0) { + dev->source_list [i] = strdup (name); + dev->source_list_num [i] = mode; + return SANE_STATUS_GOOD; + } else if (strcmp (dev->source_list [i], name) == 0) { + /* already in list */ + return SANE_STATUS_GOOD; + } + } + + return SANE_STATUS_NO_MEM; +} + +static source_mode +match_source_mode (Avision_Device* dev, SANE_String name) +{ + int i; + + DBG (3, "match_source_mode: \"%s\"\n", name); + + for (i = 0; i < AV_SOURCE_MODE_LAST; ++i) + { + if (dev->source_list [i] != 0 && strcmp (dev->source_list [i], name) == 0) { + DBG (3, "match_source_mode: found at %d mode: %d\n", + i, dev->source_list_num [i]); + return dev->source_list_num [i]; + } + } + + DBG (3, "match_source_mode: source mode invalid\n"); + return AV_NORMAL; +} + +static source_mode_dim +match_source_mode_dim (source_mode sm) +{ + DBG (3, "match_source_mode_dim: %d\n", sm); + + switch (sm) { + case AV_NORMAL: + return AV_NORMAL_DIM; + case AV_TRANSPARENT: + return AV_TRANSPARENT_DIM; + case AV_ADF: + case AV_ADF_REAR: + case AV_ADF_DUPLEX: + return AV_ADF_DIM; + default: + DBG (3, "match_source_mode_dim: source mode invalid\n"); + return AV_NORMAL_DIM; + } +} + +static int +get_pixel_boundary (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + int boundary; + + switch (s->c_mode) { + case AV_TRUECOLOR: + case AV_TRUECOLOR12: + case AV_TRUECOLOR16: + boundary = dev->inquiry_color_boundary; + break; + case AV_GRAYSCALE: + case AV_GRAYSCALE12: + case AV_GRAYSCALE16: + boundary = dev->inquiry_gray_boundary; + break; + case AV_DITHERED: + if (dev->inquiry_asic_type != AV_ASIC_C5) + boundary = 32; + else + boundary = dev->inquiry_dithered_boundary; + break; + case AV_THRESHOLDED: + if (dev->inquiry_asic_type != AV_ASIC_C5) + boundary = 32; + else + boundary = dev->inquiry_thresholded_boundary; + break; + default: + boundary = 8; + } + + return boundary; +} + +static SANE_Status +compute_parameters (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + + int boundary = get_pixel_boundary (s); + SANE_Bool gray_mode = color_mode_is_shaded (s->c_mode); + + /* interlaced duplex (higher end) or flipping paper (HP8xxx)? */ + s->avdimen.interlaced_duplex = s->source_mode == AV_ADF_DUPLEX && + dev->inquiry_duplex_interlaced; + + /* for infra-red we use the same code path es for interlaced + duplex */ + if (s->val[OPT_IR].w) + s->avdimen.interlaced_duplex = 1; + +#ifdef AVISION_ENHANCED_SANE + /* quick fix for Microsoft Office Products ... */ + switch (s->c_mode) + { + case AV_THRESHOLDED: + case AV_DITHERED: + /* our backend already has this restriction - so this line is for + documentation purposes only */ + boundary = boundary > 32 ? boundary : 32; + break; + case AV_GRAYSCALE: + case AV_GRAYSCALE12: + case AV_GRAYSCALE16: + boundary = boundary > 4 ? boundary : 4; + break; + case AV_TRUECOLOR: + case AV_TRUECOLOR12: + case AV_TRUECOLOR16: + /* 12 bytes for 24bit color - 48bit is untested w/ Office */ + boundary = boundary > 4 ? boundary : 4; + break; + } +#endif + + DBG (3, "sane_compute_parameters:\n"); + + DBG (3, "sane_compute_parameters: boundary %d, gray_mode: %d, \n", + boundary, gray_mode); + + /* TODO: Implement different x/y resolutions support */ + s->avdimen.xres = s->val[OPT_RESOLUTION].w; + s->avdimen.yres = s->val[OPT_RESOLUTION].w; + + /* soft scale ? */ + if (dev->hw->feature_type & AV_SOFT_SCALE) { + /* find supported hardware resolution */ + const int* hw_res; + const int* hw_res_list = + dev->inquiry_asic_type == AV_ASIC_C5 ? hw_res_list_c5 : hw_res_list_generic; + + for (hw_res = hw_res_list; *hw_res && *hw_res < s->avdimen.xres; ++hw_res) + /* just iterate */; + s->avdimen.hw_xres = *hw_res; + + for (hw_res = hw_res_list; *hw_res && *hw_res < s->avdimen.yres; ++hw_res) + /* just iterate */; + s->avdimen.hw_yres = *hw_res; + + DBG (3, "sane_compute_parameters: soft scale, hw res: %dx%d\n", + s->avdimen.hw_xres, + s->avdimen.hw_yres); + + if (!s->avdimen.hw_xres || ! s->avdimen.hw_yres) { + DBG (1, "sane_compute_parameters: no matching HW res for: %dx%d\n", + s->avdimen.xres, + s->avdimen.yres); + return SANE_STATUS_INVAL; + } + } + else { + s->avdimen.hw_xres = s->val[OPT_RESOLUTION].w; + s->avdimen.hw_yres = s->val[OPT_RESOLUTION].w; + } + + DBG (3, "sane_compute_parameters: tlx: %f, tly: %f, brx: %f, bry: %f\n", + SANE_UNFIX (s->val[OPT_TL_X].w), SANE_UNFIX (s->val[OPT_TL_Y].w), + SANE_UNFIX (s->val[OPT_BR_X].w), SANE_UNFIX (s->val[OPT_BR_Y].w)); + + /* window parameter in pixel */ + s->avdimen.tlx = s->avdimen.hw_xres * SANE_UNFIX (s->val[OPT_TL_X].w) + / MM_PER_INCH; + s->avdimen.tly = s->avdimen.hw_yres * SANE_UNFIX (s->val[OPT_TL_Y].w) + / MM_PER_INCH; + s->avdimen.brx = s->avdimen.hw_xres * SANE_UNFIX (s->val[OPT_BR_X].w) + / MM_PER_INCH; + s->avdimen.bry = s->avdimen.hw_yres * SANE_UNFIX (s->val[OPT_BR_Y].w) + / MM_PER_INCH; + + /* line difference */ + if (color_mode_is_color (s->c_mode) && + dev->inquiry_needs_software_colorpack && + dev->inquiry_line_difference) + { + s->avdimen.line_difference = + (dev->inquiry_line_difference * s->avdimen.hw_yres) / dev->inquiry_optical_res; + + s->avdimen.bry += 2 * s->avdimen.line_difference; + + /* limit bry + line_difference to real scan boundary */ + { + long y_max = dev->inquiry_y_ranges[s->source_mode_dim] * + s->avdimen.hw_yres / MM_PER_INCH; + DBG (3, "sane_compute_parameters: y_max: %ld, bry: %ld, line_difference: %d\n", + y_max, s->avdimen.bry, s->avdimen.line_difference); + + if (s->avdimen.bry + 2 * s->avdimen.line_difference > y_max) { + DBG (1, "sane_compute_parameters: bry limited!\n"); + s->avdimen.bry = y_max - 2 * s->avdimen.line_difference; + } + } + + } /* end if needs software colorpack */ + else { + s->avdimen.line_difference = 0; + } + + /* add overscan */ + if (dev->inquiry_tune_scan_length && is_adf_scan (s)) { + /* some extra effort for precise rounding ... */ + int overscan = (s->avdimen.hw_yres * + (SANE_UNFIX (s->val[OPT_OVERSCAN_TOP].w) + + SANE_UNFIX (s->val[OPT_OVERSCAN_BOTTOM].w)) + (MM_PER_INCH - 1) + ) / MM_PER_INCH; + DBG (3, "sane_compute_parameters: overscan lines: %d\n", overscan); + s->avdimen.bry += overscan; + } + + /* rear offset compensation */ + if (s->avdimen.interlaced_duplex && dev->hw->feature_type & AV_REAR_OFFSET) { + const double offset = 0.5; /* in current affected models 1/2 inch */ + s->avdimen.rear_offset = (int) (offset * s->avdimen.hw_yres); + DBG (1, "sane_compute_parameters: rear_offset: %d!\n", s->avdimen.rear_offset); + /* we do not limit against the bottom-y here, as rear offset always + applies to ADF scans, only */ + } + else { + s->avdimen.rear_offset = 0; + } + + memset (&s->params, 0, sizeof (s->params)); + + s->avdimen.hw_pixels_per_line = (s->avdimen.brx - s->avdimen.tlx); + s->avdimen.hw_pixels_per_line -= s->avdimen.hw_pixels_per_line % boundary; + + s->avdimen.hw_lines = (s->avdimen.bry - s->avdimen.tly - + 2 * s->avdimen.line_difference); + + if (s->avdimen.interlaced_duplex && dev->scanner_type != AV_FILM) + s->avdimen.hw_lines -= s->avdimen.hw_lines % dev->read_stripe_size; + + s->params.pixels_per_line = s->avdimen.hw_pixels_per_line * s->avdimen.xres / s->avdimen.hw_xres; + s->params.lines = s->avdimen.hw_lines * s->avdimen.xres / s->avdimen.hw_xres; + if (is_adf_scan (s)) + /* we can't know how many lines we'll see with an ADF because that depends on the paper length */ + s->params.lines = -1; + if (s->c_mode == AV_THRESHOLDED || s->c_mode == AV_DITHERED) + s->params.pixels_per_line -= s->params.pixels_per_line % 8; + + debug_print_avdimen (1, "sane_compute_parameters", &s->avdimen); + + switch (s->c_mode) + { + case AV_THRESHOLDED: + s->params.format = SANE_FRAME_GRAY; + s->avdimen.hw_bytes_per_line = s->avdimen.hw_pixels_per_line / 8; + s->params.bytes_per_line = s->params.pixels_per_line / 8; + s->params.depth = 1; + break; + case AV_DITHERED: + s->params.format = SANE_FRAME_GRAY; + s->avdimen.hw_bytes_per_line = s->avdimen.hw_pixels_per_line / 8; + s->params.bytes_per_line = s->params.pixels_per_line / 8; + s->params.depth = 1; + break; + case AV_GRAYSCALE: + s->params.format = SANE_FRAME_GRAY; + s->avdimen.hw_bytes_per_line = s->avdimen.hw_pixels_per_line; + s->params.bytes_per_line = s->params.pixels_per_line; + s->params.depth = 8; + break; + case AV_GRAYSCALE12: + case AV_GRAYSCALE16: + s->params.format = SANE_FRAME_GRAY; + s->avdimen.hw_bytes_per_line = s->avdimen.hw_pixels_per_line * 2; + s->params.bytes_per_line = s->params.pixels_per_line * 2; + s->params.depth = 16; + break; + case AV_TRUECOLOR: + s->params.format = SANE_FRAME_RGB; + s->avdimen.hw_bytes_per_line = s->avdimen.hw_pixels_per_line * 3; + s->params.bytes_per_line = s->params.pixels_per_line * 3; + s->params.depth = 8; + break; + case AV_TRUECOLOR12: + case AV_TRUECOLOR16: + s->params.format = SANE_FRAME_RGB; + s->avdimen.hw_bytes_per_line = s->avdimen.hw_pixels_per_line * 3 * 2; + s->params.bytes_per_line = s->params.pixels_per_line * 3 * 2; + s->params.depth = 16; + break; + default: + DBG (1, "Invalid mode. %d\n", s->c_mode); + return SANE_STATUS_INVAL; + } /* end switch */ + + s->params.last_frame = SANE_TRUE; + + debug_print_params (1, "sane_compute_parameters", &s->params); + return SANE_STATUS_GOOD; +} + +static SANE_Status +inquiry (Avision_Connection av_con, uint8_t* data, size_t len) +{ + SANE_Status status; + command_header inquiry; + int try = 2; + + DBG (3, "inquiry: length: %ld\n", (long)len); + + memset (&inquiry, 0, sizeof(inquiry)); + inquiry.opc = AVISION_SCSI_INQUIRY; + inquiry.len = len; + + do { + size_t size = inquiry.len; + + DBG (3, "inquiry: inquiring ...\n"); + status = avision_cmd (&av_con, &inquiry, sizeof (inquiry), 0, 0, + data, &size); + if (status == SANE_STATUS_GOOD && size == inquiry.len) + break; + + DBG (1, "inquiry: inquiry failed (%s)\n", sane_strstatus (status)); + --try; + } while (try > 0); + + return status; +} + +static SANE_Status +wait_ready (Avision_Connection* av_con, int delay) +{ + SANE_Status status; + int try; + + for (try = 0; try < 10; ++ try) + { + DBG (3, "wait_ready: sending TEST_UNIT_READY\n"); + status = avision_cmd (av_con, test_unit_ready, sizeof (test_unit_ready), + 0, 0, 0, 0); + sleep (delay); + + switch (status) + { + default: + /* Ignore errors while waiting for scanner to become ready. + Some SCSI drivers return EIO while the scanner is + returning to the home position. */ + DBG (1, "wait_ready: test unit ready failed (%s)\n", + sane_strstatus (status)); + /* fall through */ + case SANE_STATUS_DEVICE_BUSY: + break; + case SANE_STATUS_GOOD: + return status; + } + } + DBG (1, "wait_ready: timed out after %d attempts\n", try); + return SANE_STATUS_INVAL; +} + +static SANE_Status +wait_4_light (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + + /* read stuff */ + struct command_read rcmd; + char* light_status[] = + { "off", "on", "warming up", "needs warm up test", + "light check error", "RESERVED" }; + + SANE_Status status; + uint8_t result; + int try; + size_t size = 1; + + DBG (3, "wait_4_light: getting light status.\n"); + + memset (&rcmd, 0, sizeof (rcmd)); + + rcmd.opc = AVISION_SCSI_READ; + rcmd.datatypecode = 0xa0; /* get light status */ + set_double (rcmd.datatypequal, dev->data_dq); + set_triple (rcmd.transferlen, size); + + for (try = 0; try < 90; ++ try) { + + DBG (5, "wait_4_light: read bytes %lu\n", (u_long) size); + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), 0, 0, &result, &size); + + if (status != SANE_STATUS_GOOD || size != sizeof (result)) { + DBG (1, "wait_4_light: read failed (%s)\n", sane_strstatus (status)); + return status; + } + + DBG (3, "wait_4_light: command is %d. Result is %s\n", + status, light_status[(result>4)?5:result]); + + if (result == 1) { + return SANE_STATUS_GOOD; + } + else if (dev->hw->feature_type & AV_LIGHT_CHECK_BOGUS) { + DBG (3, "wait_4_light: scanner marked as returning bogus values in device-list!!\n"); + return SANE_STATUS_GOOD; + } + else { + struct command_send scmd; + uint8_t light_on = 1; + + /* turn on the light */ + DBG (3, "wait_4_light: setting light status.\n"); + + memset (&scmd, 0, sizeof (scmd)); + + scmd.opc = AVISION_SCSI_SEND; + scmd.datatypecode = 0xa0; /* send light status */ + set_double (scmd.datatypequal, dev->data_dq); + set_triple (scmd.transferlen, size); + + status = avision_cmd (&s->av_con, &scmd, sizeof (scmd), + &light_on, sizeof (light_on), 0, 0); + + if (status != SANE_STATUS_GOOD) { + DBG (1, "wait_4_light: send failed (%s)\n", sane_strstatus (status)); + return status; + } + } + sleep (1); + } + + DBG (1, "wait_4_light: timed out after %d attempts\n", try); + return SANE_STATUS_DEVICE_BUSY; +} + +static SANE_Status +set_power_save_time (Avision_Scanner* s, int time) +{ + struct { + struct command_send cmd; + uint8_t time[2]; + } scmd; + + Avision_Device* dev = s->hw; + SANE_Status status; + + DBG (3, "set_power_save_time: time %d\n", time); + + memset (&scmd, 0, sizeof (scmd)); + scmd.cmd.opc = AVISION_SCSI_SEND; + scmd.cmd.datatypecode = 0xA2; /* power-saving timer */ + set_double (scmd.cmd.datatypequal, dev->data_dq); + set_triple (scmd.cmd.transferlen, sizeof (scmd.time) ); + + set_double (scmd.time, time); + + status = avision_cmd (&s->av_con, &scmd.cmd, sizeof (scmd.cmd), + &scmd.time, sizeof (scmd.time), 0, 0); + if (status != SANE_STATUS_GOOD) + DBG (1, "set_power_save_time: send_data (%s)\n", sane_strstatus (status)); + return status; +} + +static SANE_Status +get_firmware_status (Avision_Connection* av_con) +{ + /* read stuff */ + struct command_read rcmd; + size_t size; + SANE_Status status; + + firmware_status result; + + DBG (3, "get_firmware_status\n"); + + size = sizeof (result); + + memset (&rcmd, 0, sizeof (rcmd)); + rcmd.opc = AVISION_SCSI_READ; + + rcmd.datatypecode = 0x90; /* firmware status */ + set_double (rcmd.datatypequal, 0); /* dev->data_dq not available */ + set_triple (rcmd.transferlen, size); + + status = avision_cmd (av_con, &rcmd, sizeof (rcmd), 0, 0, &result, &size); + if (status != SANE_STATUS_GOOD || size != sizeof (result)) { + DBG (1, "get_firmware_status: read failed (%s)\n", + sane_strstatus (status)); + return (status); + } + + debug_print_raw (6, "get_firmware_status: raw data:\n", (uint8_t*)&result, size); + + DBG (3, "get_firmware_status: [0] needs firmware %x\n", result.download_firmware); + DBG (3, "get_firmware_status: [1] side edge: %d\n", get_double ( result.first_effective_pixel_flatbed )); + DBG (3, "get_firmware_status: [3] side edge: %d\n", get_double ( result.first_effective_pixel_adf_front )); + DBG (3, "get_firmware_status: [5] side edge: %d\n", get_double ( result.first_effective_pixel_adf_rear )); + + return SANE_STATUS_GOOD; +} + +static SANE_Status +get_flash_ram_info (Avision_Connection* av_con) +{ + /* read stuff */ + struct command_read rcmd; + size_t size; + SANE_Status status; + uint8_t result[40]; + + DBG (3, "get_flash_ram_info\n"); + + size = sizeof (result); + + memset (&rcmd, 0, sizeof (rcmd)); + rcmd.opc = AVISION_SCSI_READ; + + rcmd.datatypecode = 0x6a; /* flash ram information */ + set_double (rcmd.datatypequal, 0); /* dev->data_dq not available */ + set_triple (rcmd.transferlen, size); + + status = avision_cmd (av_con, &rcmd, sizeof (rcmd), 0, 0, result, &size); + if (status != SANE_STATUS_GOOD || size != sizeof (result)) { + DBG (1, "get_flash_ram_info: read failed (%s)\n", + sane_strstatus (status)); + return (status); + } + + debug_print_raw (6, "get_flash_ram_info: raw data:\n", result, size); + + DBG (3, "get_flash_ram_info: [0] data type %x\n", result [0]); + DBG (3, "get_flash_ram_info: [1] Ability1:%s%s%s%s%s%s%s%s\n", + BIT(result[1],7)?" RESERVED_BIT7":"", + BIT(result[1],6)?" RESERVED_BIT6":"", + BIT(result[1],5)?" FONT(r/w)":"", + BIT(result[1],4)?" FPGA(w)":"", + BIT(result[1],3)?" FMDBG(r)":"", + BIT(result[1],2)?" RAWLINE(r)":"", + BIT(result[1],1)?" FIRMWARE(r/w)":"", + BIT(result[1],0)?" CTAB(r/w)":""); + + DBG (3, "get_flash_ram_info: [2-5] size CTAB: %d\n", + get_quad ( &(result[2]) ) ); + + DBG (3, "get_flash_ram_info: [6-9] size FIRMWARE: %d\n", + get_quad ( &(result[6]) ) ); + + DBG (3, "get_flash_ram_info: [10-13] size RAWLINE: %d\n", + get_quad ( &(result[10]) ) ); + + DBG (3, "get_flash_ram_info: [14-17] size FMDBG: %d\n", + get_quad ( &(result[14]) ) ); + + DBG (3, "get_flash_ram_info: [18-21] size FPGA: %d\n", + get_quad ( &(result[18]) ) ); + + DBG (3, "get_flash_ram_info: [22-25] size FONT: %d\n", + get_quad ( &(result[22]) ) ); + + DBG (3, "get_flash_ram_info: [26-29] size RESERVED: %d\n", + get_quad ( &(result[26]) ) ); + + DBG (3, "get_flash_ram_info: [30-33] size RESERVED: %d\n", + get_quad ( &(result[30]) ) ); + + return SANE_STATUS_GOOD; +} + +static SANE_Status +get_nvram_data (Avision_Scanner* s, nvram_data* nvram) +{ + /* read stuff */ + struct command_send rcmd; + + size_t size; + SANE_Status status; + + DBG (3, "get_nvram_data\n"); + + size = sizeof (*nvram); + + memset (&rcmd, 0, sizeof (rcmd)); + memset (nvram, 0, size); + + rcmd.opc = AVISION_SCSI_READ; + + rcmd.datatypecode = 0x69; /* Read NVM RAM data */ + set_double (rcmd.datatypequal, 0); /* dev->data_dq not available */ + set_triple (rcmd.transferlen, size); + + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), 0, 0, + nvram, &size); + if (status != SANE_STATUS_GOOD) { + DBG (1, "get_nvram_data: read failed (%s)\n", + sane_strstatus (status)); + return (status); + } + + debug_print_nvram_data (5, "get_nvram_data", nvram); + + return SANE_STATUS_GOOD; +} + +static SANE_Status +get_and_parse_nvram (Avision_Scanner* s, char* str, int n) +{ + SANE_Status status; + int i = 0; + int x; + nvram_data nvram; + uint8_t inquiry_result [AVISION_INQUIRY_SIZE_V1]; + + status = inquiry (s->av_con, inquiry_result, sizeof(inquiry_result)); + if (status == SANE_STATUS_GOOD) { + i += snprintf (str+i, n-i, "Vendor: %.8s", + inquiry_result+8); + i += snprintf (str+i, n-i, "\nModel: %.16s", + inquiry_result+16); + i += snprintf (str+i, n-i, "\nFirmware: %.4s", + inquiry_result+32); + } + + if (!s->hw->inquiry_nvram_read) + return SANE_STATUS_GOOD; + + status = get_nvram_data (s, &nvram); + if (status == SANE_STATUS_GOOD) + { + if (nvram.serial[0]) + i += snprintf (str+i, n-i, "\nSerial: %.24s", + nvram.serial); + + if (nvram.born_year) + i += snprintf (str+i, n-i, "\nManufacturing date: %d-%d-%d", + get_double(nvram.born_year), + get_double(nvram.born_month), + get_double(nvram.born_day)); + if (nvram.first_scan_year) + i += snprintf (str+i, n-i, "\nFirst scan date: %d-%d-%d", + get_double(nvram.first_scan_year), + get_double(nvram.first_scan_month), + get_double(nvram.first_scan_day)); + + x = get_quad (nvram.flatbed_scans); + if (x) + i += snprintf (str+i, n-i, "\nFlatbed scans: %d", x); + x = get_quad (nvram.pad_scans); + if (x) + i += snprintf (str+i, n-i, "\nPad scans: %d", x); + x = get_quad (nvram.adf_simplex_scans); + if (x) + i += snprintf (str+i, n-i, "\nADF simplex scans: %d", x); + x = get_quad (nvram.adf_duplex_scans); + if (x) + i += snprintf (str+i, n-i, "\nADF duplex scans: %d", x); + } + + return status; +} + +static SANE_Status +get_power_save_time (Avision_Scanner* s, SANE_Word* time) +{ + SANE_Status status; + nvram_data nvram; + + DBG (3, "get_power_save_time\n"); + + if (!s->hw->inquiry_nvram_read) + return SANE_STATUS_INVAL; + + status = get_nvram_data (s, &nvram); + + if (status != SANE_STATUS_GOOD) { + DBG (1, "get_power_save_time: read nvram failed (%s)\n", sane_strstatus (status)); + return status; + } + + *time = get_double (nvram.power_saving_time); + + return SANE_STATUS_GOOD; +} + +#ifdef NEEDED + +static SANE_Status +send_nvram_data (Avision_Connection* av_con) +{ + /* read stuff */ + struct command_send scmd; + size_t size; + SANE_Status status; + + DBG (3, "send_nvram_data\n"); + + size = sizeof (c7_nvram); + + memset (&scmd, 0, sizeof (scmd)); + scmd.opc = AVISION_SCSI_SEND; + + scmd.datatypecode = 0x85; /* nvram data */ + set_double (scmd.datatypequal, 0); /* dev->data_dq not available */ + set_triple (scmd.transferlen, size); + + status = avision_cmd (av_con, &scmd, sizeof (scmd), &c7_nvram, size, + 0, 0); + if (status != SANE_STATUS_GOOD) { + DBG (1, "send_nvram_data: send failed (%s)\n", + sane_strstatus (status)); + return (status); + } + + return SANE_STATUS_GOOD; +} + +static SANE_Status +send_flash_ram_data (Avision_Connection* av_con) +{ + /* read stuff */ + struct command_send scmd; + size_t size; + SANE_Status status; + + DBG (3, "send_flash_ram_data\n"); + + size = sizeof (c7_flash_ram); + + memset (&scmd, 0, sizeof (scmd)); + scmd.opc = AVISION_SCSI_SEND; + + scmd.datatypecode = 0x86; /* flash data */ + set_double (scmd.datatypequal, 0); + set_triple (scmd.transferlen, size); + + status = avision_cmd (av_con, &scmd, sizeof (scmd), &c7_flash_ram, size, + 0, 0); + if (status != SANE_STATUS_GOOD) { + DBG (1, "send_flash_ram_data: send failed (%s)\n", + sane_strstatus (status)); + return (status); + } + + return SANE_STATUS_GOOD; +} +#endif + + +static SANE_Status +adf_reset (Avision_Scanner* s) +{ + SANE_Status status; + Avision_Device* dev = s->hw; + struct command_send scmd; + struct command_read rcmd; + uint8_t payload[4]; + size_t size; + size_t n; + int i; + DBG (3, "adf_reset\n"); + + /* loop twice */ + for (i=1; i >= 0; i--) { + n=i; + memset (&scmd, 0, sizeof (scmd)); + memset (&payload, 0, sizeof (payload)); + scmd.opc = AVISION_SCSI_SEND; + scmd.datatypecode = 0xD0; /* unknown */ + set_double (scmd.datatypequal, 0); + size = 2; + set_triple (scmd.transferlen, size); + payload[1] = 0x10 * i; /* write 0x10 the first time, 0x00 the second */ + status = avision_cmd (&s->av_con, &scmd, sizeof (scmd), payload, size, 0, 0); + if (status != SANE_STATUS_GOOD) { + DBG (1, "adf_reset: write %d failed (%s)\n", (2-i), + sane_strstatus (status)); + return (status); + } + DBG (3, "adf_reset: write %d complete.\n", (2-i)); + + memset (&rcmd, 0, sizeof (rcmd)); + memset (&payload, 0, sizeof (payload)); + rcmd.opc = AVISION_SCSI_READ; + rcmd.datatypecode = 0x69; /* Read NVRAM data */ + set_double (rcmd.datatypequal, dev->data_dq); + size = 4 - i; /* read 3 bytes the first time, 4 the second */ + set_triple (rcmd.transferlen, size); + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), 0, 0, payload, &size); + if (status != SANE_STATUS_GOOD || size != (4-n)) { + DBG (1, "adf_reset: read %lu failed (%s)\n", (2-n), + sane_strstatus (status)); + return (status); + } + debug_print_raw (3, "adf_reset: raw data:\n", payload, size); + } + return SANE_STATUS_GOOD; +} + + +static SANE_Status +get_accessories_info (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + int try = 3; + + /* read stuff */ + struct command_read rcmd; + size_t size; + SANE_Status status; + uint8_t result[8]; + + char* adf_model[] = + { "Origami", "Oodles", "HP9930", "unknown" }; + const int adf_models = sizeof (adf_model) / sizeof(char*) - 1; + + DBG (3, "get_accessories_info\n"); + + size = sizeof (result); + + memset (&rcmd, 0, sizeof (rcmd)); + rcmd.opc = AVISION_SCSI_READ; + + rcmd.datatypecode = 0x64; /* detect accessories */ + set_double (rcmd.datatypequal, dev->data_dq); + set_triple (rcmd.transferlen, size); + + /* after resetting the ADF unit, try reprobing it again */ + RETRY: + + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), 0, 0, result, &size); + if (status != SANE_STATUS_GOOD || size != sizeof (result)) { + DBG (1, "get_accessories_info: read failed (%s)\n", + sane_strstatus (status)); + return (status); + } + + debug_print_raw (6, "get_accessories_info: raw data:\n", result, size); + + DBG (3, "get_accessories_info: [0] ADF: %x\n", result[0]); + DBG (3, "get_accessories_info: [1] Light Box: %x\n", result[1]); + + DBG (3, "get_accessories_info: [2] ADF model: %d (%s)\n", + result [2], + adf_model[ (result[2] < adf_models) ? result[2] : adf_models ]); + + dev->inquiry_adf |= result [0]; + + if (dev->hw->feature_type2 & AV_ADF_FLIPPING_DUPLEX) + { + if (result[0] == 1) + { + dev->inquiry_duplex = 1; + dev->inquiry_duplex_interlaced = 0; + } else if (result[0] == 0 && result[2] != 0) { + /* Sometimes the scanner will report that there is no ADF attached, yet + * an ADF model number will still be reported. This happens on the + * HP8200 series and possibly others. In this case we need to reset the + * the adf and try reading it again. + */ + DBG (3, "get_accessories_info: Found ADF model number but the ADF-present flag is not set. Trying to recover...\n"); + status = adf_reset (s); + if (status != SANE_STATUS_GOOD) { + DBG (3, "get_accessories_info: Failed to reset ADF: %s\n", sane_strstatus (status)); + return status; + } + DBG (1, "get_accessories_info: Waiting while ADF firmware resets...\n"); + sleep(3); + status = wait_ready (&s->av_con, 1); + if (status != SANE_STATUS_GOOD) { + DBG (1, "get_accessories_info: wait_ready() failed: %s\n", sane_strstatus (status)); + return status; + } + if (try) { + try--; + goto RETRY; + } + DBG (1, "get_accessories_info: Maximum retries attempted, ADF unresponsive.\n"); + return SANE_STATUS_UNSUPPORTED; + } + } + + /* only honor a 1, some scanner without adapter set 0xff */ + if (result[1] == 1) + dev->inquiry_light_box = 1; + + return SANE_STATUS_GOOD; +} + + +/* Returns a pointer to static char* strings or NULL for cancel (we do + not want to start memcmp'ing for the cancel case). */ +static const char* +string_for_button (Avision_Scanner* s, int button) +{ + static char buffer [16]; + Avision_Device* dev = s->hw; + + /* dev->sane.model + dev->inquiry_asic_type */ + + if (dev->inquiry_buttons == 1) + goto return_scan; + + /* simplex / duplex buttons */ + if (strcmp (dev->sane.vendor, "Xerox") == 0 || + strcmp (dev->sane.vendor, "Visioneer") == 0 || + strcmp (dev->sane.model, "AV121") == 0 || + strcmp (dev->sane.model, "AV122") == 0 + ) + { + switch (button) + { + case 1: return "simplex"; + case 2: return "duplex"; + } + } + + if (strcmp (dev->sane.model, "AV210C2") == 0 || + strcmp (dev->sane.model, "AV210D2+") == 0 || + strcmp (dev->sane.model, "AV220C2") == 0 || + strcmp (dev->sane.model, "AV610C2") == 0 + ) + { + if (button == 1) + return NULL; /* cancel */ + else + goto return_scan; + } + + /* those are unique, right now */ + if (strcmp (dev->sane.model, "AV610") == 0) + { + switch (button) + { + case 0: return "email"; + case 1: return "copy"; + case 2: return "scan"; + } + } + + /* last resort */ + snprintf (buffer, sizeof (buffer), "button%d", button); + return buffer; + + return_scan: + return "scan"; +} + +static SANE_Status +get_button_status (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + + /* read stuff */ + struct command_read rcmd; + size_t size; + SANE_Status status; + /* was only 6 in an old SPEC - maybe we need a feature override :-( -ReneR */ + struct { + uint8_t press_state; + uint8_t buttons[5]; + uint8_t display; /* AV220 et.al. 7 segment LED display */ + uint8_t reserved[9]; + } result; + + unsigned int i; + + DBG (3, "get_button_status:\n"); + + size = sizeof (result); + + /* AV220 et.al. */ + if (! (dev->hw->feature_type & AV_INT_BUTTON)) + { + memset (&rcmd, 0, sizeof (rcmd)); + rcmd.opc = AVISION_SCSI_READ; + + rcmd.datatypecode = 0xA1; /* button status */ + set_double (rcmd.datatypequal, dev->data_dq); + set_triple (rcmd.transferlen, size); + + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), 0, 0, + (uint8_t*)&result, &size); + if (status != SANE_STATUS_GOOD || size != sizeof (result)) { + DBG (1, "get_button_status: read failed (%s)\n", sane_strstatus (status)); + return status; + } + } + else + { + /* only try to read the first 8 bytes ...*/ + size = 8; + + /* no SCSI equivalent */ + /* either there was a button press and this completes quickly + or there is no point waiting for a future press */ + sanei_usb_set_timeout (100); /* 10th of a second */ + DBG (5, "==> (interrupt read) going down ...\n"); + status = sanei_usb_read_int (s->av_con.usb_dn, (uint8_t*)&result, + &size); + DBG (5, "==> (interrupt read) got: %ld\n", (long)size); + + if (status != SANE_STATUS_GOOD) { + DBG (1, "get_button_status: interrupt read failed (%s)\n", + sane_strstatus (status)); + return SANE_STATUS_GOOD; + } + + if (size < sizeof (result)) + memset ((char*)result.buttons + size, 0, sizeof (result) - size); + + /* hack to fill in meaningful values for the AV 210 / 610 and + under some conditions the AV 220 */ + if (size == 1) { /* AV 210, AV 610 */ + DBG (1, "get_button_status: just one byte, filling the rest\n"); + + if (result.press_state > 0) { + debug_print_raw (6, "get_button_status: raw data\n", + (uint8_t*)&result, size); + result.buttons[0] = result.press_state; + result.press_state = 0x80 | 1; + size = 2; + } + else /* nothing pressed */ + return SANE_STATUS_GOOD; + } + else if (size >= 8 && result.press_state == 0) { /* AV 220 */ + + debug_print_raw (6, "get_button_status: raw data\n", + (uint8_t*)&result, size); + + DBG (1, "get_button_status: zero buttons - filling values ...\n"); + + /* simulate button press of the last button ... */ + result.press_state = 0x80 | 1; + result.buttons[0] = dev->inquiry_buttons; /* 1 based */ + } + } + + debug_print_raw (6, "get_button_status: raw data\n", + (uint8_t*)&result, size); + + DBG (3, "get_button_status: [0] Button status: %x\n", result.press_state); + for (i = 0; i < 5; ++i) + DBG (3, "get_button_status: [%d] Button number %d: %x\n", i+1, i, + result.buttons[i]); + DBG (3, "get_button_status: [7] Display: %d\n", result.display); + + { + char* message_begin = s->val[OPT_MESSAGE].s; + char* message_end = s->val[OPT_MESSAGE].s + s->opt[OPT_MESSAGE].size; + char* message = message_begin; + +#define add_token(format,value) do { \ + int n = snprintf (message, message_end - message, "%s" format, \ + message == message_begin ? "" : ":", value); \ + message += n > 0 ? n : 0; \ + } while (0) + + if (result.display > 0) + add_token ("%d", result.display); + + if (result.press_state >> 7) /* AV220 et.al. bit 6 is long/short press? */ + { + + const unsigned int buttons_pressed = result.press_state & 0x7F; + DBG (3, "get_button_status: %d button(s) pressed\n", buttons_pressed); + + /* reset the hardware button status */ + if (! (dev->hw->feature_type & AV_INT_BUTTON)) + { + struct command_send scmd; + uint8_t button_reset = 1; + + DBG (3, "get_button_status: resetting status\n"); + + memset (&scmd, 0, sizeof (scmd)); + + scmd.opc = AVISION_SCSI_SEND; + scmd.datatypecode = 0xA1; /* button control */ + set_double (scmd.datatypequal, dev->data_dq); + set_triple (scmd.transferlen, size); + + status = avision_cmd (&s->av_con, &scmd, sizeof (scmd), + &button_reset, sizeof (button_reset), 0, 0); + + if (status != SANE_STATUS_GOOD) { + DBG (1, "get_button_status: send failed (%s)\n", + sane_strstatus (status)); + return status; + } + } + + for (i = 0; i < buttons_pressed; ++i) { + const unsigned int button = result.buttons[i] - 1; /* 1 based ... */ + DBG (3, "get_button_status: button %d pressed\n", button); + if (button >= dev->inquiry_buttons) { + DBG (1, "get_button_status: button %d not allocated as not indicated in inquiry\n", + button); + } + else { + const char* label = string_for_button (s, button); + if (label) + add_token ("%s", label); + else + return SANE_STATUS_CANCELLED; + } + } + } + else + DBG (3, "get_button_status: no button pressed\n"); + } + + return SANE_STATUS_GOOD; +#undef add_token +} + +static SANE_Status +get_frame_info (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + + /* read stuff */ + struct command_read rcmd; + size_t size; + SANE_Status status; + uint8_t result[8]; + size_t i; + + DBG (3, "get_frame_info:\n"); + + size = sizeof (result); + + memset (&rcmd, 0, sizeof (rcmd)); + rcmd.opc = AVISION_SCSI_READ; + + rcmd.datatypecode = 0x87; /* film holder sense */ + set_double (rcmd.datatypequal, dev->data_dq); + set_triple (rcmd.transferlen, size); + + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), 0, 0, result, &size); + if (status != SANE_STATUS_GOOD || size != sizeof (result)) { + DBG (1, "get_frame_info: read failed (%s)\n", sane_strstatus (status)); + return (status); + } + + debug_print_raw (6, "get_frame_info: raw data\n", result, size); + + DBG (3, "get_frame_info: [0] Holder type: %s\n", + (result[0]==1)?"APS": + (result[0]==2)?"Film holder (35mm)": + (result[0]==3)?"Slide holder": + (result[0]==0xff)?"Empty":"unknown"); + DBG (3, "get_frame_info: [1] Current frame number: %d\n", result[1]); + DBG (3, "get_frame_info: [2] Frame amount: %d\n", result[2]); + DBG (3, "get_frame_info: [3] Mode: %s\n", BIT(result[3],4)?"APS":"Not APS"); + DBG (3, "get_frame_info: [3] Exposures (if APS): %s\n", + ((i=(BIT(result[3],3)<<1)+BIT(result[2],2))==0)?"Unknown": + (i==1)?"15":(i==2)?"25":"40"); + DBG (3, "get_frame_info: [3] Film Type (if APS): %s\n", + ((i=(BIT(result[1],3)<<1)+BIT(result[0],2))==0)?"Unknown": + (i==1)?"B&W Negative":(i==2)?"Color slide":"Color Negative"); + + dev->holder_type = result[0]; + dev->current_frame = result[1]; + + dev->frame_range.min = 1; + dev->frame_range.quant = 1; + + if (result[0] != 0xff) + dev->frame_range.max = result[2]; + else + dev->frame_range.max = 1; + + return SANE_STATUS_GOOD; +} + +static SANE_Status +get_duplex_info (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + + /* read stuff */ + struct command_read rcmd; + + struct { + uint8_t mode; + uint8_t color_line_difference[2]; + uint8_t gray_line_difference[2]; + uint8_t lineart_line_difference[2]; + uint8_t image_info; + } result; + + size_t size; + SANE_Status status; + + DBG (3, "get_duplex_info:\n"); + + size = sizeof (result); + + memset (&rcmd, 0, sizeof (rcmd)); + rcmd.opc = AVISION_SCSI_READ; + + rcmd.datatypecode = 0xB1; /* read duplex info */ + set_double (rcmd.datatypequal, dev->data_dq); + set_triple (rcmd.transferlen, size); + + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), 0, 0, + &result, &size); + if (status != SANE_STATUS_GOOD || size != sizeof (result)) { + DBG (1, "get_duplex_info: read failed (%s)\n", sane_strstatus (status)); + return (status); + } + + debug_print_raw (6, "get_duplex_info: raw data\n", (uint8_t*)&result, size); + + DBG (3, "get_duplex_info: [0] Mode: %s%s\n", + BIT(result.mode,0)?"MERGED_PAGES":"", + BIT(result.mode,1)?"2ND_PAGE_FOLLOWS":""); + DBG (3, "get_duplex_info: [1-2] Color line difference: %d\n", + get_double(result.color_line_difference)); + DBG (3, "get_duplex_info: [3-4] Gray line difference: %d\n", + get_double(result.gray_line_difference)); + DBG (3, "get_duplex_info: [5-6] Lineart line difference: %d\n", + get_double(result.lineart_line_difference)); + /* isn't this supposed to be result.info ?!? */ + DBG (3, "get_duplex_info: [7] Mode: %s%s%s%s\n", + BIT(result.image_info,0)?" FLATBED_BGR":" FLATBED_RGB", + BIT(result.image_info,1)?" ADF_BGR":" ADF_RGB", + BIT(result.image_info,2)?" FLATBED_NEEDS_MIRROR_IMAGE":"", + BIT(result.image_info,3)?" ADF_NEEDS_MIRROR_IMAGE":""); + + return SANE_STATUS_GOOD; +} + +static SANE_Status +set_frame (Avision_Scanner* s, SANE_Word frame) +{ + struct { + struct command_send cmd; + uint8_t data[8]; + } scmd; + + Avision_Device* dev = s->hw; + SANE_Status status; + + DBG (3, "set_frame: request frame %d\n", frame); + + /* Better check the current status of the film holder, because it + can be changed between scans. */ + status = get_frame_info (s); + if (status != SANE_STATUS_GOOD) + return status; + + /* No film holder? */ + if (dev->holder_type == 0xff) { + DBG (1, "set_frame: No film holder!!\n"); + return SANE_STATUS_INVAL; + } + + /* Requesting frame 0xff indicates eject/rewind */ + if (frame != 0xff && (frame < 1 || frame > dev->frame_range.max) ) { + DBG (1, "set_frame: Illegal frame (%d) requested (min=1, max=%d)\n", + frame, dev->frame_range.max); + return SANE_STATUS_INVAL; + } + + memset (&scmd, 0, sizeof (scmd)); + scmd.cmd.opc = AVISION_SCSI_SEND; + scmd.cmd.datatypecode = 0x87; /* send film holder "sense" */ + set_double (scmd.cmd.datatypequal, dev->data_dq); + set_triple (scmd.cmd.transferlen, sizeof (scmd.data) ); + + scmd.data[0] = dev->holder_type; + scmd.data[1] = frame; + + status = avision_cmd (&s->av_con, &scmd.cmd, sizeof (scmd.cmd), + &scmd.data, sizeof (scmd.data), 0, 0); + if (status != SANE_STATUS_GOOD) { + DBG (1, "set_frame: send_data (%s)\n", sane_strstatus (status)); + } + + return status; +} + +static SANE_Status +attach (SANE_String_Const devname, Avision_ConnectionType con_type, + Avision_Device** devp) +{ + uint8_t result [AVISION_INQUIRY_SIZE_MAX]; + int model_num; + + Avision_Device* dev; + SANE_Status status; + + Avision_Connection av_con; + + char mfg [9]; + char model [17]; + char rev [5]; + + unsigned int i; + char* s; + SANE_Bool found; + + DBG (3, "attach:\n"); + memset (result, 0, sizeof(result)); + + for (dev = first_dev; dev; dev = dev->next) + if (strcmp (dev->sane.name, devname) == 0) { + if (devp) + *devp = dev; + return SANE_STATUS_GOOD; + } + + av_con.connection_type = con_type; + if (av_con.connection_type == AV_USB) + av_con.usb_status = AVISION_USB_UNTESTED_STATUS; + + /* set known USB status type */ + if (attaching_hw && attaching_hw->feature_type & AV_INT_STATUS) + av_con.usb_status = AVISION_USB_INT_STATUS; + + DBG (3, "attach: opening %s\n", devname); + status = avision_open (devname, &av_con, sense_handler, 0); + if (status != SANE_STATUS_GOOD) { + DBG (1, "attach: open failed (%s)\n", sane_strstatus (status)); + return SANE_STATUS_INVAL; + } + + /* first: get the standard inquiry? */ + status = inquiry (av_con, result, AVISION_INQUIRY_SIZE_V1); + if (status != SANE_STATUS_GOOD) { + DBG (1, "attach: 1st inquiry failed (%s)\n", sane_strstatus (status)); + return status; + } + + /* copy string information - and zero terminate them c-style */ + memcpy (&mfg, result + 8, 8); + mfg [8] = 0; + memcpy (&model, result + 16, 16); + model [16] = 0; + memcpy (&rev, result + 32, 4); + rev [4] = 0; + + /* shorten strings (-1 for last index + -1 for last 0; >0 because one char at least) */ + for (i = sizeof (mfg) - 2; i > 0; i--) { + if (mfg[i] == 0x20) + mfg[i] = 0; + else + break; + } + for (i = sizeof (model) - 2; i > 0; i--) { + if (model[i] == 0x20) + model[i] = 0; + else + break; + } + + DBG (1, "attach: Inquiry gives mfg=%s, model=%s, product revision=%s.\n", + mfg, model, rev); + + model_num = 0; + found = 0; + /* while not at at end of list NULL terminator */ + while (Avision_Device_List[model_num].real_mfg != NULL || + Avision_Device_List[model_num].scsi_mfg != NULL) + { + int matches = 0, match_count = 0; /* count number of matches */ + DBG (1, "attach: Checking model: %d\n", model_num); + + if (Avision_Device_List[model_num].scsi_mfg) { + ++match_count; + if (strcmp(mfg, Avision_Device_List[model_num].scsi_mfg) == 0) + ++matches; + } + if (Avision_Device_List[model_num].scsi_model) { + ++match_count; + if (strcmp(model, Avision_Device_List[model_num].scsi_model) == 0) + ++matches; + } + + /* we need 2 matches (mfg, model) for SCSI entries, or the ones available + for "we know what we are looking for" USB entries */ + if ((attaching_hw == &(Avision_Device_List [model_num]) && + matches == match_count) || + matches == 2) + { + DBG (1, "attach: Scanner matched entry: %d: \"%s\", \"%s\", 0x%.4x, 0x%.4x\n", + model_num, + Avision_Device_List[model_num].scsi_mfg, + Avision_Device_List[model_num].scsi_model, + Avision_Device_List[model_num].usb_vendor, + Avision_Device_List[model_num].usb_product); + found = 1; + break; + } + ++model_num; + } + + if (!found) { + DBG (0, "attach: \"%s\" - \"%s\" not yet in whitelist!\n", mfg, model); + DBG (0, "attach: You might want to report this output.\n"); + DBG (0, "attach: To: mike@piratehaven.org (the Avision backend maintainer)\n"); + + status = SANE_STATUS_INVAL; + goto close_scanner_and_return; + } + + /* second: maybe ask for the firmware status and flash ram info */ + if (Avision_Device_List [model_num].feature_type2 & AV_FIRMWARE) + { + DBG (3, "attach: reading firmware status\n"); + status = get_firmware_status (&av_con); + if (status != SANE_STATUS_GOOD) { + DBG (1, "attach: get firmware status failed (%s)\n", + sane_strstatus (status)); + goto close_scanner_and_return; + } + + DBG (3, "attach: reading flash ram info\n"); + status = get_flash_ram_info (&av_con); + if (status != SANE_STATUS_GOOD) { + DBG (1, "attach: get flash ram info failed (%s)\n", + sane_strstatus (status)); + goto close_scanner_and_return; + } + +#ifdef FIRMWARE_DATABASE_INCLUDED + /* Send new NV-RAM (firmware) data */ + status = send_nvram_data (&av_con); + if (status != SANE_STATUS_GOOD) + goto close_scanner_and_return; +#endif + } + + /* third: get the extended Avision inquiry */ + status = inquiry (av_con, result, AVISION_INQUIRY_SIZE_V1); + if (status != SANE_STATUS_GOOD) { + DBG (1, "attach: avision v1 inquiry failed (%s)\n", sane_strstatus (status)); + goto close_scanner_and_return; + } + + dev = malloc (sizeof (*dev)); + if (!dev) { + status = SANE_STATUS_NO_MEM; + goto close_scanner_and_return; + } + + memset (dev, 0, sizeof (*dev)); + + dev->hw = &Avision_Device_List[model_num]; + + dev->sane.name = strdup (devname); + dev->sane.vendor = dev->hw->real_mfg ? dev->hw->real_mfg : strdup (mfg); + dev->sane.model = dev->hw->real_model ? dev->hw->real_model : strdup (model); + dev->connection.connection_type = av_con.connection_type; + dev->connection.usb_status = av_con.usb_status; + + /* and finally Avision even extended this one later on + the AV220C2 does not grok this */ + dev->inquiry_asic_type = (int) result[91]; + if (dev->inquiry_asic_type == AV_ASIC_C6) + { + status = inquiry (av_con, result, AVISION_INQUIRY_SIZE_V2); + if (status != SANE_STATUS_GOOD) { + DBG (1, "attach: avision v2 inquiry failed (%s)\n", sane_strstatus (status)); + goto close_scanner_and_return; + } + } + + debug_print_raw (6, "attach: raw data:\n", result, sizeof (result) ); + + DBG (3, "attach: [8-15] Vendor id.: '%8.8s'\n", result+8); + DBG (3, "attach: [16-31] Product id.: '%16.16s'\n", result+16); + DBG (3, "attach: [32-35] Product rev.: '%4.4s'\n", result+32); + + i = (result[36] >> 4) & 0x7; + switch (result[36] & 0x07) { + case 0: + s = " RGB"; break; + case 1: + s = " BGR"; break; + default: + s = " unknown (RESERVED)"; + } + DBG (3, "attach: [36] Bitfield:%s%s%s%s%s%s%s color plane\n", + BIT(result[36],7)?" ADF":"", + (i==0)?" B&W only":"", + BIT(i, 1)?" 3-pass color":"", + BIT(i, 2)?" 1-pass color":"", + BIT(i, 2) && BIT(i, 0) ?" 1-pass color (ScanPartner only)":"", + BIT(result[36],3)?" IS_NOT_FLATBED":"", + s); + + DBG (3, "attach: [37] Optical res.: %d00 dpi\n", result[37]); + DBG (3, "attach: [38] Maximum res.: %d00 dpi\n", result[38]); + + DBG (3, "attach: [39] Bitfield1:%s%s%s%s%s%s\n", + BIT(result[39],7)?" TRANS":"", + BIT(result[39],6)?" Q_SCAN":"", + BIT(result[39],5)?" EXTENDED_RES":"", + BIT(result[39],4)?" SUPPORTS_CALIB":"", + BIT(result[39],2)?" NEW_PROTOCOL":"", + (result[39] & 0x03) == 0x03 ? " AVISION":" OEM"); + + DBG (3, "attach: [40-41] X res. in gray: %d dpi\n", + get_double ( &(result[40]) )); + DBG (3, "attach: [42-43] Y res. in gray: %d dpi\n", + get_double ( &(result[42]) )); + DBG (3, "attach: [44-45] X res. in color: %d dpi\n", + get_double ( &(result[44]) )); + DBG (3, "attach: [46-47] Y res. in color: %d dpi\n", + get_double ( &(result[46]) )); + DBG (3, "attach: [48-49] USB max read: %d\n", +get_double ( &(result[48] ) )); + + DBG (3, "attach: [50] ESA1:%s%s%s%s%s%s%s%s\n", + BIT(result[50],7)?" LIGHT_CONTROL":"", + BIT(result[50],6)?" BUTTON_CONTROL":"", + BIT(result[50],5)?" NEED_SW_COLORPACK":"", + BIT(result[50],4)?" SW_CALIB":"", + BIT(result[50],3)?" NEED_SW_GAMMA":"", + BIT(result[50],2)?" KEEPS_GAMMA":"", + BIT(result[50],1)?" KEEPS_WINDOW_CMD":"", + BIT(result[50],0)?" XYRES_DIFFERENT":""); + DBG (3, "attach: [51] ESA2:%s%s%s%s%s%s%s%s\n", + BIT(result[51],7)?" EXPOSURE_CTRL":"", + BIT(result[51],6)?" NEED_SW_TRIGGER_CAL":"", + BIT(result[51],5)?" NEED_WHITE_PAPER_CALIB":"", + BIT(result[51],4)?" SUPPORTS_QUALITY_SPEED_CAL":"", + BIT(result[51],3)?" NEED_TRANSP_CAL":"", + BIT(result[51],2)?" HAS_PUSH_BUTTON":"", + BIT(result[51],1)?" NEW_CAL_METHOD_3x3_MATRIX_(NO_GAMMA_TABLE)":"", + BIT(result[51],0)?" ADF_MIRRORS_IMAGE":""); + DBG (3, "attach: [52] ESA3:%s%s%s%s%s%s%s%s\n", + BIT(result[52],7)?" GRAY_WHITE":"", + BIT(result[52],6)?" SUPPORTS_GAIN_CONTROL":"", + BIT(result[52],5)?" SUPPORTS_TET":"", /* "Text Enhanced Technology" */ + BIT(result[52],4)?" 3x3COL_TABLE":"", + BIT(result[52],3)?" 1x3FILTER":"", + BIT(result[52],2)?" INDEX_COLOR":"", + BIT(result[52],1)?" POWER_SAVING_TIMER":"", + BIT(result[52],0)?" NVM_DATA_REC":""); + + /* print some more scanner features/params */ + DBG (3, "attach: [53] line difference (software color pack): %d\n", result[53]); + DBG (3, "attach: [54] color mode pixel boundary: %d\n", result[54]); + DBG (3, "attach: [55] gray mode pixel boundary: %d\n", result[55]); + DBG (3, "attach: [56] 4bit gray mode pixel boundary: %d\n", result[56]); + DBG (3, "attach: [57] lineart mode pixel boundary: %d\n", result[57]); + DBG (3, "attach: [58] halftone mode pixel boundary: %d\n", result[58]); + DBG (3, "attach: [59] error-diffusion mode pixel boundary: %d\n", result[59]); + + DBG (3, "attach: [60] channels per pixel:%s%s%s\n", + BIT(result[60],7)?" 1":"", + BIT(result[60],6)?" 3":"", + (result[60] & 0x3F) != 0 ? " RESERVED":""); + + DBG (3, "attach: [61] bits per channel:%s%s%s%s%s%s%s\n", + BIT(result[61],7)?" 1":"", + BIT(result[61],6)?" 4":"", + BIT(result[61],5)?" 6":"", + BIT(result[61],4)?" 8":"", + BIT(result[61],3)?" 10":"", + BIT(result[61],2)?" 12":"", + BIT(result[61],1)?" 16":""); + + DBG (3, "attach: [62] scanner type:%s%s%s%s%s%s\n", + BIT(result[62],7)?" Flatbed":"", + BIT(result[62],6)?" Roller (ADF)":"", + BIT(result[62],5)?" Flatbed (ADF)":"", + BIT(result[62],4)?" Roller":"", /* does not feed multiple pages, AV25 */ + BIT(result[62],3)?" Film scanner":"", + BIT(result[62],2)?" Duplex":""); + + DBG (3, "attach: [75-76] Max shading target : %x\n", + get_double ( &(result[75]) )); + + DBG (3, "attach: [77-78] Max X of transparency: %d dots * base_dpi\n", + get_double ( &(result[77]) )); + DBG (3, "attach: [79-80] Max Y of transparency: %d dots * base_dpi\n", + get_double ( &(result[79]) )); + + DBG (3, "attach: [81-82] Max X of flatbed: %d dots * base_dpi\n", + get_double ( &(result[81]) )); + DBG (3, "attach: [83-84] Max Y of flatbed: %d dots * base_dpi\n", + get_double ( &(result[83]) )); + + DBG (3, "attach: [85-86] Max X of ADF: %d dots * base_dpi\n", + get_double ( &(result[85]) )); + DBG (3, "attach: [87-88] Max Y of ADF: %d dots * base_dpi\n", + get_double ( &(result[87]) )); /* 0xFFFF means unlimited length */ + + DBG (3, "attach: [89-90] Res. in Ex. mode: %d dpi\n", + get_double ( &(result[89]) )); + + DBG (3, "attach: [91] ASIC: %d\n", result[91]); + + DBG (3, "attach: [92] Buttons: %d\n", result[92]); + + DBG (3, "attach: [93] ESA4:%s%s%s%s%s%s%s%s\n", + BIT(result[93],7)?" SUPPORTS_ACCESSORIES_DETECT":"", + BIT(result[93],6)?" ADF_IS_BGR_ORDERED":"", + BIT(result[93],5)?" NO_SINGLE_CHANNEL_GRAY_MODE":"", + BIT(result[93],4)?" SUPPORTS_FLASH_UPDATE":"", + BIT(result[93],3)?" SUPPORTS_ASIC_UPDATE":"", + BIT(result[93],2)?" SUPPORTS_LIGHT_DETECT":"", + BIT(result[93],1)?" SUPPORTS_READ_PRNU_DATA":"", + BIT(result[93],0)?" FLATBED_MIRRORS_IMAGE":""); + + DBG (3, "attach: [94] ESA5:%s%s%s%s%s%s%s%s\n", + BIT(result[94],7)?" IGNORE_LINE_DIFFERENCE_FOR_ADF":"", + BIT(result[94],6)?" NEEDS_SW_LINE_COLOR_PACK":"", + BIT(result[94],5)?" SUPPORTS_DUPLEX_SCAN":"", + BIT(result[94],4)?" INTERLACED_DUPLEX_SCAN":"", + BIT(result[94],3)?" SUPPORTS_TWO_MODE_ADF_SCANS":"", + BIT(result[94],2)?" SUPPORTS_TUNE_SCAN_LENGTH":"", + BIT(result[94],1)?" SUPPORTS_SWITCH_STRIP_FOR_DESKEW":"", /* Kodak i80 only */ + BIT(result[94],0)?" SEARCHES_LEADING_SIDE_EDGE_BY_FIRMWARE":""); + + DBG (3, "attach: [95] ESA6:%s%s%s%s%s%s%s%s\n", + BIT(result[95],7)?" SUPPORTS_PAPER_SIZE_AUTO_DETECTION":"", + BIT(result[95],6)?" SUPPORTS_DO_HOUSEKEEPING":"", /* Kodak i80 only */ + BIT(result[95],5)?" SUPPORTS_PAPER_LENGTH_SETTING":"", /* AV220, Kodak */ + BIT(result[95],4)?" SUPPORTS_PRE_GAMMA_LINEAR_CORRECTION":"", + BIT(result[95],3)?" SUPPORTS_PREFEEDING":"", /* OKI S9800 */ + BIT(result[95],2)?" SUPPORTS_GET_BACKGROUND_RASTER":"", /* AV220 et.al. */ + BIT(result[95],1)?" SUPPORTS_NVRAM_RESET":"", + BIT(result[95],0)?" SUPPORTS_BATCH_SCAN":""); + + DBG (3, "attach: [128] ESA7:%s%s%s%s%s%s%s%s\n", + BIT(result[128],7)?" SUPPORTS_ADF_CONTINUOUS":"", + BIT(result[128],6)?" SUPPORTS_YCbCr_COLOR":"", + BIT(result[128],5)?" SUPPORTS_ADF_3PASS":"", + BIT(result[128],4)?" SUPPORTS_TUNE_SCAN_LENGTH_HORIZ":"", + BIT(result[128],3)?" SUPPORTS_READ_WRITE_ABILITY_PARAMETER":"", + BIT(result[128],2)?" SUPPORTS_JOB_CONTROL":"", + BIT(result[128],1)?" SUPPORTS_INF_LENGTH":"", + BIT(result[128],0)?" ULTRA_SONIC_DOUBLE_FEED_DETECTION":""); + + DBG (3, "attach: [129] YCbCr:%s%s%s%s%s%s%s%s\n", + BIT(result[129],7)?" YCC4:2:0":"", + BIT(result[129],6)?" YCC(profile2)":"", + BIT(result[129],5)?" YCC(profile3)":"", + BIT(result[129],4)?" YCC(profile4)":"", + BIT(result[129],3)?" JPEG(profile1)":"", + BIT(result[129],2)?" JPEG(profile2)":"", + BIT(result[129],1)?" JPEG(profile3)":"", + BIT(result[129],0)?" JPEG(profile4)":""); + + /* I have no idea how film scanner could reliably be detected -ReneR */ + if (dev->hw->feature_type & AV_FILMSCANNER) { + dev->scanner_type = AV_FILM; + dev->sane.type = "film scanner"; + } + else if ( BIT(result[62],6) || BIT(result[62],4) ) { + dev->scanner_type = AV_SHEETFEED; + dev->sane.type = "sheetfed scanner"; + } + else { + dev->scanner_type = AV_FLATBED; + dev->sane.type = "flatbed scanner"; + } + + dev->inquiry_new_protocol = BIT (result[39],2); + dev->inquiry_asic_type = (int) result[91]; + + dev->inquiry_nvram_read = BIT(result[52],0); + dev->inquiry_power_save_time = BIT(result[52],1); + + dev->inquiry_adf = BIT (result[62], 5); + dev->inquiry_duplex = BIT (result[62], 2) || BIT (result[94], 5); + dev->inquiry_duplex_interlaced = BIT(result[62],2) || BIT (result[94], 4); + /* the first avision scanners (AV3200) do not set the interlaced bit */ + if (dev->inquiry_duplex && dev->inquiry_asic_type < AV_ASIC_C6) + dev->inquiry_duplex_interlaced = 1; + + dev->inquiry_paper_length = BIT (result[95], 5); + dev->inquiry_batch_scan = BIT (result[95], 0); /* AV122, DM152 */ + + dev->inquiry_detect_accessories = BIT (result[93], 7); + + dev->inquiry_needs_calibration = BIT (result[50], 4); + + dev->inquiry_keeps_window = BIT (result[50], 1); + if (Avision_Device_List [model_num].feature_type & AV_DOES_NOT_KEEP_WINDOW) + dev->inquiry_keeps_window = 0; + if (Avision_Device_List [model_num].feature_type & AV_DOES_KEEP_WINDOW) + dev->inquiry_keeps_window = 1; + + dev->inquiry_needs_gamma = BIT (result[50], 3); + dev->inquiry_keeps_gamma = BIT (result[50], 2); + if (Avision_Device_List [model_num].feature_type & AV_DOES_NOT_KEEP_GAMMA) + dev->inquiry_keeps_gamma = 0; + if (Avision_Device_List [model_num].feature_type & AV_DOES_KEEP_GAMMA) + dev->inquiry_keeps_gamma = 1; + + dev->inquiry_3x3_matrix = BIT (result[51], 1); + dev->inquiry_needs_software_colorpack = BIT (result[50],5); + + dev->inquiry_needs_line_pack = BIT (result[94], 6); + + dev->inquiry_adf_need_mirror = BIT (result[51], 0); + dev->inquiry_adf_bgr_order = BIT (result[93], 6); + if (Avision_Device_List [model_num].feature_type & AV_ADF_BGR_ORDER_INVERT) + dev->inquiry_adf_bgr_order = ! dev->inquiry_adf_bgr_order; + + dev->inquiry_light_detect = BIT (result[93], 2); + dev->inquiry_light_control = BIT (result[50], 7); + dev->inquiry_button_control = BIT (result[50], 6) | BIT (result[51],2); + + dev->inquiry_exposure_control = BIT(result[51],7); + dev->inquiry_max_shading_target = get_double ( &(result[75]) ); + + dev->inquiry_color_boundary = result[54]; + if (dev->inquiry_color_boundary == 0) + dev->inquiry_color_boundary = 8; + + dev->inquiry_gray_boundary = result[55]; + if (dev->inquiry_gray_boundary == 0) + dev->inquiry_gray_boundary = 8; + + dev->inquiry_dithered_boundary = result[59]; + if (dev->inquiry_dithered_boundary == 0) + dev->inquiry_dithered_boundary = 8; + + dev->inquiry_thresholded_boundary = result[57]; + if (dev->inquiry_thresholded_boundary == 0) + dev->inquiry_thresholded_boundary = 8; + + dev->inquiry_line_difference = result[53]; + /* compensation according to real world hardware */ + switch (dev->inquiry_asic_type) + { + case AV_ASIC_C2: /* HP 5300 */ + case AV_ASIC_C5: /* HP 53xx R2 */ + dev->inquiry_line_difference /= 2; /* HP 5300 */ + break; + case AV_ASIC_C7: + dev->inquiry_line_difference *= 2; /* AV610C2 */ + break; + default: + ; + } + + if (dev->inquiry_new_protocol) { + dev->inquiry_optical_res = get_double ( &(result[89]) ); + dev->inquiry_max_res = get_double ( &(result[44]) ); + } + else { + dev->inquiry_optical_res = result[37] * 100; + dev->inquiry_max_res = result[38] * 100; + } + + /* fixup max res */ + if (dev->inquiry_optical_res > dev->inquiry_max_res) { + DBG (1, "Inquiry optical resolution > max_resolution, adjusting!\n"); + dev->inquiry_max_res = dev->inquiry_optical_res; + } + + if (dev->inquiry_optical_res == 0) + { + DBG (1, "Inquiry optical resolution is invalid!\n"); + if (dev->hw->feature_type & AV_FORCE_FILM) + dev->inquiry_optical_res = 2438; /* verify */ + if (dev->scanner_type == AV_SHEETFEED) + dev->inquiry_optical_res = 300; + else + dev->inquiry_optical_res = 600; + } + if (dev->inquiry_max_res == 0) { + DBG (1, "Inquiry max resolution is invalid, using 1200 dpi!\n"); + dev->inquiry_max_res = 1200; + } + + DBG (1, "attach: optical resolution set to: %d dpi\n", dev->inquiry_optical_res); + DBG (1, "attach: max resolution set to: %d dpi\n", dev->inquiry_max_res); + + if (BIT(result[60],6)) + dev->inquiry_channels_per_pixel = 3; + else if (BIT(result[60],7)) + dev->inquiry_channels_per_pixel = 1; + else if ( ((result[36] >> 4) & 0x7) > 0) + dev->inquiry_channels_per_pixel = 3; + else + dev->inquiry_channels_per_pixel = 1; + + if (BIT(result[61],1)) + dev->inquiry_bits_per_channel = 16; + else if (BIT(result[61],2)) + dev->inquiry_bits_per_channel = 12; + else if (BIT(result[61],3)) + dev->inquiry_bits_per_channel = 10; + else if (BIT(result[61],4)) + dev->inquiry_bits_per_channel = 8; + else if (BIT(result[61],5)) + dev->inquiry_bits_per_channel = 6; + else if (BIT(result[61],6)) + dev->inquiry_bits_per_channel = 4; + else if (BIT(result[61],7)) + dev->inquiry_bits_per_channel = 1; + else + dev->inquiry_bits_per_channel = 8; /* default for old scanners */ + + if (dev->hw->feature_type & AV_12_BIT_MODE) + dev->inquiry_bits_per_channel = 12; + + if (! (dev->hw->feature_type & AV_GRAY_MODES)) + dev->inquiry_no_gray_modes = BIT(result[93],5); + + DBG (1, "attach: max channels per pixel: %d, max bits per channel: %d\n", + dev->inquiry_channels_per_pixel, dev->inquiry_bits_per_channel); + + if (! (dev->hw->feature_type & AV_NO_BUTTON)) + dev->inquiry_buttons = result[92]; + + /* get max x/y ranges for the different modes */ + { + double base_dpi; /* TODO: make int */ + if (dev->scanner_type != AV_FILM) { + base_dpi = AVISION_BASE_RES; + } else { + /* ZP: The right number is 2820, whether it is 40-41, 42-43, 44-45, + * 46-47 or 89-90 I don't know but I would bet for the last ! + * ReneR: OK. We use it via the optical_res which we need anyway ... + */ + base_dpi = dev->inquiry_optical_res; + } + + /* .1 to slightly increase the size to match the one of American standard paper + formats that would otherwise be .1 mm too large to scan ... */ + dev->inquiry_x_ranges [AV_NORMAL_DIM] = + (double)get_double (&(result[81])) * MM_PER_INCH / base_dpi + .1; + dev->inquiry_y_ranges [AV_NORMAL_DIM] = + (double)get_double (&(result[83])) * MM_PER_INCH / base_dpi; + + dev->inquiry_x_ranges [AV_TRANSPARENT_DIM] = + (double)get_double (&(result[77])) * MM_PER_INCH / base_dpi + .1; + dev->inquiry_y_ranges [AV_TRANSPARENT_DIM] = + (double)get_double (&(result[79])) * MM_PER_INCH / base_dpi; + + dev->inquiry_x_ranges [AV_ADF_DIM] = + (double)get_double (&(result[85])) * MM_PER_INCH / base_dpi + .1; + dev->inquiry_y_ranges [AV_ADF_DIM] = + (double)get_double (&(result[87])) * MM_PER_INCH / base_dpi; + } + + dev->inquiry_tune_scan_length = BIT(result[94],2); + if (Avision_Device_List [model_num].feature_type2 & AV_NO_TUNE_SCAN_LENGTH) + dev->inquiry_tune_scan_length = 0; + + dev->inquiry_background_raster = BIT(result[95],2); + + if (dev->hw->feature_type & AV_NO_BACKGROUND) + dev->inquiry_background_raster = 0; + + if (dev->inquiry_background_raster) { + dev->inquiry_background_raster_pixel = + get_double(&(result[85])) * dev->inquiry_optical_res / AVISION_BASE_RES; + } + + /* check if x/y ranges are valid :-((( */ + { + source_mode_dim mode; + + for (mode = AV_NORMAL_DIM; mode < AV_SOURCE_MODE_DIM_LAST; ++ mode) + { + if (dev->inquiry_x_ranges [mode] != 0 && + dev->inquiry_y_ranges [mode] != 0) + { + DBG (3, "attach: x/y-range for mode %d is valid!\n", mode); + if (force_a4) { + DBG (1, "attach: \"force_a4\" found! Using default (ISO A4).\n"); + dev->inquiry_x_ranges [mode] = A4_X_RANGE * MM_PER_INCH; + dev->inquiry_y_ranges [mode] = A4_Y_RANGE * MM_PER_INCH; + } else if (force_a3) { + DBG (1, "attach: \"force_a3\" found! Using default (ISO A3).\n"); + dev->inquiry_x_ranges [mode] = A3_X_RANGE * MM_PER_INCH; + dev->inquiry_y_ranges [mode] = A3_Y_RANGE * MM_PER_INCH; + } + } + else /* mode is invalid */ + { + DBG (1, "attach: x/y-range for mode %d is invalid! Using a default.\n", mode); + if (dev->hw->feature_type & AV_FORCE_A3) { + dev->inquiry_x_ranges [mode] = A3_X_RANGE * MM_PER_INCH; + dev->inquiry_y_ranges [mode] = A3_Y_RANGE * MM_PER_INCH; + } + else if (dev->hw->feature_type & AV_FORCE_FILM) { + dev->inquiry_x_ranges [mode] = FILM_X_RANGE * MM_PER_INCH; + dev->inquiry_y_ranges [mode] = FILM_Y_RANGE * MM_PER_INCH; + } + else { + dev->inquiry_x_ranges [mode] = A4_X_RANGE * MM_PER_INCH; + + if (dev->scanner_type == AV_SHEETFEED) + dev->inquiry_y_ranges [mode] = SHEETFEED_Y_RANGE * MM_PER_INCH; + else + dev->inquiry_y_ranges [mode] = A4_Y_RANGE * MM_PER_INCH; + } + } + DBG (1, "attach: Mode %d range is now: %f x %f mm.\n", + mode, + dev->inquiry_x_ranges [mode], dev->inquiry_y_ranges [mode]); + } /* end for all modes */ + } + + /* We need a bigger buffer for USB devices, since they seem to have + a firmware bug and do not support reading the calibration data in + tiny chunks */ + if (av_con.connection_type == AV_USB) + dev->scsi_buffer_size = 1024 * 1024; /* or 0x10000, used by AV Windows driver during background raster read, ... ? */ + else + dev->scsi_buffer_size = sanei_scsi_max_request_size; + + if (dev->inquiry_asic_type >= AV_ASIC_C5) + dev->read_stripe_size = 32; + else /* tested on AV3200 with it's max of 300dpi @color */ + dev->read_stripe_size = 8; /* maybe made dynamic on scan res ... */ + + /* normally the data_dq is 0x0a0d - but some newer scanner hang with it ... */ + if (dev->inquiry_new_protocol) /* TODO: match on ASIC? which model hung? */ + dev->data_dq = 0x0a0d; + else + dev->data_dq = 0; + + avision_close (&av_con); + + ++ num_devices; + dev->next = first_dev; + first_dev = dev; + if (devp) + *devp = dev; + + return SANE_STATUS_GOOD; + + close_scanner_and_return: + avision_close (&av_con); + + return status; +} + + +static SANE_Status +get_tune_scan_length (Avision_Scanner* s) +{ + SANE_Status status; + int i; + + struct command_read rcmd; + size_t size; + + struct max_value { + uint8_t max [2]; + } payload; + + /* turn on the light */ + DBG (3, "get_tune_scan_length:\n"); + + memset (&rcmd, 0, sizeof (rcmd)); + size = sizeof (payload); + + rcmd.opc = AVISION_SCSI_READ; + rcmd.datatypecode = 0xD2; /* Read General Ability/Parameter */ + + for (i = 1; i <= 8; ++i) { + memset (&payload, 0, sizeof (payload)); + + set_double (rcmd.datatypequal, i); /* type */ + set_triple (rcmd.transferlen, size); + + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), + 0, 0, &payload, &size); + + if (status != SANE_STATUS_GOOD) { + DBG (1, "get_tune_scan_length: read %d failed (%s)\n", i, sane_strstatus (status)); + return status; + } + DBG (1, "get_tune_scan_length: %d: %d\n", i, get_double (payload.max)); + } + + return SANE_STATUS_GOOD; +} + +static SANE_Status +send_tune_scan_length (Avision_Scanner* s) +{ + int top, bottom; + + SANE_Status status; + size_t size; + struct command_send scmd; + struct truncate_attach { + uint8_t vertical [2]; + /* uint8_t horizontal [2]; not send by the Windows driver, yet */ + } payload; + + DBG (3, "send_tune_scan_length:\n"); + + memset (&scmd, 0, sizeof (scmd)); + + size = sizeof (payload); + scmd.opc = AVISION_SCSI_SEND; + scmd.datatypecode = 0x96; /* Attach/Truncate head(left) of scan length */ + set_triple (scmd.transferlen, size); + + /* the SPEC says optical DPI, but real world measuring suggests it is 1200 + as in the window descriptor */ + top = 1200 * SANE_UNFIX (s->val[OPT_OVERSCAN_TOP].w) / MM_PER_INCH; + DBG (3, "send_tune_scan_length: top: %d\n", top); + + set_double (scmd.datatypequal, 0x0001); /* attach, 0x000 is shorten */ + set_double (payload.vertical, top); + /* set_double (payload.horizontal, 0); */ + + /* we alway send it, even for 0 as the scanner keeps it in RAM and + previous runs could already have set something */ + + status = avision_cmd (&s->av_con, &scmd, sizeof (scmd), + &payload, sizeof (payload), 0, 0); + + if (status != SANE_STATUS_GOOD) { + DBG (1, "send_tune_scan_length: send top/left failed (%s)\n", sane_strstatus (status)); + return status; + } + + scmd.datatypecode = 0x95; /* Attach/Truncate tail(right) of scan length */ + bottom = 1200 * SANE_UNFIX (s->val[OPT_OVERSCAN_BOTTOM].w) / MM_PER_INCH; + DBG (3, "send_tune_scan_length: bottom: %d\n", bottom); + + set_double (payload.vertical, bottom); + /*set_double (payload.horizontal, 0); */ + + size = sizeof (payload); + status = avision_cmd (&s->av_con, &scmd, sizeof (scmd), + &payload, sizeof (payload), 0, 0); + + if (status != SANE_STATUS_GOOD) { + DBG (1, "send_tune_scan_length: send bottom/right failed (%s)\n", sane_strstatus (status)); + return status; + } + + return SANE_STATUS_GOOD; +} + +static SANE_Status +additional_probe (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + + /* we should wait until the scanner is ready before we + perform further actions */ + + SANE_Status status; + /* try to retrieve additional accessory information */ + if (dev->inquiry_detect_accessories) { + status = get_accessories_info (s); + if (status != SANE_STATUS_GOOD) + return status; + } + + /* for a film scanner try to retrieve additional frame information */ + if (dev->scanner_type == AV_FILM) { + status = get_frame_info (s); + if (status != SANE_STATUS_GOOD) + return status; + } + + /* no scanner did support this so far: tried on AV220, DM152 */ + if (0 && dev->inquiry_duplex) { + status = get_duplex_info (s); + if (status != SANE_STATUS_GOOD) + return status; + } + + /* get overscan ("head/tail tune") information: hangs AV220, zeros on AV122 */ + if (0 && dev->inquiry_tune_scan_length) { + status = get_tune_scan_length (s); + if (status != SANE_STATUS_GOOD) + return status; + } + + /* create dynamic *-mode entries */ + if (!dev->inquiry_no_gray_modes) + { + if (dev->inquiry_bits_per_channel > 0) { + add_color_mode (dev, AV_THRESHOLDED, SANE_VALUE_SCAN_MODE_LINEART); + add_color_mode (dev, AV_DITHERED, "Dithered"); + } + + if (dev->inquiry_bits_per_channel >= 8) + add_color_mode (dev, AV_GRAYSCALE, SANE_VALUE_SCAN_MODE_GRAY); + + if (dev->inquiry_bits_per_channel == 12) + add_color_mode (dev, AV_GRAYSCALE12, "12bit Gray"); + + if (dev->inquiry_bits_per_channel >= 16) + add_color_mode (dev, AV_GRAYSCALE16, "16bit Gray"); + } + + if (dev->inquiry_channels_per_pixel > 1) { + add_color_mode (dev, AV_TRUECOLOR, SANE_VALUE_SCAN_MODE_COLOR); + + if (dev->inquiry_bits_per_channel == 12) + add_color_mode (dev, AV_TRUECOLOR12, "12bit Color"); + + if (dev->inquiry_bits_per_channel >= 16) + add_color_mode (dev, AV_TRUECOLOR16, "16bit Color"); + } + + /* now choose the default mode - avoiding the 12/16 bit modes */ + dev->color_list_default = last_color_mode (dev); + if (dev->inquiry_bits_per_channel > 8 && dev->color_list_default > 0) { + dev->color_list_default--; + } + + if (dev->scanner_type == AV_SHEETFEED) + { + add_source_mode (dev, AV_ADF, "ADF Front"); + } + else + { + add_source_mode (dev, AV_NORMAL, "Normal"); + + if (dev->inquiry_light_box) + add_source_mode (dev, AV_TRANSPARENT, "Transparency"); + + if (dev->inquiry_adf) + add_source_mode (dev, AV_ADF, "ADF Front"); + } + + if (dev->inquiry_duplex) { + if (dev->inquiry_duplex_interlaced && !(dev->hw->feature_type & AV_NO_REAR)) + add_source_mode (dev, AV_ADF_REAR, "ADF Back"); + add_source_mode (dev, AV_ADF_DUPLEX, "ADF Duplex"); + } + + return SANE_STATUS_GOOD; +} + +static SANE_Status +get_calib_format (Avision_Scanner* s, struct calibration_format* format) +{ + SANE_Status status; + + struct command_read rcmd; + uint8_t result [32]; + size_t size; + + DBG (3, "get_calib_format:\n"); + + size = sizeof (result); + + memset (&rcmd, 0, sizeof (rcmd)); + rcmd.opc = AVISION_SCSI_READ; + rcmd.datatypecode = 0x60; /* get calibration format */ + set_double (rcmd.datatypequal, s->hw->data_dq); + set_triple (rcmd.transferlen, size); + + DBG (3, "get_calib_format: read_data: %lu bytes\n", (u_long) size); + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), 0, 0, result, &size); + if (status != SANE_STATUS_GOOD || size != sizeof (result) ) { + DBG (1, "get_calib_format: read calib. info failed (%s)\n", + sane_strstatus (status) ); + return status; + } + + debug_print_calib_format (3, "get_calib_format", result); + + format->pixel_per_line = get_double (&(result[0])); + format->bytes_per_channel = result[2]; + format->lines = result[3]; + format->flags = result[4]; + format->ability1 = result[5]; + format->r_gain = result[6]; + format->g_gain = result[7]; + format->b_gain = result[8]; + format->r_shading_target = get_double (&(result[9])); + format->g_shading_target = get_double (&(result[11])); + format->b_shading_target = get_double (&(result[13])); + format->r_dark_shading_target = get_double (&(result[15])); + format->g_dark_shading_target = get_double (&(result[17])); + format->b_dark_shading_target = get_double (&(result[19])); + + /* now translate to normal! */ + /* firmware return R--RG--GB--B with 3 line count */ + /* software format it as 1 line if true color scan */ + /* only line interleave format to be supported */ + + if (color_mode_is_color (s->c_mode) || BIT(format->ability1, 3)) { + format->channels = 3; + format->lines /= 3; /* line interleave */ + } + else + format->channels = 1; + + DBG (3, "get_calib_format: channels: %d\n", format->channels); + + return SANE_STATUS_GOOD; +} + +static SANE_Status +get_calib_data (Avision_Scanner* s, uint8_t data_type, + uint8_t* calib_data, + size_t calib_size) +{ + SANE_Status status; + uint8_t *calib_ptr; + + size_t get_size, data_size, chunk_size; + + struct command_read rcmd; + + chunk_size = calib_size; + + DBG (3, "get_calib_data: type %x, size %lu, chunk_size: %lu\n", + data_type, (u_long) calib_size, (u_long) chunk_size); + + memset (&rcmd, 0, sizeof (rcmd)); + + rcmd.opc = AVISION_SCSI_READ; + rcmd.datatypecode = data_type; + set_double (rcmd.datatypequal, s->hw->data_dq); + + calib_ptr = calib_data; + get_size = chunk_size; + data_size = calib_size; + + while (data_size) { + if (get_size > data_size) + get_size = data_size; + + read_constrains(s, get_size); + + set_triple (rcmd.transferlen, get_size); + + DBG (3, "get_calib_data: Reading %ld bytes calibration data\n", + (long)get_size); + + status = avision_cmd (&s->av_con, &rcmd, + sizeof (rcmd), 0, 0, calib_ptr, &get_size); + if (status != SANE_STATUS_GOOD) { + DBG (1, "get_calib_data: read data failed (%s)\n", + sane_strstatus (status)); + return status; + } + + DBG (3, "get_calib_data: Got %ld bytes calibration data\n", (long)get_size); + + data_size -= get_size; + calib_ptr += get_size; + } + + return SANE_STATUS_GOOD; +} + +static SANE_Status +set_calib_data (Avision_Scanner* s, struct calibration_format* format, + uint8_t* dark_data, uint8_t* white_data) +{ + Avision_Device* dev = s->hw; + + const int elements_per_line = format->pixel_per_line * format->channels; + + SANE_Status status; + + uint8_t send_type; + uint16_t send_type_q; + + struct command_send scmd; + + int i; + size_t out_size; + + DBG (3, "set_calib_data:\n"); + + send_type = 0x82; /* download calibration data */ + + /* do we use a color mode? */ + if (format->channels > 1) { + send_type_q = 0x12; /* color calib data */ + } + else { + if (dev->hw->feature_type & AV_GRAY_CALIB_BLUE) + send_type_q = 0x2; /* gray/bw calib data on the blue channel (AV610) */ + else + send_type_q = 0x11; /* gray/bw calib data */ + } + + memset (&scmd, 0x00, sizeof (scmd)); + scmd.opc = AVISION_SCSI_SEND; + scmd.datatypecode = send_type; + + /* data corrections due to dark calibration data merge */ + if (BIT (format->ability1, 2) ) { + DBG (3, "set_calib_data: merging dark calibration data\n"); + for (i = 0; i < elements_per_line; ++i) { + uint16_t value_orig = get_double_le (white_data + i*2); + uint16_t value_new = value_orig; + + value_new &= 0xffc0; + value_new |= (get_double_le (dark_data + i*2) >> 10) & 0x3f; + + DBG (9, "set_calib_data: element %d, dark difference %d\n", + i, value_orig - value_new); + + set_double_le ((white_data + i*2), value_new); + } + } + + out_size = format->pixel_per_line * 2; + + /* send data in one command? */ + /* FR: HP5370 reports one-pass, but needs multi (or other format in single) */ + if (format->channels == 1 || + ( ( (dev->hw->feature_type & AV_ONE_CALIB_CMD) || + ! BIT(format->ability1, 0) ) && + ! (dev->hw->feature_type & AV_MULTI_CALIB_CMD) ) ) + /* one command (most scanners) */ + { + size_t send_size = elements_per_line * 2; + DBG (3, "set_calib_data: all channels in one command\n"); + DBG (3, "set_calib_data: send_size: %lu\n", (u_long) send_size); + + memset (&scmd, 0, sizeof (scmd) ); + scmd.opc = AVISION_SCSI_SEND; + scmd.datatypecode = send_type; + set_double (scmd.datatypequal, send_type_q); + set_triple (scmd.transferlen, send_size); + + status = avision_cmd (&s->av_con, &scmd, sizeof (scmd), + (char*) white_data, send_size, 0, 0); + /* not return immediately to free mem at the end */ + } + else /* send data channel by channel (some USB ones) */ + { + int conv_out_size = format->pixel_per_line * 2; + uint16_t* conv_out_data; /* here it is save to use 16bit data + since we only move whole words around */ + + DBG (3, "set_calib_data: channels in single commands\n"); + + conv_out_data = (uint16_t*) malloc (conv_out_size); + if (!conv_out_data) { + status = SANE_STATUS_NO_MEM; + } + else { + int channel; + for (channel = 0; channel < 3; ++ channel) + { + int i; + + /* no need for endianness handling since whole word copy */ + uint16_t* casted_avg_data = (uint16_t*) white_data; + + DBG (3, "set_calib_data_calibration: channel: %i\n", channel); + + for (i = 0; i < format->pixel_per_line; ++ i) + conv_out_data [i] = casted_avg_data [i * 3 + channel]; + + DBG (3, "set_calib_data: sending %i bytes now\n", + conv_out_size); + + memset (&scmd, 0, sizeof (scmd)); + scmd.opc = AVISION_SCSI_SEND; + scmd.datatypecode = send_type; /* send calibration data */ + set_double (scmd.datatypequal, channel); + set_triple (scmd.transferlen, conv_out_size); + + status = avision_cmd (&s->av_con, &scmd, sizeof (scmd), + conv_out_data, conv_out_size, 0, 0); + if (status != SANE_STATUS_GOOD) { + DBG (3, "set_calib_data: send_data failed (%s)\n", + sane_strstatus (status)); + /* not return immediately to free mem at the end */ + } + } /* end for each channel */ + free (conv_out_data); + } /* end else send calib data*/ + } + + return SANE_STATUS_GOOD; +} + +/* Sort data pixel by pixel and average first 2/3 of the data. + The caller has to free return pointer. R,G,B pixels + interleave to R,G,B line interleave. + + The input data data is in 16 bits little endian, always. + That is a = b[1] << 8 + b[0] in all system. + + We convert it to SCSI high-endian (big-endian) since we use it all + over the place anyway .... - Sorry for this mess. */ + +static uint8_t* +sort_and_average (struct calibration_format* format, uint8_t* data) +{ + const int elements_per_line = format->pixel_per_line * format->channels; + const int stride = format->bytes_per_channel * elements_per_line; + int i, line; + + uint8_t *sort_data, *avg_data; + + DBG (1, "sort_and_average:\n"); + + if (!format || !data) + return NULL; + + sort_data = malloc (format->lines * 2); + if (!sort_data) + return NULL; + + avg_data = malloc (elements_per_line * 2); + if (!avg_data) { + free (sort_data); + return NULL; + } + + /* for each pixel */ + for (i = 0; i < elements_per_line; ++ i) + { + uint8_t* ptr1 = data + i * format->bytes_per_channel; + uint16_t temp; + + /* copy all lines for pixel i into the linear array sort_data */ + for (line = 0; line < format->lines; ++ line) { + uint8_t* ptr2 = ptr1 + line * stride; /* pixel */ + + if (format->bytes_per_channel == 1) + temp = 0xffff * *ptr2 / 255; + else + temp = get_double_le (ptr2); /* little-endian! */ + set_double ((sort_data + line*2), temp); /* store big-endian */ + /* DBG (7, "ReneR to sort: %x\n", temp); */ + } + + temp = bubble_sort (sort_data, format->lines); + /* DBG (7, "ReneR averaged: %x\n", temp); */ + set_double ((avg_data + i*2), temp); /* store big-endian */ + } + + free ((void *) sort_data); + return avg_data; +} + +/* shading data is 16bits little endian format when send/read from firmware */ +static void +compute_dark_shading_data (Avision_Scanner* s, + struct calibration_format* format, uint8_t* data) +{ + uint16_t map_value = DEFAULT_DARK_SHADING; + uint16_t rgb_map_value[3]; + + int elements_per_line, i; + + DBG (3, "compute_dark_shading_data:\n"); + + if (s->hw->inquiry_max_shading_target != INVALID_DARK_SHADING) + map_value = s->hw->inquiry_max_shading_target << 8; + + rgb_map_value[0] = format->r_dark_shading_target; + rgb_map_value[1] = format->g_dark_shading_target; + rgb_map_value[2] = format->b_dark_shading_target; + + for (i = 0; i < format->channels; ++i) { + if (rgb_map_value[i] == INVALID_DARK_SHADING) + rgb_map_value[i] = map_value; + } + + if (format->channels == 1) { + /* set to green, TODO: should depend on color drop-out and true-gray -ReneR */ + rgb_map_value[0] = rgb_map_value[1] = rgb_map_value[2] = rgb_map_value[1]; + } + + elements_per_line = format->pixel_per_line * format->channels; + + /* Check line interleave or pixel interleave. */ + /* It seems no ASIC use line interleave right now. */ + /* Avision SCSI protocol document has bad description. */ + for (i = 0; i < elements_per_line; ++i) + { + uint16_t tmp_data = get_double_le((data + i*2)); + if (tmp_data > rgb_map_value[i % 3]) { + set_double ((data + i*2), tmp_data - rgb_map_value[i % 3]); + } + else { + set_double ((data + i*2), 0); + } + } +} + +static void +compute_white_shading_data (Avision_Scanner* s, + struct calibration_format* format, uint8_t* data) +{ + int i; + uint16_t inquiry_mst = DEFAULT_WHITE_SHADING; + uint16_t mst[3]; + + int elements_per_line = format->pixel_per_line * format->channels; + + /* debug counter */ + int values_invalid = 0; + int values_limitted = 0; + + DBG (3, "compute_white_shading_data:\n"); + + if (s->hw->inquiry_max_shading_target != INVALID_WHITE_SHADING) + inquiry_mst = s->hw->inquiry_max_shading_target << 4; + + mst[0] = format->r_shading_target; + mst[1] = format->g_shading_target; + mst[2] = format->b_shading_target; + + for (i = 0; i < 3; ++i) { + if (mst[i] == INVALID_WHITE_SHADING) /* mst[i] > MAX_WHITE_SHADING) */ { + DBG (3, "compute_white_shading_data: target %d invalid (%x) using inquiry (%x)\n", + i, mst[i], inquiry_mst); + mst[i] = inquiry_mst; + } + /* some firmware versions seems to return the bytes swapped? */ + else if (mst[i] < 0x110) { + uint8_t* swap_mst = (uint8_t*) &mst[i]; + uint8_t low_nibble_mst = swap_mst [0]; + swap_mst [0] = swap_mst[1]; + swap_mst [1] = low_nibble_mst; + + DBG (3, "compute_white_shading_data: target %d: bytes swapped.\n", i); + } + if (mst[i] < DEFAULT_WHITE_SHADING / 2) { + DBG (3, "compute_white_shading_data: target %d: too low (%d) using default (%d).\n", + i, mst[i], DEFAULT_WHITE_SHADING); + mst[i] = DEFAULT_WHITE_SHADING; + } + else + DBG (3, "compute_white_shading_data: target %d: %x\n", i, mst[0]); + } + + /* some Avision example code was present here until SANE/Avision + * BUILD 57. */ + + if (format->channels == 1) { + /* set to green, TODO: should depend on color drop-out and true-gray -ReneR */ + mst[0] = mst[1] = mst[2] = mst[1]; + } + + /* calculate calibration data */ + for (i = 0; i < elements_per_line; ++ i) + { + int result; + /* calculate calibration value for pixel i */ + uint16_t tmp_data = get_double((data + i*2)); + + if (tmp_data == INVALID_WHITE_SHADING) { + tmp_data = DEFAULT_WHITE_SHADING; + ++ values_invalid; + } + + result = ( (int)mst[i % 3] * WHITE_MAP_RANGE / (tmp_data + 0.5)); + + /* sanity check for over-amplification, clipping */ + if (result > MAX_WHITE_SHADING) { + result = WHITE_MAP_RANGE; + ++ values_limitted; + } + + /* for visual debugging ... */ + if (static_calib_list [i % 3] == SANE_TRUE) + result = 0xA000; + + /* the output to the scanner will be 16 bit little endian again */ + set_double_le ((data + i*2), result); + } + DBG (3, "compute_white_shading_data: %d invalid, %d limited\n", + values_invalid, values_limitted); +} + +/* old_r_calibration was here until SANE/Avision BUILD 90 */ + +static SANE_Status +normal_calibration (Avision_Scanner* s) +{ + SANE_Status status; + + struct calibration_format calib_format; + + int calib_data_size, calib_bytes_per_line; + uint8_t read_type; + uint8_t *calib_tmp_data; + + DBG (1, "normal_calibration:\n"); + + /* get calibration format and data */ + status = get_calib_format (s, &calib_format); + if (status != SANE_STATUS_GOOD) + return status; + + /* check if need do calibration */ + if (calib_format.flags != 1) { + DBG (1, "normal_calibration: Scanner claims no calibration needed -> skipped!\n"); + return SANE_STATUS_GOOD; + } + + /* calculate calibration data size for read from scanner */ + /* size = lines * bytes_per_channel * pixels_per_line * channel */ + calib_bytes_per_line = calib_format.bytes_per_channel * + calib_format.pixel_per_line * calib_format.channels; + + calib_data_size = calib_format.lines * calib_bytes_per_line; + + calib_tmp_data = malloc (calib_data_size); + if (!calib_tmp_data) + return SANE_STATUS_NO_MEM; + + /* check if we need to do dark calibration (shading) */ + if (BIT(calib_format.ability1, 3)) + { + DBG (1, "normal_calibration: reading dark data\n"); + /* read dark calib data */ + status = get_calib_data (s, 0x66, calib_tmp_data, calib_data_size); + + if (status != SANE_STATUS_GOOD) { + free (calib_tmp_data); + return status; + } + + /* process dark data: sort and average. */ + + if (s->dark_avg_data) { + free (s->dark_avg_data); + s->dark_avg_data = 0; + } + s->dark_avg_data = sort_and_average (&calib_format, calib_tmp_data); + if (!s->dark_avg_data) { + free (calib_tmp_data); + return SANE_STATUS_NO_MEM; + } + compute_dark_shading_data (s, &calib_format, s->dark_avg_data); + } + + /* do we use a color mode? */ + if (calib_format.channels > 1) { + DBG (3, "normal_calibration: using color calibration\n"); + read_type = 0x62; /* read color calib data */ + } + else { + DBG (3, "normal_calibration: using gray calibration\n"); + read_type = 0x61; /* gray calib data */ + } + + /* do white calibration: read gray or color data */ + status = get_calib_data (s, read_type, calib_tmp_data, calib_data_size); + + if (status != SANE_STATUS_GOOD) { + free (calib_tmp_data); + return status; + } + + if (0) /* debug */ + { + FILE* f = NULL; + f = fopen ("calibration-white.pnm", "w"); + write_pnm_header (f, AV_GRAYSCALE, calib_format.bytes_per_channel * 8, + calib_format.pixel_per_line, + calib_format.lines * calib_format.channels); + + fwrite (calib_tmp_data, 1, calib_data_size, f); + fclose (f); + } + + if (s->white_avg_data) { + free (s->white_avg_data); + s->white_avg_data = 0; + } + s->white_avg_data = sort_and_average (&calib_format, calib_tmp_data); + if (!s->white_avg_data) { + free (calib_tmp_data); + return SANE_STATUS_NO_MEM; + } + + /* decrease white average data (if dark average data is present) */ + if (s->dark_avg_data) { + int elements_per_line = calib_format.pixel_per_line * calib_format.channels; + int i; + + DBG (1, "normal_calibration: dark data present - decreasing white average data\n"); + + for (i = 0; i < elements_per_line; ++ i) { + s->white_avg_data[i] -= s->dark_avg_data[i]; + } + } + + compute_white_shading_data (s, &calib_format, s->white_avg_data); + + status = set_calib_data (s, &calib_format, + s->dark_avg_data, s->white_avg_data); + + free (calib_tmp_data); + return status; +} + +/* next was taken from the GIMP and is a bit modified ... ;-) + * original Copyright (C) 1995 Spencer Kimball and Peter Mattis + */ +static double +brightness_contrast_func (double brightness, double contrast, double value) +{ + double nvalue; + double power; + + /* apply brightness */ + if (brightness < 0.0) + value = value * (1.0 + brightness); + else + value = value + ((1.0 - value) * brightness); + + /* apply contrast */ + if (contrast < 0.0) + { + if (value > 0.5) + nvalue = 1.0 - value; + else + nvalue = value; + if (nvalue < 0.0) + nvalue = 0.0; + nvalue = 0.5 * pow (nvalue * 2.0 , (double) (1.0 + contrast)); + if (value > 0.5) + value = 1.0 - nvalue; + else + value = nvalue; + } + else + { + if (value > 0.5) + nvalue = 1.0 - value; + else + nvalue = value; + if (nvalue < 0.0) + nvalue = 0.0; + power = (contrast == 1.0) ? 127 : 1.0 / (1.0 - contrast); + nvalue = 0.5 * pow (2.0 * nvalue, power); + if (value > 0.5) + value = 1.0 - nvalue; + else + value = nvalue; + } + return value; +} + +static SANE_Status +send_gamma (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + SANE_Status status = SANE_STATUS_GOOD; + + int invert_table = 0; + + size_t gamma_table_raw_size; + size_t gamma_table_size; + size_t gamma_values; + + struct command_send scmd; + uint8_t *gamma_data; + + int color; /* current color */ + size_t i; /* big table index */ + size_t j; /* little table index */ + size_t k; /* big table sub index */ + double v1, v2; + + double brightness; + double contrast; + + if (dev->inquiry_asic_type != AV_ASIC_OA980) + invert_table = (s->c_mode == AV_THRESHOLDED) || (s->c_mode == AV_DITHERED); + + switch (dev->inquiry_asic_type) + { + case AV_ASIC_Cx: + case AV_ASIC_C1: /* from avision code */ + gamma_table_raw_size = 4096; + gamma_table_size = 2048; + break; + case AV_ASIC_C5: + gamma_table_raw_size = 256; + gamma_table_size = 256; + break; + case AV_ASIC_C6: /* SPEC claims: 256 ... ? */ + case AV_ASIC_C7: + gamma_table_raw_size = 512; + gamma_table_size = 512; + break; + case AV_ASIC_OA980: + gamma_table_raw_size = 4096; + gamma_table_size = 4096; + break; + case AV_ASIC_OA982: + gamma_table_raw_size = 256; + gamma_table_size = 256; + break; + default: + gamma_table_raw_size = gamma_table_size = 4096; + } + + gamma_values = gamma_table_size / 256; + + DBG (3, "send_gamma: table_raw_size: %lu, table_size: %lu\n", + (u_long) gamma_table_raw_size, (u_long) gamma_table_size); + DBG (3, "send_gamma: values: %lu, invert_table: %d\n", + (u_long) gamma_values, invert_table); + + /* prepare for emulating contrast, brightness ... via the gamma-table */ + brightness = SANE_UNFIX (s->val[OPT_BRIGHTNESS].w); + brightness /= 100; + contrast = SANE_UNFIX (s->val[OPT_CONTRAST].w); + contrast /= 100; + + DBG (3, "send_gamma: brightness: %f, contrast: %f\n", brightness, contrast); + + gamma_data = malloc (gamma_table_raw_size); + if (!gamma_data) + return SANE_STATUS_NO_MEM; + + memset (&scmd, 0, sizeof (scmd) ); + + scmd.opc = AVISION_SCSI_SEND; + scmd.datatypecode = 0x81; /* 0x81 for download gamma table */ + set_triple (scmd.transferlen, gamma_table_raw_size); + + for (color = 0; color < 3 && status == SANE_STATUS_GOOD; ++ color) + { + /* color: 0=red; 1=green; 2=blue */ + set_double (scmd.datatypequal, color); + + i = 0; /* big table index */ + for (j = 0; j < 256; ++ j) /* little table index */ + { + /* calculate mode dependent values v1 and v2 + * v1 <- current value for table + * v2 <- next value for table (for interpolation) + */ + switch (s->c_mode) + { + case AV_TRUECOLOR: + case AV_TRUECOLOR12: + case AV_TRUECOLOR16: + { + v1 = (double) s->gamma_table [1 + color][j]; + if (j == 255) + v2 = (double) v1; + else + v2 = (double) s->gamma_table [1 + color][j + 1]; + } + break; + default: + /* for all other modes: */ + { + v1 = (double) s->gamma_table [0][j]; + if (j == 255) + v2 = (double) v1; + else + v2 = (double) s->gamma_table [0][j + 1]; + } + } /*end switch */ + + /* Emulate brightness and contrast (at least the Avision AV6[2,3]0 + * as well as many others do not have a hardware implementation, + * --$. The function was taken from the GIMP source - maybe I'll + * optimize it in the future (when I have spare time). */ + + v1 /= 255; + v2 /= 255; + + v1 = (brightness_contrast_func (brightness, contrast, v1) ); + v2 = (brightness_contrast_func (brightness, contrast, v2) ); + + v1 *= 255; + v2 *= 255; + + if (invert_table) { + v1 = 255 - v1; + v2 = 255 - v2; + if (v1 <= 0) + v1 = 0; + if (v2 <= 0) + v2 = 0; + } + + for (k = 0; k < gamma_values; ++ k, ++ i) { + gamma_data [i] = (uint8_t) + (((v1 * (gamma_values - k)) + (v2 * k) ) / (double) gamma_values); + } + } + /* fill the gamma table - (e.g.) if 11bit (old protocol) table */ + { + size_t t_i = i-1; + if (i < gamma_table_raw_size) { + DBG (4, "send_gamma: (old protocol) - filling the table.\n"); + for ( ; i < gamma_table_raw_size; ++ i) + gamma_data [i] = gamma_data [t_i]; + } + } + + DBG (4, "send_gamma: sending %lu bytes gamma table.\n", + (u_long) gamma_table_raw_size); + status = avision_cmd (&s->av_con, &scmd, sizeof (scmd), + gamma_data, gamma_table_raw_size, 0, 0); + + if (status != SANE_STATUS_GOOD) { + DBG (1, "send_gamma: gamma table upload failed: %s\n", + sane_strstatus (status)); + } + } + free (gamma_data); + return status; +} + +static SANE_Status +send_3x3_matrix (Avision_Scanner* s) +{ + SANE_Status status; + +#define SIGN_BIT 0x1000 +#define INT_PART 10 + + struct matrix_cmd + { + struct command_send scmd; + struct matrix_3x3 matrix; + } cmd; + + /* 04 00 00 00 00 00 + 00 00 04 00 00 00 + 00 00 00 00 04 00 */ + + int i, a_i; + static const double c5_matrix[] = + { 1.000, 0.000, 0.000, + 0.000, 1.000, 0.000, + 0.000, 0.000, 1.000 }; + + double a_f, b_f; + uint16_t m; + + DBG (3, "send_3x3_matrix:\n"); + + memset (&cmd, 0, sizeof (cmd)); + + for (i = 0; i < 9; i++) + { + m = 0; + a_f = c5_matrix[i]; + if (a_f < 0) { + m |= SIGN_BIT; + a_f = -a_f; + } + + a_i = (int) a_f; /* integer */ + b_f = a_f - (double) a_i; /* float */ + m |= ((a_i & 0x3) << INT_PART); + m |= (uint16_t) (b_f * 1024); + set_double (((uint8_t*)(&cmd.matrix.v[i])), m); + } + + cmd.scmd.opc = AVISION_SCSI_SEND; + cmd.scmd.datatypecode = 0x83; /* 0x83 for 3x3 color matrix */ + set_triple (cmd.scmd.transferlen, sizeof (struct matrix_3x3)); + + if (1) { + DBG (3, "send_3x3_matrix: sending matrix split into two commands\n"); + status = avision_cmd (&s->av_con, &cmd.scmd, sizeof (cmd.scmd), + &cmd.matrix, sizeof(cmd.matrix), 0, 0); + } + else { + DBG (3, "send_3x3_matrix: sending matrix in one command\n"); + status = avision_cmd (&s->av_con, &cmd, sizeof (cmd), 0, 0, 0, 0); + } + + return status; +} + +static SANE_Status +get_acceleration_info (Avision_Scanner* s, struct acceleration_info* info) +{ + SANE_Status status; + + struct command_read rcmd; + uint8_t result [24]; + size_t size; + + DBG (3, "get_acceleration_info:\n"); + + size = sizeof (result); + + memset (&rcmd, 0, sizeof (rcmd)); + rcmd.opc = AVISION_SCSI_READ; + rcmd.datatypecode = 0x6c; /* get acceleration information */ + set_double (rcmd.datatypequal, s->hw->data_dq); + set_triple (rcmd.transferlen, size); + + DBG (3, "get_acceleration_info: read_data: %lu bytes\n", (u_long) size); + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), 0, 0, result, &size); + if (status != SANE_STATUS_GOOD || size != sizeof (result) ) { + DBG (1, "get_acceleration_info: read accel. info failed (%s)\n", + sane_strstatus (status) ); + return status; + } + + debug_print_accel_info (3, "get_acceleration_info", result); + + info->total_steps = get_double (&(result[0])); + info->stable_steps = get_double (&(result[2])); + info->table_units = get_quad (&(result[4])); + info->base_units = get_quad (&(result[8])); + info->start_speed = get_double (&(result[12])); + info->target_speed = get_double (&(result[14])); + info->ability = result[16]; + info->table_count = result[17]; + + return SANE_STATUS_GOOD; +} + +static SANE_Status +send_acceleration_table (Avision_Scanner* s) +{ + SANE_Status status; + + struct command_send scmd; + int table = 0; + int i; + struct acceleration_info accel_info = accel_info; + uint8_t* table_data; + + DBG (3, "send_acceleration_table:\n"); + + do { + status = get_acceleration_info (s, &accel_info); + + if (accel_info.table_count == 0) { + DBG (3, "send_acceleration_table: device does not need tables\n"); + return SANE_STATUS_GOOD; + } + + if (accel_info.target_speed > accel_info.start_speed || + accel_info.target_speed == 0 || + accel_info.total_steps <= accel_info.stable_steps) { + DBG (1, "send_acceleration_table: table does not look right.\n"); + return SANE_STATUS_INVAL; + } + + if (accel_info.ability != 0) { + DBG (1, "send_acceleration_table: ability non-zero - insert code\n"); + return SANE_STATUS_INVAL; + } + + /* so far I assume we have one byte tables as used in the C6 ASIC ... */ + table_data = malloc (accel_info.total_steps + 1000); + + memset (&scmd, 0x00, sizeof (scmd)); + scmd.opc = AVISION_SCSI_SEND; + scmd.datatypecode = 0x6c; /* send acceleration table */ + + set_double (scmd.datatypequal, table); + set_triple (scmd.transferlen, accel_info.total_steps); + + /* construct the table - Warning: This code is derived from Avision + sample code and is a bit scary! I have no idea why the scanner + needs such a dumb table and also do not know /why/ it has to be + constructed this way. "Works for me" -ReneR */ + { + float low_lim = 0.001; + float up_lim = 1.0; + + uint16_t accel_steps = accel_info.total_steps - accel_info.stable_steps + 1; + + /* acceleration ramp */ + while ((up_lim - low_lim) > 0.0001) + { + float mid = (up_lim + low_lim) / 2; /* accel rate */ + + uint16_t now_count = accel_info.start_speed; + + uint16_t i = 0; + + float now_count_f = now_count; + table_data [i++] = (uint8_t) accel_info.start_speed; + + while (now_count != accel_info.target_speed) + { + now_count_f = now_count_f - (now_count_f - + accel_info.target_speed) * mid; + now_count = (uint16_t)(now_count_f + 0.5); + table_data[i++] = (uint8_t) now_count; + } + + + if (i == accel_steps) + break; + if (i > accel_steps) + low_lim = mid; + else + up_lim = mid; + } + + /* fill stable steps */ + for (i = accel_steps; i < accel_info.total_steps; i++) + table_data [i] = table_data [i-1]; + + debug_print_hex_raw (5, "send_acceleration_table: first pass:\n", + table_data, accel_info.total_steps); + + /* maybe post fix-up */ + { + int add_count; + + /* count total steps in table */ + int table_total = 0; + for (i = 0; i < accel_info.total_steps; i++) + table_total += table_data [i]; + + i = 0; + if (((table_total * accel_info.table_units) % accel_info.base_units) == 0) + add_count = 0; + else + add_count = (accel_info.base_units - + ((table_total*accel_info.table_units) % accel_info.base_units)) + / accel_info.table_units; + + /* add_count should not be bigger than 255 */ + if (add_count > 255) { + DBG (1, "send_acceleration_table: add_count limited, was: %d\n", add_count); + add_count = 255; + } + for (i = 0; i < accel_info.total_steps - 1 && add_count > 0; i++) + { + uint16_t temp_count = 255 - table_data [i]; + temp_count = temp_count > add_count ? add_count : temp_count; + + table_data [i] += (uint8_t) temp_count; + add_count -= temp_count; + } + } + } + + debug_print_hex_raw (5, "send_acceleration_table: fixed up:\n", + table_data, accel_info.total_steps); + + /* decrease all by one ... */ + for (i = 0; i < accel_info.total_steps; i++) { + table_data[i]--; + } + + DBG (1, "send_acceleration_table: sending table %d\n", table); + + debug_print_hex_raw (5, "send_acceleration_table: final:\n", + table_data, accel_info.total_steps); + + status = avision_cmd (&s->av_con, &scmd, sizeof (scmd), + (char*) table_data, accel_info.total_steps, + 0, 0); + if (status != SANE_STATUS_GOOD) { + DBG (3, "send_acceleration_table: send_data failed (%s)\n", + sane_strstatus (status)); + } + + free (table_data); table_data = 0; + + table++; + } while (table < accel_info.table_count); + + + return status; +} + +static SANE_Status +set_window (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + SANE_Status status; + int base_dpi_abs, base_dpi_rel; + int transferlen; + int paralen; + + int bytes_per_line; + int line_count; + + struct { + struct command_set_window cmd; + struct command_set_window_window window; + } cmd; + + DBG (1, "set_window:\n"); + + /* plain old scanners, the C3 ASIC HP 53xx and the C6 ASIC HP 74xx + and up do use 1200 as base - only the C5 differs */ + switch (dev->inquiry_asic_type) { + case AV_ASIC_C5: + base_dpi_abs = 1200; + /* round down to the next multiple of 300 */ + base_dpi_rel = s->avdimen.hw_xres - s->avdimen.hw_xres % 300; + if (base_dpi_rel > dev->inquiry_optical_res) + base_dpi_rel = dev->inquiry_optical_res; + else if (s->avdimen.hw_xres <= 150) + base_dpi_rel = 150; + break; + default: + base_dpi_abs = 1200; + base_dpi_rel = 1200; + } + + DBG (2, "set_window: base_dpi_abs: %d, base_dpi_rel: %d\n", base_dpi_abs, base_dpi_rel); + + /* wipe out anything */ + memset (&cmd, 0, sizeof (cmd) ); + cmd.window.descriptor.winid = AV_WINID; /* normally defined to be zero */ + + /* optional parameter length to use */ + paralen = sizeof (cmd.window.avision) - sizeof (cmd.window.avision.type); + + DBG (2, "set_window: base paralen: %d\n", paralen); + + if (dev->hw->feature_type & AV_FUJITSU) + paralen += sizeof (cmd.window.avision.type.fujitsu); + else if (!dev->inquiry_new_protocol) + paralen += sizeof (cmd.window.avision.type.old); + else + paralen += sizeof (cmd.window.avision.type.normal); + + DBG (2, "set_window: final paralen: %d\n", paralen); + + transferlen = sizeof (cmd.window) + - sizeof (cmd.window.avision) + paralen; + + DBG (2, "set_window: transferlen: %d\n", transferlen); + + /* command setup */ + cmd.cmd.opc = AVISION_SCSI_SET_WINDOW; + set_triple (cmd.cmd.transferlen, transferlen); + set_double (cmd.window.header.desclen, + sizeof (cmd.window.descriptor) + paralen); + + /* resolution parameters */ + set_double (cmd.window.descriptor.xres, s->avdimen.hw_xres); + set_double (cmd.window.descriptor.yres, s->avdimen.hw_yres); + + /* upper left corner x/y as well as width/length in inch * base_dpi + - avdimen are world pixels */ + set_quad (cmd.window.descriptor.ulx, s->avdimen.tlx * base_dpi_abs / s->avdimen.hw_xres); + set_quad (cmd.window.descriptor.uly, s->avdimen.tly * base_dpi_abs / s->avdimen.hw_yres); + + set_quad (cmd.window.descriptor.width, + s->avdimen.hw_pixels_per_line * base_dpi_rel / s->avdimen.hw_xres + 1); + line_count = s->avdimen.hw_lines + 2 * s->avdimen.line_difference + s->avdimen.rear_offset; + set_quad (cmd.window.descriptor.length, + line_count * base_dpi_rel / s->avdimen.hw_yres + 1); + + /* interlaced duplex scans are twice as long */ + if (s->avdimen.interlaced_duplex && dev->scanner_type != AV_FILM) { + DBG (2, "set_window: interlaced duplex scan, doubled line count\n"); + line_count *= 2; + } + + bytes_per_line = s->avdimen.hw_bytes_per_line; + + set_double (cmd.window.avision.line_width, bytes_per_line); + set_double (cmd.window.avision.line_count, line_count); + + /* here go the most significant bits if bigger than 16 bit */ + if (dev->inquiry_new_protocol && !(dev->hw->feature_type & AV_FUJITSU) ) { + DBG (2, "set_window: large data-transfer support (>16bit)!\n"); + cmd.window.avision.type.normal.line_width_msb = + bytes_per_line >> 16; + cmd.window.avision.type.normal.line_count_msb = + line_count >> 16; + } + + if (dev->inquiry_background_raster) + cmd.window.avision.type.normal.background_lines = s->val[OPT_BACKGROUND].w; + + /* scanner should use our line-width and count */ + SET_BIT (cmd.window.avision.bitset1, 6); + + /* set speed */ + cmd.window.avision.bitset1 |= s->val[OPT_SPEED].w & 0x07; /* only 3 bit */ + + /* ADF scan? */ + DBG (3, "set_window: source mode %d source mode dim %d\n", + s->source_mode, s->source_mode_dim); + + if (s->source_mode == AV_ADF || + s->source_mode == AV_ADF_REAR || + s->source_mode == AV_ADF_DUPLEX) { + DBG (3, "set_window: filling ADF bits\n"); + SET_BIT (cmd.window.avision.bitset1, 7); + + /* normal, interlaced duplex scanners */ + if (dev->inquiry_duplex_interlaced) { + DBG (3, "set_window: interlaced duplex type\n"); + if (s->source_mode == AV_ADF_REAR) { + SET_BIT(cmd.window.avision.type.normal.bitset3, 3); /* 0x08 */ + } + if (s->source_mode == AV_ADF_DUPLEX) { + SET_BIT(cmd.window.avision.type.normal.bitset3, 4); /* 0x10 */ + } + } + else if (s->source_mode == AV_ADF_DUPLEX) /* HP 2-pass duplex */ + { + DBG (3, "set_window: non-interlaced duplex type (HP)\n"); + SET_BIT(cmd.window.avision.type.normal.bitset3, 0); /* DPLX 0x01 */ + if (s->val[OPT_ADF_FLIP].w) + SET_BIT(cmd.window.avision.type.normal.bitset3, 1); /* FLIP 0x02 */ + SET_BIT(cmd.window.avision.type.normal.bitset3, 2); /* MIRR 0x04 */ + } + } + + if (s->val[OPT_PAPERLEN].w) + set_double (cmd.window.descriptor.paper_length, (int)((double)30.0*1200)); + + if ( !(dev->hw->feature_type & AV_FUJITSU) ) + { + /* quality scan option switch */ + if (s->val[OPT_QSCAN].w == SANE_TRUE) { + SET_BIT (cmd.window.avision.type.normal.bitset2, 4); + } + + /* quality calibration option switch (inverted! if set == speed) */ + if (s->val[OPT_QCALIB].w == SANE_FALSE) { + SET_BIT (cmd.window.avision.type.normal.bitset2, 3); + } + + /* transparency option switch */ + if (s->source_mode_dim == AV_TRANSPARENT_DIM) { + SET_BIT (cmd.window.avision.type.normal.bitset2, 7); + } + + if (dev->scanner_type == AV_FILM) { + /* TODO: wire to IR exposure option? */ + cmd.window.avision.type.normal.ir_exposure_time = 100; + set_double (cmd.window.avision.type.normal.r_exposure_time, s->val[OPT_EXPOSURE].w); + set_double (cmd.window.avision.type.normal.g_exposure_time, s->val[OPT_EXPOSURE].w); + set_double (cmd.window.avision.type.normal.b_exposure_time, s->val[OPT_EXPOSURE].w); + + if (s->val[OPT_IR].w) + cmd.window.avision.type.normal.bitset3 |= (1 << 0); + + if (s->val[OPT_MULTISAMPLE].w) + cmd.window.avision.type.normal.bitset3 |= (1 << 1); + } + } + + /* fixed values */ + cmd.window.descriptor.padding_and_bitset = 3; + cmd.window.descriptor.vendor_specific = 0xFF; + cmd.window.descriptor.paralen = paralen; /* R² was: 9, later 14 */ + + /* This is normally unsupported by Avision scanners, and we do this + via the gamma table - which works for all devices ... */ + cmd.window.descriptor.threshold = 128; + cmd.window.descriptor.brightness = 128; + cmd.window.descriptor.contrast = 128; + cmd.window.avision.highlight = 0xFF; + cmd.window.avision.shadow = 0x00; + + /* mode dependant settings */ + switch (s->c_mode) + { + case AV_THRESHOLDED: + cmd.window.descriptor.bpc = 1; + cmd.window.descriptor.image_comp = 0; + break; + + case AV_DITHERED: + cmd.window.descriptor.bpc = 1; + cmd.window.descriptor.image_comp = 1; + break; + + case AV_GRAYSCALE: + cmd.window.descriptor.bpc = 8; + cmd.window.descriptor.image_comp = 2; + break; + + case AV_GRAYSCALE12: + cmd.window.descriptor.bpc = 12; + cmd.window.descriptor.image_comp = 2; + break; + + case AV_GRAYSCALE16: + cmd.window.descriptor.bpc = 16; + cmd.window.descriptor.image_comp = 2; + break; + + case AV_TRUECOLOR: + cmd.window.descriptor.bpc = 8; + cmd.window.descriptor.image_comp = 5; + break; + + case AV_TRUECOLOR12: + cmd.window.descriptor.bpc = 12; + cmd.window.descriptor.image_comp = 5; + break; + + case AV_TRUECOLOR16: + cmd.window.descriptor.bpc = 16; + cmd.window.descriptor.image_comp = 5; + break; + + default: + DBG (1, "Invalid mode. %d\n", s->c_mode); + return SANE_STATUS_INVAL; + } + + if (color_mode_is_color (s->c_mode)) { + cmd.window.avision.bitset1 |= AVISION_FILTER_RGB; + } + else { + if (dev->hw->feature_type & AV_FASTER_WITH_FILTER) + cmd.window.avision.bitset1 |= AVISION_FILTER_GREEN; + else if (dev->hw->feature_type2 & AV_USE_GRAY_FILTER) + cmd.window.avision.bitset1 |= AVISION_FILTER_GRAY; + else + cmd.window.avision.bitset1 |= AVISION_FILTER_NONE; + } + + debug_print_window_descriptor (5, "set_window", &(cmd.window)); + + DBG (3, "set_window: sending command. Bytes: %d\n", transferlen); + status = avision_cmd (&s->av_con, &cmd, sizeof (cmd.cmd), + &(cmd.window), transferlen, 0, 0); + + return status; +} + +static SANE_Status +get_background_raster (Avision_Scanner* s) +{ + const int debug = 0; + + Avision_Device* dev = s->hw; + SANE_Status status; + + struct command_read rcmd; + size_t size; + int bytes_per_line, i; + const int bpp = color_mode_is_color (s->c_mode) ? 3 : 1; + const int lines = s->val[OPT_BACKGROUND].w * (s->avdimen.interlaced_duplex ? 2 : 1); + + uint8_t* background = NULL; + + DBG (1, "get_background_raster:\n"); + + if (lines == 0) { + DBG (1, "get_background_raster: no background requested\n"); + return SANE_STATUS_GOOD; + } + + /* full width, always :-(, duplex *2 for front and rear */ + bytes_per_line = dev->inquiry_background_raster_pixel * + s->avdimen.hw_xres / dev->inquiry_optical_res; + bytes_per_line *= bpp; + + DBG (3, "get_background_raster: native raster pixels: %d, raster bytes_per_line: %d\n", + dev->inquiry_background_raster_pixel, bytes_per_line); + + /* according to spec only 8-bit gray or color, TODO: test for bi-level scans */ + size = bytes_per_line * lines; + + DBG (3, "get_background_raster: buffer size: %ld\n", (long)size); + + background = s->background_raster = realloc (s->background_raster, size); + if (!background) + return SANE_STATUS_NO_MEM; + + memset (&rcmd, 0, sizeof (rcmd)); + rcmd.opc = AVISION_SCSI_READ; + rcmd.datatypecode = 0x9b; /* get background raster */ + set_double (rcmd.datatypequal, s->hw->data_dq); + + /* Ok, well - this part is very messy. The AV122 and DM152 appear to + contain differently buggy ASICs. The only combination I found to + at least get a correct front raster out of them is to read it + line by line and then every second line appears to be valid front + data, ... */ + + /* read the raster data */ + for (i = 0; i < lines;) + { + uint8_t* dst_raster = background + bytes_per_line * i; + /* read stripe by stripe, or all in one chunk */ + size_t this_read, read_size; + int this_lines; + + if (dev->hw->feature_type & AV_2ND_LINE_INTERLACED) { + if (dev->hw->feature_type & AV_BACKGROUND_QUIRK) + this_lines = 1; + else + this_lines = lines; + } + else { + this_lines = s->val[OPT_BACKGROUND].w; + } + this_read = bytes_per_line * this_lines; + + DBG (3, "get_background_raster: line: %d, lines: %d, %lu bytes\n", + i, this_lines, (u_long) this_read); + + set_triple (rcmd.transferlen, this_read); + + read_size = this_read; + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), 0, 0, dst_raster, &read_size); + if (status != SANE_STATUS_GOOD || read_size != this_read) { + DBG (1, "get_background_raster: read raster failed (%s)\n", + sane_strstatus (status) ); + return status; + } + + i += this_lines; + } + + /* dump raw result while debugging */ + if (debug) + { + FILE* f = NULL; + f = fopen ("background-raw.pnm", "w"); + + write_pnm_header (f, (color_mode_is_color (s->c_mode) ? AV_TRUECOLOR : AV_GRAYSCALE), 8, + bytes_per_line / bpp, lines); + + fwrite (background, 1, bytes_per_line * lines, f); + fclose (f); + } + + /* line-pack - move to unified processing flow, later */ + if (dev->inquiry_needs_line_pack) + { + /* TODO: add 16bit per sample code? */ + int l, p; + + uint8_t* tmp_data = malloc (bytes_per_line); + for (l = 0; l < lines; ++l) + { + uint8_t* out_data = tmp_data; + uint8_t* r_ptr = background + (bytes_per_line * l); + uint8_t* g_ptr = r_ptr + bytes_per_line / bpp; + uint8_t* b_ptr = g_ptr + bytes_per_line / bpp; + + for (p = 0; p < bytes_per_line;) { + out_data [p++] = *(r_ptr++); + out_data [p++] = *(g_ptr++); + out_data [p++] = *(b_ptr++); + } + + memcpy (background + (bytes_per_line * l), tmp_data, bytes_per_line); + } + + free (tmp_data); + } /* end line pack */ + + /* deinterlace? */ + if (s->avdimen.interlaced_duplex && dev->hw->feature_type & AV_2ND_LINE_INTERLACED) + { + uint8_t* deinterlaced = malloc (size * 2); + if (!deinterlaced) + return SANE_STATUS_NO_MEM; + + for (i = 0; i < lines; ++i) + { + int dst_i = i / 2 + (i % 2) * (lines / 2); + uint8_t* dst_raster; /* just no C99 in SANE :-( */ + uint8_t* src_raster; + + /* for the quirky devices and some resolutions the interlacing differs */ + if (dev->hw->feature_type & AV_BACKGROUND_QUIRK && s->avdimen.hw_xres >= 150) + dst_i = i / 2 + ((i+1) % 2) * (lines / 2); + + dst_raster = deinterlaced + bytes_per_line * dst_i; + src_raster = background + bytes_per_line * i; + + DBG(3, "get_background_raster: deinterlaced %d -> %d\n", i, dst_i); + memcpy(dst_raster, src_raster, bytes_per_line); + } + + free (background); + background = s->background_raster = deinterlaced; + } + + /* dump raw result while debugging */ + for (i = 0; debug && i < (s->avdimen.interlaced_duplex ? 2 : 1); ++i) + { + FILE* f = NULL; + uint8_t* raster = background; + if (i == 0) { + f = fopen ("background.pnm", "w"); + } + else { + f = fopen ("background-rear.pnm", "w"); + raster += bytes_per_line * s->val[OPT_BACKGROUND].w; + } + + write_pnm_header (f, (color_mode_is_color (s->c_mode) ? AV_TRUECOLOR : AV_GRAYSCALE), 8, + bytes_per_line / bpp, s->val[OPT_BACKGROUND].w); + + fwrite (raster, 1, bytes_per_line * s->val[OPT_BACKGROUND].w, f); + fclose (f); + } + + /* crop from full-width scanlines to scan window */ + { + uint8_t *dst_ptr, *src_ptr; + dst_ptr = background; + src_ptr = background + s->avdimen.tlx * bpp; + for (i = 0; i < lines; ++i) + { + memmove (dst_ptr, src_ptr, s->avdimen.hw_bytes_per_line); + dst_ptr += s->avdimen.hw_bytes_per_line; + src_ptr += bytes_per_line; + } + } + + /* soft-scale - move to unified processing flow, later */ + if (s->avdimen.hw_xres != s->avdimen.xres) + { + const uint8_t* out_data = background; + uint8_t* dst = background; + + int l; + for (l = 0; l < lines; ++l) + { + const int hwbpl = s->avdimen.hw_bytes_per_line; + const int sy = l; + + int x; + for (x = 0; x < s->params.pixels_per_line; ++x) + { + const double bx = (-1.0 + s->avdimen.hw_pixels_per_line) * x / s->params.pixels_per_line; + const int sx = (int)floor(bx); + const int xdist = (int) ((bx - sx) * 256); + const int sxx = sx + 1; + + switch (bpp) { + case 1: + { + uint8_t v = + ( out_data [sy*hwbpl + sx ] * (256-xdist) + + out_data [sy*hwbpl + sxx] * xdist + ) / (256); + *dst++ = v; + } + break; + + case 3: + { + int c; + for (c = 0; c < 3; ++c) + { + uint8_t v = + ( out_data [sy*hwbpl + sx*3 + c] * (256-xdist) + + out_data [sy*hwbpl + sxx*3 + c] * xdist + ) / (256); + *dst++ = v; + } + } + break; + } + } + } + } + + /* dump final result while debugging */ + if (debug) { + for (i = 0; i < (s->avdimen.interlaced_duplex ? 2 : 1); ++i) + { + FILE* f = NULL; + uint8_t* raster = background; + if (i == 0) { + f = fopen ("background-final.pnm", "w"); + } + else { + f = fopen ("background-final-rear.pnm", "w"); + raster += s->params.bytes_per_line * s->val[OPT_BACKGROUND].w; + } + + write_pnm_header (f, (color_mode_is_color (s->c_mode) ? AV_TRUECOLOR : AV_GRAYSCALE), 8, + s->params.bytes_per_line / bpp, s->val[OPT_BACKGROUND].w); + + fwrite (raster, 1, s->params.bytes_per_line * s->val[OPT_BACKGROUND].w, f); + fclose (f); + } + } + + return SANE_STATUS_GOOD; +} + +static SANE_Status +reserve_unit (Avision_Scanner* s) +{ + char cmd[] = + {AVISION_SCSI_RESERVE_UNIT, 0, 0, 0, 0, 0}; + SANE_Status status; + + DBG (1, "reserve_unit:\n"); + + status = avision_cmd (&s->av_con, cmd, sizeof (cmd), 0, 0, 0, 0); + return status; +} + +static SANE_Status +release_unit (Avision_Scanner* s, int type) +{ + char cmd[] = + {AVISION_SCSI_RELEASE_UNIT, 0, 0, 0, 0, 0}; + SANE_Status status; + + DBG (1, "release unit: type: %d\n", type); + cmd[5] = type; /* latest scanners also allow 1: release paper and 2: end job */ + status = avision_cmd (&s->av_con, cmd, sizeof (cmd), 0, 0, 0, 0); + return status; +} + +/* Check if a sheet is present. */ +static SANE_Status +media_check (Avision_Scanner* s) +{ + char cmd[] = {AVISION_SCSI_MEDIA_CHECK, 0, 0, 0, 1, 0}; /* 1, 4 */ + SANE_Status status; + uint8_t result[1]; /* 4 */ + size_t size = sizeof(result); + + status = avision_cmd (&s->av_con, cmd, sizeof (cmd), + 0, 0, result, &size); + + debug_print_raw (5, "media_check: result\n", result, size); + + if (status == SANE_STATUS_GOOD) { + if (!(result[0] & 0x1)) + status = SANE_STATUS_NO_DOCS; + } + + return status; +} + +#if 0 /* unused */ +static SANE_Status +flush_media (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + SANE_Status status; + + if (s->source_mode_dim == AV_ADF_DIM && dev->inquiry_batch_scan) + { + DBG (1, "flush_media: flushing pages out of batch scanner\n"); + do { + status = media_check (s); + if (status == SANE_STATUS_GOOD) { + SANE_Status status2 = reserve_unit (s); + DBG (1, "flush_media: reserve status: %d\n", status2); + status2 = release_unit (s, 0); + DBG (1, "flush_media: release status: %d\n", status2); + } + } while (status == SANE_STATUS_GOOD); + } + return SANE_STATUS_GOOD; +} +#endif /* 0 - unused */ + +static SANE_Status +object_position (Avision_Scanner* s, uint8_t position) +{ + SANE_Status status; + + uint8_t cmd [10]; + + memset (cmd, 0, sizeof (cmd)); + cmd[0] = AVISION_SCSI_OBJECT_POSITION; + cmd[1] = position; + + DBG (1, "object_position: %d\n", position); + + status = avision_cmd (&s->av_con, cmd, sizeof(cmd), 0, 0, 0, 0); + return status; +} + +static SANE_Status +start_scan (Avision_Scanner* s) +{ + struct command_scan cmd; + + size_t size = sizeof (cmd); + + DBG (3, "start_scan:\n"); + + memset (&cmd, 0, sizeof (cmd)); + cmd.opc = AVISION_SCSI_SCAN; + cmd.transferlen = 1; + + /* AV610C2 in ADF preview mode does not detect the page end (...) */ + if (s->val[OPT_PREVIEW].w == SANE_TRUE && s->hw->inquiry_asic_type != AV_ASIC_C7) { + SET_BIT(cmd.bitset1,6); + } + + if (s->val[OPT_QSCAN].w == SANE_TRUE) { + SET_BIT(cmd.bitset1,7); + } + + DBG (3, "start_scan: sending command. Bytes: %lu\n", (u_long) size); + return avision_cmd (&s->av_con, &cmd, size, 0, 0, 0, 0); +} + +static SANE_Status +do_eof (Avision_Scanner *s) +{ + int exit_status; + + DBG (3, "do_eof:\n"); + + /* we do not scan anymore */ + s->prepared = s->scanning = SANE_FALSE; + + /* we can now mark the rear data as valid */ + if (s->avdimen.interlaced_duplex || + (s->hw->hw->feature_type2 & AV_ADF_FLIPPING_DUPLEX && s->source_mode == AV_ADF_DUPLEX)) { + DBG (3, "do_eof: toggling duplex rear data valid\n"); + s->duplex_rear_valid = !s->duplex_rear_valid; + DBG (3, "do_eof: duplex rear data valid: %x\n", + s->duplex_rear_valid); + } + + if (s->read_fds >= 0) { + close (s->read_fds); + s->read_fds = -1; + } + + /* join our processes - without a wait() you will produce zombies + (defunct children) */ + sanei_thread_waitpid (s->reader_pid, &exit_status); + s->reader_pid = -1; + + DBG (3, "do_eof: returning %d\n", exit_status); + return (SANE_Status)exit_status; +} + +static SANE_Status +do_cancel (Avision_Scanner* s) +{ + DBG (3, "do_cancel:\n"); + + s->prepared = s->scanning = SANE_FALSE; + s->duplex_rear_valid = SANE_FALSE; + s->page = 0; + + if (s->reader_pid != -1) { + int exit_status; + + /* ensure child knows it's time to stop: */ + sanei_thread_kill (s->reader_pid); + sanei_thread_waitpid (s->reader_pid, &exit_status); + s->reader_pid = -1; + } + + return SANE_STATUS_CANCELLED; +} + +static SANE_Status +read_data (Avision_Scanner* s, SANE_Byte* buf, size_t* count) +{ + struct command_read rcmd; + SANE_Status status; + + DBG (9, "read_data: %lu\n", (u_long) *count); + + memset (&rcmd, 0, sizeof (rcmd)); + + rcmd.opc = AVISION_SCSI_READ; + rcmd.datatypecode = 0x00; /* read image data */ + set_double (rcmd.datatypequal, s->hw->data_dq); + set_triple (rcmd.transferlen, *count); + + status = avision_cmd (&s->av_con, &rcmd, sizeof (rcmd), 0, 0, buf, count); + + return status; +} + +static SANE_Status +init_options (Avision_Scanner* s) +{ + Avision_Device* dev = s->hw; + int i; + + DBG (3, "init_options:\n"); + + memset (s->opt, 0, sizeof (s->opt)); + memset (s->val, 0, sizeof (s->val)); + + 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; + } + + /* Init the SANE option from the scanner inquiry data */ + + dev->x_range.max = SANE_FIX ( (int)dev->inquiry_x_ranges[s->source_mode_dim]); + dev->x_range.quant = 0; + dev->y_range.max = SANE_FIX ( (int)dev->inquiry_y_ranges[s->source_mode_dim]); + dev->y_range.quant = 0; + + switch (dev->inquiry_asic_type) { + case AV_ASIC_C2: + dev->dpi_range.min = 100; + break; + case AV_ASIC_C5: + dev->dpi_range.min = 80; + break; + case AV_ASIC_C6: /* TODO: AV610 in ADF mode does not scan less than 180 or so */ + dev->dpi_range.min = 50; + break; + case AV_ASIC_C7: /* AV610C2 empirically tested out */ + dev->dpi_range.min = 75; + break; + default: + dev->dpi_range.min = 50; + } + DBG (1, "init_options: dpi_range.min set to %d\n", dev->dpi_range.min); + + dev->dpi_range.quant = 1; /* any, including 72, 144, etc. */ + dev->dpi_range.max = dev->inquiry_max_res; + + dev->speed_range.min = (SANE_Int)0; + dev->speed_range.max = (SANE_Int)4; + dev->speed_range.quant = (SANE_Int)1; + + s->opt[OPT_NUM_OPTS].name = ""; + s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS; + s->opt[OPT_NUM_OPTS].desc = ""; + s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT; + s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT; + s->opt[OPT_NUM_OPTS].size = sizeof(SANE_TYPE_INT); + s->val[OPT_NUM_OPTS].w = NUM_OPTIONS; + + /* "Mode" group: */ + s->opt[OPT_MODE_GROUP].title = SANE_TITLE_SCAN_MODE; + s->opt[OPT_MODE_GROUP].desc = ""; /* for groups only title and type are valid */ + s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_MODE_GROUP].cap = 0; + s->opt[OPT_MODE_GROUP].size = 0; + s->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* color 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 = max_string_size (dev->color_list); + s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_MODE].constraint.string_list = dev->color_list; + s->val[OPT_MODE].s = strdup (dev->color_list[dev->color_list_default]); + s->c_mode = match_color_mode (dev, s->val[OPT_MODE].s); + + /* source mode */ + s->opt[OPT_SOURCE].name = SANE_NAME_SCAN_SOURCE; + s->opt[OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE; + s->opt[OPT_SOURCE].desc = SANE_DESC_SCAN_SOURCE; + s->opt[OPT_SOURCE].type = SANE_TYPE_STRING; + s->opt[OPT_SOURCE].size = max_string_size(dev->source_list); + s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + s->opt[OPT_SOURCE].constraint.string_list = &dev->source_list[0]; + s->val[OPT_SOURCE].s = strdup(dev->source_list[0]); + s->source_mode = match_source_mode (dev, s->val[OPT_SOURCE].s); + s->source_mode_dim = match_source_mode_dim (s->source_mode); + + /* 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 = &dev->dpi_range; + s->val[OPT_RESOLUTION].w = OPT_RESOLUTION_DEFAULT; + + /* preview */ + s->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW; + s->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW; + s->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW; + s->opt[OPT_PREVIEW].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT; + s->val[OPT_PREVIEW].w = 0; + + /* speed option */ + s->opt[OPT_SPEED].name = SANE_NAME_SCAN_SPEED; + s->opt[OPT_SPEED].title = SANE_TITLE_SCAN_SPEED; + s->opt[OPT_SPEED].desc = SANE_DESC_SCAN_SPEED; + s->opt[OPT_SPEED].type = SANE_TYPE_INT; + s->opt[OPT_SPEED].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_SPEED].constraint.range = &dev->speed_range; + s->val[OPT_SPEED].w = 0; + if (dev->scanner_type == AV_SHEETFEED) + s->opt[OPT_SPEED].cap |= SANE_CAP_INACTIVE; + + /* "Geometry" group: */ + s->opt[OPT_GEOMETRY_GROUP].title = "Geometry"; + s->opt[OPT_GEOMETRY_GROUP].desc = ""; /* for groups only title and type are valid */ + s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED; + s->opt[OPT_GEOMETRY_GROUP].size = 0; + s->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* 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 = &dev->x_range; + s->val[OPT_TL_X].w = 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 = &dev->y_range; + s->val[OPT_TL_Y].w = 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 = &dev->x_range; + s->val[OPT_BR_X].w = dev->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 = &dev->y_range; + s->val[OPT_BR_Y].w = dev->y_range.max; + + /* overscan top */ + s->opt[OPT_OVERSCAN_TOP].name = "overscan-top"; + s->opt[OPT_OVERSCAN_TOP].title = "Overscan top"; + s->opt[OPT_OVERSCAN_TOP].desc = "The top overscan controls the additional area to scan before the paper is detected."; + s->opt[OPT_OVERSCAN_TOP].type = SANE_TYPE_FIXED; + s->opt[OPT_OVERSCAN_TOP].unit = SANE_UNIT_MM; + s->opt[OPT_OVERSCAN_TOP].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_OVERSCAN_TOP].constraint.range = &overscan_range; + s->val[OPT_OVERSCAN_TOP].w = SANE_FIX(0); + + /* overscan bottom */ + s->opt[OPT_OVERSCAN_BOTTOM].name = "overscan-bottom"; + s->opt[OPT_OVERSCAN_BOTTOM].title = "Overscan bottom"; + s->opt[OPT_OVERSCAN_BOTTOM].desc = "The bottom overscan controls the additional area to scan after the paper end is detected."; + s->opt[OPT_OVERSCAN_BOTTOM].type = SANE_TYPE_FIXED; + s->opt[OPT_OVERSCAN_BOTTOM].unit = SANE_UNIT_MM; + s->opt[OPT_OVERSCAN_BOTTOM].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_OVERSCAN_BOTTOM].constraint.range = &overscan_range; + s->val[OPT_OVERSCAN_BOTTOM].w = SANE_FIX(0); + + if (!dev->inquiry_tune_scan_length) + s->opt[OPT_OVERSCAN_TOP].cap |= SANE_CAP_INACTIVE; + if (!dev->inquiry_tune_scan_length) + s->opt[OPT_OVERSCAN_BOTTOM].cap |= SANE_CAP_INACTIVE; + + /* background raster */ + s->opt[OPT_BACKGROUND].name = "background-lines"; + s->opt[OPT_BACKGROUND].title = "Background raster lines"; + s->opt[OPT_BACKGROUND].desc = "The background raster controls the additional background lines to scan before the paper is feed through the scanner."; + s->opt[OPT_BACKGROUND].type = SANE_TYPE_INT; + s->opt[OPT_BACKGROUND].unit = SANE_UNIT_PIXEL; + s->opt[OPT_BACKGROUND].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BACKGROUND].constraint.range = &background_range; + s->val[OPT_BACKGROUND].w = 0; + + if (!dev->inquiry_background_raster) { + s->opt[OPT_BACKGROUND].cap |= SANE_CAP_INACTIVE; + } + + /* "Enhancement" group: */ + s->opt[OPT_ENHANCEMENT_GROUP].title = "Enhancement"; + s->opt[OPT_ENHANCEMENT_GROUP].desc = ""; /* for groups only title and type are valid */ + s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_ENHANCEMENT_GROUP].cap = 0; + s->opt[OPT_ENHANCEMENT_GROUP].size = 0; + s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* brightness */ + s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS; + s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_FIXED; + if (disable_gamma_table) + s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_PERCENT; + s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_BRIGHTNESS].constraint.range = &percentage_range; + s->val[OPT_BRIGHTNESS].w = SANE_FIX(0); + + /* contrast */ + s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST; + s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST; + s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST; + s->opt[OPT_CONTRAST].type = SANE_TYPE_FIXED; + if (disable_gamma_table) + s->opt[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_CONTRAST].unit = SANE_UNIT_PERCENT; + s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_CONTRAST].constraint.range = &percentage_range; + s->val[OPT_CONTRAST].w = SANE_FIX(0); + + /* Quality Scan */ + s->opt[OPT_QSCAN].name = "quality-scan"; + s->opt[OPT_QSCAN].title = "Quality scan"; + s->opt[OPT_QSCAN].desc = "Turn on quality scanning (slower but better)."; + s->opt[OPT_QSCAN].type = SANE_TYPE_BOOL; + s->opt[OPT_QSCAN].unit = SANE_UNIT_NONE; + s->val[OPT_QSCAN].w = SANE_TRUE; + + /* Quality Calibration */ + s->opt[OPT_QCALIB].name = SANE_NAME_QUALITY_CAL; + s->opt[OPT_QCALIB].title = SANE_TITLE_QUALITY_CAL; + s->opt[OPT_QCALIB].desc = SANE_DESC_QUALITY_CAL; + s->opt[OPT_QCALIB].type = SANE_TYPE_BOOL; + s->opt[OPT_QCALIB].unit = SANE_UNIT_NONE; + s->val[OPT_QCALIB].w = SANE_TRUE; + + /* gray scale gamma vector */ + s->opt[OPT_GAMMA_VECTOR].name = SANE_NAME_GAMMA_VECTOR; + s->opt[OPT_GAMMA_VECTOR].title = SANE_TITLE_GAMMA_VECTOR; + s->opt[OPT_GAMMA_VECTOR].desc = SANE_DESC_GAMMA_VECTOR; + s->opt[OPT_GAMMA_VECTOR].type = SANE_TYPE_INT; + s->opt[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR].unit = SANE_UNIT_NONE; + s->opt[OPT_GAMMA_VECTOR].size = 256 * sizeof (SANE_Word); + s->opt[OPT_GAMMA_VECTOR].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_GAMMA_VECTOR].constraint.range = &u8_range; + s->val[OPT_GAMMA_VECTOR].wa = &s->gamma_table[0][0]; + + /* red gamma vector */ + s->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R; + s->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R; + s->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R; + s->opt[OPT_GAMMA_VECTOR_R].type = SANE_TYPE_INT; + s->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_R].unit = SANE_UNIT_NONE; + s->opt[OPT_GAMMA_VECTOR_R].size = 256 * sizeof (SANE_Word); + s->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_GAMMA_VECTOR_R].constraint.range = &u8_range; + s->val[OPT_GAMMA_VECTOR_R].wa = &s->gamma_table[1][0]; + + /* green gamma vector */ + s->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G; + s->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G; + s->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G; + s->opt[OPT_GAMMA_VECTOR_G].type = SANE_TYPE_INT; + s->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_G].unit = SANE_UNIT_NONE; + s->opt[OPT_GAMMA_VECTOR_G].size = 256 * sizeof (SANE_Word); + s->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_GAMMA_VECTOR_G].constraint.range = &u8_range; + s->val[OPT_GAMMA_VECTOR_G].wa = &s->gamma_table[2][0]; + + /* blue gamma vector */ + s->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B; + s->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B; + s->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B; + s->opt[OPT_GAMMA_VECTOR_B].type = SANE_TYPE_INT; + s->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_B].unit = SANE_UNIT_NONE; + s->opt[OPT_GAMMA_VECTOR_B].size = 256 * sizeof (SANE_Word); + s->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_GAMMA_VECTOR_B].constraint.range = &u8_range; + s->val[OPT_GAMMA_VECTOR_B].wa = &s->gamma_table[3][0]; + + if (!disable_gamma_table) + { + if (color_mode_is_color (s->c_mode)) { + s->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE; + } + else { + s->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE; + } + } + + /* exposure */ + s->opt[OPT_EXPOSURE].name = "exposure"; + s->opt[OPT_EXPOSURE].title = "Exposure"; + s->opt[OPT_EXPOSURE].desc = "Manual exposure adjustment."; + s->opt[OPT_EXPOSURE].type = SANE_TYPE_INT; + s->opt[OPT_EXPOSURE].unit = SANE_UNIT_PERCENT; + s->opt[OPT_EXPOSURE].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_EXPOSURE].constraint.range = &exposure_range; + s->val[OPT_EXPOSURE].w = 100; + + if (!dev->inquiry_exposure_control) { + s->opt[OPT_EXPOSURE].cap |= SANE_CAP_INACTIVE; + } + + /* Multi sample */ + s->opt[OPT_MULTISAMPLE].name = "multi-sample"; + s->opt[OPT_MULTISAMPLE].title = "Multi-sample"; + s->opt[OPT_MULTISAMPLE].desc = "Enable multi-sample scan mode."; + s->opt[OPT_MULTISAMPLE].type = SANE_TYPE_BOOL; + s->opt[OPT_MULTISAMPLE].unit = SANE_UNIT_NONE; + s->val[OPT_MULTISAMPLE].w = SANE_FALSE; + + /* TODO: No idea how to detect, assume exposure control devices are + new enough to support this, for now. -ReneR */ + if (!dev->inquiry_exposure_control) { + s->opt[OPT_MULTISAMPLE].cap |= SANE_CAP_INACTIVE; + } + + /* Infra-red */ + s->opt[OPT_IR].name = "infra-red"; + s->opt[OPT_IR].title = "Infra-red"; + s->opt[OPT_IR].desc = "Enable infra-red scan mode."; + s->opt[OPT_IR].type = SANE_TYPE_BOOL; + s->opt[OPT_IR].unit = SANE_UNIT_NONE; + s->val[OPT_IR].w = SANE_FALSE; + + /* TODO: No idea how to detect, assume exposure control devices are + new enough to support this, for now. -ReneR */ + if (!dev->inquiry_exposure_control) { + s->opt[OPT_IR].cap |= SANE_CAP_INACTIVE; + } + + /* "MISC" group: */ + s->opt[OPT_MISC_GROUP].title = SANE_TITLE_SCAN_MODE; + s->opt[OPT_MISC_GROUP].desc = ""; /* for groups only title and type are valid */ + s->opt[OPT_MISC_GROUP].type = SANE_TYPE_GROUP; + s->opt[OPT_MISC_GROUP].cap = 0; + s->opt[OPT_MISC_GROUP].size = 0; + s->opt[OPT_MISC_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* film holder control */ + if (dev->scanner_type != AV_FILM) + s->opt[OPT_FRAME].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_FRAME].name = SANE_NAME_FRAME; + s->opt[OPT_FRAME].title = SANE_TITLE_FRAME; + s->opt[OPT_FRAME].desc = SANE_DESC_FRAME; + s->opt[OPT_FRAME].type = SANE_TYPE_INT; + s->opt[OPT_FRAME].unit = SANE_UNIT_NONE; + s->opt[OPT_FRAME].constraint_type = SANE_CONSTRAINT_RANGE; + s->opt[OPT_FRAME].constraint.range = &dev->frame_range; + s->val[OPT_FRAME].w = dev->current_frame; + + /* power save time */ + if (!dev->inquiry_power_save_time) + s->opt[OPT_POWER_SAVE_TIME].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_POWER_SAVE_TIME].name = "power-save-time"; + s->opt[OPT_POWER_SAVE_TIME].title = "Power save timer control"; + s->opt[OPT_POWER_SAVE_TIME].desc = "Allows control of the scanner's power save timer, dimming or turning off the light."; + s->opt[OPT_POWER_SAVE_TIME].type = SANE_TYPE_INT; + s->opt[OPT_POWER_SAVE_TIME].unit = SANE_UNIT_NONE; + s->opt[OPT_POWER_SAVE_TIME].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_POWER_SAVE_TIME].w = 0; + + /* message, like options set on the scanner, LED no. & co */ + s->opt[OPT_MESSAGE].name = "message"; + s->opt[OPT_MESSAGE].title = "message text from the scanner"; + s->opt[OPT_MESSAGE].desc = "This text contains device specific options controlled by the user on the scanner hardware."; + s->opt[OPT_MESSAGE].type = SANE_TYPE_STRING; + s->opt[OPT_MESSAGE].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + s->opt[OPT_MESSAGE].size = 129; + s->opt[OPT_MESSAGE].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_MESSAGE].s = malloc(s->opt[OPT_MESSAGE].size); + s->val[OPT_MESSAGE].s[0] = 0; + + /* NVRAM */ + s->opt[OPT_NVRAM].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + if (!dev->inquiry_nvram_read) + s->opt[OPT_NVRAM].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_NVRAM].name = "nvram-values"; + s->opt[OPT_NVRAM].title = "Obtain NVRAM values"; + s->opt[OPT_NVRAM].desc = "Allows access obtaining the scanner's NVRAM values as pretty printed text."; + s->opt[OPT_NVRAM].type = SANE_TYPE_STRING; + s->opt[OPT_NVRAM].unit = SANE_UNIT_NONE; + s->opt[OPT_NVRAM].size = 1024; + s->opt[OPT_NVRAM].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_NVRAM].s = malloc(s->opt[OPT_NVRAM].size); + s->val[OPT_NVRAM].s[0] = 0; + + /* paper_length */ + s->opt[OPT_PAPERLEN].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + if (!dev->inquiry_paper_length) + s->opt[OPT_PAPERLEN].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_PAPERLEN].name = "paper-length"; + s->opt[OPT_PAPERLEN].title = "Use paper length"; + s->opt[OPT_PAPERLEN].desc = "Newer scanners can utilize this paper length to detect double feeds. However some others (DM152) can get confused during media flush if it is set."; + s->opt[OPT_PAPERLEN].type = SANE_TYPE_BOOL; + s->opt[OPT_PAPERLEN].unit = SANE_UNIT_NONE; + s->opt[OPT_PAPERLEN].size = sizeof(SANE_Word); + s->opt[OPT_PAPERLEN].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_PAPERLEN].w = SANE_FALSE; + + /* ADF page flipping */ + s->opt[OPT_ADF_FLIP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_AUTOMATIC | SANE_CAP_ADVANCED; + if (!(s->hw->hw->feature_type2 & AV_ADF_FLIPPING_DUPLEX && s->source_mode == AV_ADF_DUPLEX)) + s->opt[OPT_ADF_FLIP].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_ADF_FLIP].name = "flip-page"; + s->opt[OPT_ADF_FLIP].title = "Flip document after duplex scanning"; + s->opt[OPT_ADF_FLIP].desc = "Tells page-flipping document scanners to flip the paper back to its original orientation before dropping it in the output tray. Turning this off might make scanning a little faster if you don't care about manually flipping the pages afterwards."; + s->opt[OPT_ADF_FLIP].type = SANE_TYPE_BOOL; + s->opt[OPT_ADF_FLIP].unit = SANE_UNIT_NONE; + s->opt[OPT_ADF_FLIP].size = sizeof(SANE_Word); + s->opt[OPT_ADF_FLIP].constraint_type = SANE_CONSTRAINT_NONE; + s->val[OPT_ADF_FLIP].w = SANE_TRUE; + + return SANE_STATUS_GOOD; +} + +/* This function is executed as a child process. The reason this is + executed as a subprocess is because some (most?) generic SCSI + interfaces block a SCSI request until it has completed. With a + subprocess, we can let it block waiting for the request to finish + while the main process can go about to do more important things + (such as recognizing when the user presses a cancel button). + + WARNING: Since this is executed as a subprocess, it's NOT possible + to update any of the variables in the main process (in particular + the scanner state cannot be updated). */ + +static int +reader_process (void *data) +{ + struct Avision_Scanner *s = (struct Avision_Scanner *) data; + int fd = s->write_fds; + + Avision_Device* dev = s->hw; + + SANE_Status status; + SANE_Status exit_status = SANE_STATUS_GOOD; + sigset_t sigterm_set; + sigset_t ignore_set; + struct SIGACTION act; + + FILE* fp; + FILE* rear_fp = 0; /* used to store the deinterlaced rear data */ + FILE* raw_fp = 0; /* used to write the RAW image data for debugging */ + + /* the complex params */ + unsigned int lines_per_stripe; + unsigned int lines_per_output; + unsigned int max_bytes_per_read; + + SANE_Bool gray_mode; + + /* the simple params for the data reader */ + int hw_line = 0; + int line = 0; + + unsigned int stripe_size; + unsigned int stripe_fill; + unsigned int out_size; + + size_t total_size; + size_t processed_bytes; + + enum { + NONE, /* do not de-interlace at all */ + STRIPE, /* every 2nd stripe */ + HALF, /* the 2nd half */ + LINE /* every 2nd line */ + } deinterlace = NONE; + + /* the fat strip we currently puzzle together to perform software-colorpack + and more */ + uint8_t* stripe_data; + /* the corrected output data */ + uint8_t* out_data; + /* interpolation output data, one line */ + uint8_t* ip_history = 0; + uint8_t* ip_data = 0; + + DBG (3, "reader_process:\n"); + + if (sanei_thread_is_forked()) + close (s->read_fds); + + sigfillset (&ignore_set); + sigdelset (&ignore_set, SIGTERM); +#if defined (__APPLE__) && defined (__MACH__) + sigdelset (&ignore_set, SIGUSR2); +#endif + sigprocmask (SIG_SETMASK, &ignore_set, 0); + + memset (&act, 0, sizeof (act)); + sigaction (SIGTERM, &act, 0); + + sigemptyset (&sigterm_set); + sigaddset (&sigterm_set, SIGTERM); + + gray_mode = color_mode_is_shaded (s->c_mode); + + if (s->avdimen.interlaced_duplex) { + deinterlace = STRIPE; + + if ( (dev->hw->feature_type & AV_NON_INTERLACED_DUPLEX_300) && + (s->avdimen.hw_xres <= 300 && s->avdimen.hw_yres <= 300) ) + deinterlace = HALF; + if (dev->hw->feature_type & AV_2ND_LINE_INTERLACED) + deinterlace = LINE; + + if (dev->scanner_type == AV_FILM) + deinterlace = LINE; + } + + fp = fdopen (fd, "w"); + if (!fp) + return SANE_STATUS_NO_MEM; + + /* start scan ? */ + if ((deinterlace == NONE && !((dev->hw->feature_type2 & AV_ADF_FLIPPING_DUPLEX) && s->source_mode == AV_ADF_DUPLEX && s->duplex_rear_valid)) || + (deinterlace != NONE && !s->duplex_rear_valid)) + { + /* reserve unit - in the past we did this in open - but the + windows driver does reserves for each scan and some ADF + devices need a release for each sheet anyway ... */ + status = reserve_unit (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "reader_process: reserve_unit failed: %s\n", + sane_strstatus (status)); + return status; + } + + if (dev->hw->feature_type & AV_NO_START_SCAN) { + DBG (1, "reader_process: start_scan skipped due to device-list!\n"); + } + else { + status = start_scan (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "reader_process: start_scan failed: %s\n", + sane_strstatus (status)); + return status; + } + } + + if (dev->hw->feature_type & AV_ACCEL_TABLE) + /* (s->hw->inquiry_asic_type == AV_ASIC_C6) */ { + status = send_acceleration_table (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "reader_process: send_acceleration_table failed: %s\n", + sane_strstatus (status)); + return status; + } + } + } + + /* setup file i/o for deinterlacing scans or if we are the back page with a flipping duplexer */ + if (deinterlace != NONE || + (dev->hw->feature_type2 & AV_ADF_FLIPPING_DUPLEX && s->source_mode == AV_ADF_DUPLEX && !(s->page % 2))) + { + if (!s->duplex_rear_valid) { /* create new file for writing */ + DBG (3, "reader_process: opening duplex rear file for writing.\n"); + rear_fp = fopen (s->duplex_rear_fname, "w"); + if (! rear_fp) { + fclose (fp); + return SANE_STATUS_NO_MEM; + } + } + else { /* open saved rear data */ + DBG (3, "reader_process: opening duplex rear file for reading.\n"); + rear_fp = fopen (s->duplex_rear_fname, "r"); + if (! rear_fp) { + fclose (fp); + return SANE_STATUS_IO_ERROR; + } + } + } + + /* it takes quite a few lines to saturate the (USB) bus */ + lines_per_stripe = dev->read_stripe_size; + if (s->avdimen.line_difference) + lines_per_stripe += 2 * s->avdimen.line_difference; + + stripe_size = s->avdimen.hw_bytes_per_line * lines_per_stripe; + lines_per_output = lines_per_stripe - 2 * s->avdimen.line_difference; + + if (s->av_con.connection_type == AV_SCSI) + /* maybe better not /2 ... */ + max_bytes_per_read = dev->scsi_buffer_size / 2; + else + /* vast buffer size to saturate the bus */ + max_bytes_per_read = 0x100000; + + out_size = s->avdimen.hw_bytes_per_line * lines_per_output; + + DBG (3, "dev->scsi_buffer_size / 2: %d\n", + dev->scsi_buffer_size / 2); + + DBG (3, "bytes_per_line: %d, pixels_per_line: %d\n", + s->avdimen.hw_bytes_per_line, s->avdimen.hw_pixels_per_line); + + DBG (3, "lines_per_stripe: %d, lines_per_output: %d\n", + lines_per_stripe, lines_per_output); + + DBG (3, "max_bytes_per_read: %d, stripe_size: %d, out_size: %d\n", + max_bytes_per_read, stripe_size, out_size); + + stripe_data = malloc (stripe_size); + + /* for software scaling we need an additional interpolation line buffer */ + if (s->avdimen.hw_xres != s->avdimen.xres || + s->avdimen.hw_yres != s->avdimen.yres) + { + /* layout out_data so that the interpolation history is exactly in front */ + ip_history = malloc (s->avdimen.hw_bytes_per_line + out_size); + out_data = ip_history + s->avdimen.hw_bytes_per_line; + + ip_data = malloc (s->params.bytes_per_line); + } + else { + out_data = malloc (out_size); + } + + /* calculate params for the reading loop */ + total_size = s->avdimen.hw_bytes_per_line * (s->avdimen.hw_lines + + 2 * s->avdimen.line_difference + + s->avdimen.rear_offset); + if (deinterlace != NONE && !s->duplex_rear_valid) + total_size *= 2; + DBG (3, "reader_process: total_size: %lu\n", (u_long) total_size); + + /* write a RAW PNM file for debugging -ReneR */ + if (0 /* DEBUG */ && + (deinterlace == NONE || (deinterlace != NONE && !s->duplex_rear_valid)) ) + { + raw_fp = fopen ("/tmp/sane-avision.raw", "w"); + write_pnm_header (fp, s->c_mode, s->params.depth, + s->avdimen.hw_pixels_per_line, total_size / s->avdimen.hw_bytes_per_line); + } + + processed_bytes = 0; + stripe_fill = 0; + + /* First, dump background raster, bypassing all the other processing. */ + if (dev->inquiry_background_raster && s->val[OPT_BACKGROUND].w) + { + uint8_t* background = s->background_raster; + if (s->duplex_rear_valid) + background += s->params.bytes_per_line * s->val[OPT_BACKGROUND].w; + + DBG (5, "reader_process: dumping background raster\n"); + fwrite (background, s->params.bytes_per_line, s->val[OPT_BACKGROUND].w, fp); + } + + /* Data read; loop until all data has been processed. Might exit + before all lines are transferred for ADF paper end. */ + while (exit_status == SANE_STATUS_GOOD && processed_bytes < total_size) + { + unsigned int useful_bytes; + + DBG (5, "reader_process: stripe filled: %d\n", stripe_fill); + + /* fill the stripe buffer with real data */ + while (!s->duplex_rear_valid && + processed_bytes < total_size && stripe_fill < stripe_size && + exit_status == SANE_STATUS_GOOD) + { + size_t this_read = stripe_size - stripe_fill; + + /* Limit reads to max_bytes_per_read and global data + boundaries. Rounded to the next lower multiple of + byte_per_lines, otherwise some scanners freeze. */ + if (this_read > max_bytes_per_read) + this_read = (max_bytes_per_read - + max_bytes_per_read % s->avdimen.hw_bytes_per_line); + + if (processed_bytes + this_read > total_size) + this_read = total_size - processed_bytes; + + read_constrains(s, this_read); + + DBG (5, "reader_process: processed_bytes: %lu, total_size: %lu\n", + (u_long) processed_bytes, (u_long) total_size); + DBG (5, "reader_process: this_read: %lu\n", (u_long) this_read); + + sigprocmask (SIG_BLOCK, &sigterm_set, 0); + status = read_data (s, stripe_data + stripe_fill, &this_read); + sigprocmask (SIG_UNBLOCK, &sigterm_set, 0); + + /* only EOF on the second stripe, as otherwise the rear page + is shorter */ + if (status == SANE_STATUS_EOF && deinterlace == STRIPE) { + static int already_eof = 0; + if (!already_eof) { + DBG (5, "reader_process: first EOF on stripe interlace: hiding.\n"); + status = SANE_STATUS_GOOD; + already_eof = 1; + } + } + + /* write RAW data to file for debugging */ + if (raw_fp && this_read > 0) + fwrite (stripe_data + stripe_fill, this_read, 1, raw_fp); + + if (status == SANE_STATUS_EOF || this_read == 0) { + DBG (1, "reader_process: read_data failed due to EOF\n"); + exit_status = SANE_STATUS_EOF; + } + + if (status != SANE_STATUS_GOOD) { + DBG (1, "reader_process: read_data failed with status: %d\n", + status); + exit_status = status; + } + + stripe_fill += this_read; + processed_bytes += this_read; + } + + /* fill the stripe buffer with stored, virtual data */ + if (s->duplex_rear_valid) + { + size_t this_read = stripe_size - stripe_fill; + size_t got; + + /* limit reads to max_read and global data boundaries */ + if (this_read > max_bytes_per_read) + this_read = max_bytes_per_read; + + if (processed_bytes + this_read > total_size) + this_read = total_size - processed_bytes; + + DBG (5, "reader_process: virtual processed_bytes: %lu, total_size: %lu\n", + (u_long) processed_bytes, (u_long) total_size); + DBG (5, "reader_process: virtual this_read: %lu\n", (u_long) this_read); + + got = fread (stripe_data + stripe_fill, 1, this_read, rear_fp); + stripe_fill += got; + processed_bytes += got; + if (got != this_read) + exit_status = SANE_STATUS_EOF; + } + + DBG (5, "reader_process: stripe filled: %d\n", stripe_fill); + + useful_bytes = stripe_fill; + + if (color_mode_is_color (s->c_mode)) + useful_bytes -= 2 * s->avdimen.line_difference * s->avdimen.hw_bytes_per_line; + + DBG (3, "reader_process: useful_bytes %i\n", useful_bytes); + + /* Deinterlace, save the rear stripes. For some scanners (AV220) + that is every 2nd stripe, the 2nd half of the transferred + data ((AV83xx), or every 2nd line (AV122)). */ + if (deinterlace != NONE && !s->duplex_rear_valid) + { + /* for all lines we have in the buffer: */ + unsigned int absline = (processed_bytes - stripe_fill) / s->avdimen.hw_bytes_per_line; + unsigned int abslines = absline + useful_bytes / s->avdimen.hw_bytes_per_line; + uint8_t* ptr = stripe_data; + for ( ; absline < abslines; ++absline) + { + DBG (9, "reader_process: deinterlacing line %d\n", absline); + /* interlaced? save the back data to the rear buffer */ + if ( (deinterlace == STRIPE && absline % (lines_per_stripe*2) >= lines_per_stripe) || + (deinterlace == HALF && absline >= total_size / s->avdimen.hw_bytes_per_line / 2) || + (deinterlace == LINE && absline & 0x1) ) /* last bit equals % 2 */ + { + DBG (9, "reader_process: saving rear line %d to temporary file.\n", absline); + fwrite (ptr, s->avdimen.hw_bytes_per_line, 1, rear_fp); + if (deinterlace == LINE) + memmove (ptr, ptr+s->avdimen.hw_bytes_per_line, + stripe_data + stripe_fill - ptr - s->avdimen.hw_bytes_per_line); + else + ptr += s->avdimen.hw_bytes_per_line; + useful_bytes -= s->avdimen.hw_bytes_per_line; + stripe_fill -= s->avdimen.hw_bytes_per_line; + } + else + ptr += s->avdimen.hw_bytes_per_line; + } + DBG (9, "reader_process: after deinterlacing: useful_bytes: %d, stripe_fill: %d\n", + useful_bytes, stripe_fill); + } + if (dev->hw->feature_type2 & AV_ADF_FLIPPING_DUPLEX && s->source_mode == AV_ADF_DUPLEX && !(s->page % 2) && !s->duplex_rear_valid) { + /* Here we flip the image by writing the lines from the end of the file to the beginning. */ + unsigned int absline = (processed_bytes - stripe_fill) / s->avdimen.hw_bytes_per_line; + unsigned int abslines = absline + useful_bytes / s->avdimen.hw_bytes_per_line; + uint8_t* ptr = stripe_data; + for ( ; absline < abslines; ++absline) { + fseek (rear_fp, ((0 - s->params.lines) - absline - 2) * s->avdimen.hw_bytes_per_line, SEEK_SET); + fwrite (ptr, s->avdimen.hw_bytes_per_line, 1, rear_fp); + useful_bytes -= s->avdimen.hw_bytes_per_line; + stripe_fill -= s->avdimen.hw_bytes_per_line; + ptr += s->avdimen.hw_bytes_per_line; + } + DBG (9, "reader_process: after page flip: useful_bytes: %d, stripe_fill: %d\n", + useful_bytes, stripe_fill); + } else { + + /* + * Perform needed data conversions (packing, ...) and/or copy the + * image data. + */ + + if (s->c_mode != AV_TRUECOLOR && s->c_mode != AV_TRUECOLOR16) + /* simple copy */ + { + memcpy (out_data, stripe_data, useful_bytes); + } + else /* AV_TRUECOLOR* */ + { + /* WARNING: DO NOT MODIFY MY (HOPEFULLY WELL) OPTIMIZED + ALGORITHMS BELOW, WITHOUT UNDERSTANDING THEM FULLY ! */ + if (s->avdimen.line_difference > 0) /* color-pack */ + { + /* TODO: add 16bit per sample code? */ + unsigned int i; + int c_offset = s->avdimen.line_difference * s->avdimen.hw_bytes_per_line; + + uint8_t* r_ptr = stripe_data; + uint8_t* g_ptr = stripe_data + c_offset + 1; + uint8_t* b_ptr = stripe_data + 2 * c_offset + 2; + + for (i = 0; i < useful_bytes;) { + out_data [i++] = *r_ptr; r_ptr += 3; + out_data [i++] = *g_ptr; g_ptr += 3; + out_data [i++] = *b_ptr; b_ptr += 3; + } + } /* end color pack */ + else if (dev->inquiry_needs_line_pack) /* line-pack */ + { + /* TODO: add 16bit per sample code? */ + int i = 0, l, p; + const int lines = useful_bytes / s->avdimen.hw_bytes_per_line; + + for (l = 0; l < lines; ++l) + { + uint8_t* r_ptr = stripe_data + (s->avdimen.hw_bytes_per_line * l); + uint8_t* g_ptr = r_ptr + s->avdimen.hw_pixels_per_line; + uint8_t* b_ptr = g_ptr + s->avdimen.hw_pixels_per_line; + + for (p = 0; p < s->avdimen.hw_pixels_per_line; ++p) { + out_data [i++] = *(r_ptr++); + out_data [i++] = *(g_ptr++); + out_data [i++] = *(b_ptr++); + } + } + } /* end line pack */ + else /* else no packing was required -> simple copy */ + { + memcpy (out_data, stripe_data, useful_bytes); + } + } /* end if AV_TRUECOLOR* */ + + /* FURTHER POST-PROCESSING ON THE FINAL OUTPUT DATA */ + + /* maybe mirroring in ADF mode */ + if (s->source_mode_dim == AV_ADF_DIM && dev->inquiry_adf_need_mirror) + { + if ( (s->c_mode != AV_TRUECOLOR) || + (s->c_mode == AV_TRUECOLOR && dev->inquiry_adf_bgr_order) ) + { + /* Mirroring with bgr -> rgb conversion: Just mirror the + * whole line */ + + int l; + int lines = useful_bytes / s->avdimen.hw_bytes_per_line; + + for (l = 0; l < lines; ++l) + { + uint8_t* begin_ptr = out_data + (l * s->avdimen.hw_bytes_per_line); + uint8_t* end_ptr = begin_ptr + s->avdimen.hw_bytes_per_line; + + while (begin_ptr < end_ptr) { + uint8_t tmp; + tmp = *begin_ptr; + *begin_ptr++ = *end_ptr; + *end_ptr-- = tmp; + } + } + } + else /* non trivial mirroring */ + { + /* Non-trivial Mirroring with element swapping */ + + int l; + int lines = useful_bytes / s->avdimen.hw_bytes_per_line; + + for (l = 0; l < lines; ++l) + { + uint8_t* begin_ptr = out_data + (l * s->avdimen.hw_bytes_per_line); + uint8_t* end_ptr = begin_ptr + s->avdimen.hw_bytes_per_line - 3; + + while (begin_ptr < end_ptr) { + uint8_t tmp; + + /* R */ + tmp = *begin_ptr; + *begin_ptr++ = *end_ptr; + *end_ptr++ = tmp; + + /* G */ + tmp = *begin_ptr; + *begin_ptr++ = *end_ptr; + *end_ptr++ = tmp; + + /* B */ + tmp = *begin_ptr; + *begin_ptr++ = *end_ptr; + *end_ptr = tmp; + + end_ptr -= 5; + } + } + } + } /* end if mirroring needed */ + + /* byte swapping and software calibration 16bit mode */ + if (s->c_mode == AV_GRAYSCALE12 || + s->c_mode == AV_GRAYSCALE16 || + s->c_mode == AV_TRUECOLOR12 || + s->c_mode == AV_TRUECOLOR16) { + + int l; + int lines = useful_bytes / s->avdimen.hw_bytes_per_line; + + uint8_t* dark_avg_data = s->dark_avg_data; + uint8_t* white_avg_data = s->white_avg_data; + + uint8_t* begin_ptr = out_data; + uint8_t* end_ptr = begin_ptr + s->avdimen.hw_bytes_per_line; + uint8_t* line_ptr; + + double scale = 1.0; + if (s->c_mode == AV_GRAYSCALE12 || s->c_mode == AV_TRUECOLOR12) + scale = (double) (1<<4); + + while (begin_ptr < end_ptr) { + uint16_t dark_avg = 0; + uint16_t white_avg = WHITE_MAP_RANGE; + + if (dark_avg_data) + dark_avg = get_double_le (dark_avg_data); + if (white_avg_data) + white_avg = get_double_le (white_avg_data); + + line_ptr = begin_ptr; + for (l = 0; l < lines; ++ l) + { + double v = (double) get_double_le (line_ptr) * scale; + uint16_t v2; + if (0) + v = (v - dark_avg) * white_avg / WHITE_MAP_RANGE; + + v2 = v < 0xFFFF ? v : 0xFFFF; + + /* SANE Standard 3.2.1 "... bytes of each sample value are + transmitted in the machine's native byte order." */ + *line_ptr = v2; + + line_ptr += s->avdimen.hw_bytes_per_line; + } + + begin_ptr += 2; + if (dark_avg_data) + dark_avg_data += 2; + if (white_avg_data) + white_avg_data += 2; + } + } + + /* SOFTWARE SCALING WITH INTERPOLATION (IF NECESSARY) */ + + if (s->avdimen.hw_xres == s->avdimen.xres && + s->avdimen.hw_yres == s->avdimen.yres) /* No scaling */ + { + int lines, _hw_line = hw_line; + uint8_t* src = out_data; + /* we support cropping at the beginning and end due to rear offset */ + for (lines = useful_bytes / s->avdimen.hw_bytes_per_line; + lines > 0; --lines, ++_hw_line, src += s->avdimen.hw_bytes_per_line) + { + if (deinterlace != NONE) { + /* crop rear offset :-( */ + if ( (!s->duplex_rear_valid && _hw_line >= s->avdimen.hw_lines) || + (s->duplex_rear_valid && _hw_line < s->avdimen.rear_offset) ) + { + DBG (7, "reader_process: skip due read offset line: %d\n", line); + continue; + } + } + fwrite (src, s->avdimen.hw_bytes_per_line, 1, fp); + ++line; + } + } + else /* Software scaling - watch out - this code bites back! */ + { + int x; + /* for convenience in the 16bit code path */ + uint16_t* out_data16 = (uint16_t*) out_data; + + const int hw_line_end = hw_line + useful_bytes / s->avdimen.hw_bytes_per_line; + + /* on-the-fly bi-linear interpolation */ + while (1) { + double by = (-1.0 + s->avdimen.hw_lines) * line / (s->avdimen.hw_lines * s->avdimen.xres / s->avdimen.hw_xres + s->val[OPT_BACKGROUND].w); + int sy = (int)floor(by); + int ydist = (int) ((by - sy) * 256); + int syy = sy + 1; + + const int hwbpl = s->avdimen.hw_bytes_per_line; + + uint8_t* dst = ip_data; + uint16_t* dst16 = (uint16_t*) ip_data; + unsigned int v; /* accumulator */ + + /* Break out if we do not have the hw source line - yet, + or when we are past the end of wanted data (e.g. on the + front page due to rear_offset). Also take the read_offset + into account on the rear side */ + if (deinterlace != NONE) { + if (!s->duplex_rear_valid && syy >= s->avdimen.hw_lines) { + DBG (7, "reader_process: skip due past intended front page lines: %d\n", sy); + break; + } + else if (s->duplex_rear_valid) { + /* the beginning is to be skipped, accessed thru offset */ + DBG (7, "reader_process: rear_offset adjusting source: %d\n", sy); + sy += s->avdimen.rear_offset; + syy += s->avdimen.rear_offset; + } + } + + if (sy >= hw_line_end || syy >= hw_line_end) { + DBG (3, "reader_process: source line %d-%d not yet avail\n", + sy, syy); + break; + } + + /* convert to offset in current stripe */ + sy -= hw_line; + syy -= hw_line; + + if (sy < -1) { + DBG (1, "reader_process: need more history: %d???\n", sy); + sy = -1; + } + + DBG (8, "reader_process: out line: %d <- from: %d-%d\n", + line, sy, syy); + + for (x = 0; x < s->params.pixels_per_line; ++x) { + const double bx = (-1.0 + s->avdimen.hw_pixels_per_line) * x / s->params.pixels_per_line; + const int sx = (int)floor(bx); + const int xdist = (int) ((bx - sx) * 256); + const int sxx = sx + 1; + + if (x == 0 || x == s->params.pixels_per_line - 1) + DBG (8, "reader_process: x: %d <- from: %d-%d\n", + x, sx, sxx); + + switch (s->c_mode) { + case AV_THRESHOLDED: + case AV_DITHERED: + { + /* Repeating this over and over again is not fast, but + as a seldom used code-path we want it readable. + x/8 is the byte, and x%8 the bit position. */ + v = + ( ((out_data [sy*hwbpl + sx/8 ] >> (7-sx%8 )) & 1) * (256-xdist) * (256-ydist) + + ((out_data [sy*hwbpl + sxx/8] >> (7-sxx%8)) & 1) * xdist * (256-ydist) + + ((out_data [syy*hwbpl + sx/8 ] >> (7-sx%8 )) & 1) * (256-xdist) * ydist + + ((out_data [syy*hwbpl + sxx/8] >> (7-sxx%8)) & 1) * xdist * ydist + ) / (1 + 1 * 256); + + /* Shift and or the result together and eventually + jump to the next byte. */ + *dst = (*dst << 1) | ((v>>7)&1); + if (x % 8 == 7) + ++dst; + } + break; + + case AV_GRAYSCALE: + { + v = + ( out_data [sy*hwbpl + sx ] * (256-xdist) * (256-ydist) + + out_data [sy*hwbpl + sxx] * xdist * (256-ydist) + + out_data [syy*hwbpl + sx ] * (256-xdist) * ydist + + out_data [syy*hwbpl + sxx] * xdist * ydist + ) / (256 * 256); + *dst++ = v; + } + break; + + case AV_GRAYSCALE12: + case AV_GRAYSCALE16: + { + /* TODO: test! */ + v = + ( out_data16 [sy*hwbpl + sx ] * (256-xdist) * (256-ydist) + + out_data16 [sy*hwbpl + sxx] * xdist * (256-ydist) + + out_data16 [syy*hwbpl + sx ] * (256-xdist) * ydist + + out_data16 [syy*hwbpl + sxx] * xdist * ydist + ) / (256 * 256); + *dst16++ = v; + } + break; + + case AV_TRUECOLOR: + { + int c; + for (c = 0; c < 3; ++c) + { + v = + ( out_data [sy*hwbpl + sx*3 + c] * (256-xdist) * (256-ydist) + + out_data [sy*hwbpl + sxx*3 + c] * xdist * (256-ydist) + + out_data [syy*hwbpl + sx*3 + c] * (256-xdist) * ydist + + out_data [syy*hwbpl + sxx*3 + c] * xdist * ydist + ) / (256 * 256); + *dst++ = v; + } + } + break; + + case AV_TRUECOLOR12: + case AV_TRUECOLOR16: + { + /* TODO: test! */ + int c; + for (c = 0; c < 3; ++c) + { + v = + ( out_data16 [sy*hwbpl + sx*3 + c] * (256-xdist) * (256-ydist) + + out_data16 [sy*hwbpl + sxx*3 + c] * xdist * (256-ydist) + + out_data16 [syy*hwbpl + sx*3 + c] * (256-xdist) * ydist + + out_data16 [syy*hwbpl + sxx*3 + c] * xdist * ydist + ) / (256 * 256); + *dst16++ = v; + } + } + break; + + case AV_COLOR_MODE_LAST: + ; /* silence compiler warning */ + } + } + fwrite (ip_data, s->params.bytes_per_line, 1, fp); + ++line; + } + /* copy one line of history for the next pass */ + memcpy (ip_history, + out_data + useful_bytes - s->avdimen.hw_bytes_per_line, + s->avdimen.hw_bytes_per_line); + } + } + + /* save image date in stripe buffer for next next stripe */ + stripe_fill -= useful_bytes; + if (stripe_fill > 0) + memcpy (stripe_data, stripe_data + useful_bytes, stripe_fill); + + hw_line += useful_bytes / s->avdimen.hw_bytes_per_line; + + DBG (3, "reader_process: end of iteration\n"); + } /* end while not all lines or inf. mode */ + + DBG (3, "reader_process: i/o loop finished\n"); + if (exit_status == SANE_STATUS_GOOD) + exit_status = SANE_STATUS_EOF; + + if (raw_fp) + fclose (raw_fp); + + /* maybe we need to fill in some white data */ + if (exit_status == SANE_STATUS_EOF && line < s->params.lines) { + DBG (3, "reader_process: padding with white data\n"); + memset (out_data, gray_mode ? 0xff : 0x00, s->params.bytes_per_line); + + DBG (6, "reader_process: padding line %d - %d\n", + line, s->params.lines); + while (line < s->params.lines) { + fwrite (out_data, s->params.bytes_per_line, 1, fp); + ++line; + } + } + + /* Eject film holder and/or release_unit - but only for + non-duplex-rear / non-virtual scans. */ + if ((deinterlace != NONE && s->duplex_rear_valid) || + ((dev->hw->feature_type2 & AV_ADF_FLIPPING_DUPLEX) && s->source_mode == AV_ADF_DUPLEX && !(s->page % 2) && s->duplex_rear_valid)) + { + DBG (1, "reader_process: virtual duplex scan - no device cleanup!\n"); + } + else + { + /* poll the cancel button if the scanner is marked as having one */ + if (dev->hw->feature_type & AV_CANCEL_BUTTON) { + if (get_button_status (s) == SANE_STATUS_CANCELLED) + exit_status = SANE_STATUS_CANCELLED; + } + + status = release_unit (s, 0); + if (status != SANE_STATUS_GOOD) + DBG (1, "reader_process: release_unit failed\n"); + + if (dev->inquiry_new_protocol && dev->scanner_type == AV_FILM) { + status = object_position (s, AVISION_SCSI_OP_GO_HOME); + if (status != SANE_STATUS_GOOD) + DBG (1, "reader_process: object position go-home failed!\n"); + } + } + + if ((dev->hw->feature_type2 & AV_ADF_FLIPPING_DUPLEX) && s->source_mode == AV_ADF_DUPLEX && s->page % 2) { + /* front page of flipping duplex */ + if (exit_status == SANE_STATUS_EOF) { + if (s->val[OPT_ADF_FLIP].w) { + /* The page flip bit must be reset after every scan, but if the + * user doesn't care, there's no reason to reset. + */ + status = set_window (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "reader_process: set scan window command failed: %s\n", + sane_strstatus (status)); + return status; + } + } + /* we can set anything here without fear because the process will terminate soon and take our changes with it */ + s->page += 1; + s->params.lines = -line; + exit_status = reader_process (s); + } + /* TODO: + * else { + * spit out the page if an error was encountered... + * assuming the error won't prevent it. + * } */ + } else { + fclose (fp); + } + if (rear_fp) + fclose (rear_fp); + + if (ip_data) free (ip_data); + if (ip_history) + free (ip_history); + else + free (out_data); /* if we have ip_history out_data is included there */ + + free (stripe_data); + + DBG (3, "reader_process: returning success\n"); + return exit_status; +} + +/* SANE callback to attach a SCSI device */ +static SANE_Status +attach_one_scsi (const char* dev) +{ + attach (dev, AV_SCSI, 0); + return SANE_STATUS_GOOD; +} + +/* SANE callback to attach a USB device */ +static SANE_Status +attach_one_usb (const char* dev) +{ + attach (dev, AV_USB, 0); + return SANE_STATUS_GOOD; +} + +static SANE_Status +sane_reload_devices (void) +{ + FILE* fp; + + char line[PATH_MAX]; + const char* cp = 0; + char* word; + int linenumber = 0; + int model_num = 0; + + sanei_usb_init (); + fp = sanei_config_open (AVISION_CONFIG_FILE); + if (fp <= (FILE*)0) + { + DBG (1, "sane_reload_devices: No config file present!\n"); + } + else + { + /* first parse the config file */ + while (sanei_config_read (line, sizeof (line), fp)) + { + attaching_hw = 0; + + word = NULL; + ++ linenumber; + + DBG (5, "sane_reload_devices: parsing config line \"%s\"\n", + line); + + cp = sanei_config_get_string (line, &word); + + if (!word || cp == line) { + DBG (5, "sane_reload_devices: config file line %d: ignoring empty line\n", + linenumber); + if (word) { + free (word); + word = NULL; + } + continue; + } + + if (!word) { + DBG (1, "sane_reload_devices: config file line %d: could not be parsed\n", + linenumber); + continue; + } + + if (word[0] == '#') { + DBG (5, "sane_reload_devices: config file line %d: ignoring comment line\n", + linenumber); + free (word); + word = NULL; + continue; + } + + if (strcmp (word, "option") == 0) + { + free (word); + word = NULL; + cp = sanei_config_get_string (cp, &word); + + if (strcmp (word, "disable-gamma-table") == 0) { + DBG (3, "sane_reload_devices: config file line %d: disable-gamma-table\n", + linenumber); + disable_gamma_table = SANE_TRUE; + } + else if (strcmp (word, "disable-calibration") == 0) { + DBG (3, "sane_reload_devices: config file line %d: disable-calibration\n", + linenumber); + disable_calibration = SANE_TRUE; + } + else if (strcmp (word, "force-calibration") == 0) { + DBG (3, "sane_reload_devices: config file line %d: force-calibration\n", + linenumber); + force_calibration = SANE_TRUE; + } + else if (strcmp (word, "force-a4") == 0) { + DBG (3, "sane_reload_devices: config file line %d: enabling force-a4\n", + linenumber); + force_a4 = SANE_TRUE; + } + else if (strcmp (word, "force-a3") == 0) { + DBG (3, "sane_reload_devices: config file line %d: enabling force-a3\n", + linenumber); + force_a3 = SANE_TRUE; + } + else if (strcmp (word, "static-red-calib") == 0) { + DBG (3, "sane_reload_devices: config file line %d: static red calibration\n", + linenumber); + static_calib_list [0] = SANE_TRUE; + } + else if (strcmp (word, "static-green-calib") == 0) { + DBG (3, "sane_reload_devices: config file line %d: static green calibration\n", + linenumber); + static_calib_list [1] = SANE_TRUE; + } + else if (strcmp (word, "static-blue-calib") == 0) { + DBG (3, "sane_reload_devices: config file line %d: static blue calibration\n", + linenumber); + static_calib_list [2] = SANE_TRUE; + } + else + DBG (1, "sane_reload_devices: config file line %d: options unknown!\n", + linenumber); + } + else if (strcmp (word, "usb") == 0) { + DBG (2, "sane_reload_devices: config file line %d: trying to attach USB:`%s'\n", + linenumber, line); + /* try to attach USB device */ + sanei_usb_attach_matching_devices (line, attach_one_usb); + } + else if (strcmp (word, "scsi") == 0) { + DBG (2, "sane_reload_devices: config file line %d: trying to attach SCSI: %s'\n", + linenumber, line); + + /* the last time I verified (2003-03-18) this function + only matches SCSI devices ... */ + sanei_config_attach_matching_devices (line, attach_one_scsi); + } + else { + DBG (1, "sane_reload_devices: config file line %d: OBSOLETE !! use the scsi keyword!\n", + linenumber); + DBG (1, "sane_reload_devices: (see man sane-avision for details): trying to attach SCSI: %s'\n", + line); + + /* the last time I verified (2003-03-18) this function + only matched SCSI devices ... */ + sanei_config_attach_matching_devices (line, attach_one_scsi); + } + free (word); + word = NULL; + } /* end while read */ + + fclose (fp); + + if (word) + free (word); + } /* end if fp */ + + /* search for all supported SCSI/USB devices */ + while (Avision_Device_List [model_num].scsi_mfg != NULL || + Avision_Device_List [model_num].real_mfg != NULL) + { + /* also potentially accessed from the attach_* callbacks */ + attaching_hw = &(Avision_Device_List [model_num]); + if (attaching_hw->scsi_mfg != NULL) + sanei_scsi_find_devices (attaching_hw->scsi_mfg, + attaching_hw->scsi_model, NULL, + -1, -1, -1, -1, + attach_one_scsi); + + if (attaching_hw->usb_vendor != 0 && attaching_hw->usb_product != 0 ) + { + DBG (1, "sane_reload_devices: Trying to find USB device %.4x %.4x ...\n", + attaching_hw->usb_vendor, + attaching_hw->usb_product); + + /* TODO: check return value */ + if (sanei_usb_find_devices (attaching_hw->usb_vendor, + attaching_hw->usb_product, + attach_one_usb) != SANE_STATUS_GOOD) { + DBG (1, "sane_reload_devices: error during USB device detection!\n"); + } + } + ++ model_num; + } /* end for all devices in supported list */ + + attaching_hw = 0; + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_init (SANE_Int* version_code, SANE_Auth_Callback authorize) +{ + authorize = authorize; /* silence gcc */ + + DBG_INIT(); + +#ifdef AVISION_STATIC_DEBUG_LEVEL + DBG_LEVEL = AVISION_STATIC_DEBUG_LEVEL; +#endif + + DBG (3, "sane_init:(Version: %i.%i Build: %i)\n", + SANE_CURRENT_MAJOR, V_MINOR, BACKEND_BUILD); + + /* must come first */ + sanei_thread_init (); + + if (version_code) + *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, BACKEND_BUILD); + + sane_reload_devices (); + + return SANE_STATUS_GOOD; +} + +void +sane_exit (void) +{ + Avision_Device* dev; + Avision_Device* next; + + DBG (3, "sane_exit:\n"); + + for (dev = first_dev; dev; dev = next) { + next = dev->next; + /* no warning for stripping const - C lacks a const_cast<> */ + free ((void*)(size_t) dev->sane.name); + + free (dev); + } + first_dev = NULL; + + free(devlist); + devlist = NULL; +} + +SANE_Status +sane_get_devices (const SANE_Device*** device_list, SANE_Bool local_only) +{ + Avision_Device* dev; + int i; + + local_only = local_only; /* silence gcc */ + + DBG (3, "sane_get_devices:\n"); + + sane_reload_devices (); + + if (devlist) + free (devlist); + + devlist = malloc ((num_devices + 1) * sizeof (devlist[0])); + if (!devlist) + return SANE_STATUS_NO_MEM; + + i = 0; + for (dev = first_dev; i < num_devices; dev = dev->next) + devlist[i++] = &dev->sane; + devlist[i++] = 0; + + *device_list = devlist; + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_open (SANE_String_Const devicename, SANE_Handle *handle) +{ + Avision_Device* dev; + SANE_Status status; + Avision_Scanner* s; + int i, j; + uint8_t inquiry_result[AVISION_INQUIRY_SIZE_V1]; + + DBG (3, "sane_open:\n"); + + if (devicename[0]) { + for (dev = first_dev; dev; dev = dev->next) + if (strcmp (dev->sane.name, devicename) == 0) + break; + + if (dev) { + status = attach (devicename, dev->connection.connection_type, &dev); + if (status != SANE_STATUS_GOOD) + return status; + } + } else { + /* empty devicename -> use first device */ + dev = first_dev; + } + + if (!dev) + return SANE_STATUS_INVAL; + + s = malloc (sizeof (*s)); + if (!s) + return SANE_STATUS_NO_MEM; + + /* initialize ... */ + /* the other states (scanning, ...) rely on this memset (0) */ + memset (s, 0, sizeof (*s)); + + /* initialize connection state */ + s->av_con.connection_type = dev->connection.connection_type; + s->av_con.usb_status = dev->connection.usb_status; + s->av_con.scsi_fd = -1; + s->av_con.usb_dn = -1; + + s->reader_pid = -1; + s->read_fds = -1; + + s->hw = dev; + + /* We initialize the table to a gamma value of 2.22, since this is what + papers about Colorimetry suggest. + + http://www.poynton.com/GammaFAQ.html + + Avision's driver defaults to 2.2 though. */ + { + const double gamma = 2.22; + const double one_over_gamma = 1. / gamma; + + for (i = 0; i < 4; ++ i) + for (j = 0; j < 256; ++ j) + s->gamma_table[i][j] = pow( (double) j / 255, one_over_gamma) * 255; + } + + /* insert newly opened handle into list of open handles: */ + s->next = first_handle; + first_handle = s; + *handle = s; + + /* open the device */ + if (! avision_is_open (&s->av_con) ) { +#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED + DBG (1, "sane_open: using open_extended\n"); + status = avision_open_extended (s->hw->sane.name, &s->av_con, sense_handler, 0, + &(dev->scsi_buffer_size)); +#else + status = avision_open (s->hw->sane.name, &s->av_con, sense_handler, 0); +#endif + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_open: open of %s failed: %s\n", + s->hw->sane.name, sane_strstatus (status)); + return status; + } + DBG (1, "sane_open: got %d scsi_max_request_size\n", dev->scsi_buffer_size); + } + + /* first: re-awake the device with an inquiry, some devices are flunk while initializing + the usb connection and like a inquiry to come first ... (AV610 et.al.) */ + status = inquiry (s->av_con, inquiry_result, sizeof(inquiry_result)); + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_open: awakening inquiry failed: %s\n", sane_strstatus (status)); + return status; + } + + status = wait_ready (&s->av_con, 1); + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_open: wait_ready() failed: %s\n", sane_strstatus (status)); + return status; + } + + /* update settings based on additional accessory information */ + status = additional_probe (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_open: additional probe failed: %s\n", sane_strstatus (status)); + return status; + } + + /* initialize the options */ + init_options (s); + + if (dev->inquiry_duplex_interlaced || dev->scanner_type == AV_FILM || + dev->hw->feature_type2 & AV_ADF_FLIPPING_DUPLEX) { + /* Might need at least *DOS (Windows flavour and OS/2) portability fix + However, I was told Cygwin (et al.) takes care of it. */ + strncpy(s->duplex_rear_fname, "/tmp/avision-rear-XXXXXX", PATH_MAX); + + if (! mktemp(s->duplex_rear_fname) ) { + DBG (1, "sane_open: failed to generate temporary fname for duplex scans\n"); + return SANE_STATUS_NO_MEM; + } + else { + DBG (1, "sane_open: temporary fname for duplex scans: %s\n", + s->duplex_rear_fname); + } + } + + /* calibrate film scanners, as this must be done without the + film holder and at the full resolution */ + if (dev->scanner_type == AV_FILM) + { + int default_res = s->val[OPT_RESOLUTION].w; + s->val[OPT_RESOLUTION].w = dev->inquiry_optical_res; + + DBG (1, "sane_open: early calibration for film scanner.\n"); + + compute_parameters (s); + + status = set_window (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_open: set scan window command failed: %s\n", + sane_strstatus (status)); + return status; + } + + if (!(dev->hw->feature_type & AV_NO_CALIB)) + { + status = normal_calibration (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_open: perform calibration failed: %s\n", + sane_strstatus (status)); + return status; + } + } + + if (dev->scanner_type == AV_FILM) { + status = object_position (s, AVISION_SCSI_OP_GO_HOME); + if (status != SANE_STATUS_GOOD) + DBG (1, "reader_open: object position go-home failed!\n"); + } + + s->val[OPT_RESOLUTION].w = default_res; + } + + return SANE_STATUS_GOOD; +} + +void +sane_close (SANE_Handle handle) +{ + Avision_Scanner* prev; + Avision_Scanner* s = handle; + int i; + + DBG (3, "sane_close:\n"); + + /* close the device */ + if (avision_is_open (&s->av_con) ) { + avision_close (&s->av_con); + } + + /* remove handle from list of open handles: */ + prev = 0; + for (s = first_handle; s; s = s->next) { + if (s == handle) + break; + prev = s; + } + + /* a handle we know about ? */ + if (!s) { + DBG (1, "sane_close: invalid handle %p\n", handle); + return; + } + + if (s->scanning) + do_cancel (handle); + + if (prev) + prev->next = s->next; + else + first_handle = s->next; + + for (i = 1; i < NUM_OPTIONS; ++ i) { + if (s->opt[i].type == SANE_TYPE_STRING && s->val[i].s) { + free (s->val[i].s); + } + } + + if (s->white_avg_data) + free (s->white_avg_data); + if (s->dark_avg_data) + free (s->dark_avg_data); + + if (s->background_raster) + free (s->background_raster); + + if (*(s->duplex_rear_fname)) { + unlink (s->duplex_rear_fname); + *(s->duplex_rear_fname) = 0; + } + + free (handle); +} + +const SANE_Option_Descriptor* +sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) +{ + Avision_Scanner* s = handle; + + DBG (3, "sane_get_option_descriptor: %d\n", option); + + if ((unsigned) option >= NUM_OPTIONS) + return 0; + return s->opt + option; +} + +SANE_Status +sane_control_option (SANE_Handle handle, SANE_Int option, + SANE_Action action, void* val, SANE_Int* info) +{ + Avision_Scanner* s = handle; + Avision_Device* dev = s->hw; + SANE_Status status; + SANE_Word cap; + + DBG (3, "sane_control_option: option=%d, action=%d\n", + (int)option, (int)action); + + DBG (5, "sane_control_option: option=%s, action=%s\n", + s->opt[option].name, + action == SANE_ACTION_GET_VALUE ? "GET" : + (action == SANE_ACTION_SET_VALUE ? "SET" : + (action == SANE_ACTION_SET_AUTO ? "AUTO" : "UNKNOWN") ) ); + + if (info) + *info = 0; + + if (s->scanning) + return SANE_STATUS_DEVICE_BUSY; + + if (option >= NUM_OPTIONS) + return SANE_STATUS_INVAL; + + cap = s->opt[option].cap; + + if (!SANE_OPTION_IS_ACTIVE (cap)) + return SANE_STATUS_INVAL; + + if (action == SANE_ACTION_GET_VALUE) + { + switch (option) + { + /* word options: */ + case OPT_PREVIEW: + + case OPT_RESOLUTION: + case OPT_SPEED: + case OPT_TL_X: + case OPT_TL_Y: + case OPT_BR_X: + case OPT_BR_Y: + case OPT_OVERSCAN_TOP: + case OPT_OVERSCAN_BOTTOM: + case OPT_BACKGROUND: + case OPT_NUM_OPTS: + + case OPT_BRIGHTNESS: + case OPT_CONTRAST: + case OPT_EXPOSURE: + case OPT_IR: + case OPT_MULTISAMPLE: + case OPT_QSCAN: + case OPT_QCALIB: + case OPT_PAPERLEN: + case OPT_ADF_FLIP: + *(SANE_Word*) val = s->val[option].w; + return SANE_STATUS_GOOD; + + /* specially treated word options */ + + case OPT_FRAME: + status = get_frame_info (s); + *(SANE_Word*) val = s->val[option].w; + return status; + + case OPT_POWER_SAVE_TIME: + get_power_save_time (s, &(s->val[option].w)); + *(SANE_Word*) val = s->val[option].w; + return SANE_STATUS_GOOD; + + /* word-array options: */ + case OPT_GAMMA_VECTOR: + case OPT_GAMMA_VECTOR_R: + case OPT_GAMMA_VECTOR_G: + case OPT_GAMMA_VECTOR_B: + memcpy (val, s->val[option].wa, s->opt[option].size); + return SANE_STATUS_GOOD; + + /* string options: */ + case OPT_MODE: + case OPT_SOURCE: + strcpy (val, s->val[option].s); + return SANE_STATUS_GOOD; + + /* specially treated string options */ + case OPT_MESSAGE: + if (dev->inquiry_button_control || dev->inquiry_buttons) + status = get_button_status (s); + + strcpy (val, s->val[option].s); + s->val[option].s[0] = 0; + return SANE_STATUS_GOOD; + + case OPT_NVRAM: + get_and_parse_nvram (s, s->val[option].s, 1024); + + strcpy (val, s->val[option].s); + return SANE_STATUS_GOOD; + + } /* end switch option */ + } /* end if GET_ACTION_GET_VALUE */ + else if (action == SANE_ACTION_SET_VALUE) + { + if (!SANE_OPTION_IS_SETTABLE (cap)) + return SANE_STATUS_INVAL; + + status = constrain_value (s, option, val, info); + if (status != SANE_STATUS_GOOD) + return status; + + switch (option) + { + /* side-effect-free word options: */ + case OPT_SPEED: + case OPT_PREVIEW: + case OPT_BRIGHTNESS: + case OPT_CONTRAST: + case OPT_EXPOSURE: + case OPT_IR: + case OPT_MULTISAMPLE: + case OPT_QSCAN: + case OPT_QCALIB: + case OPT_OVERSCAN_TOP: + case OPT_OVERSCAN_BOTTOM: + case OPT_BACKGROUND: + case OPT_PAPERLEN: + case OPT_ADF_FLIP: + s->val[option].w = *(SANE_Word*) val; + return SANE_STATUS_GOOD; + + /* side-effect-free word-array options: */ + case OPT_GAMMA_VECTOR: + case OPT_GAMMA_VECTOR_R: + case OPT_GAMMA_VECTOR_G: + case OPT_GAMMA_VECTOR_B: + memcpy (s->val[option].wa, val, s->opt[option].size); + return SANE_STATUS_GOOD; + + /* options with side-effects: */ + case OPT_RESOLUTION: + case OPT_TL_X: + case OPT_TL_Y: + case OPT_BR_X: + case OPT_BR_Y: + + s->val[option].w = *(SANE_Word*) val; + + if (info) + *info |= SANE_INFO_RELOAD_PARAMS; + + return SANE_STATUS_GOOD; + + /* string options with side-effects: */ + case OPT_SOURCE: + + if (s->val[option].s) { + free(s->val[option].s); + } + s->val[option].s = strdup(val); + s->source_mode = match_source_mode (dev, s->val[option].s); + s->source_mode_dim = match_source_mode_dim (s->source_mode); + + /* set side-effects */ + dev->x_range.max = + SANE_FIX ( dev->inquiry_x_ranges[s->source_mode_dim]); + dev->y_range.max = + SANE_FIX ( dev->inquiry_y_ranges[s->source_mode_dim]); + + if (s->hw->hw->feature_type2 & AV_ADF_FLIPPING_DUPLEX && s->source_mode == AV_ADF_DUPLEX) { + s->opt[OPT_ADF_FLIP].cap &= ~SANE_CAP_INACTIVE; + } else { + s->opt[OPT_ADF_FLIP].cap |= SANE_CAP_INACTIVE; + } + + if (info) + *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS; + + return SANE_STATUS_GOOD; + + case OPT_MODE: + { + if (s->val[option].s) + free (s->val[option].s); + + s->val[option].s = strdup (val); + s->c_mode = match_color_mode (dev, s->val[OPT_MODE].s); + + /* set to mode specific values */ + + /* the gamma table related */ + if (!disable_gamma_table) + { + if (color_mode_is_color (s->c_mode) ) { + s->opt[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE; + } + else /* gray or mono */ + { + s->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE; + s->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE; + } + } + if (info) + *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS; + return SANE_STATUS_GOOD; + } + case OPT_FRAME: + { + SANE_Word frame = *((SANE_Word *) val); + + status = set_frame (s, frame); + if (status == SANE_STATUS_GOOD) { + s->val[OPT_FRAME].w = frame; + dev->current_frame = frame; + } + return status; + } + case OPT_POWER_SAVE_TIME: + { + SANE_Word time = *((SANE_Word *) val); + + status = set_power_save_time (s, time); + if (status == SANE_STATUS_GOOD) + s->val[OPT_POWER_SAVE_TIME].w = time; + return status; + } + } /* end switch option */ + } + else if (action == SANE_ACTION_SET_AUTO) + { + if (!SANE_OPTION_IS_SETTABLE (cap)) + return SANE_STATUS_INVAL; + + switch (option) + { + case OPT_ADF_FLIP: + s->val[option].w = SANE_TRUE; + return SANE_STATUS_GOOD; + } /* end switch option */ + } /* end else SET_VALUE */ + return SANE_STATUS_INVAL; +} + +SANE_Status +sane_get_parameters (SANE_Handle handle, SANE_Parameters* params) +{ + Avision_Scanner* s = handle; + + DBG (3, "sane_get_parameters:\n"); + + /* During an actual scan these parameters will have been computed in + sane_start(). Otherwise, the values must be computed on demand. The + values cannot be changed during a scan to avoid inconsistency. */ + if (!s->scanning) + { + DBG (3, "sane_get_parameters: computing parameters\n"); + compute_parameters (s); + } + + if (params) { + /* add background raster lines */ + s->params.lines += s->val[OPT_BACKGROUND].w; + + /* copy structure members */ + params->format = s->params.format; + params->last_frame = s->params.last_frame; + params->bytes_per_line = s->params.bytes_per_line; + params->pixels_per_line = s->params.pixels_per_line; + params->lines = s->params.lines; + params->depth = s->params.depth; + } + + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_start (SANE_Handle handle) +{ + Avision_Scanner* s = handle; + Avision_Device* dev = s->hw; + + SANE_Status status; + int fds [2]; + DBG (1, "sane_start:\n"); + + /* Make sure there is no scan running!!! */ + if (s->scanning) + return SANE_STATUS_DEVICE_BUSY; + + /* Make sure we have a current parameter set. Some of the + parameters will be overwritten below, but that's OK. */ + status = sane_get_parameters (s, &s->params); + if (status != SANE_STATUS_GOOD) { + return status; + } + + /* for non ADF scans (e.g. scanimage --batch-prompt on a Flatbed + scanner) make sure we do not assume it's an ADF scan and + optimize something away*/ + if (!is_adf_scan (s)) + s->page = 0; + + if (s->page > 0 && s->duplex_rear_valid) { + DBG (1, "sane_start: virtual duplex rear data valid.\n"); + goto start_scan_end; + } + + /* Check for paper during ADF scans and for sheetfed scanners. */ + if (is_adf_scan (s)) { + status = media_check (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_start: media_check failed: %s\n", + sane_strstatus (status)); + return status; + } + else + DBG (1, "sane_start: media_check ok\n"); + } + + /* Check the light early, to return to the GUI and notify the user. */ + if (s->prepared == SANE_FALSE) { + if (dev->inquiry_light_control) { + status = wait_4_light (s); + if (status != SANE_STATUS_GOOD) { + return status; + } + } + } + + if (s->page > 0 && dev->inquiry_keeps_window) { + DBG (1, "sane_start: Optimized set_window away.\n"); + } + else + { + status = set_window (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_start: set scan window command failed: %s\n", + sane_strstatus (status)); + goto stop_scanner_and_return; + } + } + +#ifdef DEBUG_TEST + /* debug window size test ... */ + if (dev->inquiry_new_protocol) + { + size_t size = 16; + uint8_t result[16]; + + DBG (5, "sane_start: reading scanner window size\n"); + + status = simple_read (s, 0x80, 0, &size, result); + + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_start: get pixel size command failed: %s\n", + sane_strstatus (status)); + goto stop_scanner_and_return; + } + debug_print_raw (5, "sane_start: pixel_size:", result, size); + DBG (5, "sane_start: x-pixels: %d, y-pixels: %d\n", + get_quad (&(result[0])), get_quad (&(result[4]))); + } +#endif + + /* no calibration for ADF pages */ + if (s->page > 0) { + DBG (1, "sane_start: optimized calibration away.\n"); + goto calib_end; + } + + /* check whether the user enforces calibration */ + if (force_calibration) { + DBG (1, "sane_start: calibration enforced in config!\n"); + goto calib; + } + + /* Only perform the calibration for newer scanners - it is not needed + for my Avision AV 630 - and also does not even work ... */ + if (!dev->inquiry_new_protocol) { + DBG (1, "sane_start: old protocol no calibration needed!\n"); + goto calib_end; + } + + if (!dev->inquiry_needs_calibration) { + DBG (1, "sane_start: due to inquiry no calibration needed!\n"); + goto calib_end; + } + + /* calibration allowed for this scanner? */ + if (dev->hw->feature_type & AV_NO_CALIB) { + DBG (1, "sane_start: calibration disabled in device list!!\n"); + goto calib_end; + } + + /* Not for film scanners, ... */ + if (dev->scanner_type == AV_FILM) { + DBG (1, "sane_start: no calibration for film scanner!\n"); + goto calib_end; + } + + /* check whether calibration is disabled by the user */ + if (disable_calibration) { + DBG (1, "sane_start: calibration disabled in config - skipped!\n"); + goto calib_end; + } + + /* R² reminder: We must not skip the calibration for ADF scans, some + scanner (HP 53xx/74xx ASIC series) rely on a calibration data + read (and will hang otherwise) */ + + calib: + status = normal_calibration (s); + + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_start: perform calibration failed: %s\n", + sane_strstatus (status)); + goto stop_scanner_and_return; + } + + calib_end: + + if (dev->inquiry_3x3_matrix && dev->inquiry_asic_type >= AV_ASIC_C6 && + s->page == 0) + { + status = send_3x3_matrix (s); + if (status != SANE_STATUS_GOOD) { + return status; + } + } + + /* check whether gamma-table is disabled by the user? */ + if (disable_gamma_table) { + DBG (1, "sane_start: gamma-table disabled in config - skipped!\n"); + goto gamma_end; + } + + if (dev->hw->feature_type & AV_NO_GAMMA) { + DBG (1, "sane_start: gamma table skipped due to device-list!!\n"); + goto gamma_end; + } + + if (s->page > 0 && dev->inquiry_keeps_gamma) + DBG (1, "sane_start: Optimized send_gamma away.\n"); + else + { + status = send_gamma (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_start: send gamma failed: %s\n", + sane_strstatus (status)); + goto stop_scanner_and_return; + } + } + + gamma_end: + + if (dev->inquiry_tune_scan_length && is_adf_scan (s)) { + status = send_tune_scan_length (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_start: tune_scan_length command failed: %s\n", + sane_strstatus (status)); + goto stop_scanner_and_return; + } + } + + /* if the device supports retrieving background raster data + inquire the data no matter if the user/applications asks for + it in order to use it for bottom padding */ + if (s->page == 0 && dev->inquiry_background_raster) { + status = get_background_raster (s); + if (status != SANE_STATUS_GOOD) { + DBG (1, "sane_start: get background raster command failed: %s\n", + sane_strstatus (status)); + goto stop_scanner_and_return; + } + } + + /* check film holder */ + if (dev->scanner_type == AV_FILM && dev->holder_type == 0xff) { + DBG (1, "sane_start: no film holder or APS cassette!\n"); + + /* Normally "go_home" is executed from the reader process, + but as it will not start we have to reset things here */ + if (dev->inquiry_new_protocol) { + status = object_position (s, AVISION_SCSI_OP_GO_HOME); + if (status != SANE_STATUS_GOOD) + DBG (1, "sane_start: go home failed: %s\n", + sane_strstatus (status)); + } + goto stop_scanner_and_return; + } + + start_scan_end: + + s->scanning = SANE_TRUE; + s->page += 1; /* processing next page */ + + if (pipe (fds) < 0) { + return SANE_STATUS_IO_ERROR; + } + + s->read_fds = fds[0]; + s->write_fds = fds[1]; + + /* create reader routine as new process or thread */ + DBG (3, "sane_start: starting thread\n"); + s->reader_pid = sanei_thread_begin (reader_process, (void *) s); + + if (sanei_thread_is_forked()) + close (s->write_fds); + + return SANE_STATUS_GOOD; + + stop_scanner_and_return: + + /* cancel the scan nicely */ + do_cancel (s); + + return status; +} + +SANE_Status +sane_read (SANE_Handle handle, SANE_Byte* buf, SANE_Int max_len, SANE_Int* len) +{ + Avision_Scanner* s = handle; + ssize_t nread; + *len = 0; + + DBG (8, "sane_read: max_len: %d\n", max_len); + + nread = read (s->read_fds, buf, max_len); + if (nread > 0) { + DBG (8, "sane_read: got %ld bytes\n", (long) nread); + } + else { + DBG (3, "sane_read: got %ld bytes, err: %d %s\n", (long) nread, errno, strerror(errno)); + } + + if (!s->scanning) + return SANE_STATUS_CANCELLED; + + if (nread < 0) { + if (errno == EAGAIN) { + return SANE_STATUS_GOOD; + } else { + do_cancel (s); + return SANE_STATUS_IO_ERROR; + } + } + + *len = nread; + + /* if all data was passed through */ + if (nread == 0) + return do_eof (s); + + return SANE_STATUS_GOOD; +} + +void +sane_cancel (SANE_Handle handle) +{ + Avision_Scanner* s = handle; + DBG (3, "sane_cancel:\n"); + + /* always do the housekeeping, e.g. flush batch scanner pages */ + do_cancel (s); +} + +SANE_Status +sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking) +{ + Avision_Scanner* s = handle; + + DBG (3, "sane_set_io_mode:\n"); + if (!s->scanning) { + DBG (3, "sane_set_io_mode: not yet scanning\n"); + return SANE_STATUS_INVAL; + } + + if (fcntl (s->read_fds, F_SETFL, non_blocking ? O_NONBLOCK : 0) < 0) + return SANE_STATUS_IO_ERROR; + + return SANE_STATUS_GOOD; +} + +SANE_Status +sane_get_select_fd (SANE_Handle handle, SANE_Int* fd) +{ + Avision_Scanner* s = handle; + + DBG (3, "sane_get_select_fd:\n"); + + if (!s->scanning) { + DBG (3, "sane_get_select_fd: not yet scanning\n"); + return SANE_STATUS_INVAL; + } + + *fd = s->read_fds; + return SANE_STATUS_GOOD; +} |