aboutsummaryrefslogtreecommitdiff
path: root/backend/epkowa.c
diff options
context:
space:
mode:
Diffstat (limited to 'backend/epkowa.c')
-rw-r--r--backend/epkowa.c6513
1 files changed, 6513 insertions, 0 deletions
diff --git a/backend/epkowa.c b/backend/epkowa.c
new file mode 100644
index 0000000..14887bb
--- /dev/null
+++ b/backend/epkowa.c
@@ -0,0 +1,6513 @@
+/* epkowa.c - SANE backend for EPSON flatbed scanners
+ (Image Scan! version)
+
+ Based on the SANE Epson backend (originally from sane-1.0.3)
+ - updated to sane-backends-1.0.6
+ - renamed from epson to epkowa to avoid confusion
+ - updated to sane-backends-1.0.12
+ - updated to sane-backends-1.0.15
+
+ Based on Kazuhiro Sasayama previous
+ Work on epson.[ch] file from the SANE package.
+
+ Original code taken from sane-0.71
+ Copyright (C) 1997 Hypercore Software Design, Ltd.
+
+ modifications
+ Copyright (C) 1998-1999 Christian Bucher <bucher@vernetzt.at>
+ Copyright (C) 1998-1999 Kling & Hautzinger GmbH
+ Copyright (C) 1999 Norihiko Sawa <sawa@yb3.so-net.ne.jp>
+ Copyright (C) 2000 Mike Porter <mike@udel.edu> (mjp)
+ Copyright (C) 1999-2004 Karl Heinz Kremer <khk@khk.net>
+ Copyright (C) 2001-2016 SEIKO EPSON CORPORATION
+
+ This file is part of the EPKOWA SANE backend.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+
+
+#ifndef SANE_I18N
+#define SANE_I18N(text) (text)
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <math.h>
+
+#include <sane/saneopts.h>
+
+#ifndef BACKEND_NAME
+#define BACKEND_NAME epkowa
+#endif
+
+#include "backend.h"
+#include "command.h"
+#include "hw-data.h"
+#include "utils.h"
+#include "timing.h"
+#include "cfg-obj.h"
+#include "dip-obj.h"
+#include "model-info.h"
+#include "utils.h"
+
+#include "epkowa_scsi.h"
+#include "sane/sanei_pio.h"
+#include "epkowa_ip.h" /* interpreter-based scanner support */
+
+#include "sane/sanei.h"
+
+#define DEFAULT_RESOLUTION 300 /* dpi */
+#define DEFAULT_X_RESOLUTION DEFAULT_RESOLUTION
+#define DEFAULT_Y_RESOLUTION DEFAULT_RESOLUTION
+
+#define S_ACK "\006"
+#define S_CAN "\030"
+
+/* Usable values when defining EPSON_LEVEL_DEFAULT
+ * There is also a function level "A5", used for the GT-300, a
+ * monochrome only scanner. This level is not supported.
+ */
+#define EPSON_LEVEL_A1 0
+#define EPSON_LEVEL_A2 1
+#define EPSON_LEVEL_B1 2
+#define EPSON_LEVEL_B2 3
+#define EPSON_LEVEL_B3 4
+#define EPSON_LEVEL_B4 5
+#define EPSON_LEVEL_B5 6
+#define EPSON_LEVEL_B6 7
+#define EPSON_LEVEL_B7 8
+#define EPSON_LEVEL_B8 9
+#define EPSON_LEVEL_F5 10
+#define EPSON_LEVEL_D1 11
+#define EPSON_LEVEL_D2 12
+#define EPSON_LEVEL_D7 13
+#define EPSON_LEVEL_D8 14
+
+#define EPSON_LEVEL_DEFAULT EPSON_LEVEL_B3
+
+static EpsonCmdRec epson_cmd[] = {
+/*
+ * request identity
+ * | request identity2
+ * | | request status
+ * | | | request condition
+ * | | | | set color mode
+ * | | | | | start scanning
+ * | | | | | | set data format
+ * | | | | | | | set resolution
+ * | | | | | | | | set zoom
+ * | | | | | | | | | set scan area
+ * | | | | | | | | | | set brightness
+ * | | | | | | | | | | | set gamma
+ * | | | | | | | | | | | | set halftoning
+ * | | | | | | | | | | | | | set color correction
+ * | | | | | | | | | | | | | | initialize scanner
+ * | | | | | | | | | | | | | | | set speed
+ * | | | | | | | | | | | | | | | | set lcount
+ * | | | | | | | | | | | | | | | | | mirror image
+ * | | | | | | | | | | | | | | | | | | set gamma table
+ * | | | | | | | | | | | | | | | | | | | set outline emphasis
+ * | | | | | | | | | | | | | | | | | | | | set dither
+ * | | | | | | | | | | | | | | | | | | | | | set color correction coefficients
+ * | | | | | | | | | | | | | | | | | | | | | | request extension status
+ * | | | | | | | | | | | | | | | | | | | | | | | control an extension
+ * | | | | | | | | | | | | | | | | | | | | | | | | forward feed / eject
+ * | | | | | | | | | | | | | | | | | | | | | | | | | feed
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | request push button status
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | control auto area segmentation
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | set film type
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set exposure time
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set bay
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set threshold
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set focus position
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | request focus position
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
+ */
+ {"A1",'I', 0 ,'F','S', 0 ,'G', 0 ,'R', 0 ,'A', 0 ,{ 0, 0, 0}, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"A2",'I', 0 ,'F','S', 0 ,'G','D','R','H','A','L',{-3, 3, 0},'Z','B', 0 ,'@', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B1",'I', 0 ,'F','S','C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0}, 0 ,'B', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B2",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B', 0 ,'@', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B3",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@', 0 , 0 , 0 , 0 , 0 , 0 ,'m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B4",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d', 0 ,'z','Q','b','m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B5",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B6",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B7",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e','\f', 0 ,'!','s','N', 0 , 0 ,'t', 0 , 0 },
+ {"B8",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e','\f', 0x19,'!','s','N', 0 , 0 ,'t','p','q'},
+ {"F5",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z', 0 ,'M','@','g','d','K','z','Q', 0 ,'m','f','e','\f', 0 , 0 , 0 ,'N','T','P', 0 , 0 , 0 },
+ {"D1",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f', 0 , 0 , 0 ,'!', 0 , 0 , 0 , 0 ,'t', 0 , 0 },
+ {"D2",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f','e', 0 , 0 ,'!', 0 ,'N', 0 , 0 ,'t', 0 , 0 },
+ {"D7",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f','e','\f', 0 ,'!', 0 ,'N', 0 , 0 ,'t', 0 , 0 },
+ {"D8",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f','e','\f', 0 ,'!', 0 ,'N', 0 , 0 ,'t', 0 , 0 },
+};
+
+
+/* Definition of the mode_param struct, that is used to
+ * specify the valid parameters for the different scan modes.
+ *
+ * The depth variable gets updated when the bit depth is modified.
+ */
+
+struct mode_param
+{
+ int color;
+ int mode_flags;
+ int dropout_mask;
+ int depth;
+};
+
+static struct mode_param mode_params[] = {
+ {0, 0x00, 0x30, 1},
+ {0, 0x00, 0x30, 8},
+ {1, 0x02, 0x00, 8}
+};
+
+static const SANE_String_Const mode_list[] = {
+ SANE_I18N ("Binary"),
+ SANE_I18N ("Gray"),
+ SANE_I18N ("Color"),
+ NULL
+};
+
+static const SANE_String_Const adf_mode_list[] = {
+ SANE_I18N ("Simplex"),
+ SANE_I18N ("Duplex"),
+ NULL
+};
+
+static const SANE_String_Const dfd_sensitivity[] = {
+ SANE_I18N ("None"),
+ SANE_I18N ("Low"),
+ SANE_I18N ("High"),
+ NULL
+};
+
+/* Define the different scan sources:
+ */
+
+#define FBF_STR SANE_I18N("Flatbed")
+#define TPU_STR SANE_I18N("Transparency Unit")
+#define ADF_STR SANE_I18N("Automatic Document Feeder")
+
+#define FILM_TYPE_POSITIVE (0)
+#define FILM_TYPE_NEGATIVE (1)
+
+static const SANE_String_Const film_list[] = {
+ SANE_I18N ("Positive Film"),
+ SANE_I18N ("Negative Film"),
+ NULL
+};
+
+static const SANE_String_Const focus_list[] = {
+ SANE_I18N ("Focus on glass"),
+ SANE_I18N ("Focus 2.5mm above glass"),
+ NULL
+};
+
+#define HALFTONE_NONE 0x01
+#define HALFTONE_TET 0x03
+
+static int halftone_params[] = {
+ HALFTONE_NONE,
+ 0x00,
+ 0x10,
+ 0x20,
+ 0x80,
+ 0x90,
+ 0xa0,
+ 0xb0,
+ HALFTONE_TET,
+ 0xc0,
+ 0xd0
+};
+
+static const SANE_String_Const halftone_list[] = {
+ SANE_I18N ("None"),
+ SANE_I18N ("Halftone A (Hard Tone)"),
+ SANE_I18N ("Halftone B (Soft Tone)"),
+ SANE_I18N ("Halftone C (Net Screen)"),
+ NULL
+};
+
+static const SANE_String_Const halftone_list_4[] = {
+ SANE_I18N ("None"),
+ SANE_I18N ("Halftone A (Hard Tone)"),
+ SANE_I18N ("Halftone B (Soft Tone)"),
+ SANE_I18N ("Halftone C (Net Screen)"),
+ SANE_I18N ("Dither A (4x4 Bayer)"),
+ SANE_I18N ("Dither B (4x4 Spiral)"),
+ SANE_I18N ("Dither C (4x4 Net Screen)"),
+ SANE_I18N ("Dither D (8x4 Net Screen)"),
+ NULL
+};
+
+static const SANE_String_Const halftone_list_7[] = {
+ SANE_I18N ("None"),
+ SANE_I18N ("Halftone A (Hard Tone)"),
+ SANE_I18N ("Halftone B (Soft Tone)"),
+ SANE_I18N ("Halftone C (Net Screen)"),
+ SANE_I18N ("Dither A (4x4 Bayer)"),
+ SANE_I18N ("Dither B (4x4 Spiral)"),
+ SANE_I18N ("Dither C (4x4 Net Screen)"),
+ SANE_I18N ("Dither D (8x4 Net Screen)"),
+ SANE_I18N ("Text Enhanced Technology"),
+ SANE_I18N ("Download pattern A"),
+ SANE_I18N ("Download pattern B"),
+ NULL
+};
+
+static int dropout_params[] = {
+ 0x00, /* none */
+ 0x10, /* red */
+ 0x20, /* green */
+ 0x30 /* blue */
+};
+
+static const SANE_String_Const dropout_list[] = {
+ SANE_I18N ("None"),
+ SANE_I18N ("Red"),
+ SANE_I18N ("Green"),
+ SANE_I18N ("Blue"),
+ NULL
+};
+
+static const SANE_String_Const brightness_method_list[] = {
+ SANE_I18N ("hardware"), /* needs to be first */
+ SANE_I18N ("iscan"),
+ SANE_I18N ("gimp"),
+ NULL
+};
+
+#define max_val 100 /* any integer value will do */
+static const SANE_Range brightness_range = { -max_val, max_val, 1};
+static const SANE_Range contrast_range = { -max_val, max_val, 1};
+#if 0
+/* We don't provide highlight and shadow options yet because we
+ * haven't worked out the GIMP part of that and how that should
+ * interact with the brightness-method option. For the "iscan"
+ * method, the values below are OK.
+ */
+static const SANE_Range highlight_range = { 0, max_val, 0 };
+static const SANE_Range shadow_range = { 0, max_val, 0 };
+#endif
+#undef max_val
+
+/* Color correction:
+ * One array for the actual parameters that get sent to the scanner (color_params[]),
+ * one array for the strings that get displayed in the user interface (color_list[])
+ * and one array to mark the user defined color correction (dolor_userdefined[]).
+ */
+
+static int color_params_ab[] = {
+ 0x00,
+ 0x01,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80
+};
+
+static SANE_Bool color_userdefined_ab[] = {
+ SANE_FALSE,
+ SANE_TRUE,
+ SANE_FALSE,
+ SANE_FALSE,
+ SANE_FALSE,
+ SANE_FALSE
+};
+
+static const SANE_String_Const color_list_ab[] = {
+ SANE_I18N ("No Correction"),
+ SANE_I18N ("User defined"),
+ SANE_I18N ("Impact-dot printers"),
+ SANE_I18N ("Thermal printers"),
+ SANE_I18N ("Ink-jet printers"),
+ SANE_I18N ("CRT monitors"),
+ NULL
+};
+
+static int color_params_d[] = {
+ 0x00
+};
+
+static SANE_Bool color_userdefined_d[] = {
+ SANE_TRUE
+};
+
+static const SANE_String_Const color_list_d[] = {
+ "User defined",
+ NULL
+};
+
+
+/* Gamma correction:
+ * The A and B level scanners work differently than the D level scanners, therefore
+ * I define two different sets of arrays, plus one set of variables that get set to
+ * the actally used params and list arrays at runtime.
+ */
+
+static int gamma_params_ab[] = {
+ 0x01,
+ 0x03,
+ 0x04,
+ 0x00,
+ 0x10,
+ 0x20
+};
+
+static const SANE_String_Const gamma_list_ab[] = {
+ SANE_I18N ("Default"),
+ SANE_I18N ("User defined (Gamma=1.0)"),
+ SANE_I18N ("User defined (Gamma=1.8)"),
+ SANE_I18N ("High density printing"),
+ SANE_I18N ("Low density printing"),
+ SANE_I18N ("High contrast printing"),
+ NULL
+};
+
+static SANE_Bool gamma_userdefined_ab[] = {
+ SANE_FALSE,
+ SANE_TRUE,
+ SANE_TRUE,
+ SANE_FALSE,
+ SANE_FALSE,
+ SANE_FALSE,
+};
+
+static int gamma_params_d[] = {
+ 0x03,
+ 0x04
+};
+
+static const SANE_String_Const gamma_list_d[] = {
+ SANE_I18N ("User defined (Gamma=1.0)"),
+ SANE_I18N ("User defined (Gamma=1.8)"),
+ NULL
+};
+
+static SANE_Bool gamma_userdefined_d[] = {
+ SANE_TRUE,
+ SANE_TRUE
+};
+
+
+/* Bay list:
+ * this is used for the FilmScan
+ */
+
+static const SANE_String_Const bay_list[] = {
+ " 1 ",
+ " 2 ",
+ " 3 ",
+ " 4 ",
+ " 5 ",
+ " 6 ",
+ NULL
+};
+
+static const SANE_Range u8_range = { 0, 255, 0 };
+static const SANE_Range s8_range = { -127, 127, 0 };
+static const SANE_Range zoom_range = { 50, 200, 0 };
+
+/* The "switch_params" are used for several boolean choices
+ */
+static int switch_params[] = {
+ 0,
+ 1
+};
+
+#define mirror_params switch_params
+#define speed_params switch_params
+#define film_params switch_params
+
+static const SANE_Range outline_emphasis_range = { -2, 2, 0 };
+
+enum
+ {
+ EXT_SANE_STATUS_NONE,
+ EXT_SANE_STATUS_MULTI_FEED,
+ EXT_SANE_STATUS_TRAY_CLOSED,
+ EXT_SANE_STATUS_MAX,
+ };
+static const SANE_Range ext_sane_status = {
+ EXT_SANE_STATUS_NONE,
+ EXT_SANE_STATUS_MAX - 1,
+ 0
+};
+
+typedef struct {
+ double width; /* in mm */
+ double height; /* in mm */
+ SANE_String_Const name;
+} media_type;
+
+static SANE_String_Const media_maximum = SANE_I18N ("Maximum");
+static SANE_String_Const media_automatic = SANE_I18N ("Automatic");
+
+/*! \brief List of preset media sizes
+ * This list is used to populate the constraint list for the
+ * scan-area option.
+ *
+ * \remark When adding Landscape and Portrait variants of a medium
+ * size, make sure to add three(!) entries using the same
+ * pattern as used for those already listed. The algorithm
+ * used to populate the constraint list depends on this
+ * convention to create the most user-friendly list.
+ */
+static const media_type media_list[] = {
+ /* ISO A Series */
+ { 297, 420, SANE_I18N ("A3") },
+ { 297, 210, SANE_I18N ("A4 Landscape") },
+ { 210, 297, SANE_I18N ("A4 Portrait") },
+ { 210, 297, SANE_I18N ("A4") },
+ { 210, 148, SANE_I18N ("A5 Landscape") },
+ { 148, 210, SANE_I18N ("A5 Portrait") },
+ { 148, 210, SANE_I18N ("A5") },
+ /* JIS B Series */
+ { 257, 364, SANE_I18N ("B4") },
+ { 257, 184, SANE_I18N ("B5 Landscape") },
+ { 184, 257, SANE_I18N ("B5 Portrait") },
+ { 184, 257, SANE_I18N ("B5") },
+ /* North American sizes */
+ { 11.00 * MM_PER_INCH, 17.00 * MM_PER_INCH, SANE_I18N ("Ledger") },
+ { 8.50 * MM_PER_INCH, 14.00 * MM_PER_INCH, SANE_I18N ("Legal") },
+ { 11.00 * MM_PER_INCH, 8.50 * MM_PER_INCH, SANE_I18N ("Letter Landscape") },
+ { 8.50 * MM_PER_INCH, 11.00 * MM_PER_INCH, SANE_I18N ("Letter Portrait") },
+ { 8.50 * MM_PER_INCH, 11.00 * MM_PER_INCH, SANE_I18N ("Letter") },
+ { 10.50 * MM_PER_INCH, 7.25 * MM_PER_INCH, SANE_I18N ("Executive Landscape") },
+ { 7.25 * MM_PER_INCH, 10.50 * MM_PER_INCH, SANE_I18N ("Executive Portrait") },
+ { 7.25 * MM_PER_INCH, 10.50 * MM_PER_INCH, SANE_I18N ("Executive") },
+ /* Miscellaneous */
+ { 120, 120, SANE_I18N ("CD")},
+};
+
+
+static SANE_Word *bitDepthList = NULL;
+
+
+/* Some utility macros
+ */
+
+/*! Returns the larger of the arguments \a a and \a b.
+ */
+#define max(a, b) ((a) < (b) ? (b) : (a))
+
+/*! Returns the smaller of the arguments \a a and \a b.
+ */
+#define min(a, b) ((a) < (b) ? (a) : (b))
+
+
+static size_t
+max_string_size (const SANE_String_Const strings[])
+{
+ size_t size, max_size = 0;
+ int i;
+
+ for (i = 0; strings[i]; i++)
+ {
+ size = strlen (strings[i]) + 1;
+ if (size > max_size)
+ max_size = size;
+ }
+ return max_size;
+}
+
+
+static inline
+bool
+need_autocrop_override (const Epson_Scanner *s)
+{
+ return (SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap)
+ && s->val[OPT_AUTOCROP].b
+ && 0 != autocrop_max_y (s->hw));
+}
+
+typedef struct
+{
+ u_char code;
+ u_char status;
+ u_char count1;
+ u_char count2;
+
+ u_char type;
+ u_char level;
+
+ u_char buf[1];
+
+} EpsonIdentRec, *EpsonIdent;
+
+typedef struct
+{
+ u_char code;
+ u_char status;
+ u_char count1;
+ u_char count2;
+ u_char buf[1];
+
+} EpsonHdrRec, *EpsonHdr;
+
+
+static SANE_Status
+read_image_info_block (device *hw);
+
+static SANE_Status
+get_identity_information (device *hw);
+
+static SANE_Status
+get_hardware_property (device *hw);
+
+static SANE_Status
+get_identity2_information (device *hw, Epson_Scanner *s);
+
+static SANE_Status
+check_warmup (device *hw);
+
+static SANE_Status
+check_ext_status (device *hw);
+
+SANE_Status
+control_option_unit (device *hw, SANE_Bool use_duplex);
+
+static SANE_Status
+initialize (device *hw);
+
+static SANE_Status
+get_resolution_constraints (device *hw, Epson_Scanner *s);
+
+static SANE_Status
+get_push_button_status (device *hw, SANE_Bool *button_pushed);
+
+static SANE_Status color_shuffle (Epson_Scanner *s, int *new_length);
+static void activateOption (Epson_Scanner * s, SANE_Int option,
+ SANE_Bool * change);
+static void deactivateOption (Epson_Scanner * s, SANE_Int option,
+ SANE_Bool * change);
+static void setOptionState (Epson_Scanner * s, SANE_Bool state,
+ SANE_Int option, SANE_Bool * change);
+static void filter_resolution_list (Epson_Scanner * s);
+static void scan_finish (Epson_Scanner * s);
+
+static void get_colorcoeff_from_profile (double *profile,
+ unsigned char *color_coeff);
+static void round_cct (double org_cct[], int rnd_cct[]);
+static int get_roundup_index (double frac[], int n);
+static int get_rounddown_index (double frac[], int n);
+
+static void handle_mode (Epson_Scanner * s, SANE_Int optindex,
+ SANE_Bool * reload);
+static void handle_resolution (Epson_Scanner * s, SANE_Int option,
+ SANE_Word value);
+static void change_profile_matrix (Epson_Scanner * s);
+static void handle_depth_halftone (Epson_Scanner * s, SANE_Int optindex,
+ SANE_Bool * reload);
+
+static SANE_Status create_epson_device (device **dev, channel* ch);
+static SANE_Status create_epson_scanner (device *dev, Epson_Scanner **scanner);
+static SANE_Status create_sane_handle (Epson_Scanner **scanner, const char *name, const void *dip);
+static SANE_Status init_options (Epson_Scanner * s);
+
+static scan_area_t get_model_info_max_scan_area (device *hw, int adf_mode);
+static SANE_Status handle_scan_area(Epson_Scanner *s, int adf_mode);
+
+static SANE_Status handle_source (Epson_Scanner * s,
+ SANE_Int optindex,
+ char *value);
+
+static void adf_handle_out_of_paper (Epson_Scanner * s);
+static void adf_handle_adjust_alignment (Epson_Scanner *s, SANE_Bool finalize);
+
+static void handle_autocrop (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload);
+static void handle_deskew (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload);
+static void handle_detect_doc_size (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload);
+static void handle_preview (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload);
+
+static SANE_Status
+expect_ack (device *hw)
+{
+ u_char result[1];
+ size_t len;
+ SANE_Status status;
+
+ log_call ();
+
+ len = sizeof (result);
+
+ channel_recv (hw->channel, result, len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ return status;
+
+ if (ACK != result[0])
+ return SANE_STATUS_INVAL;
+
+ return SANE_STATUS_GOOD;
+}
+
+
+static SANE_Status
+set_cmd (device *hw, u_char cmd, u_char val)
+{
+ SANE_Status status;
+ u_char params[2];
+
+ if (!cmd)
+ return SANE_STATUS_UNSUPPORTED;
+
+ log_call ("(%c)", cmd);
+
+ /* Override with extended parameter command (FS W).
+ * Do not override A, R and e, they are handled separately.
+ */
+ if (hw->using_fs && strchr ("CDgdZLMBtsQKN", cmd))
+ return dev_set_scanning_parameter (hw, cmd, &val);
+
+ params[0] = ESC;
+ params[1] = cmd;
+
+ channel_send (hw->channel, params, 2, &status);
+ if (SANE_STATUS_GOOD != (status = expect_ack (hw)))
+ return status;
+
+ params[0] = val;
+ channel_send (hw->channel, params, 1, &status);
+ status = expect_ack (hw);
+
+ return status;
+}
+
+static void
+print_params (const SANE_Parameters params)
+{
+ log_data ("params.format = %d", params.format);
+ log_data ("params.last_frame = %d", params.last_frame);
+ log_data ("params.bytes_per_line = %d", params.bytes_per_line);
+ log_data ("params.pixels_per_line = %d", params.pixels_per_line);
+ log_data ("params.lines = %d", params.lines);
+ log_data ("params.depth = %d", params.depth);
+}
+
+
+#define set_focus_position(h,v) set_cmd (h,(h)->cmd->set_focus_position,v)
+#define set_color_mode(h,v) set_cmd (h,(h)->cmd->set_color_mode,v)
+#define set_data_format(h,v) set_cmd (h,(h)->cmd->set_data_format, v)
+#define set_halftoning(h,v) set_cmd (h,(h)->cmd->set_halftoning, v)
+#define set_gamma(h,v) set_cmd (h,(h)->cmd->set_gamma, v)
+#define set_color_correction(h,v) set_cmd (h,(h)->cmd->set_color_correction, v)
+#define set_lcount(h,v) set_cmd (h,(h)->cmd->set_lcount, v)
+#define set_bright(h,v) set_cmd (h,(h)->cmd->set_bright, v)
+#define mirror_image(h,v) set_cmd (h,(h)->cmd->mirror_image, v)
+#define set_speed(h,v) set_cmd (h,(h)->cmd->set_speed, v)
+#define set_outline_emphasis(h,v) set_cmd (h,(h)->cmd->set_outline_emphasis, v)
+#define control_auto_area_segmentation(h,v) set_cmd (h,(h)->cmd->control_auto_area_segmentation, v)
+#define set_film_type(h,v) set_cmd (h,(h)->cmd->set_film_type, v)
+#define set_exposure_time(h,v) set_cmd (h,(h)->cmd->set_exposure_time, v)
+#define set_bay(h,v) set_cmd (h,(h)->cmd->set_bay, v)
+#define set_threshold(h,v) set_cmd (h,(h)->cmd->set_threshold, v)
+
+
+static SANE_Status
+set_zoom (device *hw, int x_zoom, int y_zoom)
+{
+ SANE_Status status;
+ u_char cmd[2];
+ u_char params[2];
+
+ if (!hw->cmd->set_zoom)
+ return SANE_STATUS_GOOD;
+
+ log_call ();
+
+ cmd[0] = ESC;
+ cmd[1] = hw->cmd->set_zoom;
+
+ channel_send (hw->channel, cmd, 2, &status);
+ status = expect_ack (hw);
+
+ if (status != SANE_STATUS_GOOD)
+ return status;
+
+ params[0] = x_zoom;
+ params[1] = y_zoom;
+
+ channel_send (hw->channel, params, 2, &status);
+ status = expect_ack (hw);
+
+ return status;
+}
+
+
+static SANE_Status
+set_resolution (device *hw, int xres, int yres)
+{
+ SANE_Status status;
+ u_char params[4];
+
+ if (!hw->cmd->set_resolution)
+ return SANE_STATUS_GOOD;
+
+ log_call ();
+
+ params[0] = ESC;
+ params[1] = hw->cmd->set_resolution;
+
+ channel_send (hw->channel, params, 2, &status);
+ status = expect_ack (hw);
+
+ if (status != SANE_STATUS_GOOD)
+ return status;
+
+ params[0] = xres;
+ params[1] = xres >> 8;
+ params[2] = yres;
+ params[3] = yres >> 8;
+
+ channel_send (hw->channel, params, 4, &status);
+ status = expect_ack (hw);
+
+ return status;
+}
+
+/* set_scan_area()
+ *
+ * Sends the "set scan area" command to the scanner with the currently selected
+ * scan area. This scan area is already corrected for "color shuffling" if
+ * necessary.
+ */
+static SANE_Status
+set_scan_area (device *hw, int x, int y, int width, int height)
+{
+ SANE_Status status;
+ u_char params[8];
+
+ log_call ("(%d, %d, %d, %d)", x, y, width, height);
+
+ if (!hw->cmd->set_scan_area)
+ {
+ err_major ("set_scan_area not supported");
+ return SANE_STATUS_GOOD;
+ }
+
+ /* verify the scan area */
+ if (x < 0 || y < 0 || width <= 0 || height <= 0)
+ return SANE_STATUS_INVAL;
+
+ params[0] = ESC;
+ params[1] = hw->cmd->set_scan_area;
+
+ channel_send (hw->channel, params, 2, &status);
+ status = expect_ack (hw);
+ if (status != SANE_STATUS_GOOD)
+ return status;
+
+ params[0] = x;
+ params[1] = x >> 8;
+ params[2] = y;
+ params[3] = y >> 8;
+ params[4] = width;
+ params[5] = width >> 8;
+ params[6] = height;
+ params[7] = height >> 8;
+
+ channel_send (hw->channel, params, 8, &status);
+ status = expect_ack (hw);
+
+ return status;
+}
+
+/* set_color_correction_coefficients()
+ *
+ * Sends the "set color correction coefficients" command with the currently selected
+ * parameters to the scanner.
+ */
+
+static SANE_Status
+set_color_correction_coefficients (device *hw, Epson_Scanner *s)
+{
+ SANE_Status status;
+ u_char cmd = hw->cmd->set_color_correction_coefficients;
+ u_char params[2];
+ const int length = 9;
+ unsigned char cccoeff[9];
+
+ log_call ();
+
+ s->cct[0] = SANE_UNFIX (s->val[OPT_CCT_1].w);
+ s->cct[1] = SANE_UNFIX (s->val[OPT_CCT_2].w);
+ s->cct[2] = SANE_UNFIX (s->val[OPT_CCT_3].w);
+ s->cct[3] = SANE_UNFIX (s->val[OPT_CCT_4].w);
+ s->cct[4] = SANE_UNFIX (s->val[OPT_CCT_5].w);
+ s->cct[5] = SANE_UNFIX (s->val[OPT_CCT_6].w);
+ s->cct[6] = SANE_UNFIX (s->val[OPT_CCT_7].w);
+ s->cct[7] = SANE_UNFIX (s->val[OPT_CCT_8].w);
+ s->cct[8] = SANE_UNFIX (s->val[OPT_CCT_9].w);
+
+ if (!cmd) /* effect will be emulated */
+ return SANE_STATUS_GOOD;
+
+ params[0] = ESC;
+ params[1] = cmd;
+
+ channel_send (hw->channel, params, 2, &status);
+ if (SANE_STATUS_GOOD != (status = expect_ack (hw)))
+ return status;
+
+ get_colorcoeff_from_profile (s->cct, cccoeff);
+
+ log_data ("[ %d %d %d ][ %d %d %d ][ %d %d %d]",
+ cccoeff[0], cccoeff[1], cccoeff[2],
+ cccoeff[3], cccoeff[4], cccoeff[5],
+ cccoeff[6], cccoeff[7], cccoeff[8]);
+
+ channel_send (hw->channel, cccoeff, length, &status);
+ status = expect_ack (hw);
+
+ log_call ("exit");
+
+ return status;
+}
+
+
+static SANE_Status
+set_gamma_table (device *hw, const Epson_Scanner *s)
+{
+ SANE_Status status;
+ u_char cmd = hw->cmd->set_gamma_table;
+ u_char params[2];
+ const int length = 257;
+ u_char gamma[257];
+ int n;
+ int table;
+ static char gamma_cmds[] = { 'R', 'G', 'B' };
+
+ if (!cmd)
+ return SANE_STATUS_UNSUPPORTED;
+
+ log_call ();
+
+ params[0] = ESC;
+ params[1] = cmd;
+
+/* When handling inverted images, we must also invert the user
+ * supplied gamma function. This is *not* just 255-gamma -
+ * this gives a negative image.
+ */
+
+ if (strcmp_c ("GT-6600", hw->fw_name) == 0 ||
+ strcmp_c ("Perfection 610", hw->fw_name) == 0)
+ {
+ gamma_cmds[0] = 'R';
+ gamma_cmds[1] = 'B';
+ gamma_cmds[2] = 'G';
+ }
+
+ for (table = 0; table < 3; table++)
+ {
+ gamma[0] = gamma_cmds[table];
+ if (s->invert_image)
+ {
+ for (n = 0; n < 256; ++n)
+ {
+ gamma[n + 1] = 255 - s->gamma_table[table][255 - n];
+ }
+ }
+ else
+ {
+ for (n = 0; n < 256; ++n)
+ {
+ gamma[n + 1] = s->gamma_table[table][n];
+ }
+ }
+
+ channel_send (hw->channel, params, 2, &status);
+ if (SANE_STATUS_GOOD != (status = expect_ack (hw)))
+ return status;
+
+ channel_send (hw->channel, gamma, length, &status);
+ if (SANE_STATUS_GOOD != (status = expect_ack (hw)))
+ return status;
+ }
+
+ log_call ("exit");
+
+ return status;
+}
+
+static SANE_Status
+check_warmup (device *hw)
+{
+ SANE_Status status = check_ext_status (hw);
+
+ log_call ();
+
+ if (status == SANE_STATUS_DEVICE_BUSY)
+ {
+ int timeout;
+
+ for (timeout = 0; timeout < 60; timeout++)
+ {
+ status = check_ext_status (hw);
+
+ if (status == SANE_STATUS_DEVICE_BUSY)
+ sleep (1);
+ else
+ {
+ return status;
+ }
+ }
+ }
+ else
+ return status;
+
+ return status;
+}
+
+
+static SANE_Status
+check_ext_status (device *hw)
+{
+ SANE_Status status;
+
+ log_call ();
+ require (hw);
+
+ status = dev_request_extended_status (hw);
+
+ if (EXT_STATUS_WU & hw->ext_status)
+ {
+ log_info ("option: warming up");
+ status = SANE_STATUS_DEVICE_BUSY;
+ }
+
+ if (EXT_STATUS_FER & hw->ext_status)
+ {
+ log_info ("option: fatal error");
+ status = SANE_STATUS_INVAL;
+ }
+
+ if (hw->adf)
+ {
+ if (ADF_STATUS_ERR & hw->adf->status
+ || ADF_EXT_STATUS_ERR & hw->adf->ext_status)
+ {
+ log_info ("ADF: other error");
+ status = SANE_STATUS_INVAL;
+ }
+
+ if (ADF_STATUS_PE & hw->adf->status)
+ {
+ log_info ("ADF: no paper");
+ status = SANE_STATUS_NO_DOCS;
+ }
+
+ if (ADF_STATUS_PJ & hw->adf->status)
+ {
+ log_info ("ADF: paper jam");
+ status = SANE_STATUS_JAMMED;
+ }
+
+ if (ADF_STATUS_OPN & hw->adf->status)
+ {
+ log_info ("ADF: cover open");
+ status = SANE_STATUS_COVER_OPEN;
+ }
+
+ if (ADF_EXT_STATUS_DFE & hw->adf->ext_status)
+ {
+ log_info ("ADF: multi sheet feed");
+ status = SANE_STATUS_JAMMED;
+ }
+
+ if (ADF_EXT_STATUS_TR_OPN & hw->adf->ext_status)
+ {
+ log_info ("ADF: tray open");
+ status = SANE_STATUS_COVER_OPEN;
+ }
+ }
+
+ if (hw->tpu)
+ {
+ if (TPU_STATUS_ERR & hw->tpu->status)
+ {
+ log_info ("TPU: other error");
+ status = SANE_STATUS_INVAL;
+ }
+ }
+
+ if (hw->fbf)
+ {
+ /* ??? is this for MFDs? */
+ if (DV3_STATUS_OPN & hw->fbf->status)
+ {
+ log_info ("UNIT: Scanner Unit open");
+ status = SANE_STATUS_COVER_OPEN;
+ }
+ }
+
+ return status;
+}
+
+
+static SANE_Status
+initialize (device *hw)
+{
+ SANE_Status status;
+ u_char param[2];
+
+ log_call ();
+
+ if (!hw->cmd->initialize_scanner)
+ return SANE_STATUS_GOOD;
+
+ param[0] = ESC;
+ param[1] = hw->cmd->initialize_scanner;
+
+ channel_send (hw->channel, param, 2, &status);
+ status = expect_ack (hw);
+
+ return status;
+}
+
+
+static Epson_Scanner *first_handle = NULL;
+
+
+static EpsonHdr
+command (device *hw, const u_char * cmd, size_t cmd_size,
+ SANE_Status * status)
+{
+ EpsonHdr head;
+ u_char *buf;
+ int count;
+
+ log_call ();
+
+ if (NULL == (head = t_malloc (1,EpsonHdrRec)))
+ {
+ err_fatal ("%s", strerror (errno));
+ *status = SANE_STATUS_NO_MEM;
+ return (EpsonHdr) 0;
+ }
+
+ channel_send (hw->channel, cmd, cmd_size, status);
+
+ if (SANE_STATUS_GOOD != *status)
+ {
+ /* this is necessary for the GT-8000. I don't know why, but
+ it seems to fix the problem. It should not have any
+ ill effects on other scanners. */
+ *status = SANE_STATUS_GOOD;
+ channel_send (hw->channel, cmd, cmd_size, status);
+ if (SANE_STATUS_GOOD != *status)
+ return (EpsonHdr) 0;
+ }
+
+ buf = (u_char *) head;
+ buf += channel_recv (hw->channel, buf, 4, status);
+
+ if (SANE_STATUS_GOOD != *status)
+ {
+ delete (head);
+ return (EpsonHdr) 0;
+ }
+
+ switch (head->code)
+ {
+
+ case NAK:
+ /* fall through */
+ /* !!! is this really sufficient to report an error ? */
+ case ACK:
+ break; /* no need to read any more data after ACK or NAK */
+
+ case STX:
+ hw->status = head->status;
+
+ if (SANE_STATUS_GOOD != *status)
+ {
+ delete (head);
+ return (EpsonHdr) 0;
+ }
+
+ count = head->count2 * 255 + head->count1;
+ log_info ("need to read %d data bytes", count);
+
+ /* Grow head so it has enough space to hold an additional count
+ * bytes of payload. We can _not_ use t_realloc here.
+ */
+ if (NULL == (head = realloc (head, sizeof (EpsonHdrRec) + count)))
+ {
+ err_fatal ("%s", strerror (errno));
+ *status = SANE_STATUS_NO_MEM;
+ return (EpsonHdr) 0;
+ }
+
+ buf = head->buf;
+ channel_recv (hw->channel, buf, count, status);
+
+ if (SANE_STATUS_GOOD != *status)
+ {
+ delete (head);
+ return (EpsonHdr) 0;
+ }
+
+ break;
+
+ default:
+ if (0 == head->code)
+ { err_major ("Incompatible printer port (probably bi/directional)"); }
+ else if (cmd[cmd_size - 1] == head->code)
+ { err_major ("Incompatible printer port (probably not bi/directional)"); }
+
+ err_major ("Illegal response of scanner for command: %02x", head->code);
+ break;
+ }
+
+ return head;
+}
+
+
+static SANE_Status
+create_epson_device (device **devp, channel* ch)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ device *dev = NULL;
+
+ require (devp && ch);
+
+ dev = t_calloc (1, device);
+ if (!dev)
+ {
+ err_fatal ("%s", strerror (errno));
+ return SANE_STATUS_NO_MEM;
+ }
+
+ dev->channel = ch;
+
+ init_resolution_info (&dev->res , NULL);
+ init_resolution_info (&dev->res_x, NULL);
+ init_resolution_info (&dev->res_y, NULL);
+
+ dev->cmd = &epson_cmd[EPSON_LEVEL_DEFAULT];
+
+ dev->fw_name = get_fw_name (dev->channel);
+
+ {
+ void *cfg = cfg_init (NULL, NULL);
+ dev->using_fs = !cfg_has_value (cfg, CFG_KEY_FS_BLACKLIST, dev->fw_name);
+ }
+
+ if (!dev->using_fs && CHAN_NET == ch->type)
+ {
+ err_fatal ("Network channel does not support scanning via ESC commands");
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ {
+ size_t protocol_max = (dev->using_fs ? UINT32_MAX : UINT16_MAX);
+
+ ch->set_max_request_size (ch, min (protocol_max, 512 * 1024));
+ }
+
+ initialize (dev);
+
+ if (dev->cmd->request_identity != 0)
+ {
+ status = get_identity_information (dev);
+ if (status != SANE_STATUS_GOOD)
+ {
+ return status;
+ }
+ }
+
+ if (dev->cmd->request_identity2 != 0)
+ {
+ get_hardware_property (dev);
+ if (status != SANE_STATUS_GOOD)
+ {
+ return status;
+ }
+ }
+
+ /* Check for the max. supported color depth and assign the values to
+ * the bitDepthList. Note that bitDepthList is a SANE word list and
+ * therefore has an initial element that indicates the number of its
+ * elements. We store up to 2 bit depths (8 and only one of 16, 14,
+ * and 12).
+ */
+
+ bitDepthList = t_malloc (1 + 2, SANE_Word);
+ if (bitDepthList == NULL)
+ {
+ err_fatal ("%s", strerror (errno));
+ return SANE_STATUS_NO_MEM;
+ }
+
+ bitDepthList[0] = 1; /* we start with one element in the list */
+ bitDepthList[1] = 8; /* 8bit is the default */
+
+ if (set_data_format (dev, 16) == SANE_STATUS_GOOD)
+ {
+ dev->maxDepth = 16;
+
+ bitDepthList[0]++;
+ bitDepthList[bitDepthList[0]] = 16;
+ }
+ else if (set_data_format (dev, 14) == SANE_STATUS_GOOD)
+ {
+ dev->maxDepth = 14;
+
+ bitDepthList[0]++;
+ bitDepthList[bitDepthList[0]] = 14;
+ }
+ else if (set_data_format (dev, 12) == SANE_STATUS_GOOD)
+ {
+ dev->maxDepth = 12;
+
+ bitDepthList[0]++;
+ bitDepthList[bitDepthList[0]] = 12;
+ }
+ else
+ {
+ dev->maxDepth = 8;
+ /* the default depth is already in the list */
+ }
+
+ log_info ("maximum color depth = %d", dev->maxDepth);
+
+ status = dev_request_extended_status (dev);
+ if (SANE_STATUS_GOOD != status)
+ {
+ if (SANE_STATUS_UNSUPPORTED != status)
+ {
+ err_minor ("failure processing extended status request");
+ }
+ }
+ else
+ {
+ const void *info = model_info_cache_get_info (dev->fw_name, &status);
+ if (SANE_STATUS_GOOD == status && info)
+ {
+ dev->scan_hard = model_info_get_profile (info);
+ model_info_customise_commands (info, dev->cmd);
+ dev->uses_locking = model_info_has_lock_commands (info);
+ }
+ else
+ {
+ err_minor ("failure getting model info (%s)", sane_strstatus (status));
+ }
+ }
+
+ if (0 == strcmp_c ("GT-8200", dev->fw_name))
+ {
+ /* Version 1.08 of the firmware is said to only report half of the
+ vertical size of the scan area. We should double that if it is
+ smaller than the horizontal scan area. Although we already try
+ doing this by fixing up the extended status reply, that doesn't
+ do the whole trick because we are dealing with a device type 0
+ scanner. In that case we get the FBF scan area via the 'ESC I'
+ command which does not include model info so we can not safely
+ fix up that reply.
+ */
+ if (dev->fbf && (dev->fbf->max_y < dev->fbf->max_x))
+ {
+ err_minor ("Fixing up buggy FBF max scan dimensions.");
+ dev->fbf->max_y *= 2;
+ update_ranges (dev, dev->fbf);
+ dev->need_color_reorder = SANE_TRUE;
+ }
+ }
+
+ if (dev->fbf)
+ {
+ log_info (" FBF: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]",
+ SANE_UNFIX (dev->fbf->x_range.min),
+ SANE_UNFIX (dev->fbf->y_range.min),
+ SANE_UNFIX (dev->fbf->x_range.max),
+ SANE_UNFIX (dev->fbf->y_range.max));
+
+ // check for auto size detection support and cache the results
+ dev->fbf->has_size_check = ((0 != dev->fbf->doc_x)
+ && (0 != dev->fbf->doc_y));
+ }
+ if (dev->adf)
+ {
+ log_info (" ADF: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]",
+ SANE_UNFIX (dev->adf->x_range.min),
+ SANE_UNFIX (dev->adf->y_range.min),
+ SANE_UNFIX (dev->adf->x_range.max),
+ SANE_UNFIX (dev->adf->y_range.max));
+ log_info (" ADF: %s, %s feed type",
+ (EXT_STATUS_ADFS & dev->ext_status ? "duplex" : "simplex"),
+ (EXT_STATUS_ADFT & dev->ext_status ? "page" : "sheet"));
+
+ // check for auto size detection support and cache the results
+ dev->adf->has_size_check = ((0 != dev->adf->doc_x)
+ && (0 != dev->adf->doc_y));
+
+ // handle the special case where some scanners claim to have auto
+ // document size detection support for flatbed but not for adf
+ if (dev->fbf && (dev->fbf->has_size_check && !dev->adf->has_size_check))
+ {
+ cmd_control_option_unit (dev, 0x01); // turn on ADF
+ dev_request_extended_status (dev); // now re-query scanner
+ // re-check for document size detection support
+ dev->adf->has_size_check = ((0 != dev->adf->doc_x)
+ && (0 != dev->adf->doc_y));
+ cmd_control_option_unit (dev, 0x00); // turn off ADF
+ }
+ }
+ if (dev->tpu)
+ {
+ log_info (" TPU: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]",
+ SANE_UNFIX (dev->tpu->x_range.min),
+ SANE_UNFIX (dev->tpu->y_range.min),
+ SANE_UNFIX (dev->tpu->x_range.max),
+ SANE_UNFIX (dev->tpu->y_range.max));
+
+ // check for auto size detection support and cache the results
+ dev->tpu->has_size_check = ((0 != dev->tpu->doc_x)
+ && (0 != dev->tpu->doc_y));
+ }
+
+
+ /* establish defaults */
+ dev->need_reset_on_source_change = SANE_FALSE;
+
+ if (strcmp_c ("ES-9000H", dev->fw_name) == 0 ||
+ strcmp_c ("GT-30000", dev->fw_name) == 0)
+ {
+ dev->cmd->set_focus_position = 0;
+ dev->cmd->feed = 0x19;
+ }
+ else if (strcmp_c ("GT-8200", dev->fw_name) == 0 ||
+ strcmp_c ("Perfection1640", dev->fw_name) == 0 ||
+ strcmp_c ("GT-8700", dev->fw_name) == 0)
+ {
+ dev->cmd->feed = 0;
+ dev->cmd->set_focus_position = 0;
+ dev->need_reset_on_source_change = SANE_TRUE;
+ }
+
+ { /* set up supported scan sources */
+ int i = 0;
+ if (dev->fbf) dev->sources[i++] = FBF_STR;
+ if (dev->adf) dev->sources[i++] = ADF_STR;
+ if (dev->tpu) dev->sources[i++] = TPU_STR;
+ dev->sources[i] = NULL; /* terminator */
+ }
+
+ if (!dev->src) dev->src = (extension *) dev->fbf;
+ if (!dev->src) dev->src = (extension *) dev->adf;
+ if (!dev->src) dev->src = (extension *) dev->tpu;
+
+ require (dev->src);
+
+ if (!using (dev, fbf))
+ dev_set_option_unit (dev, false);
+
+ dev->polling_time = DEFAULT_POLLING_TIME;
+ if (push_button_needs_polling (dev)
+ || maintenance_is_supported (dev))
+ {
+ dev->polling_time = (250 * 1000);
+ }
+
+ *devp = dev;
+
+ return SANE_STATUS_GOOD;
+} /* create epson device */
+
+
+static SANE_Status
+create_epson_scanner (device *dev, Epson_Scanner **scanner)
+{
+ Epson_Scanner *s = t_calloc (1, Epson_Scanner);
+
+ if (!s)
+ {
+ err_fatal ("%s", strerror (errno));
+ return SANE_STATUS_NO_MEM;
+ }
+
+ s->hw = dev;
+ s->src = &s->raw;
+
+ if (s->hw->cmd->request_identity2 != 0)
+ {
+ /* reset constraints because we are pointing to different lists now
+ FIXME: Only needs to be done the first time we (successfully)
+ send the ESC i command. This should be done when constructing
+ the device and is therefore done by the time we construct a
+ scanner. While the content and size of the lists may vary
+ depending on the selected option, the list nature is constant.
+ Hmm, we may actually be zapping the lists ...
+ */
+ s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_X_RESOLUTION].constraint.word_list = s->hw->res_x.list;
+ s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_Y_RESOLUTION].constraint.word_list = s->hw->res_y.list;
+ }
+
+ s->frame_count = 0;
+
+ promise (s->hw);
+ promise (s->src);
+
+ *scanner = s;
+ return SANE_STATUS_GOOD;
+}
+
+
+/*! \todo Release resources in error recovery.
+ */
+static SANE_Status
+create_sane_handle (Epson_Scanner **handle, const char *name, const void *dip)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ device *dev = NULL;
+ Epson_Scanner *s = NULL;
+ channel *ch = NULL;
+
+ dev = NULL;
+
+ ch = channel_create (name, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ ch->open (ch, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ status = create_epson_device (&dev, ch);
+ if (SANE_STATUS_GOOD != status)
+ return status;
+
+ status = create_epson_scanner (dev, &s);
+ if (!s || SANE_STATUS_GOOD != status)
+ return status;
+
+ s->dip = dip;
+ init_options (s);
+
+ *handle = s;
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+init_options (Epson_Scanner * s)
+{
+ int i;
+ SANE_Bool dummy;
+ SANE_Bool reload;
+
+ log_call ();
+
+ for (i = 0; i < NUM_OPTIONS; ++i)
+ {
+ s->opt[i].size = sizeof (SANE_Word);
+ s->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+ }
+
+ s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
+ s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
+ s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
+ s->val[OPT_NUM_OPTS].w = NUM_OPTIONS;
+
+ /* "Scan Mode" group: */
+ s->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode");
+ s->opt[OPT_MODE_GROUP].desc = "";
+ s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_MODE_GROUP].cap = 0;
+
+ /* scan mode */
+ s->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
+ s->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
+ s->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
+ s->opt[OPT_MODE].type = SANE_TYPE_STRING;
+ s->opt[OPT_MODE].size = max_string_size (mode_list);
+ s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_MODE].constraint.string_list = mode_list;
+ s->val[OPT_MODE].w = 2; /* Color */
+
+ /* bit depth */
+ s->opt[OPT_BIT_DEPTH].name = SANE_NAME_BIT_DEPTH;
+ s->opt[OPT_BIT_DEPTH].title = SANE_TITLE_BIT_DEPTH;
+ s->opt[OPT_BIT_DEPTH].desc = SANE_DESC_BIT_DEPTH;
+ s->opt[OPT_BIT_DEPTH].type = SANE_TYPE_INT;
+ s->opt[OPT_BIT_DEPTH].unit = SANE_UNIT_NONE;
+ s->opt[OPT_BIT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_BIT_DEPTH].constraint.word_list = bitDepthList;
+ s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE;
+ s->val[OPT_BIT_DEPTH].w = bitDepthList[1]; /* the first "real" element is the default */
+
+ if (bitDepthList[0] == 1) /* only one element in the list -> hide the option */
+ s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE;
+
+ /* halftone */
+ s->opt[OPT_HALFTONE].name = SANE_NAME_HALFTONE;
+ s->opt[OPT_HALFTONE].title = SANE_TITLE_HALFTONE;
+ s->opt[OPT_HALFTONE].desc = SANE_I18N ("Selects the halftone.");
+
+ s->opt[OPT_HALFTONE].type = SANE_TYPE_STRING;
+ s->opt[OPT_HALFTONE].size = max_string_size (halftone_list_7);
+ s->opt[OPT_HALFTONE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+
+ if (s->hw->level >= 7)
+ s->opt[OPT_HALFTONE].constraint.string_list = halftone_list_7;
+ else if (s->hw->level >= 4)
+ s->opt[OPT_HALFTONE].constraint.string_list = halftone_list_4;
+ else
+ s->opt[OPT_HALFTONE].constraint.string_list = halftone_list;
+
+ s->val[OPT_HALFTONE].w = 1; /* Halftone A */
+
+ if (!s->hw->cmd->set_halftoning)
+ {
+ s->opt[OPT_HALFTONE].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* dropout */
+ s->opt[OPT_DROPOUT].name = "dropout";
+ s->opt[OPT_DROPOUT].title = SANE_I18N ("Dropout");
+ s->opt[OPT_DROPOUT].desc = SANE_I18N ("Selects the dropout.");
+
+ s->opt[OPT_DROPOUT].type = SANE_TYPE_STRING;
+ s->opt[OPT_DROPOUT].size = max_string_size (dropout_list);
+ s->opt[OPT_DROPOUT].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_DROPOUT].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_DROPOUT].constraint.string_list = dropout_list;
+ s->val[OPT_DROPOUT].w = 0; /* None */
+
+ /* brightness algorithm selection */
+ s->opt[OPT_BRIGHTNESS_METHOD].name = "brightness-method";
+ s->opt[OPT_BRIGHTNESS_METHOD].title = SANE_I18N ("Brightness Method");
+ s->opt[OPT_BRIGHTNESS_METHOD].desc = SANE_I18N ("Selects a method to change the brightness of the acquired image.");
+ s->opt[OPT_BRIGHTNESS_METHOD].type = SANE_TYPE_STRING;
+ s->opt[OPT_BRIGHTNESS_METHOD].unit = SANE_UNIT_NONE;
+ s->opt[OPT_BRIGHTNESS_METHOD].size = max_string_size (brightness_method_list);
+ s->opt[OPT_BRIGHTNESS_METHOD].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_BRIGHTNESS_METHOD].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_BRIGHTNESS_METHOD].constraint.string_list = brightness_method_list; s->val[OPT_BRIGHTNESS_METHOD].w = 0; /* first supported */
+
+ if (!s->hw->cmd->set_bright) /* skip "hardware" */
+ ++s->opt[OPT_BRIGHTNESS_METHOD].constraint.string_list;
+
+ /* 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_INT;
+ s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
+ s->opt[OPT_BRIGHTNESS].size = sizeof (SANE_Int);
+ s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->val[OPT_BRIGHTNESS].w = 0; /* neutral */
+
+ if (s->hw->cmd->set_bright)
+ {
+ s->opt[OPT_BRIGHTNESS].constraint.range = &s->hw->cmd->bright_range;
+ }
+ else
+ {
+ s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
+ }
+
+ /* 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_INT;
+ s->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CONTRAST].size = sizeof (SANE_Int);
+ s->opt[OPT_CONTRAST].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CONTRAST].constraint.range = &contrast_range;
+ s->val[OPT_CONTRAST].w = 0; /* neutral */
+
+ /* sharpness */
+ s->opt[OPT_SHARPNESS].name = "sharpness";
+ s->opt[OPT_SHARPNESS].title = SANE_I18N ("Sharpness");
+ s->opt[OPT_SHARPNESS].desc = "";
+
+ s->opt[OPT_SHARPNESS].type = SANE_TYPE_INT;
+ s->opt[OPT_SHARPNESS].unit = SANE_UNIT_NONE;
+ s->opt[OPT_SHARPNESS].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_SHARPNESS].constraint.range = &outline_emphasis_range;
+ s->val[OPT_SHARPNESS].w = 0; /* Normal */
+
+ if (!s->hw->cmd->set_outline_emphasis)
+ {
+ s->opt[OPT_SHARPNESS].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* gamma */
+ s->opt[OPT_GAMMA_CORRECTION].name = SANE_NAME_GAMMA_CORRECTION;
+ s->opt[OPT_GAMMA_CORRECTION].title = SANE_TITLE_GAMMA_CORRECTION;
+ s->opt[OPT_GAMMA_CORRECTION].desc = SANE_DESC_GAMMA_CORRECTION;
+
+ s->opt[OPT_GAMMA_CORRECTION].type = SANE_TYPE_STRING;
+ s->opt[OPT_GAMMA_CORRECTION].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ /*
+ * special handling for D1 function level - at this time I'm not
+ * testing for D1, I'm just assuming that all D level scanners will
+ * behave the same way. This has to be confirmed with the next D-level
+ * scanner
+ */
+ if (s->hw->cmd->level[0] == 'D')
+ {
+ s->opt[OPT_GAMMA_CORRECTION].size = max_string_size (gamma_list_d);
+ s->opt[OPT_GAMMA_CORRECTION].constraint.string_list = gamma_list_d;
+ s->val[OPT_GAMMA_CORRECTION].w = 1; /* Default */
+ s->hw->gamma_user_defined = gamma_userdefined_d;
+ s->hw->gamma_type = gamma_params_d;
+ }
+ else
+ {
+ s->opt[OPT_GAMMA_CORRECTION].size = max_string_size (gamma_list_ab);
+ s->opt[OPT_GAMMA_CORRECTION].constraint.string_list = gamma_list_ab;
+ s->val[OPT_GAMMA_CORRECTION].w = 0; /* Default */
+ s->hw->gamma_user_defined = gamma_userdefined_ab;
+ s->hw->gamma_type = gamma_params_ab;
+ }
+
+ if (!s->hw->cmd->set_gamma)
+ {
+ s->opt[OPT_GAMMA_CORRECTION].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* 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].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[0][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].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[1][0];
+
+ /* red 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].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[2][0];
+
+ if (s->hw->cmd->set_gamma_table &&
+ s->hw->gamma_user_defined[s->val[OPT_GAMMA_CORRECTION].w])
+ {
+ 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_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;
+ }
+
+ /* initialize the Gamma tables */
+ memset (&s->gamma_table[0], 0, 256 * sizeof (SANE_Word));
+ memset (&s->gamma_table[1], 0, 256 * sizeof (SANE_Word));
+ memset (&s->gamma_table[2], 0, 256 * sizeof (SANE_Word));
+ for (i = 0; i < 256; i++)
+ {
+ s->gamma_table[0][i] = i;
+ s->gamma_table[1][i] = i;
+ s->gamma_table[2][i] = i;
+ }
+
+ /* color correction */
+ s->opt[OPT_COLOR_CORRECTION].name = "color-correction";
+ s->opt[OPT_COLOR_CORRECTION].title = SANE_I18N ("Color correction");
+ s->opt[OPT_COLOR_CORRECTION].desc =
+ SANE_I18N
+ ("Sets the color correction table for the selected output device.");
+
+ s->opt[OPT_COLOR_CORRECTION].type = SANE_TYPE_STRING;
+ s->opt[OPT_COLOR_CORRECTION].size = 32;
+ s->opt[OPT_COLOR_CORRECTION].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_COLOR_CORRECTION].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ if (s->hw->cmd->level[0] == 'D')
+ {
+ s->opt[OPT_COLOR_CORRECTION].size = max_string_size (color_list_d);
+ s->opt[OPT_COLOR_CORRECTION].constraint.string_list = color_list_d;
+ s->val[OPT_COLOR_CORRECTION].w = 0; /* Default */
+ s->hw->color_user_defined = color_userdefined_d;
+ s->hw->color_type = color_params_d;
+ }
+ else
+ {
+ s->opt[OPT_COLOR_CORRECTION].size = max_string_size (color_list_ab);
+ s->opt[OPT_COLOR_CORRECTION].constraint.string_list = color_list_ab;
+ s->val[OPT_COLOR_CORRECTION].w = 5; /* scanner default: CRT monitors */
+ s->hw->color_user_defined = color_userdefined_ab;
+ s->hw->color_type = color_params_ab;
+ }
+
+ if (!s->hw->cmd->set_color_correction)
+ {
+ s->opt[OPT_COLOR_CORRECTION].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* 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;
+
+ if (s->hw->cmd->level[0] != 'D')
+ {
+ s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_RESOLUTION].constraint.range = &s->hw->dpi_range;
+ }
+ else
+ {
+ s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_RESOLUTION].constraint.word_list = s->hw->res.list;
+ }
+ handle_resolution (s, OPT_RESOLUTION, DEFAULT_RESOLUTION);
+
+#undef SANE_NAME_SCAN_X_RESOLUTION
+#define SANE_NAME_SCAN_X_RESOLUTION "x-resolution"
+
+ /* resolution in main scan direction */
+ s->opt[OPT_X_RESOLUTION].name = SANE_NAME_SCAN_X_RESOLUTION;
+ s->opt[OPT_X_RESOLUTION].title = SANE_TITLE_SCAN_X_RESOLUTION;
+ s->opt[OPT_X_RESOLUTION].desc = SANE_DESC_SCAN_X_RESOLUTION;
+ s->opt[OPT_X_RESOLUTION].type = SANE_TYPE_INT;
+ s->opt[OPT_X_RESOLUTION].unit = SANE_UNIT_DPI;
+
+ if (s->hw->cmd->level[0] != 'D')
+ {
+ s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_X_RESOLUTION].constraint.range = &s->hw->dpi_range;
+ }
+ else
+ {
+ s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_X_RESOLUTION].constraint.word_list = s->hw->res_x.list;
+ s->opt[OPT_X_RESOLUTION].cap |= SANE_CAP_ADVANCED;
+ }
+ handle_resolution (s, OPT_X_RESOLUTION, DEFAULT_X_RESOLUTION);
+
+ /* resolution in sub scan direction */
+ s->opt[OPT_Y_RESOLUTION].name = SANE_NAME_SCAN_Y_RESOLUTION;
+ s->opt[OPT_Y_RESOLUTION].title = SANE_TITLE_SCAN_Y_RESOLUTION;
+ s->opt[OPT_Y_RESOLUTION].desc = SANE_DESC_SCAN_Y_RESOLUTION;
+ s->opt[OPT_Y_RESOLUTION].type = SANE_TYPE_INT;
+ s->opt[OPT_Y_RESOLUTION].unit = SANE_UNIT_DPI;
+
+ if (s->hw->cmd->level[0] != 'D')
+ {
+ s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_Y_RESOLUTION].constraint.range = &s->hw->dpi_range;
+ }
+ else
+ {
+ s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_Y_RESOLUTION].constraint.word_list = s->hw->res_y.list;
+ s->opt[OPT_Y_RESOLUTION].cap |= SANE_CAP_ADVANCED;
+ }
+ handle_resolution (s, OPT_Y_RESOLUTION, DEFAULT_Y_RESOLUTION);
+
+ switch (s->hw->productID)
+ {
+ /* We already use kludges for a number of models and don't want to
+ be burdened with rechecking their functionality. Really fixing
+ their support requires more changes than merely adding main/sub
+ resolution lists.
+ */
+ case 0x0116:
+ case 0x0118:
+ case 0x0119:
+ case 0x012b:
+ s->opt[OPT_X_RESOLUTION].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_Y_RESOLUTION].cap |= SANE_CAP_INACTIVE;
+ break;
+ default:
+ /* use the default capability set at the top of this function */
+ ;
+ }
+
+ /* threshold */
+ s->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD;
+ s->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD;
+ s->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD;
+
+ s->opt[OPT_THRESHOLD].type = SANE_TYPE_INT;
+ s->opt[OPT_THRESHOLD].unit = SANE_UNIT_NONE;
+ s->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_THRESHOLD].constraint.range = &u8_range;
+ s->val[OPT_THRESHOLD].w = 0x80;
+
+ if (!s->hw->cmd->set_threshold)
+ {
+ s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_CCT_GROUP].title = SANE_I18N ("Color correction coefficients");
+ s->opt[OPT_CCT_GROUP].desc = SANE_I18N ("Matrix multiplication of RGB");
+ s->opt[OPT_CCT_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_CCT_GROUP].cap = SANE_CAP_ADVANCED;
+
+ /* color correction coefficients */
+ s->opt[OPT_CCT_1].name = "cct-1";
+ s->opt[OPT_CCT_2].name = "cct-2";
+ s->opt[OPT_CCT_3].name = "cct-3";
+ s->opt[OPT_CCT_4].name = "cct-4";
+ s->opt[OPT_CCT_5].name = "cct-5";
+ s->opt[OPT_CCT_6].name = "cct-6";
+ s->opt[OPT_CCT_7].name = "cct-7";
+ s->opt[OPT_CCT_8].name = "cct-8";
+ s->opt[OPT_CCT_9].name = "cct-9";
+
+ s->opt[OPT_CCT_1].title = SANE_I18N ("Red");
+ s->opt[OPT_CCT_2].title = SANE_I18N ("Shift green to red");
+ s->opt[OPT_CCT_3].title = SANE_I18N ("Shift blue to red");
+ s->opt[OPT_CCT_4].title = SANE_I18N ("Shift red to green");
+ s->opt[OPT_CCT_5].title = SANE_I18N ("Green");
+ s->opt[OPT_CCT_6].title = SANE_I18N ("Shift blue to green");
+ s->opt[OPT_CCT_7].title = SANE_I18N ("Shift red to blue");
+ s->opt[OPT_CCT_8].title = SANE_I18N ("Shift green to blue");
+ s->opt[OPT_CCT_9].title = SANE_I18N ("Blue");
+
+ s->opt[OPT_CCT_1].desc = SANE_I18N ("Controls red level");
+ s->opt[OPT_CCT_2].desc = SANE_I18N ("Adds to red based on green level");
+ s->opt[OPT_CCT_3].desc = SANE_I18N ("Adds to red based on blue level");
+ s->opt[OPT_CCT_4].desc = SANE_I18N ("Adds to green based on red level");
+ s->opt[OPT_CCT_5].desc = SANE_I18N ("Controls green level");
+ s->opt[OPT_CCT_6].desc = SANE_I18N ("Adds to green based on blue level");
+ s->opt[OPT_CCT_7].desc = SANE_I18N ("Adds to blue based on red level");
+ s->opt[OPT_CCT_8].desc = SANE_I18N ("Adds to blue based on green level");
+ s->opt[OPT_CCT_9].desc = SANE_I18N ("Control blue level");
+
+ s->opt[OPT_CCT_1].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_2].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_3].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_4].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_5].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_6].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_7].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_8].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_9].type = SANE_TYPE_FIXED;
+
+ s->opt[OPT_CCT_1].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_2].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_3].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_4].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_5].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_6].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_7].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_8].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_9].cap |= SANE_CAP_ADVANCED;
+
+ s->opt[OPT_CCT_1].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_2].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_3].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_4].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_5].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_6].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_7].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_8].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_9].cap |= SANE_CAP_INACTIVE;
+
+ if (!s->hw->cmd->set_color_correction_coefficients)
+ {
+ s->opt[OPT_CCT_1].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_2].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_3].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_4].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_5].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_6].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_7].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_8].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_9].cap |= SANE_CAP_EMULATED;
+ }
+
+ s->opt[OPT_CCT_1].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_2].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_3].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_4].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_5].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_6].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_7].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_8].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_9].unit = SANE_UNIT_NONE;
+
+ s->opt[OPT_CCT_1].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_2].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_3].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_4].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_5].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_6].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_7].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_8].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_9].constraint_type = SANE_CONSTRAINT_RANGE;
+
+ s->hw->matrix_range.min = SANE_FIX (-2.0);
+ s->hw->matrix_range.max = SANE_FIX (2.0);
+ s->hw->matrix_range.quant = 0;
+
+ s->opt[OPT_CCT_1].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_2].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_3].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_4].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_5].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_6].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_7].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_8].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_9].constraint.range = &s->hw->matrix_range;
+
+ s->val[OPT_CCT_1].w = SANE_FIX (1.0);
+ s->val[OPT_CCT_2].w = 0;
+ s->val[OPT_CCT_3].w = 0;
+ s->val[OPT_CCT_4].w = 0;
+ s->val[OPT_CCT_5].w = SANE_FIX (1.0);
+ s->val[OPT_CCT_6].w = 0;
+ s->val[OPT_CCT_7].w = 0;
+ s->val[OPT_CCT_8].w = 0;
+ s->val[OPT_CCT_9].w = SANE_FIX (1.0);
+
+ /* "Advanced" group: */
+ s->opt[OPT_ADVANCED_GROUP].title = SANE_I18N ("Advanced");
+ s->opt[OPT_ADVANCED_GROUP].desc = "";
+ s->opt[OPT_ADVANCED_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_ADVANCED_GROUP].cap = SANE_CAP_ADVANCED;
+
+ /* mirror */
+ s->opt[OPT_MIRROR].name = "mirror";
+ s->opt[OPT_MIRROR].title = SANE_I18N ("Mirror image");
+ s->opt[OPT_MIRROR].desc = SANE_I18N ("Mirror the image.");
+
+ s->opt[OPT_MIRROR].type = SANE_TYPE_BOOL;
+ s->val[OPT_MIRROR].w = SANE_FALSE;
+
+ if (!s->hw->cmd->mirror_image)
+ {
+ s->opt[OPT_MIRROR].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* speed */
+ 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_BOOL;
+ s->val[OPT_SPEED].w = SANE_FALSE;
+
+ if (!s->hw->cmd->set_speed)
+ {
+ s->opt[OPT_SPEED].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* preview speed */
+ s->opt[OPT_PREVIEW_SPEED].name = "preview-speed";
+ s->opt[OPT_PREVIEW_SPEED].title = SANE_I18N ("Speed");
+ s->opt[OPT_PREVIEW_SPEED].desc = "";
+
+ s->opt[OPT_PREVIEW_SPEED].type = SANE_TYPE_BOOL;
+ s->val[OPT_PREVIEW_SPEED].w = SANE_FALSE;
+
+ if (!s->hw->cmd->set_speed)
+ {
+ s->opt[OPT_PREVIEW_SPEED].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* auto area segmentation */
+ s->opt[OPT_AAS].name = "auto-area-segmentation";
+ s->opt[OPT_AAS].title = SANE_I18N ("Auto area segmentation");
+ s->opt[OPT_AAS].desc = "";
+
+ s->opt[OPT_AAS].type = SANE_TYPE_BOOL;
+ s->val[OPT_AAS].w = SANE_TRUE;
+
+ if (!s->hw->cmd->control_auto_area_segmentation)
+ {
+ s->opt[OPT_AAS].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* limit resolution list */
+ s->opt[OPT_LIMIT_RESOLUTION].name = "short-resolution";
+ s->opt[OPT_LIMIT_RESOLUTION].title = SANE_I18N ("Short resolution list");
+ s->opt[OPT_LIMIT_RESOLUTION].desc =
+ SANE_I18N ("Display short resolution list");
+ s->opt[OPT_LIMIT_RESOLUTION].type = SANE_TYPE_BOOL;
+ s->val[OPT_LIMIT_RESOLUTION].w = SANE_FALSE;
+
+ if (SANE_CONSTRAINT_WORD_LIST != s->opt[OPT_RESOLUTION].constraint_type)
+ {
+ s->opt[OPT_LIMIT_RESOLUTION].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* zoom */
+ s->opt[OPT_ZOOM].name = "zoom";
+ s->opt[OPT_ZOOM].title = SANE_I18N ("Zoom");
+ s->opt[OPT_ZOOM].desc =
+ SANE_I18N ("Defines the zoom factor the scanner will use");
+
+ s->opt[OPT_ZOOM].type = SANE_TYPE_INT;
+ s->opt[OPT_ZOOM].unit = SANE_UNIT_NONE;
+ s->opt[OPT_ZOOM].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_ZOOM].constraint.range = &zoom_range;
+ s->val[OPT_ZOOM].w = 100;
+
+ if (s->hw->using_fs) s->hw->cmd->set_zoom = 0;
+
+ if (!s->hw->cmd->set_zoom)
+ {
+ s->opt[OPT_ZOOM].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* "Preview settings" group: */
+ s->opt[OPT_PREVIEW_GROUP].title = SANE_TITLE_PREVIEW;
+ s->opt[OPT_PREVIEW_GROUP].desc = "";
+ s->opt[OPT_PREVIEW_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_PREVIEW_GROUP].cap = SANE_CAP_ADVANCED;
+
+ /* 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].type = SANE_TYPE_BOOL;
+ s->val[OPT_PREVIEW].w = SANE_FALSE;
+
+ /* "Geometry" group: */
+ s->opt[OPT_GEOMETRY_GROUP].title = SANE_I18N ("Geometry");
+ s->opt[OPT_GEOMETRY_GROUP].desc = "";
+ s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED;
+
+ /* media size oriented scan area setting */
+ s->opt[OPT_SCAN_AREA].name = "scan-area";
+ s->opt[OPT_SCAN_AREA].title = SANE_I18N ("Scan area");
+ s->opt[OPT_SCAN_AREA].desc =
+ SANE_I18N ("Select an area to scan based on well-known media sizes.");
+ s->opt[OPT_SCAN_AREA].type = SANE_TYPE_STRING;
+ s->opt[OPT_SCAN_AREA].size = 0;
+ s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_SCAN_AREA].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_SCAN_AREA].constraint.string_list = NULL;
+ s->val[OPT_SCAN_AREA].w = 0;
+
+ /* Quick format */
+ s->opt[OPT_QUICK_FORMAT].name = "quick-format";
+ s->opt[OPT_QUICK_FORMAT].title = SANE_I18N ("Quick format");
+ s->opt[OPT_QUICK_FORMAT].desc =
+ SANE_I18N ("Select an area to scan based on well-known media sizes. (DEPRECATED)");
+ s->opt[OPT_QUICK_FORMAT].type = SANE_TYPE_STRING;
+ s->opt[OPT_QUICK_FORMAT].size = 0;
+ s->opt[OPT_QUICK_FORMAT].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_QUICK_FORMAT].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_QUICK_FORMAT].constraint.string_list = NULL;
+ s->val[OPT_QUICK_FORMAT].w = 0;
+
+ handle_scan_area (s, 0); /* divines device/setting dependent bits */
+
+ /* top-left x */
+ s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
+ s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
+ s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
+
+ s->opt[OPT_TL_X].type = SANE_TYPE_FIXED;
+ s->opt[OPT_TL_X].unit = SANE_UNIT_MM;
+ s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_TL_X].constraint.range = &(s->hw->src->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 = &(s->hw->src->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 = &(s->hw->src->x_range);
+ s->val[OPT_BR_X].w = s->hw->src->x_range.max;
+
+ /* bottom-right y */
+ s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
+ s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
+ s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
+
+ s->opt[OPT_BR_Y].type = SANE_TYPE_FIXED;
+ s->opt[OPT_BR_Y].unit = SANE_UNIT_MM;
+ s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BR_Y].constraint.range = &(s->hw->src->y_range);
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+
+ /* "Optional equipment" group: */
+ s->opt[OPT_EQU_GROUP].title = SANE_I18N ("Optional equipment");
+ s->opt[OPT_EQU_GROUP].desc = "";
+ s->opt[OPT_EQU_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_EQU_GROUP].cap = SANE_CAP_ADVANCED;
+
+ /* source */
+ 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 (s->hw->sources);
+
+ s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_SOURCE].constraint.string_list = s->hw->sources;
+
+ if (!s->hw->sources[1]) /* two or more scan sources */
+ {
+ s->opt[OPT_SOURCE].cap |= SANE_CAP_INACTIVE;
+ }
+ s->val[OPT_SOURCE].w = 0;
+
+
+ /* film type */
+ s->opt[OPT_FILM_TYPE].name = "film-type";
+ s->opt[OPT_FILM_TYPE].title = SANE_I18N ("Film type");
+ s->opt[OPT_FILM_TYPE].desc = "";
+
+ s->opt[OPT_FILM_TYPE].type = SANE_TYPE_STRING;
+ s->opt[OPT_FILM_TYPE].size = max_string_size (film_list);
+
+ s->opt[OPT_FILM_TYPE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_FILM_TYPE].constraint.string_list = film_list;
+
+ s->val[OPT_FILM_TYPE].w = 0;
+
+ deactivateOption (s, OPT_FILM_TYPE, &dummy); /* default is inactive */
+
+ /* focus position */
+ s->opt[OPT_FOCUS].name = SANE_EPSON_FOCUS_NAME;
+ s->opt[OPT_FOCUS].title = SANE_EPSON_FOCUS_TITLE;
+ s->opt[OPT_FOCUS].desc = SANE_EPSON_FOCUS_DESC;
+ s->opt[OPT_FOCUS].type = SANE_TYPE_STRING;
+ s->opt[OPT_FOCUS].size = max_string_size (focus_list);
+ s->opt[OPT_FOCUS].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_FOCUS].constraint.string_list = focus_list;
+ s->val[OPT_FOCUS].w = 0;
+
+ s->opt[OPT_FOCUS].cap |= SANE_CAP_ADVANCED;
+ if (s->hw->tpu && s->hw->tpu->has_focus)
+ {
+ s->opt[OPT_FOCUS].cap &= ~SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ s->opt[OPT_FOCUS].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* forward feed / eject */
+ s->opt[OPT_EJECT].name = "eject";
+ s->opt[OPT_EJECT].title = SANE_I18N ("Eject");
+ s->opt[OPT_EJECT].desc = SANE_I18N ("Eject the sheet in the ADF");
+
+ s->opt[OPT_EJECT].type = SANE_TYPE_BUTTON;
+
+ if ((!s->hw->adf) && (!s->hw->cmd->set_bay))
+ { /* Hack: Using set_bay to indicate. */
+ s->opt[OPT_EJECT].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* auto forward feed / eject */
+ s->opt[OPT_AUTO_EJECT].name = "auto-eject";
+ s->opt[OPT_AUTO_EJECT].title = SANE_I18N ("Auto eject");
+ s->opt[OPT_AUTO_EJECT].desc = SANE_I18N ("Eject document after scanning");
+
+ s->opt[OPT_AUTO_EJECT].type = SANE_TYPE_BOOL;
+ s->val[OPT_AUTO_EJECT].w = SANE_TRUE;
+
+ if (s->hw->adf) s->hw->adf->auto_eject = SANE_TRUE;
+
+ if (!s->hw->adf)
+ {
+ s->opt[OPT_AUTO_EJECT].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_ADF_MODE].name = "adf-mode";
+ s->opt[OPT_ADF_MODE].title = SANE_I18N ("ADF Mode");
+ s->opt[OPT_ADF_MODE].desc =
+ SANE_I18N ("Selects the ADF mode (simplex/duplex)");
+ s->opt[OPT_ADF_MODE].type = SANE_TYPE_STRING;
+ s->opt[OPT_ADF_MODE].size = max_string_size (adf_mode_list);
+ s->opt[OPT_ADF_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_ADF_MODE].constraint.string_list = adf_mode_list;
+ s->val[OPT_ADF_MODE].w = 0; /* simplex */
+
+ if (!(using (s->hw, adf) && (EXT_STATUS_ADFS & s->hw->ext_status)))
+ {
+ s->opt[OPT_ADF_MODE].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* select bay */
+ s->opt[OPT_BAY].name = "bay";
+ s->opt[OPT_BAY].title = SANE_I18N ("Bay");
+ s->opt[OPT_BAY].desc = SANE_I18N ("Select bay to scan");
+
+ s->opt[OPT_BAY].type = SANE_TYPE_STRING;
+ s->opt[OPT_BAY].size = max_string_size (bay_list);
+ s->opt[OPT_BAY].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_BAY].constraint.string_list = bay_list;
+ s->val[OPT_BAY].w = 0; /* Bay 1 */
+
+ if (!s->hw->cmd->set_bay)
+ {
+ s->opt[OPT_BAY].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_WAIT_FOR_BUTTON].name = SANE_EPSON_WAIT_FOR_BUTTON_NAME;
+ s->opt[OPT_WAIT_FOR_BUTTON].title = SANE_EPSON_WAIT_FOR_BUTTON_TITLE;
+ s->opt[OPT_WAIT_FOR_BUTTON].desc = SANE_EPSON_WAIT_FOR_BUTTON_DESC;
+
+ s->opt[OPT_WAIT_FOR_BUTTON].type = SANE_TYPE_BOOL;
+ s->opt[OPT_WAIT_FOR_BUTTON].unit = SANE_UNIT_NONE;
+ s->opt[OPT_WAIT_FOR_BUTTON].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_WAIT_FOR_BUTTON].constraint.range = NULL;
+ s->opt[OPT_WAIT_FOR_BUTTON].cap |= SANE_CAP_ADVANCED;
+ s->val[OPT_WAIT_FOR_BUTTON].w = SANE_FALSE;
+
+ if (!s->hw->cmd->request_push_button_status
+ || push_button_is_black_listed (s->hw))
+ {
+ s->opt[OPT_WAIT_FOR_BUTTON].cap |= SANE_CAP_INACTIVE;
+ }
+ else if (push_button_needs_polling (s->hw))
+ {
+ s->val[OPT_WAIT_FOR_BUTTON].w = SANE_TRUE;
+ }
+
+ s->opt[OPT_MONITOR_BUTTON].name = SANE_EPSON_MONITOR_BUTTON_NAME;
+ s->opt[OPT_MONITOR_BUTTON].title = SANE_EPSON_MONITOR_BUTTON_TITLE;
+ s->opt[OPT_MONITOR_BUTTON].desc = SANE_EPSON_MONITOR_BUTTON_DESC;
+
+ s->opt[OPT_MONITOR_BUTTON].type = SANE_TYPE_BOOL;
+ s->opt[OPT_MONITOR_BUTTON].unit = SANE_UNIT_NONE;
+ s->opt[OPT_MONITOR_BUTTON].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_MONITOR_BUTTON].constraint.range = NULL;
+ s->opt[OPT_MONITOR_BUTTON].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_MONITOR_BUTTON].cap &= ~SANE_CAP_SOFT_SELECT;
+ s->val[OPT_MONITOR_BUTTON].w = SANE_FALSE;
+
+ if (!s->hw->cmd->request_push_button_status
+ || push_button_is_black_listed (s->hw))
+ {
+ s->opt[OPT_MONITOR_BUTTON].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_POLLING_TIME].name = SANE_EPSON_POLLING_TIME_NAME;
+ s->opt[OPT_POLLING_TIME].title = SANE_EPSON_POLLING_TIME_TITLE;
+ s->opt[OPT_POLLING_TIME].desc = SANE_EPSON_POLLING_TIME_DESC;
+
+ s->opt[OPT_POLLING_TIME].type = SANE_TYPE_INT;
+ s->opt[OPT_POLLING_TIME].unit = SANE_UNIT_MICROSECOND;
+ s->opt[OPT_POLLING_TIME].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_POLLING_TIME].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_POLLING_TIME].cap &= ~SANE_CAP_SOFT_SELECT;
+ s->val[OPT_POLLING_TIME].w = s->hw->polling_time;
+
+ if (!s->hw->cmd->request_push_button_status
+ || push_button_is_black_listed (s->hw))
+ {
+ s->opt[OPT_POLLING_TIME].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_NEEDS_POLLING].name = SANE_EPSON_NEEDS_POLLING_NAME;
+ s->opt[OPT_NEEDS_POLLING].title = SANE_EPSON_NEEDS_POLLING_TITLE;
+ s->opt[OPT_NEEDS_POLLING].desc = SANE_EPSON_NEEDS_POLLING_DESC;
+
+ s->opt[OPT_NEEDS_POLLING].type = SANE_TYPE_BOOL;
+ s->opt[OPT_NEEDS_POLLING].unit = SANE_UNIT_NONE;
+ s->opt[OPT_NEEDS_POLLING].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_NEEDS_POLLING].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_NEEDS_POLLING].cap &= ~SANE_CAP_SOFT_SELECT;
+ s->val[OPT_NEEDS_POLLING].w = SANE_FALSE;
+
+ if (!s->hw->cmd->request_push_button_status
+ || push_button_is_black_listed (s->hw))
+ {
+ s->opt[OPT_NEEDS_POLLING].cap |= SANE_CAP_INACTIVE;
+ }
+ else if (push_button_needs_polling (s->hw))
+ {
+ s->val[OPT_NEEDS_POLLING].w = SANE_TRUE;
+ }
+
+ s->opt[OPT_DETECT_DOC_SIZE].name = SANE_EPSON_DETECT_DOC_SIZE_NAME;
+ s->opt[OPT_DETECT_DOC_SIZE].title = SANE_EPSON_DETECT_DOC_SIZE_TITLE;
+ s->opt[OPT_DETECT_DOC_SIZE].desc = SANE_EPSON_DETECT_DOC_SIZE_DESC;
+
+ s->opt[OPT_DETECT_DOC_SIZE].type = SANE_TYPE_BOOL;
+ s->opt[OPT_DETECT_DOC_SIZE].unit = SANE_UNIT_NONE;
+ s->opt[OPT_DETECT_DOC_SIZE].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_DETECT_DOC_SIZE].constraint.range = NULL;
+ s->opt[OPT_DETECT_DOC_SIZE].cap |= SANE_CAP_ADVANCED;
+ s->val[OPT_DETECT_DOC_SIZE].w = SANE_FALSE;
+
+ if (!has_size_check_support (s->hw->src))
+ {
+ s->opt[OPT_DETECT_DOC_SIZE].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_SCAN_AREA_IS_VALID].name = SANE_EPSON_SCAN_AREA_IS_VALID_NAME;
+ s->opt[OPT_SCAN_AREA_IS_VALID].title = SANE_EPSON_SCAN_AREA_IS_VALID_TITLE;
+ s->opt[OPT_SCAN_AREA_IS_VALID].desc = SANE_EPSON_SCAN_AREA_IS_VALID_DESC;
+
+ s->opt[OPT_SCAN_AREA_IS_VALID].type = SANE_TYPE_BOOL;
+ s->opt[OPT_SCAN_AREA_IS_VALID].unit = SANE_UNIT_NONE;
+ s->opt[OPT_SCAN_AREA_IS_VALID].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_SCAN_AREA_IS_VALID].constraint.range = NULL;
+ s->opt[OPT_SCAN_AREA_IS_VALID].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
+ s->val[OPT_SCAN_AREA_IS_VALID].w = SANE_FALSE;
+
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].name =
+ SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_NAME;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].title =
+ SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_TITLE;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].desc =
+ SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_DESC;
+
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].type = SANE_TYPE_BOOL;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].unit = SANE_UNIT_NONE;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].constraint_type =
+ SANE_CONSTRAINT_NONE;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].constraint.range = NULL;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
+ s->val[OPT_ADF_DUPLEX_DIRECTION_MATCHES].w = SANE_FALSE;
+
+ if (!(using (s->hw, adf) && (EXT_STATUS_ADFS & s->hw->ext_status)))
+ {
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_ADF_AUTO_SCAN].name = SANE_EPSON_ADF_AUTO_SCAN_NAME;
+ s->opt[OPT_ADF_AUTO_SCAN].title = SANE_EPSON_ADF_AUTO_SCAN_TITLE;
+ s->opt[OPT_ADF_AUTO_SCAN].desc = SANE_EPSON_ADF_AUTO_SCAN_DESC;
+
+ s->opt[OPT_ADF_AUTO_SCAN].type = SANE_TYPE_BOOL;
+ s->opt[OPT_ADF_AUTO_SCAN].unit = SANE_UNIT_NONE;
+ s->opt[OPT_ADF_AUTO_SCAN].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_ADF_AUTO_SCAN].constraint.range = NULL;
+ s->opt[OPT_ADF_AUTO_SCAN].cap |= SANE_CAP_ADVANCED;
+ s->val[OPT_ADF_AUTO_SCAN].w = SANE_FALSE;
+
+ if (!using (s->hw, adf) || !(FSI_CAP_ADFAS & s->hw->fsi_cap_2))
+ {
+ s->opt[OPT_ADF_AUTO_SCAN].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_ADF_DFD_SENSITIVITY].name = SANE_EPSON_ADF_DFD_SENSITIVITY_NAME;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].title = SANE_EPSON_ADF_DFD_SENSITIVITY_TITLE;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].desc = SANE_EPSON_ADF_DFD_SENSITIVITY_DESC;
+
+ s->opt[OPT_ADF_DFD_SENSITIVITY].type = SANE_TYPE_STRING;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].unit = SANE_UNIT_NONE;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].size = max_string_size (dfd_sensitivity);
+ s->opt[OPT_ADF_DFD_SENSITIVITY].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].constraint.string_list = dfd_sensitivity;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].cap |= SANE_CAP_ADVANCED;
+ s->val[OPT_ADF_DFD_SENSITIVITY].w = 0;
+
+ if (!using (s->hw, adf) || !(FSI_CAP_DFD & s->hw->fsi_cap_2))
+ {
+ s->opt[OPT_ADF_DFD_SENSITIVITY].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_EXT_SANE_STATUS].name = "ext-sane-status";
+ s->opt[OPT_EXT_SANE_STATUS].title = "Extended SANE Status";
+ s->opt[OPT_EXT_SANE_STATUS].desc = "Ugly kludge to provide additional status message strings to a frontend.";
+
+ s->opt[OPT_EXT_SANE_STATUS].type = SANE_TYPE_INT;
+ s->opt[OPT_EXT_SANE_STATUS].unit = SANE_UNIT_NONE;
+ s->opt[OPT_EXT_SANE_STATUS].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_EXT_SANE_STATUS].constraint.range = &ext_sane_status;
+ s->opt[OPT_EXT_SANE_STATUS].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
+ s->val[OPT_EXT_SANE_STATUS].w = 0;
+
+ /* deskew */
+ s->opt[OPT_DESKEW].name = "deskew";
+ s->opt[OPT_DESKEW].title = SANE_I18N ("Deskew");
+ s->opt[OPT_DESKEW].desc = SANE_I18N ("Rotate image so it appears upright.");
+ s->opt[OPT_DESKEW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_DESKEW].unit = SANE_UNIT_NONE;
+ s->opt[OPT_DESKEW].size = sizeof (SANE_Bool);
+ s->opt[OPT_DESKEW].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_DESKEW].cap |= SANE_CAP_ADVANCED;
+ if ( dip_has_deskew (s->dip, s->hw) )
+ {
+ s->opt[OPT_DESKEW].cap &= ~SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ s->opt[OPT_DESKEW].cap |= SANE_CAP_INACTIVE;
+ }
+ s->opt[OPT_DESKEW].constraint_type = SANE_CONSTRAINT_NONE;
+ s->val[OPT_DESKEW].w = SANE_FALSE;
+
+ /* auto-crop */
+ s->opt[OPT_AUTOCROP].name = "autocrop";
+ s->opt[OPT_AUTOCROP].title = SANE_I18N ("Trim image to paper size");
+ s->opt[OPT_AUTOCROP].desc = SANE_I18N ("Determines empty margins in the scanned image and removes them. This normally reduces the image to the size of the original document but may remove more.");
+ s->opt[OPT_AUTOCROP].type = SANE_TYPE_BOOL;
+ s->opt[OPT_AUTOCROP].unit = SANE_UNIT_NONE;
+ s->opt[OPT_AUTOCROP].size = sizeof (SANE_Bool);
+ s->opt[OPT_AUTOCROP].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_AUTOCROP].cap |= SANE_CAP_ADVANCED;
+ if ( dip_has_autocrop (s->dip, s->hw) )
+ {
+ s->opt[OPT_AUTOCROP].cap &= ~SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ s->opt[OPT_AUTOCROP].cap |= SANE_CAP_INACTIVE;
+ }
+ s->opt[OPT_AUTOCROP].constraint_type = SANE_CONSTRAINT_NONE;
+ s->val[OPT_AUTOCROP].w = SANE_FALSE;
+
+ s->opt[OPT_CALIBRATE].name = SANE_EPSON_CALIBRATE_NAME;
+ s->opt[OPT_CALIBRATE].title = SANE_EPSON_CALIBRATE_TITLE;
+ s->opt[OPT_CALIBRATE].desc = SANE_EPSON_CALIBRATE_DESC;
+ s->opt[OPT_CALIBRATE].type = SANE_TYPE_BUTTON;
+ s->opt[OPT_CALIBRATE].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CALIBRATE].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CALIBRATE].constraint_type = SANE_CONSTRAINT_NONE;
+ if (!maintenance_is_supported (s->hw))
+ {
+ s->opt[OPT_CALIBRATE].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_CLEAN].name = SANE_EPSON_CLEAN_NAME;
+ s->opt[OPT_CLEAN].title = SANE_EPSON_CLEAN_TITLE;
+ s->opt[OPT_CLEAN].desc = SANE_EPSON_CLEAN_DESC;
+ s->opt[OPT_CLEAN].type = SANE_TYPE_BUTTON;
+ s->opt[OPT_CLEAN].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CLEAN].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CLEAN].constraint_type = SANE_CONSTRAINT_NONE;
+ if (!maintenance_is_supported (s->hw))
+ {
+ s->opt[OPT_CLEAN].cap |= SANE_CAP_INACTIVE;
+ }
+
+ handle_mode (s, s->val[OPT_MODE].w, &reload);
+ change_profile_matrix (s);
+
+ /* adjust default settings based on configuration options */
+ {
+ void *cfg = cfg_init (NULL, NULL);
+ if (cfg_has_value (cfg, CFG_KEY_OPTION, "prefer-adf"))
+ {
+ const char *found = NULL;
+ int i = 0;
+ while ((found = s->hw->sources[i])
+ && 0 != strcmp_c (ADF_STR, found))
+ {
+ ++i;
+ }
+ if (found) handle_source (s, i, ADF_STR);
+ }
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+
+SANE_Status
+epkowa_open (const char *name, SANE_Handle *handle, const void *dip)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ Epson_Scanner *s = NULL;
+
+ status = create_sane_handle (&s, name, dip);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ /* insert newly opened handle into list of open handles */
+ s->next = first_handle;
+ first_handle = s;
+
+ *handle = (SANE_Handle) s;
+
+ return SANE_STATUS_GOOD;
+}
+
+
+void
+sane_close (SANE_Handle handle)
+{
+ Epson_Scanner *s, *prev;
+ size_t i;
+
+ /* Test if there is still data pending from the scanner. If so, then
+ * do a cancel.
+ */
+
+ log_call ();
+
+ s = (Epson_Scanner *) handle;
+
+ /* remove handle from list of open handles */
+ prev = 0;
+ for (s = first_handle; s; s = s->next)
+ {
+ if (s == handle)
+ break;
+ prev = s;
+ }
+
+ if (!s)
+ {
+ err_fatal ("invalid handle (0x%p)", handle);
+ return;
+ }
+
+ if (prev)
+ prev->next = s->next;
+ else
+ first_handle = s->next;
+
+ s->hw = dev_dtor (s->hw);
+
+ const_delete (s->opt[OPT_BIT_DEPTH].constraint.word_list, SANE_Word *);
+ const_delete (s->opt[OPT_SCAN_AREA].constraint.string_list,
+ SANE_String_Const *);
+
+ /* image data acquisition related resources */
+ delete (s->raw.buf);
+ delete (s->img.buf);
+ for (i = 0; i < LINES_SHUFFLE_MAX; ++i)
+ delete (s->line_buffer[i]);
+
+ dip_destroy_LUT (s->dip, s->lut);
+
+ delete (s);
+}
+
+
+const SANE_Option_Descriptor *
+sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
+{
+ Epson_Scanner *s = (Epson_Scanner *) handle;
+
+ if (option < 0 || option >= NUM_OPTIONS)
+ {
+ log_call ("(%d)", option);
+ return NULL;
+ }
+
+ log_call ("(%s)", s->opt[option].name);
+ return (s->opt + option);
+}
+
+
+static const SANE_String_Const *
+search_string_list (const SANE_String_Const * list, SANE_String value)
+{
+ log_call ("(%s)", value);
+
+ while (*list != NULL && strcmp_c (value, *list) != 0)
+ {
+ ++list;
+ }
+
+ return ((*list == NULL) ? NULL : list);
+}
+
+
+/* Activate, deactivate an option. Subroutines so we can add
+ debugging info if we want. The change flag is set to TRUE
+ if we changed an option. If we did not change an option,
+ then the value of the changed flag is not modified.
+ */
+static void
+activateOption (Epson_Scanner * s, SANE_Int option, SANE_Bool * change)
+{
+ log_call ("(%s)", s->opt[option].name);
+ if (!SANE_OPTION_IS_ACTIVE (s->opt[option].cap))
+ {
+ s->opt[option].cap &= ~SANE_CAP_INACTIVE;
+ *change = SANE_TRUE;
+ }
+}
+
+static void
+deactivateOption (Epson_Scanner * s, SANE_Int option, SANE_Bool * change)
+{
+ log_call ("(%s)", s->opt[option].name);
+ if (SANE_OPTION_IS_ACTIVE (s->opt[option].cap))
+ {
+ s->opt[option].cap |= SANE_CAP_INACTIVE;
+ *change = SANE_TRUE;
+ }
+}
+
+static void
+setOptionState (Epson_Scanner * s, SANE_Bool state,
+ SANE_Int option, SANE_Bool * change)
+{
+ if (state)
+ {
+ activateOption (s, option, change);
+ }
+ else
+ {
+ deactivateOption (s, option, change);
+ }
+}
+
+static void
+calculate_scan_area_offset (const Option_Value *v, int *left, int *top)
+{
+ *left =
+ SANE_UNFIX (v[OPT_TL_X].w) / MM_PER_INCH *
+ v[OPT_X_RESOLUTION].w * v[OPT_ZOOM].w / 100 + 0.5;
+
+ *top =
+ SANE_UNFIX (v[OPT_TL_Y].w) / MM_PER_INCH *
+ v[OPT_Y_RESOLUTION].w * v[OPT_ZOOM].w / 100 + 0.5;
+}
+
+static void
+calculate_scan_area_max (const Epson_Scanner *s, int *x, int *y)
+{
+ /* Cast first value to double to force the whole computation to be
+ done in double. Works around integer overflows.
+ */
+ *x = ((double) s->hw->src->max_x * s->val[OPT_X_RESOLUTION].w *
+ s->val[OPT_ZOOM].w / (s->hw->base_res * 100));
+ *y = ((double) s->hw->src->max_y * s->val[OPT_Y_RESOLUTION].w *
+ s->val[OPT_ZOOM].w / (s->hw->base_res * 100));
+}
+
+static bool
+scan_area_is_valid (Epson_Scanner *s)
+{
+ int left = 0;
+ int top = 0;
+ int max_x = 0;
+ int max_y = 0;
+
+ bool rv = true;
+
+ /* finalize parameters before we validate*/
+ estimate_parameters (s, NULL);
+
+ calculate_scan_area_max (s, &max_x, &max_y);
+ calculate_scan_area_offset (s->val, &left, &top);
+
+ if ( s->raw.ctx.pixels_per_line > max_x) rv = false;
+ if ((left + s->raw.ctx.pixels_per_line) > max_x) rv = false;
+
+ if (!need_autocrop_override (s))
+ {
+ if ( s->raw.ctx.lines > max_y ) rv = false;
+ if ((top + s->raw.ctx.lines) > max_y) rv = false;
+ }
+
+ /* check physical channel limitations */
+ {
+ size_t max_req = s->hw->channel->max_request_size (s->hw->channel);
+ if (s->raw.ctx.bytes_per_line > max_req) rv = false;
+ }
+
+ if (s->hw->using_fs)
+ {
+ if (s->raw.ctx.pixels_per_line > s->hw->scan_width_limit) rv = false;
+ return rv;
+ }
+
+ if (SANE_FRAME_RGB == s->raw.ctx.format) /* max x according to to spec */
+ if (s->raw.ctx.pixels_per_line > 21840) rv = false;
+ if (top > 65530) rv = false;
+ if (left > 65530) rv = false;
+
+ return rv;
+}
+
+static SANE_Status
+getvalue (Epson_Scanner *s, SANE_Int option, void *value)
+{
+ SANE_Option_Descriptor *sopt = &(s->opt[option]);
+ Option_Value *sval = &(s->val[option]);
+
+ log_call ("(%s)", sopt->name);
+
+ switch (option)
+ {
+ case OPT_GAMMA_VECTOR_R:
+ case OPT_GAMMA_VECTOR_G:
+ case OPT_GAMMA_VECTOR_B:
+ memcpy (value, sval->wa, sopt->size);
+ break;
+
+ case OPT_NUM_OPTS:
+ case OPT_RESOLUTION:
+ case OPT_X_RESOLUTION:
+ case OPT_Y_RESOLUTION:
+ case OPT_TL_X:
+ case OPT_TL_Y:
+ case OPT_BR_X:
+ case OPT_BR_Y:
+ case OPT_MIRROR:
+ case OPT_SPEED:
+ case OPT_PREVIEW_SPEED:
+ case OPT_AAS:
+ case OPT_PREVIEW:
+ case OPT_BRIGHTNESS:
+ case OPT_CONTRAST:
+ case OPT_SHARPNESS:
+ case OPT_AUTO_EJECT:
+ case OPT_CCT_1:
+ case OPT_CCT_2:
+ case OPT_CCT_3:
+ case OPT_CCT_4:
+ case OPT_CCT_5:
+ case OPT_CCT_6:
+ case OPT_CCT_7:
+ case OPT_CCT_8:
+ case OPT_CCT_9:
+ case OPT_THRESHOLD:
+ case OPT_ZOOM:
+ case OPT_BIT_DEPTH:
+ case OPT_WAIT_FOR_BUTTON:
+ case OPT_DETECT_DOC_SIZE:
+ case OPT_LIMIT_RESOLUTION:
+ case OPT_ADF_AUTO_SCAN:
+ case OPT_DESKEW:
+ case OPT_AUTOCROP:
+ case OPT_NEEDS_POLLING:
+ *((SANE_Word *) value) = sval->w;
+ break;
+ case OPT_MODE:
+ case OPT_ADF_MODE:
+ case OPT_HALFTONE:
+ case OPT_DROPOUT:
+ case OPT_BRIGHTNESS_METHOD:
+ case OPT_SCAN_AREA:
+ case OPT_SOURCE:
+ case OPT_FILM_TYPE:
+ case OPT_GAMMA_CORRECTION:
+ case OPT_COLOR_CORRECTION:
+ case OPT_BAY:
+ case OPT_FOCUS:
+ case OPT_ADF_DFD_SENSITIVITY:
+ strcpy ((char *) value, sopt->constraint.string_list[sval->w]);
+ break;
+
+ case OPT_QUICK_FORMAT:
+ getvalue (s, OPT_SCAN_AREA, value);
+ break;
+
+ case OPT_EXT_SANE_STATUS:
+ if (using (s->hw, adf)
+ && (ADF_EXT_STATUS_DFE & s->hw->adf->ext_status))
+ sval->w = EXT_SANE_STATUS_MULTI_FEED;
+ if (using (s->hw, adf)
+ && (ADF_EXT_STATUS_TR_OPN & s->hw->adf->ext_status))
+ sval->w = EXT_SANE_STATUS_TRAY_CLOSED;
+ *((SANE_Word *) value) = sval->w;
+ sval->w = 0;
+ break;
+
+ case OPT_MONITOR_BUTTON:
+ if (SANE_OPTION_IS_ACTIVE (option))
+ {
+ SANE_Bool pressed;
+ SANE_Status status = SANE_STATUS_GOOD;
+ if (SANE_STATUS_GOOD == status)
+ {
+ status = get_push_button_status (s->hw, &pressed);
+ if (SANE_STATUS_GOOD == status)
+ {
+ *((SANE_Bool *) value) = pressed;
+ }
+ }
+ return status;
+ }
+ else
+ {
+ return SANE_STATUS_UNSUPPORTED;
+ }
+ break;
+ case OPT_POLLING_TIME:
+ *((SANE_Word *) value) = sval->w;
+ break;
+ case OPT_SCAN_AREA_IS_VALID:
+ {
+ sval->w = scan_area_is_valid (s);
+ *((SANE_Word *) value) = sval->w;
+ }
+ break;
+ case OPT_ADF_DUPLEX_DIRECTION_MATCHES:
+ {
+ sval->w = adf_duplex_direction_matches (s->hw);
+ *((SANE_Word *) value) = sval->w;
+ }
+ break;
+ default:
+ return SANE_STATUS_INVAL;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+
+static void
+handle_mode (Epson_Scanner * s, SANE_Int optindex, SANE_Bool * reload)
+{
+ SANE_Bool dropout, aas, halftone, threshold, cct;
+ SANE_Bool brightness, contrast;
+
+ log_call ();
+
+ *reload = SANE_FALSE;
+
+ switch (optindex)
+ {
+ case 0: /* b & w */
+ dropout = SANE_TRUE;
+ aas = SANE_TRUE;
+ halftone = SANE_TRUE;
+ threshold = SANE_TRUE;
+ cct = SANE_FALSE;
+ brightness = SANE_FALSE;
+ contrast = SANE_FALSE;
+ break;
+ case 1: /* gray */
+ dropout = SANE_TRUE;
+ aas = SANE_FALSE;
+ halftone = SANE_FALSE;
+ threshold = SANE_FALSE;
+ cct = SANE_FALSE;
+ brightness = SANE_TRUE;
+ contrast = SANE_TRUE;
+ break;
+ case 2: /* color */
+ dropout = SANE_FALSE;
+ aas = SANE_FALSE;
+ halftone = SANE_FALSE;
+ threshold = SANE_FALSE;
+ cct = SANE_TRUE;
+ brightness = SANE_TRUE;
+ contrast = SANE_TRUE;
+ break;
+ default:
+ return;
+ }
+
+ if (s->hw->cmd->level[0] == 'D')
+ {
+ dropout = SANE_FALSE;
+ aas = SANE_FALSE;
+ halftone = SANE_FALSE;
+ }
+
+ setOptionState (s, dropout, OPT_DROPOUT, reload);
+ s->val[OPT_DROPOUT].w = 0;
+ setOptionState (s, halftone, OPT_HALFTONE, reload);
+ s->val[OPT_HALFTONE].w = 0;
+ setOptionState (s, aas, OPT_AAS, reload);
+ s->val[OPT_AAS].w = SANE_FALSE;
+
+ setOptionState (s, threshold, OPT_THRESHOLD, reload);
+
+ setOptionState (s, brightness, OPT_BRIGHTNESS, reload);
+ setOptionState (s, contrast, OPT_CONTRAST, reload);
+
+ setOptionState (s, cct, OPT_CCT_1, reload);
+ setOptionState (s, cct, OPT_CCT_2, reload);
+ setOptionState (s, cct, OPT_CCT_3, reload);
+ setOptionState (s, cct, OPT_CCT_4, reload);
+ setOptionState (s, cct, OPT_CCT_5, reload);
+ setOptionState (s, cct, OPT_CCT_6, reload);
+ setOptionState (s, cct, OPT_CCT_7, reload);
+ setOptionState (s, cct, OPT_CCT_8, reload);
+ setOptionState (s, cct, OPT_CCT_9, reload);
+
+ /* if binary, then disable the bit depth selection */
+ if (optindex == 0)
+ {
+ s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ if (bitDepthList[0] == 1)
+ s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE;
+ else
+ {
+ s->opt[OPT_BIT_DEPTH].cap &= ~SANE_CAP_INACTIVE;
+ s->val[OPT_BIT_DEPTH].w = mode_params[optindex].depth;
+ }
+ }
+
+ if (optindex == 0) /* b & w */
+ handle_depth_halftone (s, 0, reload);
+
+ *reload = SANE_TRUE;
+}
+
+
+static void
+change_profile_matrix (Epson_Scanner * s)
+{
+ int index = 0;
+
+ log_call ();
+
+ require (s->hw->scan_hard);
+
+ if (using (s->hw, tpu)) /* TPU */
+ {
+ if (s->val[OPT_FILM_TYPE].w == 0) /* posi */
+ index = 3;
+ else
+ index = 1;
+ }
+ else /* Flatbed or ADF */
+ {
+ index = 0;
+ }
+
+ s->val[OPT_CCT_1].w = SANE_FIX (s->hw->scan_hard->color_profile[index][0]);
+ s->val[OPT_CCT_2].w = SANE_FIX (s->hw->scan_hard->color_profile[index][1]);
+ s->val[OPT_CCT_3].w = SANE_FIX (s->hw->scan_hard->color_profile[index][2]);
+ s->val[OPT_CCT_4].w = SANE_FIX (s->hw->scan_hard->color_profile[index][3]);
+ s->val[OPT_CCT_5].w = SANE_FIX (s->hw->scan_hard->color_profile[index][4]);
+ s->val[OPT_CCT_6].w = SANE_FIX (s->hw->scan_hard->color_profile[index][5]);
+ s->val[OPT_CCT_7].w = SANE_FIX (s->hw->scan_hard->color_profile[index][6]);
+ s->val[OPT_CCT_8].w = SANE_FIX (s->hw->scan_hard->color_profile[index][7]);
+ s->val[OPT_CCT_9].w = SANE_FIX (s->hw->scan_hard->color_profile[index][8]);
+}
+
+
+static unsigned char
+int2cpt (int val)
+{
+ if (val >= 0)
+ {
+ if (val > 127)
+ val = 127;
+ return (unsigned char) val;
+ }
+ else
+ {
+ val = -val;
+ if (val > 127)
+ val = 127;
+ return (unsigned char) (0x80 | val);
+ }
+}
+
+
+static void
+get_colorcoeff_from_profile (double *profile, unsigned char *color_coeff)
+{
+ int cc_idx[] = { 4, 1, 7, 3, 0, 6, 5, 2, 8 };
+ int color_table[9];
+ int i;
+
+ round_cct (profile, color_table);
+
+ for (i = 0; i < 9; i++)
+ color_coeff[i] = int2cpt (color_table[cc_idx[i]]);
+}
+
+
+static void
+round_cct (double org_cct[], int rnd_cct[])
+{
+ int i, j, index;
+ double mult_cct[9], frac[9];
+ int sum[3];
+ int loop;
+
+ for (i = 0; i < 9; i++)
+ mult_cct[i] = org_cct[i] * 32;
+
+ for (i = 0; i < 9; i++)
+ rnd_cct[i] = (int) floor (org_cct[i] * 32 + 0.5);
+
+ loop = 0;
+
+ do
+ {
+ for (i = 0; i < 3; i++)
+ {
+ if ((rnd_cct[i * 3] == 11) &&
+ (rnd_cct[i * 3] == rnd_cct[i * 3 + 1]) &&
+ (rnd_cct[i * 3] == rnd_cct[i * 3 + 2]))
+ {
+ rnd_cct[i * 3 + i]--;
+ mult_cct[i * 3 + i] = rnd_cct[i * 3 + i];
+ }
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ sum[i] = 0;
+ for (j = 0; j < 3; j++)
+ sum[i] += rnd_cct[i * 3 + j];
+ }
+
+ for (i = 0; i < 9; i++)
+ frac[i] = mult_cct[i] - rnd_cct[i];
+
+ for (i = 0; i < 3; i++)
+ {
+ if (sum[i] < 32)
+ {
+ index = get_roundup_index (&frac[i * 3], 3);
+ if (index != -1)
+ {
+ rnd_cct[i * 3 + index]++;
+ mult_cct[i * 3 + index] = rnd_cct[i * 3 + index];
+ sum[i]++;
+ }
+ }
+ else if (sum[i] > 32)
+ {
+ index = get_rounddown_index (&frac[i * 3], 3);
+ if (index != -1)
+ {
+ rnd_cct[i * 3 + index]--;
+ mult_cct[i * 3 + index] = rnd_cct[i * 3 + index];
+ sum[i]--;
+ }
+ }
+ }
+ }
+ while ((++loop < 2)
+ && ((sum[0] != 32) || (sum[1] != 32) || (sum[2] != 32)));
+}
+
+
+static int
+get_roundup_index (double frac[], int n)
+{
+ int i, index = -1;
+ double max_val = 0.0;
+
+ for (i = 0; i < n; i++)
+ {
+ if (frac[i] < 0)
+ continue;
+ if (max_val < frac[i])
+ {
+ index = i;
+ max_val = frac[i];
+ }
+ }
+
+ return index;
+}
+
+
+static int
+get_rounddown_index (double frac[], int n)
+{
+ int i, index = -1;
+ double min_val = 1.0;
+
+ for (i = 0; i < n; i++)
+ {
+ if (frac[i] > 0)
+ continue;
+ if (min_val > frac[i])
+ {
+ index = i;
+ min_val = frac[i];
+ }
+ }
+
+ return index;
+}
+
+
+/* This routine handles common options between OPT_MODE and
+ OPT_HALFTONE. These options are TET (a HALFTONE mode), AAS
+ - auto area segmentation, and threshold. Apparently AAS
+ is some method to differentiate between text and photos.
+ Or something like that.
+
+ AAS is available when the scan color depth is 1 and the
+ halftone method is not TET.
+
+ Threshold is available when halftone is NONE, and depth is 1.
+*/
+static void
+handle_depth_halftone (Epson_Scanner * s, SANE_Int optindex,
+ SANE_Bool * reload)
+{
+ SANE_Bool threshold, aas, dropout;
+
+ log_call ();
+
+ *reload = SANE_FALSE;
+
+ switch (halftone_params[optindex])
+ {
+ case HALFTONE_NONE:
+ threshold = SANE_TRUE;
+ aas = SANE_TRUE;
+ dropout = SANE_TRUE;
+ break;
+ case HALFTONE_TET:
+ threshold = SANE_FALSE;
+ aas = SANE_FALSE;
+ dropout = SANE_FALSE;
+ break;
+ default:
+ threshold = SANE_FALSE;
+ aas = SANE_TRUE;
+ dropout = SANE_TRUE;
+ }
+
+ setOptionState (s, threshold, OPT_THRESHOLD, reload);
+ setOptionState (s, aas, OPT_AAS, reload);
+ setOptionState (s, dropout, OPT_DROPOUT, reload);
+
+ *reload = SANE_TRUE;
+}
+
+
+static void
+handle_resolution (Epson_Scanner * s, SANE_Int option, SANE_Word value)
+{
+ SANE_Int *last = NULL;
+
+ SANE_Int size = 0;
+ SANE_Word *list = NULL;
+
+ int f = 0;
+ int k = 0;
+ int n = 0;
+
+ log_call ("(%s, %d)", s->opt[option].name, value);
+
+ switch (option)
+ {
+ case OPT_RESOLUTION:
+ last = &s->hw->res.last;
+ size = s->hw->res.size;
+ list = s->hw->res.list;
+ break;
+ case OPT_X_RESOLUTION:
+ last = &s->hw->res_x.last;
+ size = s->hw->res_x.size;
+ list = s->hw->res_x.list;
+ break;
+ case OPT_Y_RESOLUTION:
+ last = &s->hw->res_y.last;
+ size = s->hw->res_y.size;
+ list = s->hw->res_y.list;
+ break;
+ default:
+ err_fatal ("%s", strerror (EINVAL));
+ exit (EXIT_FAILURE);
+ }
+
+ if (SANE_CONSTRAINT_RANGE == s->opt[option].constraint_type)
+ {
+ sanei_constrain_value (&(s->opt[option]), &value, NULL);
+ s->val[option].w = value;
+ }
+ else
+ {
+ SANE_Int best = list[size];
+ int min_d = INT_MAX;
+
+ /* find supported resolution closest to that requested */
+ for (n = 1; n <= size; n++)
+ {
+ int d = abs (value - list[n]);
+
+ if (d < min_d)
+ {
+ min_d = d;
+ k = n;
+ best = list[n];
+ }
+ }
+
+ /* FIXME? what's this trying to do? Use a resolution close to the
+ last one used if best is far away? Why??? */
+ if ((value != best) && *last)
+ {
+ for (f = 1; f <= size; f++)
+ if (*last == list[f])
+ break;
+
+ if (f != k && f != k - 1 && f != k + 1)
+ {
+ if (k > f)
+ best = list[f + 1];
+ else if (k < f)
+ best = list[f - 1];
+ }
+ }
+
+ *last = best;
+ s->val[option].w = (SANE_Word) best;
+ }
+
+ if (OPT_RESOLUTION == option)
+ {
+ s->val[OPT_X_RESOLUTION].w = s->val[option].w;
+ s->val[OPT_Y_RESOLUTION].w = s->val[option].w;
+
+ s->hw->res_x.last = s->hw->res.last;
+ s->hw->res_y.last = s->hw->res.last;
+ }
+ {
+ SANE_Bool dummy;
+ handle_deskew (s, NULL, &dummy);
+ }
+}
+
+static void
+limit_adf_res (Epson_Scanner * s)
+{
+ SANE_Constraint_Type type = s->opt[OPT_RESOLUTION].constraint_type;
+ int limit = large_res_kills_adf_scan (s->hw);
+
+ if (using (s->hw, adf))
+ {
+ dev_limit_res (s->hw, type, limit);
+
+ /* constrain the current values to the new limit */
+ handle_resolution (s, OPT_RESOLUTION, s->val[OPT_RESOLUTION].w);
+ handle_resolution (s, OPT_X_RESOLUTION, s->val[OPT_X_RESOLUTION].w);
+ handle_resolution (s, OPT_Y_RESOLUTION, s->val[OPT_Y_RESOLUTION].w);
+ }
+ else
+ {
+ dev_restore_res (s->hw, type);
+ }
+}
+
+
+/*
+ Handles setting the source (flatbed, transparency adapter (TPU),
+ or auto document feeder (ADF)).
+
+ For newer scanners it also sets the focus according to the
+ glass / TPU settings.
+*/
+static SANE_Status
+handle_source (Epson_Scanner * s, SANE_Int optindex, char *value)
+{
+ SANE_Bool dummy;
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ("(%s)", value);
+
+ if (s->val[OPT_SOURCE].w == optindex)
+ return SANE_STATUS_GOOD;
+
+ if (s->hw->adf && strcmp_c (ADF_STR, value) == 0)
+ {
+ s->val[OPT_SOURCE].w = optindex;
+ s->hw->src = (const extension *) s->hw->adf;
+ deactivateOption (s, OPT_FILM_TYPE, &dummy);
+ s->val[OPT_FOCUS].w = 0;
+ if (EXT_STATUS_ADFS & s->hw->ext_status)
+ {
+ activateOption (s, OPT_ADF_MODE, &dummy);
+ activateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy);
+ }
+ else
+ {
+ deactivateOption (s, OPT_ADF_MODE, &dummy);
+ s->val[OPT_ADF_MODE].w = 0;
+ deactivateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy);
+ }
+ if (FSI_CAP_ADFAS & s->hw->fsi_cap_2)
+ {
+ activateOption (s, OPT_ADF_AUTO_SCAN, &dummy);
+ }
+ if (FSI_CAP_DFD & s->hw->fsi_cap_2)
+ {
+ activateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy);
+ }
+ else
+ {
+ deactivateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy);
+ s->val[OPT_ADF_DFD_SENSITIVITY].w = 0;
+ }
+ }
+ else if (s->hw->tpu && strcmp_c (TPU_STR, value) == 0)
+ {
+ s->val[OPT_SOURCE].w = optindex;
+ s->hw->src = (const extension *) s->hw->tpu;
+ deactivateOption (s, OPT_ADF_MODE, &dummy);
+ deactivateOption (s, OPT_ADF_AUTO_SCAN, &dummy);
+ deactivateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy);
+ deactivateOption (s, OPT_EJECT, &dummy);
+ deactivateOption (s, OPT_AUTO_EJECT, &dummy);
+ deactivateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy);
+ }
+ else if (s->hw->fbf)
+ {
+ s->val[OPT_SOURCE].w = optindex;
+ s->hw->src = (const extension *) s->hw->fbf;
+ s->val[OPT_FOCUS].w = 0;
+ deactivateOption (s, OPT_ADF_MODE, &dummy);
+ deactivateOption (s, OPT_ADF_AUTO_SCAN, &dummy);
+ deactivateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy);
+ deactivateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy);
+ }
+ else
+ {
+ err_fatal ("internal inconsistency");
+ return SANE_STATUS_INVAL;
+ }
+
+ /* reset the scanner when we are changing the source setting -
+ this is necessary for the Perfection 1650 */
+ if (s->hw->need_reset_on_source_change)
+ initialize (s->hw);
+
+ handle_detect_doc_size (s, NULL, &dummy);
+
+ /*change*/
+ status = handle_scan_area(s, s->val[OPT_ADF_MODE].w);
+
+ change_profile_matrix (s);
+
+ setOptionState (s, using (s->hw, tpu), OPT_FILM_TYPE, &dummy);
+ setOptionState (s, using (s->hw, adf), OPT_AUTO_EJECT, &dummy);
+ setOptionState (s, using (s->hw, adf), OPT_EJECT, &dummy);
+
+ if (s->hw->cmd->set_focus_position)
+ {
+ if (using (s->hw, tpu))
+ {
+ s->val[OPT_FOCUS].w = 1;
+ setOptionState (s, SANE_TRUE, OPT_FOCUS, &dummy);
+ }
+ else if (using (s->hw, adf))
+ {
+ s->val[OPT_FOCUS].w = 0;
+ setOptionState (s, SANE_FALSE, OPT_FOCUS, &dummy);
+ }
+ else
+ {
+ s->val[OPT_FOCUS].w = 0;
+ setOptionState (s, SANE_TRUE, OPT_FOCUS, &dummy);
+ }
+ }
+
+ status = get_resolution_constraints (s->hw, s);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ if (s->hw->adf)
+ {
+ if (large_res_kills_adf_scan (s->hw)) limit_adf_res (s);
+
+ if (zoom_kills_adf_scan (s->hw))
+ {
+ if (using (s->hw, adf))
+ {
+ s->val[OPT_ZOOM].w = 100;
+ deactivateOption (s, OPT_ZOOM, &dummy);
+ }
+ else
+ {
+ if (s->hw->cmd->set_zoom)
+ activateOption (s, OPT_ZOOM, &dummy);
+ }
+ }
+ }
+
+ return status;
+}
+
+
+static void
+handle_filmtype (Epson_Scanner * s, SANE_Int optindex, char *value)
+{
+ log_call ();
+
+ value = value;
+
+ if (!s->hw->tpu || s->val[OPT_FILM_TYPE].w == optindex)
+ return;
+
+ s->val[OPT_FILM_TYPE].w = optindex;
+
+ require (s->hw->src == (extension *) s->hw->tpu);
+
+ s->val[OPT_TL_X].w = 0;
+ s->val[OPT_TL_Y].w = 0;
+ s->val[OPT_BR_X].w = s->hw->src->x_range.max;
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+
+ s->opt[OPT_TL_X].constraint.range = &(s->hw->src->x_range);
+ s->opt[OPT_TL_Y].constraint.range = &(s->hw->src->y_range);
+ s->opt[OPT_BR_X].constraint.range = &(s->hw->src->x_range);
+ s->opt[OPT_BR_Y].constraint.range = &(s->hw->src->y_range);
+
+ change_profile_matrix (s);
+}
+
+static void
+handle_autocrop (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload)
+{
+ *reload = SANE_FALSE;
+
+ if ( dip_has_autocrop (s->dip, s->hw)
+ && ( ! SANE_OPTION_IS_ACTIVE (s->opt[OPT_DESKEW].cap)
+ || ! s->val[OPT_DESKEW].b )
+ && !s->val[OPT_PREVIEW].b )
+ {
+ activateOption (s, OPT_AUTOCROP, reload);
+ if (value)
+ {
+ SANE_Bool dummy;
+ s->val[OPT_AUTOCROP].b = *value;
+ handle_deskew (s, NULL, &dummy);
+ *reload = SANE_TRUE;
+ }
+ }
+ else
+ {
+ deactivateOption (s, OPT_AUTOCROP, reload);
+ }
+}
+
+static void
+handle_deskew (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload)
+{
+ *reload = SANE_FALSE;
+
+ if ( dip_has_deskew (s->dip, s->hw)
+ && ( ! SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap)
+ || ! s->val[OPT_AUTOCROP].b )
+ && ( ! SANE_OPTION_IS_ACTIVE (s->opt[OPT_BIT_DEPTH].cap)
+ || 8 == s->val[OPT_BIT_DEPTH].w)
+ && 600 >= s->val[OPT_RESOLUTION].w
+ && 600 >= s->val[OPT_X_RESOLUTION].w
+ && 600 >= s->val[OPT_Y_RESOLUTION].w
+ && !s->val[OPT_PREVIEW].b )
+ {
+ activateOption (s, OPT_DESKEW , reload);
+ if (value)
+ {
+ SANE_Bool dummy;
+ s->val[OPT_DESKEW].b = *value;
+ handle_autocrop (s, NULL, &dummy);
+ *reload = SANE_TRUE;
+ }
+ }
+ else
+ {
+ deactivateOption (s, OPT_DESKEW, reload);
+ }
+}
+
+static void
+handle_detect_doc_size (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload)
+{
+ *reload = SANE_FALSE;
+
+ if (has_size_check_support (s->hw->src)
+ && !s->val[OPT_PREVIEW].b)
+ {
+ activateOption (s, OPT_DETECT_DOC_SIZE, reload);
+ if (value)
+ {
+ s->val[OPT_DETECT_DOC_SIZE].b = *value;
+ *reload = SANE_TRUE;
+ }
+ }
+ else
+ {
+ deactivateOption (s, OPT_DETECT_DOC_SIZE, reload);
+ }
+}
+
+static void
+handle_preview (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload)
+{
+ SANE_Bool dummy;
+
+ if (value)
+ {
+ s->val[OPT_PREVIEW].b = *value;
+ handle_detect_doc_size (s, &s->val[OPT_DETECT_DOC_SIZE].b, &dummy);
+ handle_autocrop (s, &s->val[OPT_AUTOCROP].b, &dummy);
+ handle_deskew (s, &s->val[OPT_DESKEW].b, &dummy);
+ *reload = SANE_TRUE;
+ }
+}
+
+static SANE_Status
+setvalue (Epson_Scanner *s, SANE_Int option, void *value, SANE_Int * info)
+{
+ SANE_Option_Descriptor *sopt = &(s->opt[option]);
+ Option_Value *sval = &(s->val[option]);
+
+ SANE_Status status;
+ const SANE_String_Const *optval;
+ int optindex;
+ SANE_Bool reload = SANE_FALSE;
+
+ log_call ("(%s, value @%p)", sopt->name, value);
+
+ status = sanei_constrain_value (sopt, value, info);
+
+ if (status != SANE_STATUS_GOOD)
+ return status;
+
+ optval = NULL;
+ optindex = 0;
+
+ if (sopt->constraint_type == SANE_CONSTRAINT_STRING_LIST)
+ {
+ optval = search_string_list (sopt->constraint.string_list,
+ (char *) value);
+
+ if (optval == NULL)
+ return SANE_STATUS_INVAL;
+ optindex = optval - sopt->constraint.string_list;
+ }
+
+ switch (option)
+ {
+ case OPT_GAMMA_VECTOR_R:
+ case OPT_GAMMA_VECTOR_G:
+ case OPT_GAMMA_VECTOR_B:
+ memcpy (sval->wa, value, sopt->size); /* Word arrays */
+ break;
+
+ case OPT_CCT_1:
+ case OPT_CCT_2:
+ case OPT_CCT_3:
+ case OPT_CCT_4:
+ case OPT_CCT_5:
+ case OPT_CCT_6:
+ case OPT_CCT_7:
+ case OPT_CCT_8:
+ case OPT_CCT_9:
+ sval->w = *((SANE_Word *) value); /* Simple values */
+ break;
+
+ case OPT_FILM_TYPE:
+ handle_filmtype (s, optindex, (char *) value);
+ reload = SANE_TRUE;
+ break;
+
+ case OPT_DROPOUT:
+ case OPT_BAY:
+ case OPT_FOCUS:
+ sval->w = optindex; /* Simple lists */
+ break;
+
+ case OPT_EJECT:
+ dev_eject_paper (s->hw);
+ break;
+
+ case OPT_RESOLUTION:
+ case OPT_X_RESOLUTION:
+ case OPT_Y_RESOLUTION:
+ handle_resolution (s, option, *((SANE_Word *) value));
+ reload = SANE_TRUE;
+ break;
+
+ case OPT_TL_X:
+ case OPT_TL_Y:
+ case OPT_BR_X:
+ case OPT_BR_Y:
+ sval->w = *((SANE_Word *) value);
+ log_info ("set = %f", SANE_UNFIX (sval->w));
+ if (NULL != info)
+ *info |= SANE_INFO_RELOAD_PARAMS;
+ break;
+
+ case OPT_SOURCE:
+ status = handle_source (s, optindex, (char *) value);
+ reload = SANE_TRUE;
+ break;
+
+ case OPT_MODE:
+ {
+ if (sval->w == optindex)
+ break;
+
+ sval->w = optindex;
+
+ handle_mode (s, optindex, &reload);
+
+ break;
+ }
+
+ case OPT_ADF_MODE:
+ status = handle_scan_area(s, optindex);
+ reload = SANE_TRUE;
+ /* through intentionally */
+ case OPT_ADF_DFD_SENSITIVITY:
+ sval->w = optindex;
+ break;
+
+ case OPT_BIT_DEPTH:
+ {
+ SANE_Bool dummy;
+ sval->w = *((SANE_Word *) value);
+ mode_params[s->val[OPT_MODE].w].depth = sval->w;
+ handle_deskew (s, NULL, &dummy);
+ reload = SANE_TRUE;
+ break;
+ }
+ case OPT_HALFTONE:
+ if (sval->w == optindex)
+ break;
+ sval->w = optindex;
+ handle_depth_halftone (s, optindex, &reload);
+ break;
+
+ case OPT_BRIGHTNESS_METHOD:
+ {
+ const SANE_Range *r;
+ SANE_Bool f
+ = s->hw->gamma_user_defined[s->val[OPT_GAMMA_CORRECTION].w];
+
+ if (sval->w == optindex) break;
+
+ r = s->opt[OPT_BRIGHTNESS].constraint.range;
+
+ if (0 == strcmp_c (brightness_method_list[0], /* "hardware" */
+ sopt->constraint.string_list[optindex]))
+ {
+ s->opt[OPT_BRIGHTNESS].cap &= ~SANE_CAP_EMULATED;
+ s->opt[OPT_BRIGHTNESS].constraint.range = &s->hw->cmd->bright_range;
+ setOptionState (s, !f, OPT_BRIGHTNESS, &reload);
+ }
+ else
+ {
+ s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
+ setOptionState (s, SANE_TRUE, OPT_BRIGHTNESS, &reload);
+ }
+
+ if (r != s->opt[OPT_BRIGHTNESS].constraint.range)
+ {
+ double v = s->val[OPT_BRIGHTNESS].w;
+
+ /**/ if (0 < v)
+ {
+ require (0 < r->max);
+
+ v /= r->max;
+ r = s->opt[OPT_BRIGHTNESS].constraint.range;
+ v *= r->max;
+ v += 0.5;
+
+ reload = SANE_TRUE;
+ }
+ else if (0 > v)
+ {
+ require (0 > r->min);
+
+ v /= r->min;
+ r = s->opt[OPT_BRIGHTNESS].constraint.range;
+ v *= r->min;
+ v -= 0.5;
+
+ reload = SANE_TRUE;
+ }
+ else /* 0 == v */
+ {}
+
+ s->val[OPT_BRIGHTNESS].w = (SANE_Int) v;
+ }
+
+ sval->w = optindex;
+
+ break;
+ }
+
+ case OPT_COLOR_CORRECTION:
+ {
+ SANE_Bool f = s->hw->color_user_defined[optindex];
+
+ sval->w = optindex;
+ setOptionState (s, f, OPT_CCT_1, &reload);
+ setOptionState (s, f, OPT_CCT_2, &reload);
+ setOptionState (s, f, OPT_CCT_3, &reload);
+ setOptionState (s, f, OPT_CCT_4, &reload);
+ setOptionState (s, f, OPT_CCT_5, &reload);
+ setOptionState (s, f, OPT_CCT_6, &reload);
+ setOptionState (s, f, OPT_CCT_7, &reload);
+ setOptionState (s, f, OPT_CCT_8, &reload);
+ setOptionState (s, f, OPT_CCT_9, &reload);
+
+ break;
+ }
+
+ case OPT_GAMMA_CORRECTION:
+ {
+ SANE_Bool f = s->hw->gamma_user_defined[optindex];
+
+ sval->w = optindex;
+ setOptionState (s, f, OPT_GAMMA_VECTOR_R, &reload);
+ setOptionState (s, f, OPT_GAMMA_VECTOR_G, &reload);
+ setOptionState (s, f, OPT_GAMMA_VECTOR_B, &reload);
+
+ if (0 == strcmp_c (brightness_method_list[0], /* "hardware" */
+ s->opt[OPT_BRIGHTNESS_METHOD].constraint
+ .string_list[s->val[OPT_BRIGHTNESS_METHOD].w]))
+ {
+ setOptionState (s, !f, OPT_BRIGHTNESS, &reload);
+ }
+
+ break;
+ }
+
+ case OPT_AUTO_EJECT:
+ sval->w = *((SANE_Word *) value);
+ if (s->hw && s->hw->adf) s->hw->adf->auto_eject = sval->w;
+ break;
+ case OPT_MIRROR:
+ case OPT_SPEED:
+ case OPT_PREVIEW_SPEED:
+ case OPT_AAS:
+ case OPT_BRIGHTNESS:
+ case OPT_CONTRAST:
+ case OPT_SHARPNESS:
+ case OPT_THRESHOLD:
+ case OPT_ZOOM:
+ case OPT_WAIT_FOR_BUTTON:
+ case OPT_ADF_AUTO_SCAN:
+ sval->w = *((SANE_Word *) value);
+ break;
+
+ case OPT_DETECT_DOC_SIZE:
+ handle_detect_doc_size (s, (SANE_Word *) value, &reload);
+ break;
+
+ case OPT_PREVIEW:
+ handle_preview (s, (SANE_Word *) value, &reload);
+ break;
+
+ case OPT_DESKEW:
+ handle_deskew (s, (SANE_Word *) value, &reload);
+ break;
+
+ case OPT_AUTOCROP:
+ handle_autocrop (s, (SANE_Word *) value, &reload);
+ break;
+
+ case OPT_LIMIT_RESOLUTION:
+ sval->w = *((SANE_Word *) value);
+ filter_resolution_list (s);
+ reload = SANE_TRUE;
+ break;
+
+ case OPT_SCAN_AREA:
+ {
+ sval->w = optindex;
+
+ /**/ if (0 == strcmp_c (sopt->constraint.string_list[sval->w],
+ media_maximum))
+ {
+ s->val[OPT_TL_X].w = SANE_FIX (0.0);
+ s->val[OPT_TL_Y].w = SANE_FIX (0.0);
+ s->val[OPT_BR_X].w = s->hw->src->x_range.max;
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+ }
+ else if (0 == strcmp_c (sopt->constraint.string_list[sval->w],
+ media_automatic))
+ {
+ SANE_Bool yes = SANE_TRUE;
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_DETECT_DOC_SIZE].cap))
+ {
+ setvalue (s, OPT_DETECT_DOC_SIZE, &yes, NULL);
+ }
+ }
+ else
+ {
+ size_t i = 0;
+
+ while (i < num_of (media_list)
+ && 0 != strcmp_c (sopt->constraint.string_list[sval->w],
+ media_list[i].name))
+ ++i;
+
+ require (i < num_of (media_list));
+
+ s->val[OPT_TL_X].w = SANE_FIX (0.0);
+ s->val[OPT_TL_Y].w = SANE_FIX (0.0);
+ s->val[OPT_BR_X].w = SANE_FIX (media_list[i].width);
+ s->val[OPT_BR_Y].w = SANE_FIX (media_list[i].height);
+
+ adf_handle_adjust_alignment (s, SANE_FALSE);
+ }
+
+ reload = SANE_TRUE;
+ break;
+ }
+ case OPT_QUICK_FORMAT:
+ setvalue (s, OPT_SCAN_AREA, value, info);
+ sval->w = s->val[OPT_SCAN_AREA].w;
+ break;
+
+ case OPT_CALIBRATE:
+ status = dev_calibrate (s->hw);
+ break;
+
+ case OPT_CLEAN:
+ status = dev_clean (s->hw);
+ break;
+
+ case OPT_MONITOR_BUTTON:
+ default:
+ return SANE_STATUS_INVAL;
+ }
+
+ if (reload && info != NULL)
+ {
+ *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
+ }
+
+ return status;
+}
+
+
+SANE_Status
+sane_control_option (SANE_Handle handle,
+ SANE_Int option,
+ SANE_Action action, void *value, SANE_Int * info)
+{
+ log_call ();
+
+ if (option < 0 || option >= NUM_OPTIONS)
+ return SANE_STATUS_INVAL;
+
+ if (info != NULL)
+ *info = 0;
+
+ switch (action)
+ {
+ case SANE_ACTION_GET_VALUE:
+ return (getvalue (handle, option, value));
+
+ case SANE_ACTION_SET_VALUE:
+ return (setvalue (handle, option, value, info));
+ default:
+ return SANE_STATUS_INVAL;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+/* This function is part of the SANE API and gets called when the front end
+ * requests information aobut the scan configuration (e.g. color depth, mode,
+ * bytes and pixels per line, number of lines. This information is returned
+ * in the SANE_Parameters structure.
+ *
+ * Once a scan was started, this routine has to report the correct values, if
+ * it is called before the scan is actually started, the values are based on
+ * the current settings.
+ *
+ */
+SANE_Status
+estimate_parameters (Epson_Scanner *s, SANE_Parameters * params)
+{
+ int zoom, max_x, max_y;
+ int bytes_per_pixel;
+
+ int x_dpi = 0;
+ int y_dpi = 0;
+
+ SANE_Int max_y_orig = -1;
+
+ log_call ();
+
+ memset (&s->raw.ctx, 0, sizeof (SANE_Parameters));
+
+ x_dpi = s->val[OPT_X_RESOLUTION].w;
+ y_dpi = s->val[OPT_Y_RESOLUTION].w;
+
+ zoom = s->val[OPT_ZOOM].w;
+
+ if (need_autocrop_override (s))
+ { /* yucky changes to be reverted below */
+ max_y_orig = s->hw->src->max_y;
+ ((extension *) s->hw->src)->max_y = autocrop_max_y (s->hw);
+ update_ranges (s->hw, s->hw->src);
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+ }
+
+ calculate_scan_area_max (s, &max_x, &max_y);
+
+ s->raw.ctx.pixels_per_line =
+ SANE_UNFIX (s->val[OPT_BR_X].w -
+ s->val[OPT_TL_X].w) / MM_PER_INCH * x_dpi * zoom / 100;
+ s->raw.ctx.lines =
+ SANE_UNFIX (s->val[OPT_BR_Y].w -
+ s->val[OPT_TL_Y].w) / MM_PER_INCH * y_dpi * zoom / 100;
+
+ log_data ("max x:%d y:%d [in pixels]", max_x, max_y);
+
+ if (max_x != 0 && max_y != 0)
+ {
+ if (max_x < s->raw.ctx.pixels_per_line)
+ s->raw.ctx.pixels_per_line = max_x;
+ if (max_y < s->raw.ctx.lines)
+ s->raw.ctx.lines = max_y;
+ }
+
+ if (s->raw.ctx.pixels_per_line < 8)
+ s->raw.ctx.pixels_per_line = 8;
+ if (s->raw.ctx.lines < 1)
+ s->raw.ctx.lines = 1;
+
+ log_data ("Preview = %d", s->val[OPT_PREVIEW].w);
+ log_data ("X Resolution = %d", s->val[OPT_X_RESOLUTION].w);
+ log_data ("Y Resolution = %d", s->val[OPT_Y_RESOLUTION].w);
+
+ log_data ("Scan area: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]",
+ 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));
+
+ /* Calculate bytes_per_pixel and bytes_per_line for any color
+ * depths. The default color depth is stored in mode_params.depth.
+ */
+ if (mode_params[s->val[OPT_MODE].w].depth == 1)
+ {
+ s->raw.ctx.depth = 1;
+ }
+ else
+ {
+ s->raw.ctx.depth = s->val[OPT_BIT_DEPTH].w;
+ }
+
+ if (s->raw.ctx.depth > 8)
+ {
+ /* The frontends can only handle 8 or 16 bits for gray or color -
+ * so if it's more than 8, it gets automatically set to 16. This
+ * works as long as EPSON does not come out with a scanner that
+ * can handle more than 16 bits per color channel.
+ */
+ s->raw.ctx.depth = 16;
+ }
+
+ bytes_per_pixel = s->raw.ctx.depth / 8; /* this works because it can only be set to 1, 8 or 16 */
+ if (s->raw.ctx.depth % 8) /* just in case ... */
+ {
+ bytes_per_pixel++;
+ }
+
+ /* All models require alignment on a multiple of 8 pixels per line.
+ However, some models require multiples of 32 pixels instead of 8
+ when scanning in monochrome mode.
+ We use the largest multiple that is not larger than the original
+ value.
+ */
+ s->raw.ctx.pixels_per_line &= ~7;
+ if (1 == s->raw.ctx.depth)
+ {
+ s->raw.ctx.pixels_per_line &= ~31;
+ }
+
+ s->raw.ctx.last_frame = SANE_TRUE;
+
+ if (mode_params[s->val[OPT_MODE].w].color)
+ {
+ s->raw.ctx.format = SANE_FRAME_RGB;
+ s->raw.ctx.bytes_per_line =
+ 3 * s->raw.ctx.pixels_per_line * bytes_per_pixel;
+ }
+ else
+ {
+ s->raw.ctx.format = SANE_FRAME_GRAY;
+ s->raw.ctx.bytes_per_line =
+ s->raw.ctx.pixels_per_line * s->raw.ctx.depth / 8;
+ }
+
+ if (NULL != params)
+ memcpy (params, &s->raw.ctx, sizeof (SANE_Parameters));
+
+ print_params (s->raw.ctx);
+
+ if (need_autocrop_override (s))
+ { /* revert yucky changes made above */
+ ((extension *) s->hw->src)->max_y = max_y_orig;
+ update_ranges (s->hw, s->hw->src);
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+device_init (Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+
+ status = initialize (s->hw);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+/* There is some undocumented special behavior with the TPU enable/disable.
+ * TPU power ESC e status
+ * on 0 NAK
+ * on 1 ACK
+ * off 0 ACK
+ * off 1 NAK
+ *
+ * It makes no sense to scan with TPU powered on and source flatbed, because
+ * light will come from both sides.
+ */
+
+ if (s->hw->adf || s->hw->tpu)
+ {
+ /* if it is previewing now, disable duplex */
+ SANE_Bool adf_duplex = ((1 == s->val[OPT_ADF_MODE].w)
+ && !s->val[OPT_PREVIEW].b);
+ status = control_option_unit (s->hw, adf_duplex);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ if (s->hw->tpu)
+ err_major ("You may have to power %s your TPU",
+ (using (s->hw, tpu) ? "on" : "off"));
+
+ err_major ("You may have to restart the SANE frontend.");
+ return status;
+ }
+
+ if (s->hw->cmd->request_extended_status != 0)
+ {
+ status = check_ext_status (s->hw);
+
+ if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status)
+ {
+ return status;
+ }
+ }
+ }
+
+ if (s->hw->using_fs)
+ {
+ cmd_request_scanning_parameter (s->hw);
+ s->hw->param_buf[39] = s->val[OPT_ADF_DFD_SENSITIVITY].w;
+ s->hw->param_buf[40] = (s->val[OPT_ADF_AUTO_SCAN].w ? 0xFF : 0x00);
+ }
+
+ return status;
+}
+
+static SANE_Status
+device_set_focus (Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+
+ if (!s->hw->tpu || !using (s->hw, tpu)) return status;
+
+ /* set the focus position according to the extension used:
+ * if the TPU is selected, then focus 2.5mm above the glass,
+ * otherwise focus on the glass. Scanners that don't support
+ * this feature, will just ignore these calls.
+ */
+
+ if (s->hw->adf || s->hw->tpu)
+ {
+ if (s->hw->tpu && s->hw->tpu->has_focus)
+ {
+ if (s->val[OPT_FOCUS].w == 0)
+ {
+ log_info ("Setting focus to glass surface");
+ status = set_focus_position (s->hw, 0x40);
+ }
+ else
+ {
+ log_info ("Setting focus to 2.5mm above glass");
+ status = set_focus_position (s->hw, 0x59);
+ }
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+ }
+ }
+ return status;
+}
+
+static SANE_Status
+set_scan_parameters (Epson_Scanner *s, int *x_res, int *y_res)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const struct mode_param *mparam;
+ int x_dpi, y_dpi;
+
+ log_call ();
+
+ mparam = mode_params + s->val[OPT_MODE].w;
+ log_data ("setting data format to %d bits", mparam->depth);
+ status = set_data_format (s->hw, mparam->depth);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_data_format failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+
+ /* The byte sequence mode was introduced in B5, for B[34] we need
+ line sequence mode
+ */
+ if ((s->hw->cmd->level[0] == 'D' ||
+ (s->hw->cmd->level[0] == 'B' && s->hw->level >= 5)) &&
+ mparam->mode_flags == 0x02)
+ {
+ status = set_color_mode (s->hw, 0x13);
+ }
+ else
+ {
+ status = set_color_mode (s->hw, (mparam->mode_flags
+ | (mparam->dropout_mask
+ & dropout_params[s->val[OPT_DROPOUT].w])));
+ }
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_color_mode failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+
+ if (s->hw->cmd->set_halftoning &&
+ SANE_OPTION_IS_ACTIVE (s->opt[OPT_HALFTONE].cap))
+ {
+ status = set_halftoning (s->hw, halftone_params[s->val[OPT_HALFTONE].w]);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_halftoning failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ s->brightness = 0;
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_BRIGHTNESS].cap))
+ {
+ SANE_Option_Descriptor *sopt = &(s->opt[OPT_BRIGHTNESS_METHOD]);
+ Option_Value *sval = &(s->val[OPT_BRIGHTNESS_METHOD]);
+
+ if (0 == strcmp_c (brightness_method_list[0], /* "hardware" */
+ sopt->constraint.string_list[sval->w]))
+ {
+ status = set_bright (s->hw, s->val[OPT_BRIGHTNESS].w);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_bright failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+ else /* software emulation */
+ {
+ s->brightness = s->val[OPT_BRIGHTNESS].w;
+ s->brightness /= s->opt[OPT_BRIGHTNESS].constraint.range->max;
+ }
+ }
+
+ s->contrast = 0;
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_CONTRAST].cap))
+ {
+ s->contrast = s->val[OPT_CONTRAST].w;
+ s->contrast /= s->opt[OPT_CONTRAST].constraint.range->max;
+ }
+
+ s->lut = dip_destroy_LUT (s->dip, s->lut);
+ if (0 != s->brightness || 0 != s->contrast) /* non-linear LUT */
+ {
+ SANE_Option_Descriptor *sopt = &(s->opt[OPT_BRIGHTNESS_METHOD]);
+ Option_Value *sval = &(s->val[OPT_BRIGHTNESS_METHOD]);
+
+ if (0 == strcmp_c (brightness_method_list[2], /* "gimp" */
+ sopt->constraint.string_list[sval->w]))
+ s->lut = dip_gimp_BC_LUT (s->dip, s->raw.ctx.depth,
+ s->brightness, s->contrast);
+ else
+ /* We use "iscan" for contrast by default. If "hardware" was
+ * given then s->brightness is 0 and doesn't produce knock-on
+ * effects with this method.
+ */
+ s->lut = dip_iscan_BC_LUT (s->dip, s->raw.ctx.depth,
+ s->brightness, s->contrast);
+
+ if (!s->lut)
+ status = SANE_STATUS_NO_MEM;
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_contrast failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_MIRROR].cap))
+ {
+ status = mirror_image (s->hw, mirror_params[s->val[OPT_MIRROR].w]);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("mirror_image failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_SPEED].cap))
+ {
+
+ if (s->val[OPT_PREVIEW].w)
+ status = set_speed (s->hw, speed_params[s->val[OPT_PREVIEW_SPEED].w]);
+ else
+ status = set_speed (s->hw, speed_params[s->val[OPT_SPEED].w]);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_speed failed (%s)", sane_strstatus (status));
+ return status;
+ }
+ }
+
+ /* use of speed_params is ok here since they are false and true.
+ * NOTE: I think I should throw that "params" stuff as long w is
+ * already the value.
+ */
+
+ s->invert_image = SANE_FALSE; /* default: to not inverting the image */
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_FILM_TYPE].cap))
+ {
+ status = set_film_type (s->hw, film_params[s->val[OPT_FILM_TYPE].w]);
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_film_type failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_BAY].cap))
+ {
+ status = set_bay (s->hw, s->val[OPT_BAY].w);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_bay failed (%s)", sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_SHARPNESS].cap))
+ {
+
+ status = set_outline_emphasis (s->hw, s->val[OPT_SHARPNESS].w);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_outline_emphasis failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (s->hw->cmd->set_gamma &&
+ SANE_OPTION_IS_ACTIVE (s->opt[OPT_GAMMA_CORRECTION].cap))
+ {
+ int val;
+ if (s->hw->cmd->level[0] == 'D')
+ {
+ /* The D1 level has only the two user defined gamma settings.
+ */
+ val = s->hw->gamma_type[s->val[OPT_GAMMA_CORRECTION].w];
+ }
+ else
+ {
+ val = s->hw->gamma_type[s->val[OPT_GAMMA_CORRECTION].w];
+
+ /* If "Default" is selected then determine the actual value to
+ * send to the scanner: If bilevel mode, just send the value
+ * from the table (0x01), for grayscale or color mode add one
+ * and send 0x02.
+ */
+ if (s->val[OPT_GAMMA_CORRECTION].w == 0)
+ {
+ val += mparam->depth == 1 ? 0 : 1;
+ }
+ }
+
+ log_info ("set_gamma (s, 0x%x)", val);
+ status = set_gamma (s->hw, val);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_gamma failed (%s)", sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (s->hw->cmd->set_gamma_table &&
+ s->hw->gamma_user_defined[s->val[OPT_GAMMA_CORRECTION].w])
+ { /* user defined. */
+ status = set_gamma_table (s->hw, s);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_gamma_table failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (s->hw->cmd->set_color_correction)
+ {
+ int val = s->hw->color_type[s->val[OPT_COLOR_CORRECTION].w];
+
+ log_data ("set_color_correction (s, 0x%x)", val);
+ status = set_color_correction (s->hw, val);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_color_correction failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (s->hw->color_user_defined[s->val[OPT_COLOR_CORRECTION].w])
+ {
+ size_t i;
+
+ status = set_color_correction_coefficients (s->hw, s);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_color_correction_coefficients failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+
+ log_info ("Color correction coefficients");
+ for (i = 0; i < 9; ++i)
+ log_info ("cct[%zd] = %f", i, s->cct[i]);
+ }
+
+ if (s->hw->cmd->set_threshold != 0
+ && SANE_OPTION_IS_ACTIVE (s->opt[OPT_THRESHOLD].cap))
+ {
+ status = set_threshold (s->hw, s->val[OPT_THRESHOLD].w);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_threshold failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ x_dpi = s->val[OPT_X_RESOLUTION].w;
+ y_dpi = s->val[OPT_Y_RESOLUTION].w;
+
+ if (s->hw->using_fs)
+ status = dev_set_scanning_resolution (s->hw, x_dpi, y_dpi);
+ else
+ status = set_resolution (s->hw, x_dpi, y_dpi);
+
+ *x_res = x_dpi;
+ *y_res = y_dpi;
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_resolution(%d, %d) failed (%s)",
+ x_dpi, y_dpi, sane_strstatus (status));
+ return status;
+ }
+
+ if (s->hw->cmd->set_zoom != 0)
+ {
+ status = set_zoom (s->hw, s->val[OPT_ZOOM].w, s->val[OPT_ZOOM].w);
+ if (status != SANE_STATUS_GOOD)
+ {
+ err_fatal ("set_zoom(%d) failed (%s)",
+ s->val[OPT_ZOOM].w, sane_strstatus (status));
+ return status;
+ }
+ }
+ return status;
+}
+
+static void
+wait_for_button (Epson_Scanner *s)
+{
+ SANE_Bool button_status;
+
+ log_call ();
+
+ /* If WAIT_FOR_BUTTON is active, then do just that: Wait until the
+ * button is pressed. If the button was already pressed, then we
+ * will get the button Pressed event right away.
+ */
+ if (s->val[OPT_WAIT_FOR_BUTTON].w == SANE_TRUE)
+ {
+ s->hw->wait_for_button = SANE_TRUE;
+
+ while (s->hw->wait_for_button == SANE_TRUE)
+ {
+ if (s->raw.cancel_requested)
+ {
+ s->hw->wait_for_button = SANE_FALSE;
+ }
+ /* get the button status from the scanner */
+ else if (get_push_button_status (s->hw, &button_status) ==
+ SANE_STATUS_GOOD)
+ {
+ if (button_status == SANE_TRUE)
+ {
+ s->hw->wait_for_button = SANE_FALSE;
+ }
+ else
+ {
+ microsleep (s->hw->polling_time);
+ }
+ }
+ else
+ {
+ /* we run into an eror condition, just continue */
+ s->hw->wait_for_button = SANE_FALSE;
+ }
+ }
+ }
+}
+
+static SANE_Status
+set_line_count (Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ int lcount = 1;
+ s->hw->block_mode = SANE_FALSE;
+
+ log_call ();
+
+ /* The set line count commands needs to be sent for certain scanners
+ * in color mode. The D1 level requires it, we are however only
+ * testing for 'D' and not for the actual numeric level.
+ */
+ if (((s->hw->cmd->level[0] == 'B') &&
+ ((s->hw->level >= 5) || ((s->hw->level >= 4) &&
+ (!mode_params[s->val[OPT_MODE].w].color))))
+ || (s->hw->cmd->level[0] == 'D'))
+ {
+ channel *ch = s->hw->channel; /* for the sake of brevity */
+ s->hw->block_mode = SANE_TRUE;
+ lcount = ch->max_request_size (ch) / s->raw.ctx.bytes_per_line;
+
+ if (0 >= lcount) lcount = 1;
+
+ if (lcount > 255)
+ {
+ lcount = 255;
+ }
+
+ if (using (s->hw, tpu) && lcount > 32)
+ {
+ lcount = 32;
+ }
+
+ /* The D1 series of scanners only allow an even line number
+ * for bi-level scanning. If a bit depth of 1 is selected, then
+ * make sure the next lower even number is selected.
+ */
+ {
+ if (3 < lcount && lcount % 2)
+ {
+ lcount -= 1;
+ }
+ }
+ s->line_count = lcount;
+
+ if (lcount == 0)
+ {
+ err_fatal ("can not set zero line count");
+ return SANE_STATUS_NO_MEM;
+ }
+
+ status = set_lcount (s->hw, lcount);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_lcount(%d) failed (%s)",
+ lcount, sane_strstatus (status));
+ return status;
+ }
+ }
+ return status;
+}
+
+static SANE_Status
+request_command_parameters (Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+
+ if (s->hw->cmd->request_condition != 0)
+ {
+ u_char params[2];
+ u_char result[4];
+ u_char *buf;
+ size_t len;
+
+ params[0] = ESC;
+ params[1] = s->hw->cmd->request_condition;
+
+ /* send request condition */
+ channel_send (s->hw->channel, params, 2, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ len = 4;
+ channel_recv (s->hw->channel, result, len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+ s->hw->status = result[1];
+
+ len = result[3] << 8 | result[2];
+ buf = t_alloca (len, u_char);
+ channel_recv (s->hw->channel, buf, len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ log_info ("SANE_START: Color: %d", (int) buf[1]);
+ log_info ("SANE_START: Resolution (x, y): (%d, %d)",
+ (int) (buf[4] << 8 | buf[3]), (int) (buf[6] << 8 | buf[5]));
+ log_info ("SANE_START: Scan offset (x, y): (%d, %d)",
+ (int) (buf[9] << 8 | buf[8]), (int) (buf[11] << 8 | buf[10]));
+ log_info ("SANE_START: Scan size (w, h): (%d, %d)",
+ (int) (buf[13] << 8 | buf[12]), (int) (buf[15] << 8 | buf[14]));
+ log_info ("SANE_START: Data format: %d", (int) buf[17]);
+ log_info ("SANE_START: Halftone: %d", (int) buf[19]);
+ log_info ("SANE_START: Brightness: %d", (int) buf[21]);
+ log_info ("SANE_START: Gamma: %d", (int) buf[23]);
+ log_info ("SANE_START: Zoom (x, y): (%d, %d)", (int) buf[26],
+ (int) buf[25]);
+ log_info ("SANE_START: Color correction: %d", (int) buf[28]);
+ log_info ("SANE_START: Sharpness control: %d", (int) buf[30]);
+ log_info ("SANE_START: Scanning mode: %d", (int) buf[32]);
+ log_info ("SANE_START: Mirroring: %d", (int) buf[34]);
+ log_info ("SANE_START: Auto area segmentation: %d", (int) buf[36]);
+ log_info ("SANE_START: Threshold: %d", (int) buf[38]);
+ log_info ("SANE_START: Line counter: %d", (int) buf[40]);
+ log_info ("SANE_START: Option unit control: %d", (int) buf[42]);
+ log_info ("SANE_START: Film type: %d", (int) buf[44]);
+ }
+ return status;
+}
+
+static SANE_Status
+device_sheet_setup (Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ int left, top;
+
+ int x_dpi = 0;
+ int y_dpi = 0;
+
+ const struct mode_param *mparam = NULL;
+
+ status = dev_load_paper (s->hw);
+ if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status)
+ {
+ return status;
+ }
+ status = device_set_focus (s);
+ if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status)
+ {
+ return status;
+ }
+ status = dev_request_extended_status (s->hw);
+ if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status)
+ {
+ return status;
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap)
+ && s->val[OPT_AUTOCROP].b)
+ {
+ s->val[OPT_TL_X].w = SANE_FIX (0);
+ s->val[OPT_TL_Y].w = SANE_FIX (0);
+ s->val[OPT_BR_X].w = s->hw->src->x_range.max;
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+ }
+ else if (has_size_check_support (s->hw->src)
+ && SANE_OPTION_IS_ACTIVE (s->opt[OPT_DETECT_DOC_SIZE].cap)
+ && s->val[OPT_DETECT_DOC_SIZE].w)
+ {
+ s->val[OPT_TL_X].w = SANE_FIX (0);
+ s->val[OPT_TL_Y].w = SANE_FIX (0);
+ s->val[OPT_BR_X].w = SANE_FIX (s->hw->src->doc_x);
+ s->val[OPT_BR_Y].w = SANE_FIX (s->hw->src->doc_y);
+ }
+
+ adf_handle_adjust_alignment (s, SANE_TRUE);
+
+ status = set_scan_parameters (s, &x_dpi, &y_dpi);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ calculate_scan_area_offset (s->val, &left, &top);
+
+ /* Calculate correction for line_distance in D1 scanner: Start
+ * line_distance lines earlier and add line_distance lines at the
+ * end.
+ *
+ * Because the actual line_distance is not yet calculated we have to
+ * do this first.
+ */
+
+ s->hw->color_shuffle = SANE_FALSE;
+ s->current_output_line = 0;
+ s->color_shuffle_line = 0;
+
+ mparam = mode_params + s->val[OPT_MODE].w;
+ if ((s->hw->optical_res != 0) && (mparam->depth == 8)
+ && (mparam->mode_flags != 0))
+ {
+ s->line_distance = s->hw->max_line_distance * x_dpi / s->hw->optical_res;
+ if (s->line_distance != 0)
+ {
+ s->hw->color_shuffle = SANE_TRUE;
+ }
+ else
+ s->hw->color_shuffle = SANE_FALSE;
+ }
+
+ estimate_parameters (s, NULL);
+ { /* finalise the number of scanlines */
+ int lines = s->raw.ctx.lines;
+ int max_lines = lines;
+
+ if (!need_autocrop_override (s))
+ {
+ max_lines = (SANE_UNFIX(s->hw->src->y_range.max)
+ * s->val[OPT_Y_RESOLUTION].w * s->val[OPT_ZOOM].w / 100
+ / MM_PER_INCH) + 0.5;
+ }
+ /* add extra lines at the top and bottom to minimise the loss of
+ scanlines due to colour shuffling */
+ if (SANE_TRUE == s->hw->color_shuffle)
+ {
+ top -= 1 * s->line_distance;
+ lines += 2 * s->line_distance;
+ }
+
+ /* make sure values are within range
+ In the worst case, this chomps 2 * s->line_distance lines from
+ the area the user wanted to scan. C'est la vie. */
+ top = max (0, top);
+ lines = min (lines, max_lines - top);
+
+ if (s->hw->using_fs)
+ {
+ status = dev_set_scanning_area (s->hw, left, top,
+ s->raw.ctx.pixels_per_line, lines);
+ }
+ else
+ {
+ status = set_scan_area (s->hw, left, top,
+ s->raw.ctx.pixels_per_line, lines);
+ }
+
+ /* substract the additional lines needed for colour shuffling so
+ the frontend can know how many lines to expect */
+ if (SANE_TRUE == s->hw->color_shuffle)
+ {
+ lines -= 2 * s->line_distance;
+ }
+ s->raw.ctx.lines = lines;
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_scan_area failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ status = set_line_count (s);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ if (s->hw->using_fs)
+ {
+ /* if it is previewing now, disable duplex */
+ byte adf_duplex = ((1 == s->val[OPT_ADF_MODE].w)
+ && !s->val[OPT_PREVIEW].b) ? 0x01 : 0x00;
+ dev_set_option_unit (s->hw, adf_duplex);
+ status = cmd_set_scanning_parameter (s->hw);
+ }
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ if (s->hw->using_fs)
+ {
+ status = cmd_request_scanning_parameter (s->hw);
+ dev_log_scanning_parameter (s->hw);
+ }
+ else
+ status = request_command_parameters (s);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ if (s->hw->channel->interpreter)
+ {
+ status = s->hw->channel->interpreter->ftor1 (s->hw->channel, &s->raw.ctx,
+ mparam->depth, left, x_dpi,
+ s->hw->optical_res);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+ }
+
+ wait_for_button (s);
+
+ return status;
+}
+
+/*! \brief Implements image data buffer resizing policy.
+ *
+ * The implementation basically reuses previously acquired image data
+ * acquisition buffers as much as possible. This mitigates the risk
+ * of a (consecutive) scan aborting someway halfway through because
+ * memory allocation failed.
+ */
+static
+bool
+resize_warranted (size_t needed, size_t capacity)
+{
+ return needed > capacity;
+}
+
+SANE_Status
+sane_start (SANE_Handle handle)
+{
+ Epson_Scanner *s = (Epson_Scanner *) handle;
+ SANE_Status status;
+ u_char params[4];
+ size_t len_raw;
+
+ log_call ();
+
+ s->raw.cancel_requested = false;
+ s->raw.all_data_fetched = false;
+ s->raw.transfer_started = false;
+ s->raw.transfer_stopped = false;
+
+ s->img.cancel_requested = false;
+ s->img.all_data_fetched = false;
+ s->img.transfer_started = false;
+ s->img.transfer_stopped = false;
+
+ s->src = &s->raw;
+
+ if (0 == s->frame_count)
+ {
+ /* Back up original SANE options.
+ * \todo string and word array have to copy individually.
+ */
+ memcpy (s->val_bak, s->val, (sizeof (s->val[0]) * NUM_OPTIONS));
+ }
+ else if (!s->val[OPT_ADF_AUTO_SCAN].w)
+ {
+ /* Restore original SANE options.
+ * \todo string and word array have to copy individually.
+ */
+ memcpy (s->val, s->val_bak, (sizeof (s->val[0]) * NUM_OPTIONS));
+ }
+
+ if (!scan_area_is_valid (s))
+ {
+ err_fatal ("The image data resulting from the combination of the "
+ "specified scan area and resolution is too large.");
+ return SANE_STATUS_INVAL;
+ }
+
+ /* for AFF devices, check paper status here instead of in scan_finish
+ * to avoid blocking there in dev_request_extended_status()
+ */
+ if (0 < s->frame_count
+ && using (s->hw, adf)
+ && adf_has_auto_form_feed (s->hw))
+ adf_handle_out_of_paper (s);
+
+ if (0 == s->frame_count)
+ {
+ status = device_init (s);
+ if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status)
+ {
+ return status;
+ }
+ }
+
+ if (ENABLE_TIMING && 0 == time_pass_count)
+ {
+ time_clear ();
+ time_stamp (time_scan, start);
+ }
+
+ if (!s->val[OPT_ADF_AUTO_SCAN].w || 0 == s->frame_count)
+ {
+ status = device_sheet_setup (s);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+ }
+
+ if (SANE_STATUS_GOOD != (status = check_warmup (s->hw))
+ && !(SANE_STATUS_NO_DOCS == status
+ && using (s->hw, adf)))
+ {
+ return status;
+ }
+
+ status = dev_lock (s->hw);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ params[0] = ESC;
+ if (s->hw->using_fs) params[0] = FS;
+ params[1] = s->hw->cmd->start_scanning;
+
+ if (ENABLE_TIMING && TIME_PASS_MAX > time_pass_count)
+ time_stamp (time_pass[time_pass_count], start);
+
+ channel_send (s->hw->channel, params, 2, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("start failed (%s)", sane_strstatus (status));
+ return status;
+ }
+
+ if (s->hw->color_shuffle == SANE_TRUE)
+ {
+ size_t len_line_buffer = s->raw.ctx.bytes_per_line;
+
+ if (resize_warranted (len_line_buffer, s->cap_line_buffer))
+ {
+ size_t i;
+
+ for (i = 0; i < 2 * s->line_distance + 1; ++i)
+ delete (s->line_buffer[i]);
+ s->cap_line_buffer = 0;
+
+ for (i = 0; i < 2 * s->line_distance + 1; ++i)
+ {
+ SANE_Byte *p = t_malloc (len_line_buffer, SANE_Byte);
+
+ if (p)
+ {
+ s->line_buffer[i] = p;
+ }
+ else /* clean up and bail */
+ {
+ size_t j;
+
+ for (j = 0; j < i; ++i)
+ delete (s->line_buffer[i]);
+
+ s->cap_line_buffer = 0;
+
+ return SANE_STATUS_NO_MEM;
+ }
+ s->cap_line_buffer = len_line_buffer;
+ }
+ }
+ }
+
+ if (s->hw->using_fs)
+ {
+ s->hw->block_mode = SANE_TRUE;
+ status = read_image_info_block (s->hw);
+ s->raw.transfer_started = (s->hw->block_total
+ * s->hw->image_block_size
+ + s->hw->final_block_size
+ > 0);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ len_raw = s->hw->image_block_size + 1; /* for error code */
+ }
+ else
+ {
+ len_raw = s->line_count * s->raw.ctx.bytes_per_line;
+ }
+
+ if (resize_warranted (len_raw, s->raw.cap))
+ {
+ delete (s->raw.buf);
+ s->raw.cap = 0;
+
+ if (!(s->raw.buf = t_malloc (len_raw, SANE_Byte)))
+ return SANE_STATUS_NO_MEM;
+
+ s->raw.cap = len_raw;
+ }
+ s->raw.ptr = s->raw.end = s->raw.buf;
+ s->raw.transfer_started = true;
+
+ /* This here will block sane_start() until the whole image has been
+ * scanned and pre-processed. The assumption made here is that the
+ * pre-processing can not be done in place and that the resulting
+ * image is no larger than the image acquired.
+ */
+ if (dip_needs_whole_image (s->dip, s->val, s->opt))
+ {
+ SANE_Int len_img = s->raw.ctx.bytes_per_line * s->raw.ctx.lines;
+ SANE_Int max = len_img;
+ SANE_Int len = 0;
+
+ log_info ("buffering image before returning from sane_start()");
+
+ if (resize_warranted (len_img, s->img.cap))
+ {
+ delete (s->img.buf);
+ s->img.cap = 0;
+
+ if (!(s->img.buf = t_malloc (len_img, SANE_Byte)))
+ return SANE_STATUS_NO_MEM;
+
+ s->img.cap = len_img;
+ }
+
+ s->img.ptr = s->img.buf;
+ do /* note: non-blocking I/O not supported */
+ {
+ s->img.ptr += len;
+ max -= len;
+ status = fetch_image_data (s, s->img.ptr, max, &len);
+ }
+ while (SANE_STATUS_GOOD == status);
+
+ if (SANE_STATUS_EOF != status)
+ return status;
+
+ if (0 != max)
+ return SANE_STATUS_IO_ERROR;
+
+ s->img.ptr = s->img.buf;
+ s->img.end = s->img.buf + len_img;
+ memcpy (&s->img.ctx, &s->raw.ctx, sizeof (s->raw.ctx));
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_DESKEW].cap)
+ && s->val[OPT_DESKEW].b)
+ {
+ dip_deskew (s->dip, s->hw, s->frame_count, &s->img, s->val);
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap)
+ && s->val[OPT_AUTOCROP].b)
+ {
+ dip_autocrop (s->dip, s->hw, s->frame_count, &s->img, s->val);
+ }
+
+ s->img.all_data_fetched = true;
+ s->img.transfer_started = true;
+ s->src = &s->img;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+/*! Retrieves an image info block and computes image data block info.
+ */
+static SANE_Status
+read_image_info_block (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ u_char buf[14]; /* largest image handshake "info" block */
+ int buf_size = num_of (buf);
+
+ const int limit = 30;
+ int ticks = 0;
+
+ log_call ();
+
+ if (!hw->using_fs)
+ buf_size = (hw->block_mode ? 6 : 4);
+
+ channel_recv_all_retry (hw->channel, buf, buf_size,
+ MAX_READ_ATTEMPTS, &status);
+
+ /* Prevent reporting of stale values if we bail early.
+ */
+ hw->image_block_size = 0;
+ hw->final_block_size = 0;
+ if (hw->using_fs)
+ {
+ hw->block_total = 0;
+ hw->block_count = 0;
+ }
+
+ if (SANE_STATUS_GOOD != status)
+ return status;
+
+ if (STX != buf[0])
+ {
+ log_data ("code %02x", buf[0]);
+ log_data ("error, expected STX");
+
+ return SANE_STATUS_INVAL;
+ }
+ hw->status = buf[1];
+
+ /* Update values here so they are also available in case we bail in
+ the while loop below.
+ */
+ if (hw->using_fs)
+ {
+ hw->image_block_size = buf_to_uint32 (buf + 2);
+ hw->final_block_size = buf_to_uint32 (buf + 10);
+ hw->block_total = buf_to_uint32 (buf + 6);
+ hw->block_count = 0;
+ }
+ else
+ {
+ hw->image_block_size = buf[3] << 8 | buf[2];
+ if (hw->block_mode)
+ hw->image_block_size *= buf[5] << 8 | buf[4];
+ }
+
+ /* Although spec compliant scanners have their warming up bit set
+ when they are getting ready for a scan, the world is less than
+ perfect :-(
+
+ We hack around non-compliant behaviour by looping until 'ESC G'
+ succeeds or our patience runs out. In the latter case we just
+ claim that the device is busy.
+
+ However, when the ADF function is used, and the last paper is
+ scanned, we do not need looping any more.
+ */
+ while (hw->status & (STATUS_FATAL_ERROR | STATUS_NOT_READY)
+ && !(using (hw, adf)
+ && (hw->status & STATUS_AREA_END))
+ && ticks++ < limit)
+ {
+ u_char cmd[2];
+
+ err_fatal ("fatal error - Status = %02x", hw->status);
+
+ if (SANE_STATUS_GOOD != (status = check_warmup (hw)))
+ return status;
+
+ cmd[0] = (hw->using_fs ? FS : ESC);
+ cmd[1] = 'G';
+
+ channel_send (hw->channel, cmd, 2, &status);
+ sleep (1);
+ channel_recv_all_retry (hw->channel, buf, buf_size,
+ MAX_READ_ATTEMPTS, &status);
+ hw->status = buf[1];
+
+ if (hw->using_fs)
+ {
+ hw->image_block_size = buf_to_uint32 (buf + 2);
+ hw->final_block_size = buf_to_uint32 (buf + 10);
+ hw->block_total = buf_to_uint32 (buf + 6);
+ hw->block_count = 0;
+ }
+ else
+ {
+ hw->image_block_size = buf[3] << 8 | buf[2];
+ if (hw->block_mode)
+ hw->image_block_size *= buf[5] << 8 | buf[4];
+ }
+ }
+ return (ticks < limit
+ ? status
+ : SANE_STATUS_DEVICE_BUSY);
+}
+
+
+static void
+scan_finish (Epson_Scanner * s)
+{
+ log_call ();
+
+ s->raw.transfer_stopped = true;
+
+ if (s->hw->channel->interpreter) /* FIXME: do we really need this? */
+ {
+ s->hw->channel->interpreter->free (s->hw->channel);
+ }
+
+ s->frame_count++;
+
+ if (!using (s->hw, adf)) return; /* we're done */
+
+ if (!(EXT_STATUS_ADFT & s->hw->ext_status) /* sheet feed */
+ && !adf_has_auto_form_feed (s->hw))
+ {
+ dev_eject_paper (s->hw);
+ }
+
+ /* avoid blocking here on dev_request_extended_status()
+ * with AFF devices, unless canceling
+ */
+ if (!adf_has_auto_form_feed (s->hw) || s->raw.cancel_requested)
+ adf_handle_out_of_paper (s);
+}
+
+/* part of the scan sequence logic and should not be used generally */
+static void
+adf_handle_out_of_paper (Epson_Scanner * s)
+{
+ if (!using (s->hw, adf)) return;
+
+ /* if we're finishing an ADF scan and the ADF unit has been disabled,
+ * enable it again in order to get accurate status information
+ */
+ if (using (s->hw, adf) && !(ADF_STATUS_EN & s->hw->adf->status))
+ {
+ /* if it is previewing now, disable duplex */
+ byte value = ((s->hw->adf->using_duplex && !s->val[OPT_PREVIEW].b)
+ ? 0x02 : 0x01);
+ cmd_control_option_unit (s->hw, value);
+ }
+
+ dev_request_extended_status (s->hw);
+ if (!(ADF_STATUS_PE & s->hw->adf->status)) return;
+
+ log_info ("ADF: out of paper, %s mode, %d sheets",
+ ((ADF_STATUS_PAG & s->hw->adf->status) ? "duplex" : "simplex"),
+ s->hw->adf->sheet_count);
+ if (!(ADF_STATUS_PAG & s->hw->adf->status)
+ || 0 == (s->hw->adf->sheet_count % 2))
+ {
+ dev_eject_paper (s->hw);
+ s->frame_count = 0;
+ /* Restore original SANE options.
+ * \todo string and word array have to copy individually.
+ */
+ memcpy (s->val, s->val_bak, (sizeof (s->val[0]) * NUM_OPTIONS));
+ }
+ else
+ log_info ("ADF: scanning reverse side");
+}
+
+static SANE_Bool
+adf_needs_realignment (const device *hw)
+{
+ SANE_Byte dpos;
+
+ require (using (hw, adf));
+
+ dpos = hw->fsi_cap_3 & FSI_CAP_DPOS_MASK;
+
+ return (adf_needs_manual_centering (hw)
+ || FSI_CAP_DPOS_CNTR == dpos
+ || FSI_CAP_DPOS_RIGT == dpos);
+}
+
+static void
+adf_handle_adjust_alignment (Epson_Scanner *s, SANE_Bool finalize)
+{
+ scan_area_t adf_scan_area;
+ double shift = 0.0;
+ double sides = 2; /* distribute shift on both sides
+ --> we center by default */
+ log_call();
+
+ if (!using (s->hw, adf)) return;
+ if (!adf_needs_realignment (s->hw)) return;
+
+ log_info ("before alignment: tl-x = %.2f, br-x = %.2f",
+ SANE_UNFIX (s->val[OPT_TL_X].w),
+ SANE_UNFIX (s->val[OPT_BR_X].w));
+
+ adf_scan_area =
+ get_model_info_max_scan_area (s->hw, s->val[OPT_ADF_MODE].w);
+
+ if(SANE_UNFIX (adf_scan_area.width) < 0
+ && SANE_UNFIX (adf_scan_area.height) < 0)
+ {
+ adf_scan_area.width = s->hw->src->x_range.max;
+ adf_scan_area.height = s->hw->src->y_range.max;
+ }
+
+ if (FSI_CAP_DPOS_RIGT == s->hw->fsi_cap_3 & FSI_CAP_DPOS_MASK)
+ {
+ sides = 1; /* put whole shift on one side */
+ }
+
+ /* scan area setting or no marquee */
+ if (!finalize ||
+ (adf_scan_area.width == (s->val[OPT_BR_X].w - s->val[OPT_TL_X].w)
+ && adf_scan_area.height == (s->val[OPT_BR_Y].w - s->val[OPT_TL_Y].w)))
+ {
+ double scan_width = (SANE_UNFIX (s->val[OPT_BR_X].w)
+ - SANE_UNFIX (s->val[OPT_TL_X].w));
+ shift = SANE_UNFIX (s->hw->src->x_range.max) - scan_width;
+ shift /= sides;
+
+ s->val[OPT_TL_X].w = SANE_FIX (shift + 0);
+ s->val[OPT_BR_X].w = SANE_FIX (shift + scan_width);
+ }
+ /* uses auto-detect document size */
+ else if (has_size_check_support (s->hw->src)
+ && SANE_OPTION_IS_ACTIVE (s->opt[OPT_DETECT_DOC_SIZE].cap)
+ && s->val[OPT_DETECT_DOC_SIZE].w)
+ {
+ shift = SANE_UNFIX (s->hw->src->x_range.max) - s->hw->src->doc_x;
+ shift /= sides;
+
+ s->val[OPT_TL_X].w = SANE_FIX (shift + 0);
+ s->val[OPT_BR_X].w = SANE_FIX (shift + s->hw->src->doc_x);
+ }
+ /* has marquee and adf-duplex scan area differ from adf-simplex scan area */
+ else if (!(adf_scan_area.width == s->hw->src->x_range.max
+ && adf_scan_area.height == s->hw->src->y_range.max))
+ {
+ shift = SANE_UNFIX (s->hw->src->x_range.max - adf_scan_area.width);
+ shift /= sides;
+
+ s->val[OPT_TL_X].w += SANE_FIX (shift);
+ s->val[OPT_BR_X].w += SANE_FIX (shift);
+ }
+
+ log_info ("after alignment : tl-x = %.2f, br-x = %.2f",
+ SANE_UNFIX (s->val[OPT_TL_X].w),
+ SANE_UNFIX (s->val[OPT_BR_X].w));
+ log_info ("shifted scan area offset by %.2f mm", shift);
+}
+
+#define GET_COLOR(x) (((x) >> 2) & 0x03)
+
+SANE_Status
+fetch_image_data (Epson_Scanner *s, SANE_Byte * data, SANE_Int max_length,
+ SANE_Int * length)
+{
+ SANE_Status status;
+ int index = 0;
+ SANE_Bool reorder = SANE_FALSE;
+ SANE_Bool needStrangeReorder = SANE_FALSE;
+
+ log_call ();
+
+ if (s->raw.transfer_stopped && s->raw.cancel_requested)
+ return SANE_STATUS_CANCELLED;
+
+START_READ:
+ if (s->raw.ptr == s->raw.end)
+ {
+ size_t buf_len;
+
+ if (s->raw.all_data_fetched)
+ {
+ *length = 0;
+ return SANE_STATUS_EOF;
+ }
+
+ if (!s->hw->using_fs)
+ {
+ status = read_image_info_block (s->hw);
+ if (SANE_STATUS_GOOD != status)
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+ buf_len = s->hw->image_block_size;
+ }
+ else
+ {
+ buf_len = s->hw->image_block_size;
+ if (s->hw->block_count >= s->hw->block_total)
+ buf_len = s->hw->final_block_size;
+ ++buf_len; /* include error byte */
+ }
+
+ if (!s->hw->block_mode && SANE_FRAME_RGB == s->raw.ctx.format)
+ {
+ /* Read color data in line mode */
+
+ /* read the first color line - the number of bytes to read
+ * is already known (from last call to read_image_info_block()
+ * We determine where to write the line from the color information
+ * in the data block. At the end we want the order RGB, but the
+ * way the data is delivered does not guarantee this - actually it's
+ * most likely that the order is GRB if it's not RGB!
+ */
+ switch (GET_COLOR (s->hw->status))
+ {
+ case 1:
+ index = 1;
+ break;
+ case 2:
+ index = 0;
+ break;
+ case 3:
+ index = 2;
+ break;
+ }
+
+ channel_recv (s->hw->channel, s->raw.buf + index * s->raw.ctx.pixels_per_line,
+ buf_len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+
+ /* send the ACK signal to the scanner in order to make
+ * it ready for the next image info block.
+ */
+ channel_send (s->hw->channel, S_ACK, 1, &status);
+
+ /* ... and request the next image info block
+ */
+ if (SANE_STATUS_GOOD != (status = read_image_info_block (s->hw)))
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+
+ buf_len = s->hw->image_block_size;
+
+ switch (GET_COLOR (s->hw->status))
+ {
+ case 1:
+ index = 1;
+ break;
+ case 2:
+ index = 0;
+ break;
+ case 3:
+ index = 2;
+ break;
+ }
+
+ channel_recv (s->hw->channel, s->raw.buf + index * s->raw.ctx.pixels_per_line,
+ buf_len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+
+ channel_send (s->hw->channel, S_ACK, 1, &status);
+
+ /* ... and the last image info block
+ */
+ if (SANE_STATUS_GOOD != (status = read_image_info_block (s->hw)))
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+
+ buf_len = s->hw->image_block_size;
+
+ switch (GET_COLOR (s->hw->status))
+ {
+ case 1:
+ index = 1;
+ break;
+ case 2:
+ index = 0;
+ break;
+ case 3:
+ index = 2;
+ break;
+ }
+
+ channel_recv (s->hw->channel, s->raw.buf + index * s->raw.ctx.pixels_per_line,
+ buf_len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+ }
+ else
+ {
+ /* Read image data in block mode */
+
+ /* do we have to reorder the image data ? */
+ if (GET_COLOR (s->hw->status) == 0x01)
+ {
+ reorder = SANE_TRUE;
+ }
+
+ channel_recv_all_retry (s->hw->channel, s->raw.buf, buf_len,
+ MAX_READ_ATTEMPTS, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+ }
+
+ if (s->hw->using_fs)
+ {
+ u_char err = 0;
+
+ if (s->hw->block_count >= s->hw->block_total)
+ s->raw.all_data_fetched = true;
+ ++(s->hw->block_count);
+ log_info ("read image block %u/%u",
+ s->hw->block_count, s->hw->block_total + 1);
+
+ err = s->raw.buf[--buf_len]; /* drop error byte */
+ log_info ("image block error byte: %x", err);
+
+ if ((FSG_FATAL_ERROR | FSG_NOT_READY) & err)
+ {
+ *length = 0;
+ scan_finish (s);
+ return check_ext_status (s->hw);
+ }
+ else if (FSG_CANCEL_REQUEST & err)
+ {
+ s->raw.cancel_requested = true;
+ }
+ else if (FSG_PAGE_END & err)
+ {
+ if (FSI_CAP_PED & s->hw->fsi_cap_2)
+ {
+ log_info ("paper end flag raised");
+ }
+ else
+ {
+ err_minor ("invalid paper end flag raised");
+ }
+ }
+ else if (0 != err)
+ {
+ log_info ("unknown error flag(s) raised");
+ *length = 0;
+ scan_finish (s);
+ return check_ext_status (s->hw);
+ }
+ }
+ else
+ {
+ s->raw.all_data_fetched = (STATUS_AREA_END & s->hw->status);
+ }
+
+ if (s->raw.all_data_fetched
+ && ENABLE_TIMING && TIME_PASS_MAX > time_pass_count)
+ {
+ time_stamp (time_pass[time_pass_count], stop);
+ ++time_pass_count;
+ }
+
+ if (!s->raw.all_data_fetched)
+ {
+ if (s->raw.cancel_requested)
+ {
+ channel_send (s->hw->channel, S_CAN, 1, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ status = expect_ack (s->hw);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ *length = 0;
+
+ scan_finish (s);
+
+ return SANE_STATUS_CANCELLED;
+ }
+ else
+ {
+ channel_send (s->hw->channel, S_ACK, 1, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ }
+ }
+
+ s->raw.end = s->raw.buf + buf_len;
+ s->raw.ptr = s->raw.buf;
+
+ /* if we have to re-order the color components (GRB->RGB) we
+ * are doing this here:
+ */
+
+ /* Some scanners (e.g. the Perfection 1640 and GT-2200) seem
+ * to have the R and G channels swapped.
+ * The GT-8700 is the Asian version of the Perfection1640.
+ * If the scanner name is one of these, and the scan mode is
+ * RGB then swap the colors.
+ */
+
+ needStrangeReorder = ((0 == strcmp_c (s->hw->fw_name, "GT-2200")
+ || 0 == strcmp_c (s->hw->fw_name, "Perfection1640")
+ || 0 == strcmp_c (s->hw->fw_name, "GT-8700"))
+ && s->raw.ctx.format == SANE_FRAME_RGB);
+
+ /* Certain Perfection 1650 also need this re-ordering of the two
+ * color channels. These scanners are identified by the problem
+ * with the half vertical scanning area. When we corrected this,
+ * we also set the variable s->hw->need_color_reorder
+ */
+ if (s->hw->need_color_reorder)
+ {
+ needStrangeReorder = SANE_TRUE;
+ }
+
+ if (needStrangeReorder)
+ reorder = SANE_FALSE; /* reordering once is enough */
+
+ if (s->raw.ctx.format != SANE_FRAME_RGB)
+ reorder = SANE_FALSE; /* don't reorder for BW or gray */
+
+ if (reorder)
+ {
+ s->raw.ptr = s->raw.buf;
+ dip_change_GRB_to_RGB (s->dip, &s->raw);
+ }
+
+ /* Do the color_shuffle if everything else is correct - at this
+ * time most of the stuff is hardcoded for the Perfection 610
+ */
+ if (s->hw->color_shuffle)
+ {
+ int new_length = 0;
+
+ status = color_shuffle (s, &new_length);
+
+ /* If no bytes are returned, check if the scanner is already
+ * done, if so, we'll probably just return, but if there is more
+ * data to process get the next batch.
+ */
+
+ if (new_length == 0 && s->raw.end != s->raw.ptr)
+ {
+ goto START_READ;
+ }
+
+ s->raw.end = s->raw.buf + new_length;
+ s->raw.ptr = s->raw.buf;
+ }
+
+ if ((SANE_CAP_EMULATED & s->opt[OPT_CCT_1].cap)
+ && s->hw->color_user_defined[s->val[OPT_COLOR_CORRECTION].w]
+ && SANE_FRAME_RGB == s->raw.ctx.format)
+ {
+ dip_apply_color_profile (s->dip, &s->raw, s->cct);
+ }
+
+ if (s->hw->channel->interpreter)
+ {
+ s->hw->channel->interpreter->ftor0 (s->hw->channel,
+ &s->raw.ctx,
+ s->raw.ptr, s->raw.end);
+ }
+
+ if (s->lut)
+ {
+ dip_apply_LUT (s->dip, &s->raw, s->lut);
+ }
+
+ /* WARNING: The SANE specification normally uses zero to indicate
+ * minimum intensity. However, SANE_FRAME_GRAY images with a bit
+ * depth of one use zero to indicate *maximum* intensity.
+ * The device always uses zero for minimum intensity, irrespective
+ * of the color mode and bit depth.
+ */
+ if (1 == s->raw.ctx.depth && SANE_FRAME_GRAY == s->raw.ctx.format)
+ {
+ if (!s->invert_image)
+ dip_flip_bits (s->dip, &s->raw);
+ }
+ else
+ {
+ if (s->invert_image)
+ dip_flip_bits (s->dip, &s->raw);
+ }
+ }
+
+ /* copy the image data to the data memory area
+ */
+ if (!s->hw->block_mode && SANE_FRAME_RGB == s->raw.ctx.format)
+ {
+ max_length /= 3;
+
+ if (max_length > s->raw.end - s->raw.ptr)
+ max_length = s->raw.end - s->raw.ptr;
+
+ *length = 3 * max_length;
+
+ if (s->raw.cancel_requested)
+ s->raw.ptr += max_length;
+ else
+ {
+ while (max_length-- != 0)
+ {
+ *data++ = s->raw.ptr[0];
+ *data++ = s->raw.ptr[s->raw.ctx.pixels_per_line];
+ *data++ = s->raw.ptr[2 * s->raw.ctx.pixels_per_line];
+ ++s->raw.ptr;
+ }
+ }
+ }
+ else
+ {
+ if (max_length > s->raw.end - s->raw.ptr)
+ max_length = s->raw.end - s->raw.ptr;
+
+ *length = max_length;
+
+ if (s->raw.cancel_requested)
+ s->raw.ptr += max_length;
+ else
+ {
+ memcpy (data, s->raw.ptr, max_length);
+ s->raw.ptr += max_length;
+ }
+ }
+
+ if (s->raw.ptr == s->raw.end && s->raw.all_data_fetched)
+ {
+ scan_finish (s);
+ if (0 == strcmp_c (s->hw->fw_name, "DS-30"))
+ {
+ status = check_ext_status (s->hw);
+ /* Ignore ADF paper empty */
+ s->hw->adf->status &= ~ADF_STATUS_PE;
+ if ( SANE_STATUS_GOOD != status
+ && SANE_STATUS_NO_DOCS != status)
+ {
+ return status;
+ }
+ }
+ }
+ log_call ("exit");
+
+ return SANE_STATUS_GOOD;
+}
+
+
+/*! Puts raw scan data into the correct scan line.
+
+ When scanning with a non-zero line distance, the RGB data is \e
+ not on a single line in the raw scan data. The RGB channels are
+ separated by a number of lines that depends on the line distance
+ as reported by the ESC i command and the resolution of the scan.
+
+ This function reorganises raw scan data so that the RGB channels
+ are no longer separated and all data is on the same scan line.
+
+ \note
+ It seems that the Perfection 610 and 640U both report a maximum
+ scan area with the two line distances included (based on 11.7"
+ at 600dpi = 7020, whereas the scan area is reportedly 7036 with
+ both line distances equal to 8).
+ */
+static SANE_Status
+color_shuffle (Epson_Scanner *s, int *new_length)
+{
+ SANE_Byte *buf = s->raw.buf;
+ int length = s->raw.end - s->raw.buf;
+
+ log_call ();
+
+ if (s->hw->color_shuffle == SANE_TRUE)
+ {
+ SANE_Byte *data_ptr; /* ptr to data to process */
+ SANE_Byte *data_end; /* ptr to end of processed data */
+ SANE_Byte *out_data_ptr; /* ptr to memory when writing data */
+ int i; /* loop counter */
+
+ log_call ();
+
+ /* Initialize the variables we are going to use for the
+ * copying of the data. data_ptr is the pointer to
+ * the currently worked on scan line. data_end is the
+ * end of the data area as calculated from adding *length
+ * to the start of data.
+ * out_data_ptr is used when writing out the processed data
+ * and always points to the beginning of the next line to
+ * write.
+ */
+
+ data_ptr = out_data_ptr = buf;
+ data_end = data_ptr + length;
+
+ /* The image data is in *raw.buf, we know that the buffer contains
+ * s->raw.end - s->raw.buf ( = length) bytes of data. The width of
+ * one line is in s->raw.ctx.bytes_per_line
+ */
+
+ /* The buffer area is supposed to have a number of full scan
+ * lines, let's test if this is the case.
+ */
+
+ if (length % s->raw.ctx.bytes_per_line != 0)
+ {
+ err_major ("ERROR in size of buffer: %d / %d",
+ length, s->raw.ctx.bytes_per_line);
+ return SANE_STATUS_INVAL;
+ }
+
+ while (data_ptr < data_end)
+ {
+ SANE_Byte *source_ptr, *dest_ptr;
+ int loop;
+
+ /* copy the green information into the current line */
+
+ source_ptr = data_ptr + 1;
+ dest_ptr = s->line_buffer[s->color_shuffle_line] + 1;
+
+ for (i = 0; i < s->raw.ctx.bytes_per_line / 3; i++)
+ {
+ *dest_ptr = *source_ptr;
+ dest_ptr += 3;
+ source_ptr += 3;
+ }
+
+ /* copy the red information n lines back */
+
+ if (s->color_shuffle_line >= s->line_distance)
+ {
+ source_ptr = data_ptr + 2;
+ dest_ptr =
+ s->line_buffer[s->color_shuffle_line - s->line_distance] + 2;
+
+ for (loop = 0; loop < s->raw.ctx.bytes_per_line / 3; loop++)
+ {
+ *dest_ptr = *source_ptr;
+ dest_ptr += 3;
+ source_ptr += 3;
+ }
+ }
+
+ /* copy the blue information n lines forward */
+
+ source_ptr = data_ptr;
+ dest_ptr = s->line_buffer[s->color_shuffle_line + s->line_distance];
+
+ for (loop = 0; loop < s->raw.ctx.bytes_per_line / 3; loop++)
+ {
+ *dest_ptr = *source_ptr;
+ dest_ptr += 3;
+ source_ptr += 3;
+ }
+
+ data_ptr += s->raw.ctx.bytes_per_line;
+ s->raw.ptr += s->raw.ctx.bytes_per_line;
+
+ if (s->color_shuffle_line == s->line_distance)
+ {
+ /* We just finished shuffling the line in line_buffer[0] -
+ * write the RGB image data in it to the output buffer and
+ * cyclically shift the line_buffers up by one.
+ */
+
+ SANE_Byte *first;
+
+ if ((s->current_output_line >= s->line_distance) &&
+ (s->current_output_line < s->raw.ctx.lines + s->line_distance))
+ {
+ memcpy (out_data_ptr, s->line_buffer[0], s->raw.ctx.bytes_per_line);
+ out_data_ptr += s->raw.ctx.bytes_per_line;
+ }
+
+ s->current_output_line++;
+
+ first = s->line_buffer[0];
+ for (i = 0; i < 2 * s->line_distance; ++i)
+ {
+ s->line_buffer[i] = s->line_buffer[i + 1];
+ }
+ s->line_buffer[2 * s->line_distance] = first;
+ }
+ else
+ {
+ s->color_shuffle_line++; /* increase the buffer number */
+ }
+ }
+
+ /* At this time we've used up all the new data from the scanner,
+ * some of it is still in the line_buffers, but we are ready to
+ * return some of it to the front end software. To do so we have
+ * to adjust the size of the data area and the *new_length
+ * variable.
+ */
+
+ *new_length = out_data_ptr - buf;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+
+static SANE_Status
+get_identity_information (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+
+ if (!hw->cmd->request_identity)
+ return SANE_STATUS_INVAL;
+
+ status = cmd_request_identity (hw);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return SANE_STATUS_INVAL;
+ }
+ log_data ("detected command level %s", hw->cmd_lvl);
+
+ {
+ char *force = getenv ("SANE_EPSON_CMD_LVL");
+
+ if (force)
+ {
+ hw->cmd_lvl[0] = force[0];
+ hw->cmd_lvl[1] = force[1];
+ log_info ("forced command level %s", hw->cmd_lvl);
+ }
+ }
+
+ /* check if option equipment is installed */
+
+ if (hw->status & STATUS_OPTION)
+ {
+ log_info ("option equipment is installed");
+ }
+
+ delete (hw->tpu); /* FIXME: is this necessary?? */
+ delete (hw->adf);
+
+ /* set command type and level */
+ {
+ int n = 0;
+
+ while (num_of (epson_cmd) > n
+ && (0 != strcmp_c (hw->cmd_lvl, epson_cmd[n].level)))
+ ++n;
+
+ if (num_of (epson_cmd) > n)
+ hw->cmd = &epson_cmd[n];
+ else
+ err_minor ("unknown command level %s, using %s instead",
+ hw->cmd_lvl, hw->cmd->level);
+
+ hw->level = hw->cmd->level[1] - '0';
+ } /* set comand type and level */
+
+ if (hw->using_fs && 'D' != hw->cmd->level[0]) return SANE_STATUS_GOOD;
+
+ /* Setting available resolutions and xy ranges for sane frontend. */
+ {
+ hw->dpi_range.min = hw->res.list[1];
+ hw->dpi_range.max = hw->res.list[hw->res.size];
+ if (!hw->using_fs) hw->base_res = hw->res.list[hw->res.size];
+ hw->dpi_range.quant = 0;
+
+ if (hw->fbf)
+ {
+ update_ranges (hw, hw->fbf);
+
+ log_info ("FBF: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]",
+ SANE_UNFIX (hw->fbf->x_range.min),
+ SANE_UNFIX (hw->fbf->y_range.min),
+ SANE_UNFIX (hw->fbf->x_range.max),
+ SANE_UNFIX (hw->fbf->y_range.max));
+ }
+ }
+
+ copy_resolution_info (&hw->res_x, &hw->res, SANE_FALSE);
+ copy_resolution_info (&hw->res_y, &hw->res, SANE_FALSE);
+
+ return SANE_STATUS_GOOD;
+} /* request identity */
+
+
+static SANE_Status
+get_hardware_property (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+
+ if (hw->cmd->request_identity2 == 0)
+ return SANE_STATUS_UNSUPPORTED;
+
+ status = cmd_request_hardware_property (hw);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ log_info ("optical resolution: %ddpi", hw->optical_res);
+
+ if (hw->line_dist_x != hw->line_dist_y)
+ {
+ return SANE_STATUS_INVAL;
+ }
+ hw->max_line_distance = hw->line_dist_y;
+ if (hw->fbf)
+ {
+ hw->fbf->y_range.max = SANE_FIX ((hw->fbf->max_y -
+ 2 * (hw->max_line_distance))
+ * MM_PER_INCH / hw->base_res);
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+get_identity2_information (device *hw, Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ require (s && (hw == s->hw));
+
+ status = get_hardware_property (hw);
+ if (SANE_STATUS_GOOD != status)
+ return status;
+
+ /* reset constraints because we are pointing to different lists now
+ FIXME: Only needs to be done the first time we (successfully)
+ send the ESC i command. This should be done when constructing
+ the device and is therefore done by the time we construct a
+ scanner. While the content and size of the lists may vary
+ depending on the selected option, the list nature is constant.
+ */
+ s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_X_RESOLUTION].constraint.word_list = hw->res_x.list;
+ s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_Y_RESOLUTION].constraint.word_list = hw->res_y.list;
+
+ return SANE_STATUS_GOOD;
+}
+
+
+void
+sane_cancel (SANE_Handle handle)
+{
+ Epson_Scanner *s = (Epson_Scanner *) handle;
+
+ log_call ();
+
+ s->img.cancel_requested = true;
+
+ if (s->src->transfer_started && !s->src->transfer_stopped)
+ {
+ SANE_Byte dumpster[1024];
+ int len;
+
+ if (s->src == &s->raw)
+ {
+ s->raw.cancel_requested = true;
+ do
+ {
+ fetch_image_data (s, dumpster, num_of (dumpster), &len);
+ }
+ while (!s->raw.transfer_stopped);
+ }
+
+ if (using (s->hw, adf) && 0 != s->hw->adf->sheet_count
+ && ((EXT_STATUS_ADFT & s->hw->ext_status) /* page type */
+ || adf_has_auto_form_feed (s->hw)))
+ dev_eject_paper (s->hw);
+ }
+ if (!s->raw.cancel_requested && s->raw.all_data_fetched
+ && s->hw->using_fs && s->val[OPT_ADF_AUTO_SCAN].w
+ && SANE_STATUS_NO_DOCS != check_ext_status (s->hw))
+ {
+ s->raw.cancel_requested = dev_force_cancel (s->hw);
+ }
+ dev_unlock (s->hw);
+
+ if (ENABLE_TIMING)
+ {
+ time_stamp (time_scan, stop);
+ time_stats (time_pass_count);
+ time_pass_count = 0;
+ }
+
+ s->frame_count = 0;
+
+ /* Restore original SANE options.
+ * \todo string and word array have to copy individually.
+ */
+ if (s->src->transfer_started)
+ {
+ memcpy (s->val, s->val_bak, (sizeof (s->val[0]) * NUM_OPTIONS));
+ }
+
+ /* release resource hogs between scan sequences */
+ delete (s->img.buf);
+ s->img.cap = 0;
+}
+
+/* Request the push button status returns SANE_TRUE if the button was
+ * pressed and SANE_FALSE if the button was not pressed. It also
+ * returns SANE_TRUE in case of an error. This is necessary so that a
+ * process that waits for the button does not block indefinitely.
+ */
+static SANE_Status
+get_push_button_status (device *hw, SANE_Bool *button_pushed)
+{
+ SANE_Status status;
+ int len;
+ u_char param[3];
+ u_char result[4];
+ u_char *buf;
+
+ log_call ();
+
+ if (hw->cmd->request_push_button_status == 0)
+ {
+ log_info ("push button status unsupported");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+
+ param[0] = ESC;
+ param[1] = hw->cmd->request_push_button_status;
+ param[2] = '\0';
+
+ channel_send (hw->channel, param, 2, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_minor ("error sending command");
+ return status;
+ }
+
+ len = 4; /* get info block */
+ channel_recv (hw->channel, result, len, &status);
+ if (SANE_STATUS_GOOD != status)
+ return status;
+
+ /* get button status */
+ hw->status = result[1];
+ len = result[3] << 8 | result[2];
+ buf = t_alloca (len, u_char);
+ channel_recv (hw->channel, buf, len, &status);
+
+ log_info ("Push button status = %d", buf[0] & 0x01);
+ *button_pushed = ((buf[0] & 0x01) != 0);
+
+ return (SANE_STATUS_GOOD);
+}
+
+
+static void
+filter_resolution_list (Epson_Scanner * s)
+{
+ log_call ();
+
+ if (SANE_TRUE == s->val[OPT_LIMIT_RESOLUTION].w)
+ { /* trim list */
+ SANE_Int i;
+
+ s->hw->res.size = 0;
+ for (i = 1; i <= s->hw->resolution.size; i++)
+ {
+ SANE_Word res = s->hw->resolution.list[i];
+
+ if ((res < 100) || (0 == (res % 300)) || (0 == (res % 400)))
+ {
+ s->hw->res.size++;
+ s->hw->res.list[s->hw->res.size] = res;
+ }
+ }
+ }
+ else
+ { /* restore full list */
+ SANE_Int i;
+
+ for (i = 1; i <= s->hw->resolution.size; i++)
+ {
+ s->hw->res.list[i] = s->hw->resolution.list[i];
+ }
+ s->hw->res.size = s->hw->resolution.size;
+ }
+ s->hw->res.list[0] = s->hw->res.size;
+
+ /* We have just created a filtered resolution list on the *full*
+ * list of resolutions. Now apply ADF capping if necessary. No
+ * need to call dev_restore_res as we've already (sorta) done so
+ * above.
+ */
+ if (using (s->hw, adf)
+ && 0 < large_res_kills_adf_scan (s->hw))
+ {
+ dev_limit_res (s->hw, s->opt[OPT_RESOLUTION].constraint_type,
+ large_res_kills_adf_scan (s->hw));
+ }
+ handle_resolution (s, OPT_RESOLUTION, s->val[OPT_RESOLUTION].w);
+}
+
+
+SANE_Status
+sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
+{
+ log_call ("(%s-blocking)", (non_blocking ? "non" : ""));
+
+ /* get rid of compiler warning */
+ handle = handle;
+
+ return SANE_STATUS_UNSUPPORTED;
+}
+
+
+SANE_Status
+sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
+{
+ log_call ();
+
+ /* get rid of compiler warnings */
+ handle = handle;
+ fd = fd;
+
+ return SANE_STATUS_UNSUPPORTED;
+}
+
+
+SANE_Status
+control_option_unit (device *hw, SANE_Bool use_duplex)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ u_char value = 0;
+
+ log_call ();
+
+ if (!hw) return SANE_STATUS_INVAL;
+ if (!(hw->adf || hw->tpu)) return SANE_STATUS_GOOD;
+
+ value = using (hw, adf) || using (hw, tpu);
+ if (value && use_duplex)
+ {
+ value = 2;
+ hw->adf->using_duplex = SANE_TRUE;
+ }
+
+ status = set_cmd (hw, hw->cmd->control_an_extension, value);
+
+ if (using (hw, adf)
+ && (0 == strcmp_c ("ES-10000G", hw->fw_name)
+ || 0 == strcmp_c ("Expression10000", hw->fw_name)))
+ {
+ u_char *buf;
+ u_char params[2];
+ EpsonHdr head;
+ int failures_allowed = 5;
+
+ params[0] = ESC;
+ params[1] = hw->cmd->request_extended_status;
+
+ head = (EpsonHdr) command (hw, params, 2, &status);
+ buf = &head->buf[0];
+
+ while (!(buf[1] & ADF_STATUS_EN))
+ {
+ sleep (1);
+ status = set_cmd (hw, hw->cmd->control_an_extension, value);
+
+ head = (EpsonHdr) command (hw, params, 2, &status);
+ if (SANE_STATUS_GOOD != status)
+ {
+ --failures_allowed;
+ if (!failures_allowed)
+ return status;
+ }
+ buf = &head->buf[0];
+ }
+ }
+ return status;
+}
+
+/* Fetches the main/sub resolution lists for the device.
+ */
+static SANE_Status
+get_resolution_constraints (device *hw, Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ SANE_Bool adf_duplex;
+
+ log_call ();
+
+ if (!hw->cmd->request_identity2)
+ return SANE_STATUS_GOOD;
+
+ /* if it is previewing now, disable duplex */
+ adf_duplex = ((1 == s->val[OPT_ADF_MODE].w)
+ && !s->val[OPT_PREVIEW].b);
+ status = control_option_unit (hw, adf_duplex);
+ status = get_identity2_information (hw, s);
+
+ handle_resolution (s, OPT_X_RESOLUTION, DEFAULT_X_RESOLUTION);
+ handle_resolution (s, OPT_Y_RESOLUTION, DEFAULT_Y_RESOLUTION);
+
+ return status;
+}
+
+
+static scan_area_t get_model_info_max_scan_area (device *hw, int adf_mode)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ scan_area_t scan_area;
+
+ scan_area.width = SANE_FIX(-1);
+ scan_area.height = SANE_FIX(-1);
+
+ if(hw->adf){
+ const void *info = model_info_cache_get_info (hw->fw_name, &status);
+ if (SANE_STATUS_GOOD == status && info){
+ const char *mode = (1 == adf_mode ? "duplex": "simplex");
+
+ scan_area = model_info_max_scan_area(info, "adf", mode);
+ }
+ }
+ return scan_area;
+}
+
+
+static SANE_Status handle_scan_area(Epson_Scanner *s, int adf_mode)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ scan_area_t scan_area;
+
+ if(s->hw->adf){
+ scan_area = get_model_info_max_scan_area (s->hw, adf_mode);
+
+ if(SANE_UNFIX (scan_area.width) >= 0
+ && SANE_UNFIX (scan_area.height) >= 0){
+ s->hw->adf->x_range.max = scan_area.width;
+ s->hw->adf->y_range.max = scan_area.height;
+ }else{
+ err_minor ("failure getting model info (%s)", sane_strstatus (status));
+ }
+ }
+
+ {
+ SANE_Option_Descriptor *sopt = &(s->opt[OPT_SCAN_AREA]);
+ Option_Value *sval = &(s->val[OPT_SCAN_AREA]);
+
+ SANE_String_Const area = NULL;
+ SANE_String_Const *p
+ = (SANE_String_Const *) sopt->constraint.string_list;
+
+ if (!p)
+ {
+ s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_INACTIVE;
+
+ /* media_list + media_maximum, media_automatic, and NULL terminator */
+ p = t_calloc (num_of (media_list) + 3, SANE_String_Const);
+
+ if (!p) /* do without the option */
+ {
+ s->opt[OPT_QUICK_FORMAT].cap = s->opt[OPT_SCAN_AREA].cap;
+ return status;
+ }
+
+ sopt->constraint.string_list = p;
+ }
+ area = p[sval->w];
+
+ memset (p, 0, (num_of (media_list) + 3) * sizeof (SANE_String_Const));
+ sopt->size = 0;
+
+ *p = media_maximum;
+ sopt->size = max (strlen (*p), sopt->size);
+ ++p;
+
+ if (using (s->hw, tpu))
+ {
+ s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ size_t i;
+
+ if (has_size_check_support (s->hw->src))
+ {
+ *p = media_automatic;
+ sopt->size = max (strlen (*p) + 1, sopt->size);
+ ++p;
+ s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_AUTOMATIC;
+ }
+
+ for (i = 0; i < num_of (media_list); ++i)
+ {
+ SANE_Fixed w = SANE_FIX (media_list[i].width);
+ SANE_Fixed h = SANE_FIX (media_list[i].height);
+
+ if ( w <= s->hw->src->x_range.max
+ && h <= s->hw->src->y_range.max)
+ {
+ *p = media_list[i].name;
+ sopt->size = max (strlen (*p) + 1, sopt->size);
+ ++p;
+ if (strstr (media_list[i].name, "Portrait"))
+ {
+ ++i;
+ }
+ }
+ else if (strstr (media_list[i].name, "Landscape"))
+ { /* no need to list "Portrait" */
+ ++i;
+ }
+ }
+
+ s->opt[OPT_SCAN_AREA].cap &= ~SANE_CAP_INACTIVE;
+ }
+
+ { // recompute index of selected area in the new list
+ size_t i = 0;
+ p = (SANE_String_Const *) sopt->constraint.string_list;
+ while (*p && 0 != strcmp_c (*p, area))
+ ++i, ++p;
+ sval->w = (*p ? i : 0);
+ }
+ }
+
+ s->val[OPT_BR_X].w = s->hw->src->x_range.max;
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+ s->val[OPT_TL_X].w = 0;
+ s->val[OPT_TL_Y].w = 0;
+
+ s->opt[OPT_TL_X].constraint.range = &(s->hw->src->x_range);
+ s->opt[OPT_TL_Y].constraint.range = &(s->hw->src->y_range);
+ s->opt[OPT_BR_X].constraint.range = &(s->hw->src->x_range);
+ s->opt[OPT_BR_Y].constraint.range = &(s->hw->src->y_range);
+
+ /* copy OPT_SCAN_AREA to OPT_QUICK_FORMAT */
+ s->opt[OPT_QUICK_FORMAT].size = s->opt[OPT_SCAN_AREA].size;
+ s->opt[OPT_QUICK_FORMAT].cap = s->opt[OPT_SCAN_AREA].cap;
+ s->opt[OPT_QUICK_FORMAT].constraint.string_list
+ = s->opt[OPT_SCAN_AREA].constraint.string_list;
+ s->val[OPT_QUICK_FORMAT].w = s->val[OPT_SCAN_AREA].w;
+
+ return status;
+}