/* --------------------------------------------------------------------------------------------------------- */

/* sane - Scanner Access Now Easy.

   umax.c 

   (C) 1997-2002 Oliver Rauch

   This file is part of the SANE package.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.

   As a special exception, the authors of SANE give permission for
   additional uses of the libraries contained in this release of SANE.

   The exception is that, if you link a SANE library with other files
   to produce an executable, this does not by itself cause the
   resulting executable to be covered by the GNU General Public
   License.  Your use of that executable is in no way restricted on
   account of linking the SANE library code into it.

   This exception does not, however, invalidate any other reasons why
   the executable file might be covered by the GNU General Public
   License.

   If you submit changes to SANE to the maintainers to be included in
   a subsequent release, you agree by submitting the changes that
   those changes may be distributed with this exception intact.

   If you write modifications of your own for SANE, it is your choice
   whether to permit this exception to apply to your modifications.
   If you do not wish that, delete this exception notice.

   This file implements a SANE backend for UMAX flatbed scanners.  */


/* --------------------------------------------------------------------------------------------------------- */

#define BUILD 44

/* --------------------------------------------------------------------------------------------------------- */


/* SANE-FLOW-DIAGRAMM

	- sane_init() : initialize backend, attach scanners(devicename,0)
	. - sane_get_devices() : query list of scanner-devices
	. - sane_open() : open a particular scanner-device and attach_scanner(devicename,&dev)
	. . - sane_set_io_mode : set blocking-mode
	. . - sane_get_select_fd : get scanner-fd
	. . - sane_get_option_descriptor() : get option information
	. . - sane_control_option() : change option values
	. .
	. . - sane_start() : start image aquisition
	. .   - sane_get_parameters() : returns actual scan-parameters
	. .   - sane_read() : read image-data (from pipe)
in ADF mode this is done often:
	. . - sane_start() : start image aquisition
	. .   - sane_get_parameters() : returns actual scan-parameters
	. .   - sane_read() : read image-data (from pipe)

	. . - sane_cancel() : cancel operation, kill reader_process

	. - sane_close() : close opened scanner-device, do_cancel, free buffer and handle
	- sane_exit() : terminate use of backend, free devicename and device-struture
*/


/* ------------------------------------------------------------ DBG OUTPUT LEVELS -------------------------- */


#define DBG_error0  0
#define DBG_error   1
#define DBG_sense   2
#define DBG_warning 3
#define DBG_inquiry 4
#define DBG_info    5
#define DBG_info2   6
#define DBG_proc    7
#define DBG_read    8
#define DBG_sane_init   10
#define DBG_sane_proc   11
#define DBG_sane_info   12
#define DBG_sane_option 13


/* ------------------------------------------------------------ SANE DEFINES ------------------------------- */

#define BACKEND_NAME     umax
#define UMAX_CONFIG_FILE "umax.conf"
#define MM_PER_INCH	 25.4

/* ------------------------------------------------------------ SANE INTERNATIONALISATION ------------------ */

#ifndef SANE_I18N
#define SANE_I18N(text) text
#endif 

/* ------------------------------------------------------------ INCLUDES ----------------------------------- */

#include "sane/config.h"

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "sane/sane.h"
#include "sane/sanei.h"
#include "sane/saneopts.h"
#include "sane/sanei_scsi.h"
#include "sane/sanei_debug.h"
#include "sane/sanei_backend.h"
#include "sane/sanei_config.h"
#include "sane/sanei_thread.h"

#ifdef UMAX_ENABLE_USB
# include "sane/sanei_usb.h"
#endif

#include <math.h>
#include <string.h>

#include "umax-scsidef.h"
#include "umax-scanner.c"

#ifdef UMAX_ENABLE_USB
# include "umax-usb.c"
#endif

#include "umax.h"

/* ------------------------------------------------------------ SANE DEFINES ------------------------------- */

#ifndef PATH_MAX
#define PATH_MAX         1024
#endif

/* ------------------------------------------------------------ OPTION DEFINITIONS ------------------------- */

#define SANE_NAME_BATCH_SCAN_START	"batch-scan-start"
#define SANE_TITLE_BATCH_SCAN_START	"Batch scan start"
#define SANE_DESC_BATCH_SCAN_START	"set for first scan of batch"

#define SANE_NAME_BATCH_SCAN_LOOP	"batch-scan-loop"
#define SANE_TITLE_BATCH_SCAN_LOOP	"Batch scan loop"
#define SANE_DESC_BATCH_SCAN_LOOP	"set for middle scans of batch"

#define SANE_NAME_BATCH_SCAN_END	"batch-scan-end"
#define SANE_TITLE_BATCH_SCAN_END	"Batch scan end"
#define SANE_DESC_BATCH_SCAN_END	"set for last scan of batch"

#define SANE_NAME_BATCH_NEXT_TL_Y	"batch-scan-next-tl-y"
#define SANE_TITLE_BATCH_NEXT_TL_Y	"Batch scan next top left Y"
#define SANE_DESC_BATCH_NEXT_TL_Y	"Set top left Y position for next scan"

#define SANE_UMAX_NAME_SELECT_CALIBRATON_EXPOSURE_TIME		"select-calibration-exposure-time"
#define SANE_UMAX_TITLE_SELECT_CALIBRATION_EXPOSURE_TIME	"Set calibration exposure time"
#define SANE_UMAX_DESC_SELECT_CALIBRATION_EXPOSURE_TIME		"Allow different settings for calibration and scan exposure times"

/* ------------------------------------------------------------ STRING DEFINITIONS ------------------------- */

#define FLB_STR			SANE_I18N("Flatbed")
#define UTA_STR			SANE_I18N("Transparency Adapter")
#define ADF_STR 		SANE_I18N("Automatic Document Feeder")

#define LINEART_STR		SANE_I18N("Lineart")
#define HALFTONE_STR		SANE_I18N("Halftone")
#define GRAY_STR		SANE_I18N("Gray")
#define COLOR_LINEART_STR	SANE_I18N("Color Lineart")
#define COLOR_HALFTONE_STR	SANE_I18N("Color Halftone")
#define COLOR_STR		SANE_I18N("Color")

/* ------------------------------------------------------------ DEFINITIONS -------------------------------- */

#define P_200_TO_255(per) ( (SANE_UNFIX(per) + 100) * 255/200 )
#define P_100_TO_255(per) SANE_UNFIX(per * 255/100 )
#define P_100_TO_254(per) 1+SANE_UNFIX(per * 254/100 )

/* ------------------------------------------------------------ GLOBAL VARIABLES --------------------------- */


static SANE_String scan_mode_list[7];
static SANE_String_Const source_list[4];
static SANE_Int bit_depth_list[9];
static SANE_Auth_Callback frontend_authorize_callback;

static int umax_scsi_buffer_size_min = 32768;  /* default: minimum scsi buffer size: 32 KB */
static int umax_scsi_buffer_size_max = 131072; /* default: maximum scsi buffer size: 128 KB */

/* number of lines that shall be scanned in one buffer for preview if possible */
/* this value should not be too large because it defines the step size in which */
/* the scanned parts are displayed while preview scan is in progress */
static int umax_preview_lines                  = 10; /* default: 10 preview lines */
/* number of lines that shall be scanned in one buffer for scan if possible */
static int umax_scan_lines                     = 40; /* default: 40 scan lines */
static int umax_scsi_maxqueue                  = 2; /* use command queueing depth 2 as default */
static int umax_handle_bad_sense_error         = 0; /* default: handle bad sense error code as device busy */
static int umax_execute_request_sense          = 0; /* default: do not use request sense in do_calibration */
static int umax_force_preview_bit_rgb          = 0; /* default: do not force preview bit in real color scan */
static int umax_slow                           = -1; /* don`t use slow scanning speed */
static int umax_smear                          = -1; /* don`t care about image smearing problem */
static int umax_calibration_area               = -1;   /* -1=auto, 0=calibration on image, 1=calibration for full ccd */
static int umax_calibration_width_offset       = -99999; /* -99999=auto */
static int umax_calibration_width_offset_batch = -99999; /* -99999=auto */
static int umax_calibration_bytespp            = -1;   /* -1=auto */
static int umax_exposure_time_rgb_bind         = -1;   /* -1=auto */
static int umax_invert_shading_data            = -1;   /* -1=auto */
static int umax_lamp_control_available         = 0;    /* 0=disabled */
static int umax_gamma_lsb_padded               = -1;   /* -1=auto */
static int umax_connection_type                = 1;    /* 1=scsi, 2=usb */

/* ------------------------------------------------------------ CALIBRATION MODE --------------------------- */

#ifdef UMAX_CALIBRATION_MODE_SELECTABLE

#define CALIB_MODE_0000		SANE_I18N("Use Image Composition")
#define CALIB_MODE_1111		SANE_I18N("Bi-level black and white (lineart mode)")
#define CALIB_MODE_1110		SANE_I18N("Dithered/halftone black & white (halftone mode)")
#define CALIB_MODE_1101		SANE_I18N("Multi-level black & white (grayscale mode)")
#define CALIB_MODE_1010		SANE_I18N("Multi-level RGB color (one pass color)")
#define CALIB_MODE_1001		SANE_I18N("Ignore calibration")

static SANE_String_Const calibration_list[] =
{
    CALIB_MODE_0000,
    CALIB_MODE_1111,
    CALIB_MODE_1110,
    CALIB_MODE_1101,
    CALIB_MODE_1010,
    CALIB_MODE_1001,
    0
};

#endif

/* --------------------------------------------------------------------------------------------------------- */

enum
{
    UMAX_CALIBRATION_AREA_IMAGE = 0,
    UMAX_CALIBRATION_AREA_CCD
};

static const SANE_Int pattern_dim_list[] =
{
  5, /* # of elements */
  2, 4, 6, 8, 12
};

static const SANE_Range u8_range =
{
    0, /* minimum */
  255, /* maximum */
    0  /* quantization */
};

static const SANE_Range percentage_range =
{
  -100 << SANE_FIXED_SCALE_SHIFT, /* minimum */
   100 << SANE_FIXED_SCALE_SHIFT, /* maximum */
     1 << SANE_FIXED_SCALE_SHIFT  /* quantization */
};

static const SANE_Range percentage_range_100 =
{
     0 << SANE_FIXED_SCALE_SHIFT, /* minimum */
   100 << SANE_FIXED_SCALE_SHIFT, /* maximum */
     0 << SANE_FIXED_SCALE_SHIFT  /* quantization */
};

static int num_devices = 0;
static const SANE_Device **devlist = NULL;
static Umax_Device *first_dev      = NULL;
static Umax_Scanner *first_handle  = NULL;


/* ------------------------------------------------------------ MATH-HELPERS ------------------------------- */


#define min(a, b) (((a)<(b))?(a):(b))
#define max(a, b) (((a)>(b))?(a):(b))
#define inrange(minimum, val, maximum) (min(maximum, max(val, minimum)))


/* ------------------------------------------------------------ umax_test_little_endian -------------------- */

static SANE_Bool umax_test_little_endian(void)
{
  SANE_Int testvalue = 255;
  unsigned char *firstbyte = (unsigned char *) &testvalue;

  if (*firstbyte == 255)
  {
    return SANE_TRUE;
  }

  return SANE_FALSE;
}

/* ------------------------------------------------------------ DBG_inq_nz --------------------------------- */


static void DBG_inq_nz(char *text, int flag)
{
  if (flag != 0) { DBG(DBG_inquiry,"%s", text); }
}


/*------------------------------------------------------------- DBG_sense_nz ------------------------------- */


static void DBG_sense_nz(char *text, int flag)
{
  if (flag != 0) { DBG(DBG_sense,"%s", text); }
}


/* ------------------------------------------------------------ UMAX PRINT INQUIRY ------------------------- */


static void umax_print_inquiry(Umax_Device *dev)
{
 unsigned char *inquiry_block;
 int i;

  inquiry_block=dev->buffer[0];

  DBG(DBG_inquiry,"INQUIRY:\n");
  DBG(DBG_inquiry,"========\n");
  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"vendor........................: '%s'\n", dev->vendor);
  DBG(DBG_inquiry,"product.......................: '%s'\n", dev->product);
  DBG(DBG_inquiry,"version.......................: '%s'\n", dev->version);

  DBG(DBG_inquiry,"peripheral qualifier..........: %d\n", get_inquiry_periph_qual(inquiry_block));
  DBG(DBG_inquiry,"peripheral device type........: %d\n", get_inquiry_periph_devtype(inquiry_block));

  DBG_inq_nz("RMB bit set (reserved)\n", get_inquiry_rmb(inquiry_block));
  DBG_inq_nz("0x01 bit 6\n", get_inquiry_0x01_bit6(inquiry_block));
  DBG_inq_nz("0x01 bit 5\n", get_inquiry_0x01_bit5(inquiry_block));

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"CBHS value range..............: %s\n", cbhs_str[dev->inquiry_cbhs]);
  DBG(DBG_inquiry,"scanmode......................: %s\n", scanmode_str[get_inquiry_scanmode(inquiry_block)]);
  if (dev->inquiry_transavail)
  {
    DBG(DBG_inquiry,"UTA (transparency)............: available\n");

    if (get_inquiry_translamp(inquiry_block) == 0)
    { DBG(DBG_inquiry,"UTA lamp status ..............: off\n"); }
    else
    { DBG(DBG_inquiry,"UTA lamp status ..............: on\n"); }

    DBG(DBG_inquiry,"\n");
  }

  DBG(DBG_inquiry,"inquiry block length..........: %d bytes\n", dev->inquiry_len);
  if (dev->inquiry_len<=0x8e)
  {
    DBG(DBG_inquiry, "Inquiry block is unexpected short, should be at least 147 bytes\n");
  } 

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"ISO  Version (reserved).......: %d\n", get_inquiry_iso_version(inquiry_block));
  DBG(DBG_inquiry,"ECMA Version (reserved).......: %d\n", get_inquiry_ecma_version(inquiry_block));
  DBG(DBG_inquiry,"ANSI Version .................: %d\n", get_inquiry_ansi_version(inquiry_block));
  DBG(DBG_inquiry,"\n");

  DBG_inq_nz("AENC bit set (reserved)\n",  get_inquiry_aenc(inquiry_block));
  DBG_inq_nz("TmIOP bit set (reserved)\n", get_inquiry_tmiop(inquiry_block));
  DBG_inq_nz("0x03 bit 5\n",               get_inquiry_0x03_bit5(inquiry_block));
  DBG_inq_nz("0x03 bit 4\n",               get_inquiry_0x03_bit4(inquiry_block));

  DBG(DBG_inquiry,"reserved byte 0x05 = %d\n", get_inquiry_0x05(inquiry_block));
  DBG(DBG_inquiry,"reserved byte 0x06 = %d\n", get_inquiry_0x06(inquiry_block));

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"scsi features (%02x):\n", get_inquiry_scsi_byte(inquiry_block));
  DBG(DBG_inquiry,"-------------------\n");
  DBG_inq_nz(" - relative address\n", get_inquiry_scsi_reladr(inquiry_block));
  DBG_inq_nz(" - wide bus 32 bit\n",  get_inquiry_scsi_wbus32(inquiry_block));
  DBG_inq_nz(" - wide bus 16 bit\n",  get_inquiry_scsi_wbus16(inquiry_block));
  DBG_inq_nz(" - syncronous neg.\n",  get_inquiry_scsi_sync(inquiry_block));
  DBG_inq_nz(" - linked commands\n",  get_inquiry_scsi_linked(inquiry_block));
  DBG_inq_nz(" - (reserved)\n",       get_inquiry_scsi_R(inquiry_block));
  DBG_inq_nz(" - command queueing\n", get_inquiry_scsi_cmdqueue(inquiry_block));
  DBG_inq_nz(" - sftre\n",            get_inquiry_scsi_sftre(inquiry_block));

  /* 0x24 */
  if (dev->inquiry_len<=0x24)
  {
    return;
  }

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"f/w support function:\n");
  DBG(DBG_inquiry,"---------------------\n");
  DBG_inq_nz(" - quality calibration\n", dev->inquiry_quality_ctrl);
  DBG_inq_nz(" - fast preview function\n", dev->inquiry_preview);
  DBG_inq_nz(" - shadow compensation by f/w\n", get_inquiry_fw_shadow_comp(inquiry_block));
  DBG_inq_nz(" - reselection phase\n", get_inquiry_fw_reselection(inquiry_block));
  DBG_inq_nz(" - lamp intensity control\n", dev->inquiry_lamp_ctrl);
  DBG_inq_nz(" - batch scan function\n", get_inquiry_fw_batch_scan(inquiry_block));
  DBG_inq_nz(" - calibration mode control by driver\n", get_inquiry_fw_calibration(inquiry_block));

  /* 0x36, 0x37 */
  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"reserved byte 0x36 = %d\n", get_inquiry_0x36(inquiry_block));
  DBG(DBG_inquiry,"reserved byte 0x37 = %d\n", get_inquiry_0x37(inquiry_block));

  if (get_inquiry_fw_adjust_exposure_tf(inquiry_block))
  {
    int unit;

    DBG(DBG_inquiry,"\n");
    DBG(DBG_inquiry,"adjust exposure time function\n");
    unit=get_inquiry_exposure_time_step_unit(inquiry_block);
    DBG(DBG_inquiry,"exposure time step units......: %d micro-sec\n", unit);
    DBG(DBG_inquiry,"exposure time maximum.........: %d micro-sec\n",
            unit*get_inquiry_exposure_time_max(inquiry_block));
    DBG(DBG_inquiry,"exposure time minimum (LHG)...: %d micro-sec\n",
            unit*get_inquiry_exposure_time_lhg_min(inquiry_block));
    DBG(DBG_inquiry,"exposure time minimum color...: %d micro-sec\n",
            unit*get_inquiry_exposure_time_color_min(inquiry_block));
    DBG(DBG_inquiry,"exposure time default FB (LH).: %d micro-sec\n",
            unit*get_inquiry_exposure_time_lh_def_fb(inquiry_block));
    DBG(DBG_inquiry,"exposure time default UTA (LH): %d micro-sec\n",
            unit*get_inquiry_exposure_time_lh_def_uta(inquiry_block));
    DBG(DBG_inquiry,"exposure time default FB gray.: %d micro-sec\n",
            unit*get_inquiry_exposure_time_gray_def_fb(inquiry_block));
    DBG(DBG_inquiry,"exposure time default UTA gray: %d micro-sec\n",
            unit*get_inquiry_exposure_time_gray_def_uta(inquiry_block));
    DBG(DBG_inquiry,"exposure time default FB red..: %d micro-sec\n",
            unit*get_inquiry_exposure_time_def_r_fb(inquiry_block));
    DBG(DBG_inquiry,"exposure time default FB grn..: %d micro-sec\n",
            unit*get_inquiry_exposure_time_def_g_fb(inquiry_block));
    DBG(DBG_inquiry,"exposure time default FB blue.: %d micro-sec\n",
            unit*get_inquiry_exposure_time_def_b_fb(inquiry_block));
    DBG(DBG_inquiry,"exposure time default UTA red.: %d micro-sec\n",
            unit*get_inquiry_exposure_time_def_r_uta(inquiry_block));
    DBG(DBG_inquiry,"exposure time default UTA grn.: %d micro-sec\n",
            unit*get_inquiry_exposure_time_def_g_uta(inquiry_block));
    DBG(DBG_inquiry,"exposure time default UTA blue: %d micro-sec\n",
            unit*get_inquiry_exposure_time_def_b_uta(inquiry_block));
  }


  /* 0x60 */
  if (dev->inquiry_len<=0x60)
  {
    return;
  }

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"scan modes (%02x):\n", get_inquiry_sc_feature_byte0(inquiry_block));
  DBG(DBG_inquiry,"----------------\n");
  DBG_inq_nz(" - three passes color mode\n",		get_inquiry_sc_three_pass_color(inquiry_block));
  DBG_inq_nz(" - single pass color mode\n",		get_inquiry_sc_one_pass_color(inquiry_block));
  DBG_inq_nz(" - lineart mode\n",			dev->inquiry_lineart);
  DBG_inq_nz(" - halftone mode\n",			dev->inquiry_halftone);
  DBG_inq_nz(" - gray mode\n",				dev->inquiry_gray);
  DBG_inq_nz(" - color mode\n",				dev->inquiry_color);
  DBG_inq_nz(" - transparency (UTA)\n",			dev->inquiry_uta);
  DBG_inq_nz(" - automatic document feeder (ADF)\n",	dev->inquiry_adf);

  /* 0x61 */
  if (dev->inquiry_len<=0x61)
  {
    return;
  }

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"scanner capability (%02x, %02x, %02x):\n",
      get_inquiry_sc_feature_byte1(inquiry_block),
      get_inquiry_sc_feature_byte2(inquiry_block),
      get_inquiry_sc_feature_byte3(inquiry_block));
  DBG(DBG_inquiry,"--------------------------------\n");
  DBG_inq_nz(" - double resolution\n",			dev->inquiry_dor);
  DBG_inq_nz(" - send high byte first\n",		get_inquiry_sc_high_byte_first(inquiry_block));
  DBG_inq_nz(" - bi-level image reverse\n",		dev->inquiry_reverse);
  DBG_inq_nz(" - multi-level image reverse\n",		dev->inquiry_reverse_multi);
  DBG_inq_nz(" - support shadow function\n",		dev->inquiry_shadow);
  DBG_inq_nz(" - support highlight function\n",		dev->inquiry_highlight);
  DBG_inq_nz(" - f/w downloadable\n",			get_inquiry_sc_downloadable_fw(inquiry_block));
  DBG_inq_nz(" - paper length can reach to 14 inch\n",	get_inquiry_sc_paper_length_14(inquiry_block));

  /* 0x62 */
  if (dev->inquiry_len<=0x62)
  {
    return;
  }

  DBG_inq_nz(" - shading data/gain uploadable\n",	get_inquiry_sc_uploadable_shade(inquiry_block));
  DBG_inq_nz(" - analog gamma correction\n",		dev->inquiry_analog_gamma);
  DBG_inq_nz(" - x, y coordinate base\n",		get_inquiry_xy_coordinate_base(inquiry_block));
  DBG_inq_nz(" - lineart starts with LSB\n",		get_inquiry_lineart_order(inquiry_block));
  DBG_inq_nz(" - start density \n",			get_inquiry_start_density(inquiry_block));
  DBG_inq_nz(" - hardware x scaling\n",			get_inquiry_hw_x_scaling(inquiry_block));
  DBG_inq_nz(" - hardware y scaling\n",			get_inquiry_hw_y_scaling(inquiry_block));

  /* 0x63 */
  if (dev->inquiry_len<=0x63)
  {
    return;
  }

  DBG_inq_nz(" + ADF: no paper\n",			get_inquiry_ADF_no_paper(inquiry_block));
  DBG_inq_nz(" + ADF: cover open\n",			get_inquiry_ADF_cover_open(inquiry_block));
  DBG_inq_nz(" + ADF: paper jam\n",			get_inquiry_ADF_paper_jam(inquiry_block));
  DBG_inq_nz(" - unknwon flag; 0x63 bit 3\n",		get_inquiry_0x63_bit3(inquiry_block));
  DBG_inq_nz(" - unknown lfag: 0x63 bit 4\n",		get_inquiry_0x63_bit4(inquiry_block));
  DBG_inq_nz(" - lens calib in doc pos\n",		get_inquiry_lens_cal_in_doc_pos(inquiry_block));
  DBG_inq_nz(" - manual focus\n",			get_inquiry_manual_focus(inquiry_block));
  DBG_inq_nz(" - UTA lens calib pos selectable\n",	get_inquiry_sel_uta_lens_cal_pos(inquiry_block));

  /* 0x64 - 0x68*/
  if (dev->inquiry_len<=0x68)
  {
    return;
  }

  if (dev->inquiry_gamma_dwload)
  {
    DBG(DBG_inquiry,"\n");
    DBG(DBG_inquiry,"gamma download available\n");
    DBG_inq_nz("gamma download type 2\n", get_inquiry_gamma_type_2(inquiry_block));
    DBG(DBG_inquiry,"lines of gamma curve: %s\n", gamma_lines_str[get_inquiry_gamma_lines(inquiry_block)]);

    /* 0x66 */
    DBG_inq_nz("gamma input   8 bits/pixel support\n", get_inquiry_gib_8bpp(inquiry_block));
    DBG_inq_nz("gamma input   9 bits/pixel support\n", get_inquiry_gib_9bpp(inquiry_block));
    DBG_inq_nz("gamma input  10 bits/pixel support\n", get_inquiry_gib_10bpp(inquiry_block));
    DBG_inq_nz("gamma input  12 bits/pixel support\n", get_inquiry_gib_12bpp(inquiry_block));
    DBG_inq_nz("gamma input  14 bits/pixel support\n", get_inquiry_gib_14bpp(inquiry_block));
    DBG_inq_nz("gamma input  16 bits/pixel support\n", get_inquiry_gib_16bpp(inquiry_block));
    DBG_inq_nz("0x66 bit 6\n", get_inquiry_0x66_bit6(inquiry_block));
    DBG_inq_nz("0x66 bit 7\n", get_inquiry_0x66_bit7(inquiry_block));

    /* 0x68 */
    DBG_inq_nz("gamma output  8 bits/pixel support\n", get_inquiry_gob_8bpp(inquiry_block));
    DBG_inq_nz("gamma output  9 bits/pixel support\n", get_inquiry_gob_9bpp(inquiry_block));
    DBG_inq_nz("gamma output 10 bits/pixel support\n", get_inquiry_gob_10bpp(inquiry_block));
    DBG_inq_nz("gamma output 12 bits/pixel support\n", get_inquiry_gob_12bpp(inquiry_block));
    DBG_inq_nz("gamma output 14 bits/pixel support\n", get_inquiry_gob_14bpp(inquiry_block));
    DBG_inq_nz("gamma output 16 bits/pixel support\n", get_inquiry_gob_16bpp(inquiry_block));
    DBG_inq_nz("0x68 bit 6\n", get_inquiry_0x68_bit6(inquiry_block));
    DBG_inq_nz("0x68 bit 7\n", get_inquiry_0x68_bit7(inquiry_block));
  }

  /* 0x64 - 0x68 reserved bits */
  DBG_inq_nz("0x64 bit 2\n", get_inquiry_0x64_bit2(inquiry_block));
  DBG_inq_nz("0x64 bit 3\n", get_inquiry_0x64_bit3(inquiry_block));
  DBG_inq_nz("0x64 bit 4\n", get_inquiry_0x64_bit4(inquiry_block));
  DBG_inq_nz("0x64 bit 6\n", get_inquiry_0x64_bit6(inquiry_block));

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"reserved byte 0x65 = %d\n", get_inquiry_0x65(inquiry_block));
  DBG(DBG_inquiry,"reserved byte 0x67 = %d\n", get_inquiry_0x67(inquiry_block));


  /* 0x69 */
  if (dev->inquiry_len<=0x69)
  {
    return;
  }

  DBG(DBG_inquiry,"\n");
  if (get_inquiry_hda(inquiry_block))
  {
    DBG(DBG_inquiry,"halftone download available\n");
    DBG(DBG_inquiry,"halftone pattern download max matrix %dx%d\n",
                    get_inquiry_max_halftone_matrix(inquiry_block),
                    get_inquiry_max_halftone_matrix(inquiry_block));
  }

  /* 0x6a */
  if (dev->inquiry_len<=0x6a)
  {
    return;
  }

  DBG_inq_nz("built-in halftone patterns:\n", get_inquiry_halftones_supported(inquiry_block));
  DBG_inq_nz("built-in halftone pattern size ............: 2x2\n", get_inquiry_halftones_2x2(inquiry_block));
  DBG_inq_nz("built-in halftone pattern size ............: 4x4\n", get_inquiry_halftones_4x4(inquiry_block));
  DBG_inq_nz("built-in halftone pattern size ............: 6x6\n", get_inquiry_halftones_6x6(inquiry_block));
  DBG_inq_nz("built-in halftone pattern size ............: 8x8\n", get_inquiry_halftones_8x8(inquiry_block));
  DBG_inq_nz("built-in halftone pattern size ............: 12x12\n", get_inquiry_halftones_12x12(inquiry_block));

  /* 0x6b, 0x6c */
  DBG(DBG_inquiry,"reserved byte 0x6b = %d\n", get_inquiry_0x6b(inquiry_block));
  DBG(DBG_inquiry,"reserved byte 0x6c = %d\n", get_inquiry_0x6c(inquiry_block));


  /* 0x6d */
  if (dev->inquiry_len<=0x6d)
  {
    return;
  }

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"color sequence............................: %s\n",
      color_sequence_str[get_inquiry_colorseq(inquiry_block)]);
  DBG_inq_nz("color ordering support....................: pixel\n",
             get_inquiry_color_order_pixel(inquiry_block));
  DBG_inq_nz("color ordering support....................: line without CCD distance\n",
             get_inquiry_color_order_line_no_ccd(inquiry_block));
  DBG_inq_nz("color ordering support....................: plane\n",
             get_inquiry_color_order_plane(inquiry_block));
  DBG_inq_nz("color ordering support....................: line with CCD distance\n",
             get_inquiry_color_order_line_w_ccd(inquiry_block));
  DBG_inq_nz("color ordering support....................: (reserved)\n",
             get_inquiry_color_order_reserved(inquiry_block));

  /* 0x6e */
  if (dev->inquiry_len<=0x71)
  {
    return;
  }

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"maximum video memory......................: %d KB\n", dev->inquiry_vidmem/1024);

  /* 0x72 */
  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"reserved byte 0x72 = %d\n", get_inquiry_0x72(inquiry_block));
  DBG(DBG_inquiry,"\n");

  /* 0x73/0x94 - 0x75/0x96 */
  if (dev->inquiry_len<=0x75)
  {
    return;
  }

  DBG(DBG_inquiry,"optical resolution........................: %d dpi\n", dev->inquiry_optical_res);
  DBG(DBG_inquiry,"maximum x-resolution......................: %d dpi\n", dev->inquiry_x_res);
  DBG(DBG_inquiry,"maximum y-resolution......................: %d dpi\n", dev->inquiry_y_res);

  /* ---------- */

  /* 0x76 0x77 */
  if (dev->inquiry_len<=0x77)
  {
    return;
  }

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"FB (flatbed-mode):\n");
  DBG(DBG_inquiry,"FB maximum scan width.....................: %2.2f inch\n", dev->inquiry_fb_width);
  DBG(DBG_inquiry,"FB maximum scan length....................: %2.2f inch\n", dev->inquiry_fb_length);

  /* ---------- */
  
  /* 0x7a - 0x81 */
  if (dev->inquiry_len<=0x81)
  {
    return;
  }

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"UTA (transparency-mode):\n");
  DBG(DBG_inquiry,"UTA x-original point......................: %2.2f inch\n", dev->inquiry_uta_x_off);
  DBG(DBG_inquiry,"UTA y-original point......................: %2.2f inch\n", dev->inquiry_uta_y_off);
  DBG(DBG_inquiry,"UTA maximum scan width....................: %2.2f inch\n", dev->inquiry_uta_width); 
  DBG(DBG_inquiry,"UTA maximum scan length...................: %2.2f inch\n", dev->inquiry_uta_length);
 
  /* ---------- */

  /* 0x82-0x85 */
  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"reserved byte 0x82 = %d\n", get_inquiry_0x82(inquiry_block));

  /* ---------- */

  /* 0x83/0xa0 - 0x85/0xa2 */
  if (dev->inquiry_len<=0x85)
  {
    return;
  }

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"DOR (double optical resolution-mode):\n");
  DBG(DBG_inquiry,"DOR optical resolution....................: %d dpi\n", dev->inquiry_dor_optical_res);
  DBG(DBG_inquiry,"DOR maximum x-resolution..................: %d dpi\n", dev->inquiry_dor_x_res);
  DBG(DBG_inquiry,"DOR maximum y-resolution..................: %d dpi\n", dev->inquiry_dor_y_res);

  /* 0x86 - 0x8d */
  if (dev->inquiry_len<=0x8d)
  {
    return;
  }

  DBG(DBG_inquiry,"DOR x-original point......................: %2.2f inch\n", dev->inquiry_dor_x_off);
  DBG(DBG_inquiry,"DOR y-original point......................: %2.2f inch\n", dev->inquiry_dor_y_off);
  DBG(DBG_inquiry,"DOR maximum scan width....................: %2.2f inch\n", dev->inquiry_dor_width);
  DBG(DBG_inquiry,"DOR maximum scan length...................: %2.2f inch\n", dev->inquiry_dor_length);
  DBG(DBG_inquiry,"\n");

  /* ---------- */

  /* 0x8e */
  DBG(DBG_inquiry,"reserved byte 0x8e = %d\n", get_inquiry_0x8e(inquiry_block));
  DBG(DBG_inquiry,"\n");

  /* ---------- */

  /* 0x8f */
  if (dev->inquiry_len<=0x8f)
  {
    return;
  }

  DBG(DBG_inquiry,"last calibration lamp density.............: %d\n",
      get_inquiry_last_calibration_lamp_density(inquiry_block));

  /* 0x90 */
  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"reserved byte 0x90 = %d\n", get_inquiry_0x90(inquiry_block));
  DBG(DBG_inquiry,"\n");

  /* 0x91 */
  if (dev->inquiry_len<=0x91)
  {
    return;
  }

  DBG(DBG_inquiry,"lamp warmup maximum time..................: %d sec\n", dev->inquiry_max_warmup_time);
 
  /* 0x92 0x93 */
  if (dev->inquiry_len<=0x93)
  {
    return;
  }

  DBG(DBG_inquiry,"window descriptor block length............: %d bytes\n", get_inquiry_wdb_length(inquiry_block));

  /* ----------------- */

  /* 0x97 */
  if (dev->inquiry_len<=0x97)
  {
    return;
  }

  if (get_inquiry_analog_gamma_table(inquiry_block) == 0)
  {
    DBG(DBG_inquiry,"no analog gamma function\n");
  }
  else
  {
    DBG(DBG_inquiry,"mp 8832 analog gamma table\n");
  }

  /* 0x98, 0x99 */
  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"reserved byte 0x98 = %d\n", get_inquiry_0x98(inquiry_block));
  DBG(DBG_inquiry,"reserved byte 0x99 = %d\n", get_inquiry_0x99(inquiry_block));
  DBG(DBG_inquiry,"\n");

  /* 0x9a */
  if (dev->inquiry_len<=0x9a)
  {
    return;
  }

  DBG(DBG_inquiry,"maximum calibration data lines for shading: %d\n",
      get_inquiry_max_calibration_data_lines(inquiry_block));

  /* 0x9b */
  if (dev->inquiry_len<=0x9b)
  {
    return;
  }

  DBG(DBG_inquiry,"fb/uta: color line arrangement mode.......: %d\n",
      get_inquiry_fb_uta_line_arrangement_mode(inquiry_block));

  /* 0x9c */
  if (dev->inquiry_len<=0x9c)
  {
    return;
  }

  DBG(DBG_inquiry,"adf:    color line arrangement mode.......: %d\n",
      get_inquiry_adf_line_arrangement_mode(inquiry_block));

  /* 0x9d */
  if (dev->inquiry_len<=0x9d)
  {
    return;
  }

  DBG(DBG_inquiry,"CCD line distance.........................: %d\n",
      get_inquiry_CCD_line_distance(inquiry_block));

  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"reserved byte 0x9e = %d\n", get_inquiry_0x9e(inquiry_block));

  /* 0xa2 following */
  if (dev->inquiry_len<=0xa2)
  {
    return;
  }

  DBG(DBG_inquiry,"\n");
  for(i=0xa3; i<dev->inquiry_len; i++)
  {
    DBG(DBG_inquiry,"unknown reserved byte 0x%x = %d\n", i, inquiry_block[i]);
  }
}


/* ------------------------------------------------------------ CBHS_CORRECT ------------------------------- */


static int umax_cbhs_correct(int minimum, int cbhs, int maximum)
{
 int range = maximum - minimum + 1;

  if (range == 256)
  {
    return cbhs;
  }

 return (int)( (cbhs/256.0)*range + minimum );
}


/* ------------------------------------------------------------ SENSE_HANDLER ------------------------------ */


static SANE_Status sense_handler(int scsi_fd, unsigned char *result, void *arg)	  /* is called by sanei_scsi */
{
 unsigned char asc, ascq, sensekey;
 int           asc_ascq, len;
 Umax_Device   *dev = arg;

  DBG(DBG_proc, "check condition sense handler (scsi_fd = %d)\n", scsi_fd);

  sensekey = get_RS_sense_key(result);
  asc      = get_RS_ASC(result);
  ascq     = get_RS_ASCQ(result);
  asc_ascq = (int)(256 * asc + ascq);
  len      = 7 + get_RS_additional_length(result);

  if ( get_RS_error_code(result) != 0x70 ) 
  {
    DBG(DBG_error, "invalid sense key error code (%d)\n", get_RS_error_code(result));

    switch (dev->handle_bad_sense_error)
    {
      default:
      case 0:
        DBG(DBG_error, "=> handled as DEVICE BUSY!\n");
       return SANE_STATUS_DEVICE_BUSY;

      case 1:
        DBG(DBG_error, "=> handled as ok!\n");
       return SANE_STATUS_GOOD;

      case 2:
        DBG(DBG_error, "=> handled as i/o error!\n");
       return SANE_STATUS_IO_ERROR;

      case 3:
        DBG(DBG_error, "=> ignored, sense handler does continue\n");
    }
  }

  DBG(DBG_sense, "check condition sense: %s\n", sense_str[sensekey]);

  /* when we reach here then we have no valid data in buffer[0] */
  /* but it may be helpful to have the result data in buffer[0] */
  memset(dev->buffer[0], 0, rs_return_block_size);  /* clear sense data buffer */
  memcpy(dev->buffer[0], result, len+1); /* copy sense data to buffer */

  if (len > 0x15)
  {
   int scanner_error = get_RS_scanner_error_code(result);

    if (scanner_error < 100)
    {
      DBG(DBG_sense, "-> %s (#%d)\n", scanner_error_str[scanner_error], scanner_error);
    }
    else
    {
      DBG(DBG_sense, "-> error %d\n", scanner_error);
    }
  }

  if (get_RS_ILI(result) != 0)
  {
    DBG(DBG_sense, "-> ILI-ERROR: requested data length is larger than actual length\n");
  }

  switch(sensekey)
  {
    case 0x00:											 /* no sense */
      return SANE_STATUS_GOOD;
     break;


    case 0x03:										     /* medium error */
      if (asc_ascq == 0x1400)
      {
        DBG(DBG_sense, "-> misfeed, paper jam\n");
        return SANE_STATUS_JAMMED;
      }
      else if (asc_ascq == 0x1401)
      {
        DBG(DBG_sense, "-> adf not ready\n");
        return SANE_STATUS_NO_DOCS;
      }
      else
      {
        DBG(DBG_sense, "-> unknown medium error: asc=%d, ascq=%d\n", asc, ascq);
      }
     break;


    case 0x04:										   /* hardware error */
      if (asc_ascq == 0x4000)
      {
        DBG(DBG_sense, "-> diagnostic error:\n");
        if (len >= 0x13)
	{
	  DBG_sense_nz("   dim light\n",			get_RS_asb_dim_light(result));
	  DBG_sense_nz("   no light\n",				get_RS_asb_no_light(result));
	  DBG_sense_nz("   sensor or motor error\n",		get_RS_asb_sensor_motor(result));
	  DBG_sense_nz("   too light\n",			get_RS_asb_too_light(result));
	  DBG_sense_nz("   calibration error\n",		get_RS_asb_calibration(result));
	  DBG_sense_nz("   rom error\n",			get_RS_asb_rom(result));
	  DBG_sense_nz("   ram error\n",			get_RS_asb_ram(result));
	  DBG_sense_nz("   cpu error\n",			get_RS_asb_cpu(result));
	  DBG_sense_nz("   scsi error\n",			get_RS_asb_scsi(result));
	  DBG_sense_nz("   timer error\n",			get_RS_asb_timer(result));
	  DBG_sense_nz("   filter motor error\n",		get_RS_asb_filter_motor(result));
	  DBG_sense_nz("   dc adjust error\n",			get_RS_asb_dc_adjust(result));
	  DBG_sense_nz("   uta home sensor or motor error\n",	get_RS_asb_uta_sensor(result));
	}
      }
      else
      {
        DBG(DBG_sense, "-> unknown hardware error: asc=%d, ascq=%d\n", asc, ascq);
      }
      return SANE_STATUS_IO_ERROR;
     break;


    case 0x05:										  /* illegal request */
      if (asc_ascq == 0x2000)
      {
        DBG(DBG_sense, "-> invalid command operation code\n");
      }
      else if (asc_ascq == 0x2400)
      {
        DBG(DBG_sense, "-> illegal field in CDB\n");
       }
      else if (asc_ascq == 0x2500)
      {
        DBG(DBG_sense, "-> logical unit not supported\n");
      }
      else if (asc_ascq == 0x2600)
      {
        DBG(DBG_sense, "-> invalid field in parameter list\n");
      }
      else if (asc_ascq == 0x2c01)
      {
        DBG(DBG_sense, "-> too many windows specified\n");
      }
      else if (asc_ascq == 0x2c02)
      {
        DBG(DBG_sense, "-> invalid combination of windows specified\n");
      }
      else
      {
        DBG(DBG_sense, "-> illegal request: asc=%d, ascq=%d\n", asc, ascq);
      }

      if (len >= 0x11)
      {
        if (get_RS_SKSV(result) != 0)
        {
          if (get_RS_CD(result) == 0)
          {
            DBG(DBG_sense, "-> illegal parameter in CDB\n");
          }
          else
          {
            DBG(DBG_sense, "-> illegal parameter is in the data parameters sent during data out phase\n");
          }

          DBG(DBG_sense, "-> error detected in byte %d\n", get_RS_field_pointer(result));
         }
      }
      return SANE_STATUS_IO_ERROR;
     break;


    case 0x06:										   /* unit attention */
      if (asc_ascq == 0x2900)
      {
        DBG(DBG_sense, "-> power on, reset or bus device reset\n");
      }
      else if (asc_ascq == 0x3f01)
      {
        DBG(DBG_sense, "-> microcode has been changed\n");
       }
      else
      {
        DBG(DBG_sense, "-> unit attention: asc=%d, ascq=%d\n", asc, ascq);
      }
     break;


    case 0x09:										  /* vendor specific */

      if (asc == 0x00)
      {
        DBG(DBG_sense, "-> button protocoll\n");
        if (ascq & 1)
        {
          dev->button0_pressed = 1;
          DBG(DBG_sense, "-> button 0 pressed\n");
        }

        if (ascq & 2)
        {
          dev->button1_pressed = 1;
          DBG(DBG_sense, "-> button 1 pressed\n");
        }

        if (ascq & 4)
        {
          dev->button2_pressed = 1;
          DBG(DBG_sense, "-> button 2 pressed\n");
        }
        return SANE_STATUS_GOOD;
      }
      else if (asc_ascq == 0x8001)
      {
        DBG(DBG_sense, "-> lamp warmup\n");
        return SANE_STATUS_DEVICE_BUSY;
      }
      else if (asc_ascq == 0x8002)
      {
        DBG(DBG_sense, "-> calibration by driver\n");
        if (dev)
	{
          dev->do_calibration = 1;				       /* set flag for calibration by driver */
        }
        return SANE_STATUS_GOOD;
      }
      else
      {
        DBG(DBG_sense, "-> vendor specific sense-code: asc=%d, ascq=%d\n", asc, ascq);
      }
     break;

  }
 return SANE_STATUS_GOOD;
}

/* ------------------------------------------------------------ UMAX SET RGB BIND -------------------------- */

static void umax_set_rgb_bind(Umax_Scanner *scanner)
{
  if ( (scanner->val[OPT_RGB_BIND].w == SANE_FALSE) &&
       (strcmp(scanner->val[OPT_MODE].s, COLOR_STR) == 0) ) /* enable rgb options */
  {
    if (scanner->device->inquiry_analog_gamma)
    {
      scanner->opt[OPT_ANALOG_GAMMA].cap   |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_ANALOG_GAMMA_R].cap &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_ANALOG_GAMMA_G].cap &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_ANALOG_GAMMA_B].cap &= ~SANE_CAP_INACTIVE;
    }
    if (scanner->device->inquiry_highlight)
    {
      scanner->opt[OPT_HIGHLIGHT].cap   |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_HIGHLIGHT_R].cap &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_HIGHLIGHT_G].cap &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_HIGHLIGHT_B].cap &= ~SANE_CAP_INACTIVE;
    }
    if (scanner->device->inquiry_shadow)
    {
      scanner->opt[OPT_SHADOW].cap   |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_SHADOW_R].cap &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_SHADOW_G].cap &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_SHADOW_B].cap &= ~SANE_CAP_INACTIVE;
    }
  }
  else /* only show gray options */
  {
    if (scanner->device->inquiry_analog_gamma)
    {
      scanner->opt[OPT_ANALOG_GAMMA].cap   &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_ANALOG_GAMMA_R].cap |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_ANALOG_GAMMA_G].cap |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_ANALOG_GAMMA_B].cap |= SANE_CAP_INACTIVE;
    }
    if (scanner->device->inquiry_highlight)
    {
      scanner->opt[OPT_HIGHLIGHT].cap   &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_HIGHLIGHT_R].cap |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_HIGHLIGHT_G].cap |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_HIGHLIGHT_B].cap |= SANE_CAP_INACTIVE;
    }
    if (scanner->device->inquiry_shadow)
    {
      scanner->opt[OPT_SHADOW].cap   &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_SHADOW_R].cap |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_SHADOW_G].cap |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_SHADOW_B].cap |= SANE_CAP_INACTIVE;
    }
  }

  if ( (scanner->device->inquiry_exposure_adj) && (scanner->val[OPT_SELECT_EXPOSURE_TIME].w) )
  {
    if ( (scanner->val[OPT_RGB_BIND].w == SANE_FALSE) &&
         (!scanner->device->exposure_time_rgb_bind) &&
         (strcmp(scanner->val[OPT_MODE].s, COLOR_STR) == 0) ) /* enable rgb exposure time options */
    {
      if (scanner->val[OPT_SELECT_CAL_EXPOSURE_TIME].w) /* exposure time setting for calibration enabled */
      {
        scanner->opt[OPT_CAL_EXPOS_TIME].cap    |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_CAL_EXPOS_TIME_R].cap  &= ~SANE_CAP_INACTIVE;
        scanner->opt[OPT_CAL_EXPOS_TIME_G].cap  &= ~SANE_CAP_INACTIVE;
        scanner->opt[OPT_CAL_EXPOS_TIME_B].cap  &= ~SANE_CAP_INACTIVE;
      }
      else /* no separate settings for calibration exposure times */
      {
        scanner->opt[OPT_CAL_EXPOS_TIME].cap    |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_CAL_EXPOS_TIME_R].cap  |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_CAL_EXPOS_TIME_G].cap  |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_CAL_EXPOS_TIME_B].cap  |= SANE_CAP_INACTIVE;
      }

      scanner->opt[OPT_SCAN_EXPOS_TIME].cap   |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_SCAN_EXPOS_TIME_R].cap &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_SCAN_EXPOS_TIME_G].cap &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_SCAN_EXPOS_TIME_B].cap &= ~SANE_CAP_INACTIVE;
    }
    else /* enable gray exposure time options */
    {
      if (scanner->val[OPT_SELECT_CAL_EXPOSURE_TIME].w) /* exposure time setting for calibration enabled */
      {
        scanner->opt[OPT_CAL_EXPOS_TIME].cap  &= ~SANE_CAP_INACTIVE;
      }
      else /* no separate settings for calibration exposure times */
      {
        scanner->opt[OPT_CAL_EXPOS_TIME].cap  |= SANE_CAP_INACTIVE;
      }
      scanner->opt[OPT_CAL_EXPOS_TIME_R].cap  |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_CAL_EXPOS_TIME_G].cap  |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_CAL_EXPOS_TIME_B].cap  |= SANE_CAP_INACTIVE;

      scanner->opt[OPT_SCAN_EXPOS_TIME].cap   &= ~SANE_CAP_INACTIVE;
      scanner->opt[OPT_SCAN_EXPOS_TIME_R].cap |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_SCAN_EXPOS_TIME_G].cap |= SANE_CAP_INACTIVE;
      scanner->opt[OPT_SCAN_EXPOS_TIME_B].cap |= SANE_CAP_INACTIVE;
    }
  }
}

/* ------------------------------------------------------------ UMAX CALCULATE PIXELS ---------------------- */

static int umax_calculate_pixels(int scansize_pixel, int resolution, int resolution_base, int coordinate_base)
/* scansize_pixel	= size in pixels at 1200 dpi */
/* resolution		= scan resolution */
/* resolution_base	= this is the optical resolution * 1 or * 2 */
/* coordinate_base	= this is 1200 dpi */
{
 unsigned int intsize_inch, intsize_pixel, diffsize_pixel, missing_pixels, del_pixel_1, pix;
 int toomuch;

  intsize_inch   = scansize_pixel / coordinate_base;	/* full inches */
  intsize_pixel  = intsize_inch * resolution;		/* pixels in full inches */

  diffsize_pixel  = scansize_pixel % coordinate_base;	/* missing pixels in last inch at 1200 dpi */
  missing_pixels  = diffsize_pixel * resolution_base / coordinate_base; /* missing pixels at resolution_base dpi */
  del_pixel_1     = resolution_base - resolution;	/* pixels to erase in one inch */
  toomuch         = 0;					/* number of pixels that must be deleted in last inch  */

  if (del_pixel_1 != 0)					/* search the number of pixels that must deleted */
  {
    pix = 0;
    while (pix <= missing_pixels)
    {
      toomuch++;
      pix = toomuch * resolution_base/del_pixel_1;
    }

    if (pix > missing_pixels)
    {
      toomuch--;
    }
  }

  return (intsize_pixel + missing_pixels - toomuch);
}

/* ------------------------------------------------------------ UMAX FORGET LINE --------------------------- */


static int umax_forget_line(Umax_Device *dev, int color)
/* tests if line related to optical resolution has to be skipped for selected resolution */
/* returns 0 if line is ok, -1 if line has to be skipped */
{
 unsigned int opt_res = dev->relevant_optical_res * dev->scale_y;
 unsigned int forget;

  dev->pixelline_opt_res++;					 /* increment number of lines in optical res */

  if (opt_res != dev->y_resolution)					    /* are there any lines to skip ? */
  {

    forget = (dev->pixelline_del[color] * opt_res)/(opt_res - dev->y_resolution);

    if (dev->pixelline_optic[color]++ == forget)
    {
      dev->pixelline_del[color]++;					 /* inc pointer to next line to skip */
      return(-1);										/* skip line */
    }
  }

 return(0);									      /* ok, order this line */
}


/* ------------------------------------------------------------ UMAX ORDER LINE TO PIXEL ------------------- */


static void umax_order_line_to_pixel(Umax_Device *dev, unsigned char *source, int color)
/* reads a one-color line and writes it into a pixel-ordered-buffer if line */
/* is not skipped */
/* color = 0:red, 1:green, 2:blue */
{
 unsigned int i;
 unsigned int line = dev->pixelline_next[color];					 /* bufferlinenumber */
 unsigned char *dest = dev->pixelbuffer;

  if (dest != NULL)
  {
    if (dev->bits_per_pixel_code == 1)								   /* 24 bpp */
    {
      dest += line * dev->width_in_pixels * 3 + color;

      for (i=0; i<dev->width_in_pixels; i++)				   /* cp each pixel into pixelbuffer */
      {
        *dest++ = *source++;
	dest++;
	dest++;
      }
    }
    else											 /* > 24 bpp */
    {
      dest += line * dev->width_in_pixels * 6 + color * 2;

      for(i=0; i<dev->width_in_pixels; i++)				   /* cp each pixel into pixelbuffer */
      {
        *dest++ = *source++;					      /* byte order correct ? , don't know ! */
        *dest++ = *source++;

        dest++; dest++;
        dest++; dest++;
      }
    }

    line++;
    if (line >= dev->pixelline_max)
    {
      line = 0;
    }

    dev->pixelline_next[color] = line;						  /* next line of this color */
    dev->pixelline_ready[color]++;					  /* number of ready lines for color */

    DBG(DBG_read, "merged line as color %d to line %d\n", color, dev->pixelline_ready[color]);
  }
}


/* ------------------------------------------------------------ UMAX ORDER LINE ---------------------------- */


static void umax_order_line(Umax_Device *dev, unsigned char *source)
{
 unsigned int CCD_distance = dev->CCD_distance * dev->scale_y;
 unsigned int length = (dev->scanlength * dev->scale_y * dev->relevant_optical_res) / dev->y_coordinate_base;
 unsigned int color;

  do										   /* search next valid line */
  {
    if (dev->pixelline_opt_res < CCD_distance)
    {
      color = dev->CCD_color[0];								  /* color 0 */
    }
    else if (dev->pixelline_opt_res < CCD_distance * 3)
    {
      color = dev->CCD_color[1 + ((dev->pixelline_opt_res - CCD_distance) % 2)];	 	/* color 1,2 */
    }
    else if (dev->pixelline_opt_res < length * 3 - CCD_distance * 3) 
    {
      color = dev->CCD_color[3 + (dev->pixelline_opt_res % 3)];				      /* color 3,4,5 */
    }
    else if (dev->pixelline_opt_res < length * 3 - CCD_distance) 
    {
      color = dev->CCD_color[6 + ((dev->pixelline_opt_res - length*3 + CCD_distance*3) % 2)];	/* color 6,7 */
    }
    else 
    {
      color = dev->CCD_color[8];								  /* color 8 */
    } 
  } while(umax_forget_line(dev, color) != 0);					 /* until found correct line */

  umax_order_line_to_pixel(dev, source, color);
}


/* ------------------------------------------------------------ UMAX GET PIXEL LINE ------------------------ */


static unsigned char * umax_get_pixel_line(Umax_Device *dev)
{
 unsigned char *source = NULL;

  if (dev->pixelbuffer != NULL)
  {
    if ( (dev->pixelline_ready[0] > dev->pixelline_written) &&
         (dev->pixelline_ready[1] > dev->pixelline_written) &&
         (dev->pixelline_ready[2] > dev->pixelline_written) )
    {
      source = dev->pixelbuffer + dev->pixelline_read * dev->width_in_pixels * 3;

      dev->pixelline_written++;
      dev->pixelline_read++;

      if (dev->pixelline_read >= dev->pixelline_max)
      {
        dev->pixelline_read = 0;
      }
    }
  }

  return source;
}


/* ============================================================ Switches between the SCSI and USB commands = */

/* ------------------------------------------------------------ UMAX SCSI CMD ------------------------------ */

static SANE_Status umax_scsi_cmd(Umax_Device *dev, const void *src, size_t src_size, void *dst, size_t * dst_size)
{
  switch (dev->connection_type)
  {
    case SANE_UMAX_SCSI:
      return sanei_scsi_cmd(dev->sfd, src, src_size, dst, dst_size);
     break;

#ifdef UMAX_ENABLE_USB
    case SANE_UMAX_USB:
      return sanei_umaxusb_cmd(dev->sfd, src, src_size, dst, dst_size);
     break;
#endif

    default:
      return(SANE_STATUS_INVAL);
  }
}

/* ------------------------------------------------------------ UMAX SCSI OPEN EXTENDED -------------------- */

static SANE_Status umax_scsi_open_extended(const char *devicename, Umax_Device *dev,
                                           SANEI_SCSI_Sense_Handler handler, void *handler_arg, int *buffersize)
{
  switch (dev->connection_type) 
  {
    case SANE_UMAX_SCSI:
      return sanei_scsi_open_extended(devicename, &dev->sfd, handler, handler_arg, buffersize);
     break;

#ifdef UMAX_ENABLE_USB
    case SANE_UMAX_USB:
      return sanei_umaxusb_open_extended(devicename, &dev->sfd, handler, handler_arg, buffersize);
     break;
#endif

    default:
      return(SANE_STATUS_INVAL);
  }
}

/* ------------------------------------------------------------ UMAX SCSI OPEN ----------------------------- */

static SANE_Status umax_scsi_open(const char *devicename, Umax_Device *dev,
                                  SANEI_SCSI_Sense_Handler handler, void *handler_arg)
{
  switch (dev->connection_type)
  {
    case SANE_UMAX_SCSI:
      return sanei_scsi_open(devicename, &dev->sfd, handler, handler_arg);
     break;

#ifdef UMAX_ENABLE_USB
    case SANE_UMAX_USB:
      return sanei_umaxusb_open(devicename, &dev->sfd, handler, handler_arg);
     break;
#endif

    default:
      return(SANE_STATUS_INVAL);
  }
}

/* ------------------------------------------------------------ UMAX SCSI CLOSE ---------------------------- */

static void umax_scsi_close(Umax_Device *dev)
{
  switch (dev->connection_type)
  {
    case SANE_UMAX_SCSI:
      sanei_scsi_close(dev->sfd);
      dev->sfd=-1;
     break;

#ifdef UMAX_ENABLE_USB
    case SANE_UMAX_USB:
      sanei_umaxusb_close(dev->sfd);
      dev->sfd=-1;
     break;
#endif
  }	
}

/* ------------------------------------------------------------ UMAX SCSI REQ ENTER ------------------------ */

static SANE_Status umax_scsi_req_enter(Umax_Device *dev, const void *src, size_t src_size,
                                       void *dst, size_t * dst_size, void **idp)
{
  switch (dev->connection_type)
  {
    case SANE_UMAX_SCSI:
      return sanei_scsi_req_enter (dev->sfd, src, src_size, dst, dst_size, idp);
     break;

#ifdef UMAX_ENABLE_USB
    case SANE_UMAX_USB:
      return sanei_umaxusb_req_enter (dev->sfd, src, src_size, dst, dst_size, idp);
     break;
#endif

    default:
      return(SANE_STATUS_INVAL);
  }
}

/* ------------------------------------------------------------ UMAX SCSI REQ WAIT ------------------------- */

static SANE_Status umax_scsi_req_wait(Umax_Device *dev, void *id)
{
  switch (dev->connection_type)
  {
    case SANE_UMAX_SCSI:
      return sanei_scsi_req_wait(id);
     break;

#ifdef UMAX_ENABLE_USB
    case SANE_UMAX_USB:
      return sanei_umaxusb_req_wait(id);
     break;
#endif

    default:
      return(SANE_STATUS_INVAL);
  }
}

/* ------------------------------------------------------------ UMAX SCSI GET LAMP STATUS ------------------ */


static SANE_Status umax_scsi_get_lamp_status(Umax_Device *dev, int *lamp_on)
{
 SANE_Status status;
 size_t size = 1;

  DBG(DBG_proc, "umax_scsi_get_lamp_status\n");

  status = umax_scsi_cmd(dev, get_lamp_status.cmd, get_lamp_status.size, dev->buffer[0], &size);
  if (status)
  {
    DBG(DBG_error, "umax_scsi_get_lamp_status: command returned status %s\n", sane_strstatus(status));
   return status;
  }

  *lamp_on = get_lamp_status_lamp_on(dev->buffer[0]);

  DBG(DBG_info, "lamp_status = %d\n", *lamp_on);

 return status;
}

/* ------------------------------------------------------------ UMAX SCSI SET LAMP STATUS ------------------ */


static SANE_Status umax_scsi_set_lamp_status(Umax_Device *dev, int lamp_on)
{
 SANE_Status status;

  DBG(DBG_proc, "umax_scsi_set_lamp_status\n");
  DBG(DBG_info, "lamp_status=%d\n", lamp_on);

  set_lamp_status_lamp_on(set_lamp_status.cmd, lamp_on);
  status = umax_scsi_cmd(dev, set_lamp_status.cmd, set_lamp_status.size, NULL, NULL);

  if (status)
  {
    DBG(DBG_error, "umax_scsi_set_lamp_status: command returned status %s\n", sane_strstatus(status));
  }

 return status;
}

/* ------------------------------------------------------------ UMAX SET LAMP STATUS ----------------------- */

static SANE_Status umax_set_lamp_status(SANE_Handle handle, int lamp_on)
{
 Umax_Scanner *scanner = handle;
 int lamp_status;
 SANE_Status status;

  DBG(DBG_proc, "umax_set_lamp_status\n");

  if (umax_scsi_open(scanner->device->sane.name, scanner->device, sense_handler,
                     scanner->device) != SANE_STATUS_GOOD )
  {
     DBG(DBG_error, "ERROR: umax_set_lamp_status: open of %s failed:\n", scanner->device->sane.name);
     return SANE_STATUS_INVAL;
  }

  status = umax_scsi_get_lamp_status(scanner->device, &lamp_status);

  if (!status)
  {
    status = umax_scsi_set_lamp_status(scanner->device, lamp_on);
  }

  umax_scsi_close(scanner->device);

 return status;
}

/* ------------------------------------------------------------ UMAX GET DATA BUFFER STATUS ---------------- */


#ifndef UMAX_HIDE_UNUSED									 /* NOT USED */
static SANE_Status umax_get_data_buffer_status(Umax_Device *dev)
{
 SANE_Status status;

  DBG(DBG_proc, "get_data_buffer_status\n");
  set_GDBS_wait(get_data_buffer_status.cmd,1);					    /* wait for scanned data */
  status = umax_scsi_cmd(dev, get_data_buffer_status.cmd, get_data_buffer_status.size, NULL, NULL);
  if (status)
  {
    DBG(DBG_error, "umax_get_data_buffer_status: command returned status %s\n", sane_strstatus(status));
  }  

 return status;
}
#endif


/* ------------------------------------------------------------ UMAX DO REQUEST SENSE ---------------------- */


static void umax_do_request_sense(Umax_Device *dev)
{
 size_t size = rs_return_block_size;
 SANE_Status status;

  DBG(DBG_proc, "do_request_sense\n");
  set_RS_allocation_length(request_sense.cmd, rs_return_block_size); 
  status = umax_scsi_cmd(dev, request_sense.cmd, request_sense.size, dev->buffer[0], &size);
  if (status)
  {
    DBG(DBG_error, "umax_do_request_sense: command returned status %s\n", sane_strstatus(status));
  }  
}


/* ------------------------------------------------------------ UMAX WAIT SCANNER -------------------------- */


static SANE_Status umax_wait_scanner(Umax_Device *dev)
{
 SANE_Status status;
 int cnt = 0;

  DBG(DBG_proc, "wait_scanner\n");

  do
  {
    if (cnt > 100)							   /* maximal 100 * 0.5 sec = 50 sec */
    {
      DBG(DBG_warning, "scanner does not get ready\n");
      return -1;
    }
											  /* test unit ready */
    status = umax_scsi_cmd(dev, test_unit_ready.cmd, test_unit_ready.size, NULL, NULL);
    cnt++;

    if (status)
    {
      if (cnt == 1)
      {
        DBG(DBG_info2, "scanner reports %s, waiting ...\n", sane_strstatus(status));
      }

      usleep(500000);									 /* wait 0.5 seconds */
    }
  } while (status != SANE_STATUS_GOOD );

  DBG(DBG_info, "scanner ready\n");

  return status;
}

#define WAIT_SCANNER { int status = umax_wait_scanner(dev); if (status) return status; }


/* ------------------------------------------------------------ UMAX GRAB SCANNER -------------------------- */


static int umax_grab_scanner(Umax_Device *dev)
{
 int status;

  DBG(DBG_proc, "grab_scanner\n");

  WAIT_SCANNER;									   /* wait for scanner ready */
  status = umax_scsi_cmd(dev, reserve_unit.cmd, reserve_unit.size, NULL, NULL);

  if (status)
  {
    DBG(DBG_error, "umax_grab_scanner: command returned status %s\n", sane_strstatus(status));
  }  
  else
  {
    DBG(DBG_info, "scanner reserved\n");
  }

  return status;
}


/* ------------------------------------------------------------ UMAX REPOSITION SCANNER -------------------- */


static int umax_reposition_scanner(Umax_Device *dev)
{
 int status;
 int pause;

  pause = dev->pause_after_reposition + dev->pause_for_moving * (dev->upper_left_y + dev->scanlength) / 
                                        ( (dev->inquiry_fb_length * dev->y_coordinate_base) );

  DBG(DBG_info2, "trying to reposition scanner ...\n");
  status = umax_scsi_cmd(dev, object_position.cmd, object_position.size, NULL, NULL);
  if (status)
  {
    DBG(DBG_error, "umax_reposition_scanner: command returned status %s\n", sane_strstatus(status));
    return status;
  }

  if (pause > 0) /* predefined time to wait (Astra 2400S) */
  {
    DBG(DBG_info2, "pause for repositioning %d msec ...\n", pause);
    usleep(((long) pause) * 1000);
    DBG(DBG_info, "repositioning pause done\n");
  }
  else if (pause == 0) /* use TEST UNIT READY */
  {
    WAIT_SCANNER;
    DBG(DBG_info, "scanner repositioned\n");
  }
  else /* pause < 0 : return without any pause */
  {
    DBG(DBG_info, "not waiting for finishing reposition scanner\n");
  }

  return SANE_STATUS_GOOD;
}


/* ------------------------------------------------------------ UMAX GIVE SCANNER -------------------------- */


static int umax_give_scanner(Umax_Device *dev)
{
 int status;

  DBG(DBG_info2, "trying to release scanner ...\n");
  status = umax_scsi_cmd(dev, release_unit.cmd, release_unit.size, NULL, NULL);
  if (status)
  {
    DBG(DBG_error, "umax_give_scanner: command returned status %s\n", sane_strstatus(status));
  }  
  else
  {
    DBG(DBG_info, "scanner released\n");
  }

  if (!dev->batch_scan || dev->batch_end)
  {
    umax_reposition_scanner(dev);
  }
  else
  {
    usleep(200000); /* 200 ms pause to make sure program does not exit before scanner is ready */
  }

  return status;
}


/* ------------------------------------------------------------ UMAX SEND GAMMA DATA ----------------------- */


static void umax_send_gamma_data(Umax_Device *dev, void *gamma_data, int color)
{
 unsigned char *data = gamma_data;
 unsigned char *dest;
 int length;
 SANE_Status status;

  DBG(DBG_proc, "send_gamma_data\n");

  if (dev->inquiry_gamma_dwload == 0)
  {
    DBG(DBG_error, "ERROR: gamma download not available\n");
    return;
  }
  
  memcpy(dev->buffer[0], send.cmd, send.size);							     /* send */
  set_S_datatype_code(dev->buffer[0], S_datatype_gamma);					      /* gamma curve */

  dest = dev->buffer[0] + send.size;

  if (dev->inquiry_gamma_DCF == 0)						      /* gamma format type 0 */
  {
    DBG(DBG_info, "using gamma download curve format type 0\n");

    memcpy(dest, gamma_DCF0.cmd, gamma_DCF0.size);

    if (color == 1)										/* one color */
    {
      set_DCF0_gamma_lines(dest, DCF0_gamma_one_line);

      set_DCF0_gamma_color(dest, 0, DCF0_gamma_color_gray);				        /* grayscale */
      if ( (dev->colormode == RGB) && (dev->three_pass != 0) )				     /* 3 pass color */
      {
        set_DCF0_gamma_color(dest, 0,  dev->three_pass_color);					/* set color */
      }

      dest = dest + 2;
      memcpy(dest, data, 1024);								/* copy data */

      set_S_xfer_length(dev->buffer[0], 1026);						       /* set length */
      status = umax_scsi_cmd(dev, dev->buffer[0], send.size + 1026, NULL, NULL);
      if (status)
      {
        DBG(DBG_error, "umax_send_gamma_data(DCF=0, one color): command returned status %s\n", sane_strstatus(status));
      }  
    }
    else										     /* three colors */
    {
      set_DCF0_gamma_lines(dest, DCF0_gamma_three_lines);

      set_DCF0_gamma_color(dest, 0, DCF0_gamma_color_red);					      /* red */
      set_DCF0_gamma_color(dest, 1, DCF0_gamma_color_green);					    /* green */
      set_DCF0_gamma_color(dest, 2, DCF0_gamma_color_blue);					     /* blue */

      dest = dest + 2;
      memcpy(dest, data, 1024);								    /* copy red data */

      dest = dest + 1025;
      data = data + 1024;
      memcpy(dest, data, 1024);								  /* copy green data */

      dest = dest + 1025;
      data = data + 1024;
      memcpy(dest, data, 1024);								   /* copy blue data */

      set_S_xfer_length(dev->buffer[0], 3076);						       /* set length */
      status = umax_scsi_cmd(dev, dev->buffer[0], send.size + 3076, NULL, NULL);
      if (status)
      {
        DBG(DBG_error, "umax_send_gamma_data(DCF=0, RGB): command returned status %s\n", sane_strstatus(status));
      }  
    }
  }
  else if (dev->inquiry_gamma_DCF == 1)						      /* gamma format type 1 */
  {
    DBG(DBG_info, "using gamma download curve format type 1\n");

    memcpy(dest, gamma_DCF1.cmd, gamma_DCF1.size);

    set_DCF1_gamma_color(dest, DCF1_gamma_color_gray);					        /* grayscale */
    if ( (dev->colormode == RGB) && (dev->three_pass != 0) )				     /* 3 pass color */
    {
      set_DCF1_gamma_color(dest,  dev->three_pass_color);					/* set color */
    }

    dest = dest + 2;
    memcpy(dest, data, 256);									/* copy data */

    set_S_xfer_length(dev->buffer[0], 258);						       /* set length */
    status = umax_scsi_cmd(dev, dev->buffer[0], send.size + 258, NULL, NULL);
    if (status)
    {
      DBG(DBG_error, "umax_send_gamma_data(DCF=1): command returned status %s\n", sane_strstatus(status));
    }  
  }
  else if (dev->inquiry_gamma_DCF == 2)						      /* gamma format type 2 */
  {
    DBG(DBG_info, "using gamma download curve format type 2\n");

    memcpy(dest, gamma_DCF2.cmd, gamma_DCF2.size);

    set_DCF2_gamma_color(dest, DCF2_gamma_color_gray);					        /* grayscale */
    if ( (dev->colormode == RGB) && (dev->three_pass != 0) )				     /* 3 pass color */
    { set_DCF2_gamma_color(dest, dev->three_pass_color); }				        /* set color */

    if (color == 1)
    {
      set_DCF2_gamma_lines(dest, DCF2_gamma_one_line);
    }
    else
    {
      set_DCF2_gamma_lines(dest, DCF2_gamma_three_lines);
    }

    set_DCF2_gamma_input_bits(dest, dev->gamma_input_bits_code);
    set_DCF2_gamma_output_bits(dest, dev->bits_per_pixel_code);

    dest = dev->buffer[0] + send.size + gamma_DCF2.size;					    /* write to dest */

    if (dev->gamma_input_bits_code & 32)
    {
      length = 65536; /* 16 input bits */
    }
    else if (dev->gamma_input_bits_code & 16)
    {
      length = 16384; /* 14 input bits */
    }
    else if (dev->gamma_input_bits_code & 8)
    {
      length = 4096; /* 12 input bits */
    }
    else if (dev->gamma_input_bits_code & 4)
    {
      length = 1024; /* 10 input bits */
    }
    else if (dev->gamma_input_bits_code & 2)
    {
      length = 512; /* 9 input bits */
    }
    else
    {
      length = 256; /* 8 input bits */
    }

    if (dev->bits_per_pixel_code != 1)					/* more than 8 output bits per pixel */
    {
      length = length * 2; /* = 2 output bytes */
    }

    if (dev->bufsize >= color*length+gamma_DCF2.size)
    {
      set_S_xfer_length(dev->buffer[0], color*length+gamma_DCF2.size);			       /* set length */
      memcpy(dest, data, color*length);								/* copy data */

      status = umax_scsi_cmd(dev, dev->buffer[0], send.size+gamma_DCF2.size + length * color, NULL, NULL);
      if (status)
      {
        DBG(DBG_error, "umax_send_gamma_data(DCF=2): command returned status %s\n", sane_strstatus(status));
      }  
    }
    else
    {
      DBG(DBG_error, "ERROR: too small scsi buffer (%d bytes) to send gamma data\n", dev->bufsize);
    }
  }
  else
  {
    DBG(DBG_error, "ERROR: unknown gamma download curve type for this scanner\n");
  }
}


/* ------------------------------------------------------------ UMAX SEND DATA  ---------------------------- */


static void umax_send_data(Umax_Device *dev, void *data, int size, int datatype)
{
 unsigned char *dest;
 SANE_Status status;

  memcpy(dev->buffer[0], send.cmd, send.size);							     /* send */
  set_S_datatype_code(dev->buffer[0], datatype);						     /* set datatype */
  set_S_xfer_length(dev->buffer[0], size);								    /* bytes */

  dest=dev->buffer[0] + send.size;
  memcpy(dest, data, size);									/* copy data */

  status = umax_scsi_cmd(dev, dev->buffer[0], send.size + size, NULL, NULL);
  if (status)
  {
    DBG(DBG_error, "umax_send_data: command returned status %s\n", sane_strstatus(status));
  }  
}


/* ------------------------------------------------------------ UMAX SEND HALFTONE PATTERN ----------------- */


#ifndef UMAX_HIDE_UNUSED
static void umax_send_halftone_pattern(Umax_Device *dev, void *data, int size)
{
  DBG(DBG_proc,"send_halftone_pattern\n");
  umax_send_data(dev, data, size*size, S_datatype_halftone);
}
#endif


/* ------------------------------------------------------------ UMAX SEND SHADING DATA  -------------------- */


static void umax_send_shading_data(Umax_Device *dev, void *data, int size)
{
  DBG(DBG_proc,"send_shading_data\n");
  umax_send_data(dev, data, size, S_datatype_shading);
}


/* ------------------------------------------------------------ UMAX SEND GAIN DATA  ----------------------- */

#ifndef UMAX_HIDE_UNUSED
static void umax_send_gain_data(Umax_Device *dev, void *data, int size)
{
  DBG(DBG_proc,"send_gain_data\n");
  umax_send_data(dev, data, size, S_datatype_gain);
}
#endif


/* ------------------------------------------------------------ UMAX QUEUE READ IMAGE DATA REQ ------------- */

static SANE_Status umax_queue_read_image_data_req(Umax_Device *dev, unsigned int length, int bufnr)
{
 SANE_Status status;

  DBG(DBG_proc, "umax_queue_read_image_data_req for buffer[%d], length = %d\n", bufnr, length);

  set_R_xfer_length(sread.cmd, length);							       /* set length */
  set_R_datatype_code(sread.cmd, R_datatype_imagedata);					     /* set datatype */

  dev->length_queued[bufnr] = length; /* set length request */
  dev->length_read[bufnr]   = length; /* set length request, can be changed asyncronous by umax_scsi_req_enter */

  status = umax_scsi_req_enter(dev, sread.cmd, sread.size, dev->buffer[bufnr], &(dev->length_read[bufnr]), &(dev->queue_id[bufnr]));
  if (status)
  {
    DBG(DBG_error, "umax_queue_read_image_data_req: command returned status %s\n", sane_strstatus(status));
    return -1;
  }
  else
  {
    DBG(DBG_info2, "umax_queue_read_image_data_req: id for buffer[%d] is %p\n", bufnr, dev->queue_id[bufnr]);
  }

  return length;
}

/* ------------------------------------------------------------ UMAX WAIT QUEUED IMAGE DATA ---------------- */


static int umax_wait_queued_image_data(Umax_Device *dev, int bufnr)
{
 SANE_Status status;

  DBG(DBG_proc, "umax_wait_queued_image_data for buffer[%d] (id=%p)\n", bufnr, dev->queue_id[bufnr]);

  status = umax_scsi_req_wait(dev, dev->queue_id[bufnr]);
  if (status)
  {
    DBG(DBG_error, "umax_wait_queued_image_data: wait returned status %s\n", sane_strstatus(status));
    return -1;
  }

 return SANE_STATUS_GOOD;
}


/* ------------------------------------------------------------ UMAX READ DATA ----------------------------- */


static int umax_read_data(Umax_Device *dev, size_t length, int datatype)
{
 SANE_Status status;

  set_R_xfer_length(sread.cmd, length);							       /* set length */
  set_R_datatype_code(sread.cmd, datatype);						     /* set datatype */

  status = umax_scsi_cmd(dev, sread.cmd, sread.size, dev->buffer[0], &length);
  if (status)
  {
    DBG(DBG_error, "umax_read_data: command returned status %s\n", sane_strstatus(status));
    return -1;
  }  

 return length;
}


/* ------------------------------------------------------------ UMAX READ SHADING DATA  -------------------- */


static int umax_read_shading_data(Umax_Device *dev, unsigned int length)
{
  DBG(DBG_proc,"read_shading_data\n");
  return umax_read_data(dev, length, R_datatype_shading);
}


/* ------------------------------------------------------------ UMAX READ GAIN DATA  ----------------------- */


#ifndef UMAX_HIDE_UNUSED
static int umax_read_gain_data(Umax_Device *dev, unsigned int length)
{
  DBG(DBG_proc,"read_gain_data\n");
  return umax_read_data(dev, length, R_datatype_gain);
}
#endif


/* ------------------------------------------------------------ UMAX READ IMAGE DATA  ---------------------- */


#ifndef UMAX_HIDE_UNUSED
static int umax_read_image_data(Umax_Device *dev, unsigned int length)
{
  DBG(DBG_proc,"read_image_data\n");
  WAIT_SCANNER;
  return umax_read_data(dev, length, R_datatype_imagedata);
}
#endif


/* ------------------------------------------------------------ UMAX CORRECT LIGHT ------------------------- */


static int umax_correct_light(int light, int analog_gamma_byte)  /* correct highlight/shadow if analog gamma is set */
{ 
  double analog_gamma;
  analog_gamma=analog_gamma_table[analog_gamma_byte];
  return( (int) 255 * pow(  ((double) light)/255.0 , (1.0/analog_gamma) )+.5 );
}


/* ------------------------------------------------------------ UMAX SET WINDOW PARAM ---------------------- */


/* set_window_param sets all the window parameters. This means building a */
/* fairly complicated SCSI command before sending it...  */

static SANE_Status umax_set_window_param(Umax_Device *dev)
{
 SANE_Status status;
 int num_dblocks = 1;		 		       /* number of window descriptor blocks, usually 1 or 3 */
 unsigned char buffer_r[max_WDB_size], buffer_g[max_WDB_size], buffer_b[max_WDB_size];

  DBG(DBG_proc, "set_window_param\n");
  memset(buffer_r, '\0', max_WDB_size);							     /* clear buffer */
  set_WDB_length(dev->wdb_len);						   /* length of win descriptor block */
  memcpy(buffer_r, window_descriptor_block.cmd, window_descriptor_block.size);		 /* copy preset data */

  set_WD_wid(buffer_r, 0);								/* window identifier */
  set_WD_auto(buffer_r, dev->set_auto);					    /* 0 or 1: don't know what it is */

												  /* geometry */
  set_WD_Xres(buffer_r, dev->x_resolution);					      /* x resolution in dpi */
  set_WD_Yres(buffer_r, dev->y_resolution);					      /* y resolution in dpi */
  set_WD_ULX(buffer_r, dev->upper_left_x);						      /* left_edge x */
  set_WD_ULY(buffer_r, dev->upper_left_y);						     /* upper_edge y */
  set_WD_width(buffer_r, dev->scanwidth);							    /* width */
  set_WD_length(buffer_r, dev->scanlength);							   /* length */

												       /* BTC */
  set_WD_brightness(buffer_r, dev->brightness);					/* brightness, only halftone */
  set_WD_threshold(buffer_r, dev->threshold);					  /* threshold, only lineart */
  set_WD_contrast(buffer_r, dev->contrast);					  /* contrast, only halftone */
    
									       /* scanmode, preset to LINEART */
  set_WD_composition(buffer_r, WD_comp_lineart);					/* image composition */
											     /* = (scan-mode) */
  set_WD_bitsperpixel(buffer_r, WD_bits_1);				   /* bits/pixel (1,8,9,10,12,14,16) */
  set_WD_halftone(buffer_r, dev->halftone);					  /* select halftone-pattern */
  set_WD_RIF(buffer_r, dev->reverse);					  /* reverse, invert black and white */
  set_WD_speed(buffer_r, dev->WD_speed);						        /* set speed */
  set_WD_select_color(buffer_r, WD_color_gray);					   /* color for window-block */

						   /* set highlight and shadow in dependence of analog gamma */
  set_WD_highlight(buffer_r, umax_correct_light(dev->highlight_r, dev->analog_gamma_r));
  set_WD_shadow(buffer_r, umax_correct_light(dev->shadow_r, dev->analog_gamma_r));

											      /* scan options */
  set_WD_gamma(buffer_r, dev->digital_gamma_r);						/* set digital gamma */ 
  set_WD_module(buffer_r, dev->module);						  /* flatbed or transparency */ 
  set_WD_CBHS(buffer_r, dev->cbhs_range);							/* 50 or 255 */ 
  set_WD_FF(buffer_r, dev->fix_focus_position);					       /* fix focus position */
  set_WD_RMIF(buffer_r, dev->reverse_multi);					     /* reverse color-values */
  set_WD_FDC(buffer_r, dev->lens_cal_in_doc_pos);		    /* lens calibration in document position */
  set_WD_PF(buffer_r, dev->disable_pre_focus);						/* disable pre focus */
  set_WD_LCL(buffer_r, dev->holder_focus_pos_0mm);		    /* 0.6mm <-> 0.0mm holder focus position */
  set_WD_HBT(buffer_r, dev->low_byte_first);			       /* set byte order for 16 bit scanners */
  set_WD_DOR(buffer_r, dev->dor);						   /* double-resolution-mode */ 
  set_WD_scan_exposure_level(buffer_r, dev->exposure_time_scan_r);		       /* scan exposure time */
  set_WD_calibration_exposure_level(buffer_r, dev->exposure_time_calibration_r);/* calibration exposure time */

  set_WD_batch(buffer_r, dev->batch_scan);					     /* batch or normal scan */
  set_WD_MF(buffer_r, dev->manual_focus);				       /* automatic <-> manual focus */
  set_WD_line_arrangement(buffer_r, WD_line_arrengement_by_fw);		      /* line arrangement by scanner */
  set_WD_warmup(buffer_r, dev->warmup);								   /* warmup */

  set_WD_calibration(buffer_r, dev->calibration); 				        /* image calibration */

  set_WD_color_sequence(buffer_r, WD_color_sequence_RGB);				     /* sequence RGB */
  set_WD_color_ordering(buffer_r, WD_color_ordering_pixel);	      /* set to pixel for pbm, pgm, pnm-file */
  set_WD_analog_gamma(buffer_r, dev->analog_gamma_r );					     /* analog gamma */
  set_WD_lamp_c_density(buffer_r, dev->c_density);				   /* calibrat. lamp density */
  set_WD_lamp_s_density(buffer_r, dev->s_density);					/* scan lamp density */
  set_WD_next_upper_left(buffer_r, dev->batch_next_tl_y);	      /* batch scan next top left y position */
  set_WD_pixel_count(buffer_r, dev->width_in_pixels);					      /* pixel count */
  set_WD_line_count(buffer_r, dev->length_in_pixels);					       /* line count */
  set_WD_x_coordinate_base(buffer_r, dev->x_coordinate_base);				       /* dpi (1200) */
  set_WD_y_coordinate_base(buffer_r, dev->y_coordinate_base);				       /* dpi (1200) */
  set_WD_calibration_data_lines(buffer_r, dev->calib_lines);     /* required lines for calibration by driver */


  switch(dev->colormode)
  {
     case LINEART:										   /* LINEART */
      set_WD_composition(buffer_r, WD_comp_lineart);
      set_WD_bitsperpixel(buffer_r, WD_bits_1);

      set_WD_select_color(buffer_r, WD_color_gray);
     break;

     case HALFTONE:										  /* HALFTONE */
      set_WD_composition(buffer_r, WD_comp_dithered);
      set_WD_bitsperpixel(buffer_r, WD_bits_1);

      set_WD_select_color(buffer_r, WD_color_gray);
     break;

     case GRAYSCALE:										 /* GRAYSCALE */
      set_WD_composition(buffer_r, WD_comp_gray);
      set_WD_bitsperpixel(buffer_r, dev->bits_per_pixel);

      set_WD_select_color(buffer_r, WD_color_gray);
     break;

     case RGB_LINEART:								              /* COLOR MODES */
     case RGB_HALFTONE:
     case RGB:
      if (dev->colormode == RGB_LINEART )
      {
        set_WD_composition(buffer_r, WD_comp_rgb_bilevel);
        set_WD_bitsperpixel(buffer_r, WD_bits_1);
      }
      else if (dev->colormode == RGB_HALFTONE )
      {
        set_WD_composition(buffer_r, WD_comp_rgb_dithered);
        set_WD_bitsperpixel(buffer_r, WD_bits_1);
      }
      else /* RGB */
      {
        set_WD_composition(buffer_r, WD_comp_rgb_full);
        set_WD_bitsperpixel(buffer_r, dev->bits_per_pixel);
      }

      if (dev->three_pass == 0)
      {											       /* singlepass */
        num_dblocks = 3;

        if (dev->do_color_ordering != 0)
	{
          set_WD_line_arrangement(buffer_r, WD_line_arrengement_by_driver); 

	  if (dev->CCD_distance == 0)
	  {
            set_WD_color_ordering(buffer_r, WD_color_ordering_line_no_ccd);
          }
	  else
	  {
            set_WD_color_ordering(buffer_r, WD_color_ordering_line_w_ccd);
          }
        }

        memcpy(buffer_g, buffer_r, max_WDB_size);				       /* copy WDB for green */
        memcpy(buffer_b, buffer_r, max_WDB_size);					/* copy WDB for blue */

        set_WD_wid(buffer_r, WD_wid_red);					    /* window identifier red */
        set_WD_wid(buffer_g, WD_wid_green);					  /* window identifier green */
        set_WD_wid(buffer_b, WD_wid_blue);					   /* window identifier blue */

        set_WD_select_color(buffer_r, WD_color_red);			       /* select red for this window */
        set_WD_select_color(buffer_g, WD_color_green);			     /* select green for this window */
        set_WD_select_color(buffer_b, WD_color_blue);			      /* select blue for this window */

        set_WD_gamma(buffer_r, dev->digital_gamma_r);					    /* digital gamma */
        set_WD_gamma(buffer_g, dev->digital_gamma_g);
        set_WD_gamma(buffer_b, dev->digital_gamma_b);

        set_WD_analog_gamma(buffer_r, dev->analog_gamma_r);				     /* analog gamma */
        set_WD_analog_gamma(buffer_g, dev->analog_gamma_g);
        set_WD_analog_gamma(buffer_b, dev->analog_gamma_b);

							      /* set highlight in dependence of analog gamma */ 
        set_WD_highlight(buffer_r, umax_correct_light(dev->highlight_r, dev->analog_gamma_r));
        set_WD_highlight(buffer_g, umax_correct_light(dev->highlight_g, dev->analog_gamma_g));
        set_WD_highlight(buffer_b, umax_correct_light(dev->highlight_g, dev->analog_gamma_b));

								 /* set shadow in dependence of analog gamma */ 
        set_WD_shadow(buffer_r, umax_correct_light(dev->shadow_r, dev->analog_gamma_r));
        set_WD_shadow(buffer_g, umax_correct_light(dev->shadow_g, dev->analog_gamma_g));
        set_WD_shadow(buffer_b, umax_correct_light(dev->shadow_b, dev->analog_gamma_b));

        set_WD_scan_exposure_level(buffer_r, dev->exposure_time_scan_r);	  /* set scan exposure times */ 
        set_WD_scan_exposure_level(buffer_g, dev->exposure_time_scan_g);
        set_WD_scan_exposure_level(buffer_b, dev->exposure_time_scan_b);

        set_WD_calibration_exposure_level(buffer_r, dev->exposure_time_calibration_r);/* set calib exp times */
        set_WD_calibration_exposure_level(buffer_g, dev->exposure_time_calibration_g);
        set_WD_calibration_exposure_level(buffer_b, dev->exposure_time_calibration_b);
      }
      else
      {												/* threepass */
        set_WD_wid(buffer_r, 0);							/* window identifier */
        set_WD_color_ordering(buffer_r, WD_color_ordering_plane);				     /* ???? */

        if (dev->colormode == RGB_LINEART )
        {
          set_WD_composition(buffer_r, WD_comp_lineart);			       /* color-lineart-mode */
        }
        else if (dev->colormode == RGB_HALFTONE )
        {
          set_WD_composition(buffer_r, WD_comp_dithered);			      /* color-halftone-mode */
        }
        else /* RGB */
        {
          set_WD_composition(buffer_r, WD_comp_gray);					       /* color-mode */
        }

        switch (dev->three_pass_color)
        {
        case WD_wid_red:
           set_WD_select_color(buffer_r, WD_color_red);					        /* color red */
           set_WD_gamma(buffer_r, dev->digital_gamma_r);
           set_WD_analog_gamma(buffer_r, dev->analog_gamma_r);
           set_WD_highlight(buffer_r, umax_correct_light(dev->highlight_r, dev->analog_gamma_r));
           set_WD_shadow(buffer_r, umax_correct_light(dev->shadow_r, dev->analog_gamma_r)); 
           set_WD_scan_exposure_level(buffer_r, dev->exposure_time_scan_r);
           set_WD_calibration_exposure_level(buffer_r, dev->exposure_time_calibration_r);
           break;

        case WD_wid_green:
           set_WD_select_color(buffer_r, WD_color_green);				      /* color green */
           set_WD_gamma(buffer_r, dev->digital_gamma_g);
           set_WD_analog_gamma(buffer_r, dev->analog_gamma_g);
           set_WD_highlight(buffer_r, umax_correct_light(dev->highlight_g, dev->analog_gamma_g));
           set_WD_shadow(buffer_r, umax_correct_light(dev->shadow_g, dev->analog_gamma_g));
           set_WD_scan_exposure_level(buffer_r, dev->exposure_time_scan_g);
           set_WD_calibration_exposure_level(buffer_r, dev->exposure_time_calibration_g);
           break;

        case WD_wid_blue:
           set_WD_select_color(buffer_r, WD_color_blue);				       /* color blue */
           set_WD_gamma(buffer_r, dev->digital_gamma_b);
           set_WD_analog_gamma(buffer_r, dev->analog_gamma_b);
           set_WD_highlight(buffer_r, umax_correct_light(dev->highlight_b, dev->analog_gamma_b));
           set_WD_shadow(buffer_r, umax_correct_light(dev->shadow_b, dev->analog_gamma_b));
           set_WD_scan_exposure_level(buffer_r, dev->exposure_time_scan_b);
           set_WD_calibration_exposure_level(buffer_r, dev->exposure_time_calibration_b);
           break;

        } /* switch dev->three_pass_color */

      } /* if (single_pass) else (three_pass) */
     break;
  } /* switch dev->colormode, case RGB */

										       /* prepare SCSI-BUFFER */
  memcpy(dev->buffer[0], set_window.cmd, set_window.size);					   /* SET-WINDOW cmd */
  memcpy(WPDB_OFF(dev->buffer[0]), window_parameter_data_block.cmd, window_parameter_data_block.size);   /* WPDB */
  set_WPDB_wdbnum(WPDB_OFF(dev->buffer[0]), num_dblocks);					       /* set WD_len */
  memcpy(WDB_OFF(dev->buffer[0],1), buffer_r, window_descriptor_block.size);		     /* add WD_block */

  if ( num_dblocks == 3)								/* if singelpass RGB */
  {
     memcpy(WDB_OFF(dev->buffer[0],2), buffer_g, window_descriptor_block.size);			/* add green */
     memcpy(WDB_OFF(dev->buffer[0],3), buffer_b, window_descriptor_block.size);			 /* add blue */
  }


  DBG(DBG_info2, "window descriptor block created with %d bytes\n", dev->wdb_len);

  set_SW_xferlen(dev->buffer[0], (window_parameter_data_block.size + (window_descriptor_block.size * num_dblocks)));

  status = umax_scsi_cmd(dev, dev->buffer[0], set_window.size + window_parameter_data_block.size +
                                              (window_descriptor_block.size * num_dblocks), NULL, NULL);
  if (status)
  {
    DBG(DBG_error, "umax_set_window_param: command returned status %s\n", sane_strstatus(status));
  }  
  else
  {
    DBG(DBG_info, "window(s) set\n"); 
  }

 return status;
}


/* ------------------------------------------------------------ UMAX DO INQUIRY ---------------------------- */


static void umax_do_inquiry(Umax_Device *dev)
{
 size_t size;
 SANE_Status status;

  DBG(DBG_proc,"do_inquiry\n");
  memset(dev->buffer[0], '\0', 256);							     /* clear buffer */

  size = 5;

  set_inquiry_return_size(inquiry.cmd, size);  /* first get only 5 bytes to get size of inquiry_return_block */
  status = umax_scsi_cmd(dev, inquiry.cmd, inquiry.size, dev->buffer[0], &size);
  if (status)
  {
    DBG(DBG_error, "umax_do_inquiry: command returned status %s\n", sane_strstatus(status));
  }  

  size = get_inquiry_additional_length(dev->buffer[0]) + 5;

  set_inquiry_return_size(inquiry.cmd, size);			        /* then get inquiry with actual size */
  status = umax_scsi_cmd(dev, inquiry.cmd, inquiry.size, dev->buffer[0], &size);
  if (status)
  {
    DBG(DBG_error, "umax_do_inquiry: command returned status %s\n", sane_strstatus(status));
  }  
}


/* ------------------------------------------------------------ UMAX START SCAN ---------------------------- */


static SANE_Status umax_start_scan(Umax_Device *dev)
{
 int size = 1;
 SANE_Status status;

  DBG(DBG_proc,"start_scan\n");

  if (dev->adf) 							/* ADF selected: test for ADF errors */
  {
    umax_do_inquiry(dev);								      /* get inquiry */

    if (get_inquiry_ADF_paper_jam(dev->buffer[0]))					   /* test for ADF paper jam */
    {
      DBG(DBG_error,"ERROR: umax_start_scan: ADF paper jam\n");
      return SANE_STATUS_JAMMED;
    }
    else if (get_inquiry_ADF_cover_open(dev->buffer[0]))				  /* test for ADF cover open */
    {
      DBG(DBG_error,"ERROR: umax_start_scan: ADF cover open\n");
      return SANE_STATUS_COVER_OPEN;
    }
    else if (get_inquiry_ADF_no_paper(dev->buffer[0]))				    /* test for ADF no paper */
    {
      DBG(DBG_error,"ERROR: umax_start_scan: ADF no paper\n");
      return SANE_STATUS_NO_DOCS;
    }
  }

  set_SC_quality(scan.cmd, dev->quality);						  /*  1=qual, 0=fast */
  set_SC_adf(    scan.cmd, dev->adf);							/* ADF, 0=off, 1=use */
  set_SC_preview(scan.cmd, dev->preview);							/* 1=preview */
  
  set_SC_wid(scan.cmd, 1, 0);								/* Window-Identifier */

  set_SC_xfer_length(scan.cmd, size);							  /* following Bytes */

  DBG(DBG_info,"starting scan\n");

  status = umax_scsi_cmd(dev, scan.cmd, scan.size + size, NULL, NULL);
  if (status)
  {
    DBG(DBG_error, "umax_start_scan: command returned status %s\n", sane_strstatus(status));
  }  

 return status;
}


/* ------------------------------------------------------------ UMAX DO CALIBRATION ------------------------ */


static SANE_Status umax_do_calibration(Umax_Device *dev)
{
 SANE_Status status;
 unsigned int width   = 0;
 unsigned int lines   = 0;
 unsigned int bytespp = 0;

  DBG(DBG_proc,"do_calibration\n");

  status = umax_wait_scanner(dev);

  if ((status == SANE_STATUS_GOOD) && (dev->do_calibration != 0))			    /* calibration by driver */
  {
   unsigned char *shading_data = 0;
   unsigned int i, j;
   long *average;


    DBG(DBG_info,"driver is doing calibration\n");


    if (umax_execute_request_sense)
    {
      DBG(DBG_info,"request sense call is enabled\n");
      memset(dev->buffer[0], 0, rs_return_block_size);					  /* clear sense data buffer */
      umax_do_request_sense(dev);					   /* new request-sense call to get all data */
    }
    else
    {
      DBG(DBG_info,"request sense call is disabled\n");
    }

    if (get_RS_SCC_condition_code(dev->buffer[0]) != 1)
    {
      DBG(DBG_warning,"WARNING: missing information about shading-data\n");
      DBG(DBG_warning,"         driver tries to guess missing values!\n");

      if ((dev->calibration_area != UMAX_CALIBRATION_AREA_CCD) && (!dev->batch_scan))
      /* calibration is done with image geometry and depth */
      {
        DBG(DBG_warning,"         Calibration is done with selected image geometry and depth!\n");

        width = dev->scanwidth * dev->relevant_optical_res / dev->x_coordinate_base;

        if (dev->calibration_width_offset > -99999) /* driver or user (umax.conf) define an offset */
        {
          width = width + dev->calibration_width_offset; 
          DBG(DBG_warning,"         Using calibration width offset of %d\n", dev->calibration_width_offset);
        }

        if (dev->colormode == RGB)
        {
          width = width * 3;
        }

        lines   = dev->calib_lines;

        if (dev->gamma_input_bits_code <= 1)
        {
          bytespp = 1; /* 8 bit mode */
        }
        else
        {
          bytespp = 2; /* 16 bit mode */
        }
      }
      else /*  calibration is done with full scanarea and full depth */
      {
        DBG(DBG_warning,"         Calibration is done for each CCD pixel with full depth!\n");

        width = (int)(dev->inquiry_fb_width * dev->inquiry_optical_res);

        if (dev->batch_scan)
        {
          if (dev->calibration_width_offset_batch > -99999) /* driver or user (umax.conf) define an offset for batch scanning */
          {
            width = width + dev->calibration_width_offset_batch; 
            DBG(DBG_warning,"         Using calibration width offset for batch scanning of %d\n", dev->calibration_width_offset_batch);
          }
        }
        else /* normal scan */
        {
          if (dev->calibration_width_offset > -99999) /* driver or user (umax.conf) define an offset */
          {
            width = width + dev->calibration_width_offset; 
            DBG(DBG_warning,"         Using calibration width offset of %d\n", dev->calibration_width_offset);
          }
        }

        if (dev->colormode == RGB)
        {
          width = width * 3;
        }

        lines = dev->calib_lines;

        if (dev->gamma_input_bits_code <= 1)
        {
          bytespp = 1; /* 8 bit mode */
         }
        else
        {
          bytespp = 2; /* 16 bit mode */
        }
      }
    }
    else
    {
      lines   =  get_RS_SCC_calibration_lines(dev->buffer[0]);
      bytespp =  get_RS_SCC_calibration_bytespp(dev->buffer[0]);
      width   =  get_RS_SCC_calibration_bytesperline(dev->buffer[0]) / bytespp;
    }

    if (dev->calibration_bytespp > 0) /* correct bytespp if necessary and driver knows about it or user did select it */
    {
      bytespp = dev->calibration_bytespp;
    }

    DBG(DBG_info,"scanner sends %d lines with %d pixels and %d bytes/pixel\n", lines, width, bytespp);

    if (width * bytespp > dev->bufsize)
    {
      DBG(DBG_error,"ERROR: scsi buffer is to small for one shading line, calibration aborted\n");
      DBG(DBG_error,"=> change umax.conf options scsi-buffer-size-min and scsi-buffer-size-max\n");
     return SANE_STATUS_NO_MEM;
    }

    /* UMAX S12 sends a kind of uncalibrated image data, bright -> 255, dark -> 0 */
    /* (although 0 is not black) my scanner sends values around 220 */
    /* for some scanners the data is simply sent back, other scanners want 255-value as awnswer */

    average = calloc(width, sizeof(long));
    if (average == 0)
    {
      DBG(DBG_error,"ERROR: could not allocate memory for averaging shading data: calibration aborted\n");
     return SANE_STATUS_NO_MEM;
    }

    shading_data = calloc(width, bytespp);
    if (shading_data == 0)
    {
      DBG(DBG_error,"ERROR: could not allocate memory for shading data: calibration aborted\n");
     return SANE_STATUS_NO_MEM;
    }

    if (bytespp == 1)					 /* 1 byte per pixel */
    {
      DBG(DBG_info,"calculating average value for 8 bit shading data!\n");

      for (i=0; i<lines; i++)
      {
        umax_read_shading_data(dev, width * bytespp);

        for (j=0; j<width; j++)
        {
          average[j] += (long) dev->buffer[0][j];
        }

        DBG(DBG_read,"8 bit shading-line %d read\n", i+1);
      }

      for (j=0; j<width; j++)
      {
        shading_data[j] = (unsigned char) (average[j] / lines);
      }
    }
    else if (dev->low_byte_first) /* 2 bytes per pixel with low byte first */
    {
      DBG(DBG_info,"calculating average value for 16 bit shading data (low byte first)!\n");
      for (i=0; i<lines; i++)
      {
        umax_read_shading_data(dev, width * bytespp);

        for (j=0; j<width; j++)
        {
          average[j] += (long) 256 * dev->buffer[0][2*j+1] + dev->buffer[0][2*j] ;
        }

        DBG(DBG_read,"16 bit shading-line %d read\n", i+1);
      }

      for (j=0; j<width; j++)
      {
        shading_data[2*j+1] = (unsigned char) (average[j] / (256 * lines));
        shading_data[2*j]   = (unsigned char) (average[j] / lines);
      }
    }
    else					/* 2 bytes per pixel with highbyte first */
    {
      DBG(DBG_info,"calculating average value for 16 bit shading data (high byte first)!\n");
      for (i=0; i<lines; i++)
      {
        umax_read_shading_data(dev, width * bytespp);

        for (j=0; j<width; j++)
        {
          average[j] += (long) 256 * dev->buffer[0][2*j] + dev->buffer[0][2*j + 1] ;
        }

        DBG(DBG_read,"16 bit shading-line %d read\n", i+1);
      }

      for (j=0; j<width; j++)
      {
        shading_data[2*j]   = (unsigned char) (average[j] / (256 * lines));
        shading_data[2*j+1] = (unsigned char) (average[j] / lines);
      }
    }

    free(average);

    if ( (dev->invert_shading_data) ) /* invert data */
    {
      if (bytespp == 1)
      {
        DBG(DBG_info,"inverting 8 bit shading data\n");

        for (j=0; j<width; j++)
        {
          shading_data[j] = 255 - shading_data[j];
        }
      }
      else
      {
       unsigned int value;

        DBG(DBG_info,"inverting 16 bit shading data\n");

        for (j=0; j<width; j++)
        {
          value = shading_data[2*j] + shading_data[2*j+1] * 256;
          value = 65535 - value;
          shading_data[2*j]   = (unsigned char) value/256;
          shading_data[2*j+1] = (unsigned char) value & 255;
        }
      }
    }

    umax_send_shading_data(dev, shading_data, width * bytespp);
    DBG(DBG_info,"shading-data sent\n");
    free(shading_data);

    status = umax_start_scan(dev);						      /* now start real scan */

    dev->do_calibration = 0;
  }

 return status;
}


/* ------------------------------------------------------------ UMAX DO NEW INQUIRY ------------------------ */


static void umax_do_new_inquiry(Umax_Device *dev, size_t size)	       /* call inquiry again if wrong length */
{
 SANE_Status status;

  DBG(DBG_proc,"do_new_inquiry\n");
  memset(dev->buffer[0], '\0', 256);							     /* clear buffer */

  set_inquiry_return_size(inquiry.cmd, size);
  status = umax_scsi_cmd(dev, inquiry.cmd, inquiry.size, dev->buffer[0], &size);
  if (status)
  {
    DBG(DBG_error, "umax_do_new_inquiry: command returned status %s\n", sane_strstatus(status));
  }  
}


/* ------------------------------------------------------------ UMAX CORRECT INQUIRY ----------------------- */


static void umax_correct_inquiry(Umax_Device *dev, char *vendor, char *product, char *version)
{
  DBG(DBG_info, "umax_correct_inquiry(\"%s %s %s\")\n", vendor, product, version);

  if (!strncmp(vendor, "UMAX ", 5))
  {
    if (!strncmp(product, "Astra 600S ", 11))
    {
     int add_len = get_inquiry_additional_length(dev->buffer[0]);

      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (add_len == 0x8f)
      {
        DBG(DBG_warning," - correcting wrong inquiry data\n");
	umax_do_new_inquiry(dev, 0x9b);		  /* get inquiry with correct length */
        set_inquiry_length(dev->buffer[0], 0x9e); /* correct inquiry len */
					      /* correct color-ordering from pixel to line_with_ccd_distance */
        set_inquiry_color_order(dev->buffer[0], IN_color_ordering_line_w_ccd);
	set_inquiry_fb_uta_line_arrangement_mode(dev->buffer[0], 32);
	set_inquiry_CCD_line_distance(dev->buffer[0], 8);
        /* we should reset ADF-bit here too */

        if (dev->invert_shading_data == -1) /* nothing defined in umax.conf */
        {
          DBG(DBG_warning," - activating inversion of shading data\n");
          dev->invert_shading_data = 1;
        }
      }
    }
    else if (!strncmp(product, "Astra 610S ", 11))
    {
     int add_len = get_inquiry_additional_length(dev->buffer[0]);

      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (add_len == 0x8f)
      {
        DBG(DBG_warning," - correcting wrong inquiry data\n");
	umax_do_new_inquiry(dev, 0x9b);		  /* get inquiry with correct length */
        set_inquiry_length(dev->buffer[0], 0x9e); /* correct inquiry len */
					      /* correct color-ordering from pixel to line_with_ccd_distance */
        set_inquiry_color_order(dev->buffer[0], IN_color_ordering_line_w_ccd);
	set_inquiry_fb_uta_line_arrangement_mode(dev->buffer[0], 33);
	set_inquiry_CCD_line_distance(dev->buffer[0], 8);

        if (dev->invert_shading_data == -1) /* nothing defined in umax.conf */
        {
          DBG(DBG_warning," - activating inversion of shading data\n");
          dev->invert_shading_data = 1;
        }
      }
    }
    else if ( (!strncmp(product, "Astra 1200S ", 12)) ||
              (!strncmp(product, "Perfection600 ", 14)) )
    {
      DBG(DBG_warning,"using standard options for %s\n", product);
    }
    else if (!strncmp(product, "Astra 1220S ", 12))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (dev->gamma_lsb_padded == -1) /* nothing defined in umax.conf and not by backend */
      {
        DBG(DBG_warning," - 16 bit gamma table is created lsb padded\n");
        dev->gamma_lsb_padded = 1;
      }

      if (!strncmp(version, "V1.5 ", 4))
      {
        DBG(DBG_warning," - lamp control enabled for version %s\n", version);
        dev->lamp_control_available = 1;
      }
    }
    else if (!strncmp(product, "Astra 2100S ", 12))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);
      DBG(DBG_warning," - lamp control enabled\n");
      dev->lamp_control_available = 1;

      if (dev->calibration_bytespp == -1) /* no calibration-bytespp defined in umax.conf */
      {
        DBG(DBG_warning," - setting calibration_bytespp = 1\n");
        dev->calibration_bytespp = 1; /* scanner says 2 bytespp for calibration but 1 bytepp is correct */
      }
    }
    else if (!strncmp(product, "Astra 2200 ", 11))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);
      DBG(DBG_warning," - lamp control enabled\n");
      dev->lamp_control_available = 1;

      if (dev->calibration_area == -1) /* no calibration area defined in umax.conf */
      {
        DBG(DBG_warning," - calibration by driver is done for each CCD pixel\n");
        dev->calibration_area = UMAX_CALIBRATION_AREA_CCD;
      }

      if (dev->calibration_bytespp == -1) /* no calibration-bytespp defined in umax.conf */
      {
        DBG(DBG_warning," - setting calibration_bytespp = 2\n");
        dev->calibration_bytespp = 2;
      }

      DBG(DBG_warning," - common x and y resolution\n");
      dev->common_xy_resolutions = 1;

      if (dev->connection_type == SANE_UMAX_USB)
      {
        DBG(DBG_warning," - disabling quality calibration for USB connection\n");
	set_inquiry_fw_quality(dev->buffer[0], 0);
      }
    }
    else if (!strncmp(product, "Astra 2400S ", 12))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);
      DBG(DBG_warning," - defining pauses\n");
      dev->pause_for_color_calibration = 7000;		/* pause between start_scan and do_calibration in ms */
      dev->pause_for_gray_calibration = 4000;		/* pause between start_scan and do_calibration in ms */
      dev->pause_after_calibration = 0000;		 /* pause between do_calibration and read data in ms */
      dev->pause_after_reposition = 3000;			      /* pause after repostion scanner in ms */
      dev->pause_for_moving = 3000;			         /* pause for moving scanhead over full area */

      DBG(DBG_warning," - correcting ADF bit in inquiry\n");
      set_inquiry_sc_adf(dev->buffer[0], 1);		   /* set second bit that indicates ADF is supported */
    }
    else if (!strncmp(product, "Vista-T630 ", 11))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (dev->slow == -1) /* option is not predefined in umax.conf */
      {
        DBG(DBG_warning," - activating slow option\n");
        dev->slow = 1;
      }
    }
    else if (!strncmp(product, "UC630 ", 6))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);
      DBG(DBG_warning," - reposition_scanner waits until move of scan head has finished\n");
      dev->pause_after_reposition = 0;	    /* call wait_scanner */
    }
    else if (!strncmp(product, "UC840 ", 6))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);
      DBG(DBG_warning," - reposition_scanner waits until move of scan head has finished\n");
      dev->pause_after_reposition = 0;	    /* call wait_scanner */
    }
    else if (!strncmp(product, "UC1260 ", 7))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);
      DBG(DBG_warning," - setting gamma download curve format to type 1\n");
      dev->inquiry_gamma_DCF = 1;				       /* define gamma download curve format */
      DBG(DBG_warning," - reposition_scanner waits until move of scan head has finished\n");
      dev->pause_after_reposition = 0;     /* call wait_scanner */
    }
    else if (!strncmp(product, "UC1200S ", 8))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);
      DBG(DBG_warning," - setting gamma download curve format to type 1\n");
      dev->inquiry_gamma_DCF = 1;				       /* define gamma download curve format */
    }
    else if (!strncmp(product, "UC1200SE ", 9))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);
      DBG(DBG_warning," - setting gamma download curve format to type 0\n");
      dev->inquiry_gamma_DCF = 0;				       /* define gamma download curve format */
    }
    else if (!strncmp(product, "ARCUS PLUS ", 11))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);
      DBG(DBG_warning," - setting gamma download curve format to type 0\n");
      dev->inquiry_gamma_DCF = 0;				       /* define gamma download curve format */
    }
    else if ( (!strncmp(product, "UMAX S-12G ", 11)) ||
              (!strncmp(product, "UMAX S-12 ", 10)) ||
              (!strncmp(product, "SuperVista S-12 ", 16)) )
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      DBG(DBG_warning," - setting maximum calibration data lines to 66\n");
      set_inquiry_max_calibration_data_lines(dev->buffer[0], 66);

      if (dev->calibration_width_offset == -99999) /* no calibration-width-offset defined in umax.conf */
      {
        dev->calibration_width_offset = -1;
        DBG(DBG_warning," - adding calibration width offset of %d pixels\n", dev->calibration_width_offset);
      }

      if (dev->calibration_area == -1) /* no calibration area defined in umax.conf */
      {
        DBG(DBG_warning," - calibration by driver is done for each CCD pixel\n");
        dev->calibration_area = UMAX_CALIBRATION_AREA_CCD;
      }
    }
    else if (!strncmp(product, "Mirage D-16L ", 13))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);
      if (dev->calibration_area == -1) /* no calibration area defined in umax.conf */
      {
        DBG(DBG_warning," - calibration by driver is done for each CCD pixel\n");
        dev->calibration_area = UMAX_CALIBRATION_AREA_CCD;
      }

      if (dev->calibration_width_offset == -99999) /* no calibration-width-offset defined in umax.conf */
      {
        dev->calibration_width_offset = 308;
        DBG(DBG_warning," - adding calibration width offset of %d pixels\n", dev->calibration_width_offset);
      }
    }
    else if (!strncmp(product, "PowerLook III ", 14))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (dev->calibration_width_offset == -99999) /* no calibration-width-offset defined in umax.conf */
      {
        dev->calibration_width_offset = 28;
        DBG(DBG_warning," - adding calibration width offset of %d pixels\n", dev->calibration_width_offset);
      }
      /* calibration_area = image */

      if (dev->calibration_width_offset_batch == -99999) /* no calibration-width-offset for batch scanning defined in umax.conf */
      {
        dev->calibration_width_offset_batch = 828;
        DBG(DBG_warning," - adding calibration width offset for batch scanning of %d pixels\n", dev->calibration_width_offset_batch);
      }
    }
    else if (!strncmp(product, "Power Look 2000", 15))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (dev->calibration_width_offset == -99999) /* no calibration-width-offset defined in umax.conf */
      {
        dev->calibration_width_offset = 22;
        DBG(DBG_warning," - adding calibration width offset of %d pixels\n", dev->calibration_width_offset);
      }
      /* calibration_area = image */

      if (dev->calibration_width_offset_batch == -99999) /* no calibration-width-offset for batch scanning defined in umax.conf */
      {
        dev->calibration_width_offset_batch = 24;
        DBG(DBG_warning," - adding calibration width offset for batch scanning of %d pixels\n", dev->calibration_width_offset_batch);
      }
    }
    else if (!strncmp(product, "PowerLook 2100XL", 16))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (dev->calibration_width_offset == -99999) /* no calibration-width-offset defined in umax.conf */
      {
        dev->calibration_width_offset = 52;
        DBG(DBG_warning," - adding calibration width offset of %d pixels\n", dev->calibration_width_offset);
      }
      /* calibration_area = image */

      if (dev->calibration_width_offset_batch == -99999) /* no calibration-width-offset for batch scanning defined in umax.conf */
      {
        dev->calibration_width_offset_batch = 1052;
        DBG(DBG_warning," - adding calibration width offset for batch scanning of %d pixels\n", dev->calibration_width_offset_batch);
      }

      dev->force_quality_calibration = 1;
      DBG(DBG_warning," - always set quality calibration\n");

      /* the scanner uses the same exposure times for red, green and blue exposure_time_rgb_bind = 1 */
    }
    else if (!strncmp(product, "PowerLook 3000 ", 15))
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (dev->calibration_width_offset == -99999) /* no calibration-width-offset defined in umax.conf */
      {
        dev->calibration_width_offset = 52;
        DBG(DBG_warning," - adding calibration width offset of %d pixels\n", dev->calibration_width_offset);
      }
      /* calibration_area = image */

      if (dev->calibration_width_offset_batch == -99999) /* no calibration-width-offset for batch scanning defined in umax.conf */
      {
        /* not tested */
        dev->calibration_width_offset_batch = 1052;
        DBG(DBG_warning," - adding calibration width offset for batch scanning of %d pixels\n", dev->calibration_width_offset_batch);
      }
    }
    else
    {
      DBG(DBG_warning,"using standard options for %s\n", product);
    }
  }
  else if (!strncmp(vendor, "LinoHell ", 9))
  {
    if ( (!strncmp(product, "Office ", 7)) || (!strncmp(product, "JADE ", 5)) ) /* is a Supervista S-12 */
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      DBG(DBG_warning," - setting maximum calibration data lines to 66\n");
      set_inquiry_max_calibration_data_lines(dev->buffer[0], 66);

      if (dev->calibration_width_offset == -99999) /* no calibration-width-offset defined in umax.conf */
      {
        dev->calibration_width_offset = -1;
        DBG(DBG_warning," - adding calibration width offset of %d pixels\n", dev->calibration_width_offset);
      }

      if (dev->calibration_area == -1) /* no calibration area defined in umax.conf */
      {
        DBG(DBG_warning," - calibration by driver is done for each CCD pixel\n");
        dev->calibration_area = UMAX_CALIBRATION_AREA_CCD;
      }
    }
    else if (!strncmp(product, "OPAL2 ", 6)) /* looks like a Mirage II */
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (dev->gamma_lsb_padded == -1) /* nothing defined in umax.conf and not by backend */
      {
        DBG(DBG_warning," - 16 bit gamma table is created lsb padded\n");
        dev->gamma_lsb_padded = 1;
      }
    }
  }
  else if (!strncmp(vendor, "Linotype ", 9))
  {
    if (!strncmp(product, "SAPHIR4 ", 8)) /* is a Powerlook III */
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (dev->calibration_width_offset == -99999) /* no calibration-width-offset defined in umax.conf */
      {
        dev->calibration_width_offset = 28;
        DBG(DBG_warning," - adding calibration width offset of %d pixels\n", dev->calibration_width_offset);
      }
      /* calibration_area = image */

      if (dev->calibration_width_offset_batch == -99999) /* no calibration-width-offset for batch scanning defined in umax.conf */
      {
        dev->calibration_width_offset_batch = 828;
        DBG(DBG_warning," - adding calibration width offset for batch scanning of %d pixels\n", dev->calibration_width_offset_batch);
      }
    }
  }
  else if (!strncmp(vendor, "HDM ", 4))
  {
    if (!strncmp(product, "LS4H1S ", 7)) /* is a Powerlook III */
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (dev->calibration_width_offset == -99999) /* no calibration-width-offset defined in umax.conf */
      {
        dev->calibration_width_offset = 28;
        DBG(DBG_warning," - adding calibration width offset of %d pixels\n", dev->calibration_width_offset);
      }
      /* calibration_area = image */

      if (dev->calibration_width_offset_batch == -99999) /* no calibration-width-offset for batch scanning defined in umax.conf */
      {
        dev->calibration_width_offset_batch = 828;
        DBG(DBG_warning," - adding calibration width offset for batch scanning of %d pixels\n", dev->calibration_width_offset_batch);
      }
    }
  }
  else if (!strncmp(vendor, "ESCORT ", 7))
  {
    if (!strncmp(product, "Galleria 600S ", 14)) /* this is an Astra 600S */
    {
     int add_len = get_inquiry_additional_length(dev->buffer[0]);

      DBG(DBG_warning,"setting up special options for %s\n", product);

      if (add_len == 0x8f)
      {
        DBG(DBG_warning," - correcting wrong inquiry data\n");
	umax_do_new_inquiry(dev, 0x9b);		  /* get inquiry with correct length */
        set_inquiry_length(dev->buffer[0], 0x9e); /* correct inquiry len */
					      /* correct color-ordering from pixel to line_with_ccd_distance */
        set_inquiry_color_order(dev->buffer[0], IN_color_ordering_line_w_ccd);
	set_inquiry_fb_uta_line_arrangement_mode(dev->buffer[0], 32);
	set_inquiry_CCD_line_distance(dev->buffer[0], 8);
        /* we should reset ADF-bit here too */

        if (dev->invert_shading_data == -1) /* nothing defined in umax.conf */
        {
          DBG(DBG_warning," - activating inversion of shading data\n");
          dev->invert_shading_data = 1;
        }
      }
    }
  }
  else if (!strncmp(vendor, "TriGem ", 7))
  {
    if (!strncmp(product, "PowerScanII ", 12)) /* is a Supervista S-12 */
    {
      DBG(DBG_warning,"setting up special options for %s\n", product);

      DBG(DBG_warning," - setting maximum calibration data lines to 66\n");
      set_inquiry_max_calibration_data_lines(dev->buffer[0], 66);

      if (dev->calibration_width_offset == -99999) /* no calibration-width-offset defined in umax.conf */
      {
        dev->calibration_width_offset = -1;
        DBG(DBG_warning," - adding calibration width offset of %d pixels\n", dev->calibration_width_offset);
      }

      if (dev->calibration_area == -1) /* no calibration area defined in umax.conf */
      {
        DBG(DBG_warning," - calibration by driver is done for each CCD pixel\n");
        dev->calibration_area = UMAX_CALIBRATION_AREA_CCD;
      }
    }
  }
}


/* ------------------------------------------------------------ UMAX IDENTIFY SCANNER ---------------------- */


static int umax_identify_scanner(Umax_Device *dev)
{
 char vendor[10];
 char product[0x12];
 char version[6];
 char *pp;

  DBG(DBG_proc,"identify_scanner\n");
  umax_do_inquiry(dev);									      /* get inquiry */
  if (get_inquiry_periph_devtype(dev->buffer[0]) != IN_periph_devtype_scanner) { return 1; }      /* no scanner */

  get_inquiry_vendor( (char *)dev->buffer[0], vendor);  vendor[8]   = ' '; vendor[9]   = '\0';
  get_inquiry_product((char *)dev->buffer[0], product); product[16] = ' '; product[17] = '\0';
  get_inquiry_version((char *)dev->buffer[0], version); version[4]  = ' '; version[5]  = '\0';

  pp = &vendor[8];
  while (*(pp-1) == ' ')
  {
    *pp-- = '\0';
  }

  pp = &product[0x10];
  while (*(pp-1) == ' ')
  {
    *pp-- = '\0';
  }
  
  pp = &version[4];
  while (*pp == ' ')
  {
    *pp-- = '\0';
  }

  DBG(DBG_info, "Found %s scanner %sversion %s on device %s\n", vendor, product, version, dev->devicename);

					      /* look for scanners that do not give all inquiry-information */
							   /* and if possible use driver-known inquiry-data  */

  if (get_inquiry_additional_length(dev->buffer[0])>=0x8f)
  {
    int i = 0;
    while (strncmp("END_OF_LIST", scanner_str[2*i], 11) != 0)	     /* Now identify full supported scanners */
    {
      if (!strncmp(vendor, scanner_str[2*i], strlen(scanner_str[2*i])) )
      { 
        if (!strncmp(product, scanner_str[2*i+1], strlen(scanner_str[2*i+1])) )
        {
	  umax_correct_inquiry(dev, vendor, product, version);
          return 0;
        } 
      }
      i++;
    }

    if (strncmp(vendor, "UMAX ", 5)) { return 1; }				      /* not UMAX then abort */

    DBG(DBG_error0, "WARNING: %s scanner %s version %s on device %s\n"
     "is currently an unrecognized device for this backend version.\n"
     "Please make sure you use the most recent version of the umax backend.\n"
     "You can download new umax-backend versions from:\n"
     "http://www.rauch-domain.de/sane-umax\n"
     "Inquiry seems to be ok.\n"
     "******************************************************************\n"
     "***             !!!! CONTINUE AT YOUR OWN RISK !!!!            ***\n"
     "******************************************************************\n"
     "If you already use the most recent umax-backend version\n"
     "then please contact me: Oliver.Rauch@rauch-domain.de\n",
     vendor, product, version, dev->devicename);

    return 0; 
  }
  else									        /* inquiry-data not complete */
  if (!strncmp(vendor, "UMAX ", 5))				    /* test UMAX-scanners with short inquiry */
  {
   inquiry_blk inq_data;
   int  i;

    for(i=0; i < known_inquiry; i++)
    {
      inq_data = *inquiry_table[i];
      if (!strncmp(product, inq_data.scanner, strlen(inq_data.scanner))) 
      {
	DBG(DBG_warning, "inquiry-block-length: %d\n", get_inquiry_additional_length(dev->buffer[0])+5);
	DBG(DBG_warning, "using driver-internal inquiry-data for this scanner!\n");

						      /* copy driver-defined inquiry-data into inquiry-block */
        memcpy(dev->buffer[0]+0x24, inq_data.inquiry, inq_data.inquiry_len-0x24);

        /* correct variables */
        set_inquiry_sc_uta(dev->buffer[0], get_inquiry_transavail(dev->buffer[0]));	/* transparancy available ? */
        set_inquiry_sc_adf(dev->buffer[0], get_inquiry_scanmode(dev->buffer[0]));	/* automatic document feeder available ? */

        set_inquiry_length(dev->buffer[0], inq_data.inquiry_len); 
        umax_correct_inquiry(dev, vendor, product, version);

        return 0;										       /* ok */
      }
    }
    DBG(DBG_error0, "ERROR: %s scanner %s version %s on device %s\n"
         "is currently an unrecognized device, and inquiry is too short,\n"
         "so we are not able to continue!\n"
         "Please make sure you use the most recent version of the umax backend.\n"
         "You can download new umax-backend versions from:\n"
         "http://www.rauch-domain.de/sane-umax\n"
         "You already use the most recent umax-backend version:\n"
         "Please contact me: Oliver.Rauch@rauch-domain.de\n",
         vendor, product, version, dev->devicename);
  }

 return 1;				    /* NO SUPPORTED SCANNER: short inquiry-block and unknown scanner */
}


/* ------------------------------------------------------------ UMAX TRIM BUFSIZE -------------------------- */


static void umax_trim_rowbufsize(Umax_Device *dev)
{
 unsigned int lines=0;

  if (dev->row_bufsize > dev->row_len)
  {
    lines = dev->row_bufsize / dev->row_len;

    if (lines > dev->lines_max) /* reduce number of lines to scan if set up in config file */
    {
      lines = dev->lines_max;
    }

    dev->row_bufsize = lines * dev->row_len;
  }

  DBG(DBG_proc,"trim_rowbufsize: row_bufsize = %d bytes = %d lines\n", dev->row_bufsize, lines);
}


/* ------------------------------------------------------------ UMAX CALCULATE EXPOSURE TIME --------------- */

  
static void umax_calculate_exposure_time(Umax_Device *dev, int def, int *value)
{
 int level;

  DBG(DBG_proc,"calculate_exposure_time\n");
  if ( (*value))
  {
    if ( (*value) == -1 ) { (*value) = def; }
    else
    {
      level = (*value) / dev->inquiry_exposure_time_step_unit;
      (*value) = inrange(dev->use_exposure_time_min, level, dev->inquiry_exposure_time_max);
    }
  }
}

			  
/* ------------------------------------------------------------ UMAX CHECK VALUES -------------------------- */


static int umax_check_values(Umax_Device *dev)
{
 double inquiry_x_orig;
 double inquiry_y_orig;
 double inquiry_width;
 double inquiry_length;
 unsigned int maxwidth;
 unsigned int maxlength;

  DBG(DBG_proc,"check_values\n");

  /* ------------------------------- flatbed ------------------------------- */

  dev->module = WD_module_flatbed;					  /* reset scanmode to flatbed first */

  /* --------------------------------- uta --------------------------------- */

  if (dev->uta != 0) 
  {
    dev->module = WD_module_transparency;
    if ( (dev->inquiry_uta == 0) || (dev->inquiry_transavail == 0) )
    {
      DBG(DBG_error, "ERROR: transparency mode not supported by scanner\n");
     return(1);
    }
  }

  /* --------------------------------- adf --------------------------------- */

  if (dev->adf != 0) 
  {
    if (dev->inquiry_adf == 0)
    {
      DBG(DBG_error,"ERROR: adf mode not supported by scanner\n");
     return(1);
    }
  }

  /* --------------------------------- dor --------------------------------- */

  if (dev->dor != 0)
  {
    if (dev->inquiry_dor == 0)
    {
      DBG(DBG_error, "ERROR: double optical resolution not supported by scanner\n");
     return(1); 
    }
  }

  /* ------------------------------- resolution ------------------------ */

  if (dev->dor == 0) /* standard (FB) */
  {
    dev->relevant_optical_res = dev->inquiry_optical_res;
    dev->relevant_max_x_res   = dev->inquiry_x_res;
    dev->relevant_max_y_res   = dev->inquiry_y_res;
  }
  else /* DOR mode */
  {
    dev->relevant_optical_res = dev->inquiry_dor_optical_res;
    dev->relevant_max_x_res   = dev->inquiry_dor_x_res;
    dev->relevant_max_y_res   = dev->inquiry_dor_y_res;
  }

  if (dev->x_resolution <= 0)
  {
    DBG(DBG_error,"ERROR: no x-resolution given\n");
    return(1);
  }

  if (dev->x_resolution > dev->relevant_max_x_res)
  {
    dev->x_resolution = dev->relevant_max_x_res;
  }

  if (dev->x_resolution > dev->relevant_optical_res)
  {
    dev->scale_x = 2;
  }
  else
  {
    dev->scale_x = 1;
  }

  if (dev->y_resolution <= 0)
  {
    DBG(DBG_error,"ERROR: no y-resolution given\n");
    return(1);
  }

  if (dev->y_resolution > dev->relevant_max_y_res)
  {
    dev->y_resolution = dev->relevant_max_y_res;
  }

  if (dev->y_resolution > dev->relevant_optical_res)
  {
    dev->scale_y = 2;
  }
  else if (dev->y_resolution > dev->relevant_optical_res/2)
  {
    dev->scale_y = 1;
  }
  else
  {
    /* astra 600S and 610S need this in umax_forget_line */
    dev->scale_y = 0.5;
  }


  /* ------------------------------- scanarea ------------------------ */

  if (dev->module == WD_module_flatbed)							     /* flatbed mode */
  {
    inquiry_x_orig = 0;								   /* flatbed origin */
    inquiry_y_orig = 0;
    inquiry_width  = dev->inquiry_fb_width;						    /* flatbed width */
    inquiry_length = dev->inquiry_fb_length;
  }
  else										        /* transparency mode */
  {
    inquiry_x_orig = dev->inquiry_uta_x_off;						       /* uta origin */
    inquiry_y_orig = dev->inquiry_uta_y_off;
    inquiry_width  = dev->inquiry_uta_x_off + dev->inquiry_uta_width;				/* uta width */
    inquiry_length = dev->inquiry_uta_y_off + dev->inquiry_uta_length;
  }

  if (dev->dor != 0)
  {
    inquiry_x_orig = dev->inquiry_dor_x_off;						       /* dor origin */
    inquiry_y_orig = dev->inquiry_dor_y_off;
    inquiry_width  = dev->inquiry_dor_x_off + dev->inquiry_dor_width;				/* dor width */
    inquiry_length = dev->inquiry_dor_y_off + dev->inquiry_dor_length;
  }

							     /* limit the size to what the scanner can scan. */
					   /* this is particularly important because the scanners don't have */
				  /* built-in checks and will happily grind their gears if this is exceeded. */


  maxwidth = inquiry_width  * dev->x_coordinate_base - dev->upper_left_x - 1;

  if ( (dev->scanwidth <= 0) || (dev->scanwidth > maxwidth) )
  {
    dev->scanwidth = maxwidth;
  }

  if (dev->upper_left_x < inquiry_x_orig)
  {
    dev->upper_left_x = inquiry_x_orig;
  }


  maxlength = inquiry_length * dev->y_coordinate_base - dev->upper_left_y - 1;

  if ( (dev->scanlength <= 0) || (dev->scanlength > maxlength) )
  {
    dev->scanlength = maxlength;
  }

  if (dev->upper_left_y < inquiry_y_orig)
  {
    dev->upper_left_y = inquiry_y_orig;
  }


  /* Now calculate width and length in pixels */
  dev->width_in_pixels  = umax_calculate_pixels(dev->scanwidth,  dev->x_resolution,
                                                dev->relevant_optical_res * dev->scale_x, dev->x_coordinate_base);

  dev->length_in_pixels = umax_calculate_pixels(dev->scanlength, dev->y_resolution,
                                                dev->relevant_optical_res * dev->scale_y, dev->y_coordinate_base);

  if ((dev->scanwidth <= 0) || (dev->scanlength <= 0))
  {
    DBG(DBG_error,"ERROR: scanwidth or scanlength not given\n");
    return(1);
  }

  if (dev->bits_per_pixel_code == 1)
  {
    dev->bytes_per_color = 1;
  }
  else
  {
    dev->bytes_per_color = 2;
  }

  switch(dev->colormode)
  {
   case LINEART:
     dev->width_in_pixels -= dev->width_in_pixels % 8;
     dev->row_len = (dev->width_in_pixels / 8);
    break;

   case HALFTONE:
     dev->width_in_pixels -= dev->width_in_pixels % 8;
     dev->row_len = (dev->width_in_pixels / 8);
    break;

   case GRAYSCALE:
     dev->row_len = dev->width_in_pixels * dev->bytes_per_color;
    break;

   case RGB_LINEART:
   case RGB_HALFTONE:
     if (dev->three_pass)
     {
       dev->row_len = dev->width_in_pixels / 8 ;
     }
     else
     {
       dev->row_len = (dev->width_in_pixels / 8 ) * 3;
     }
    break;

   case RGB:
     if (dev->three_pass)				     /* three (24bpp) or six (30bpp) bytes per pixel */
     {
       dev->row_len = dev->width_in_pixels * dev->bytes_per_color;
     }
     else
     {
       dev->row_len = dev->width_in_pixels * 3 * dev->bytes_per_color;
     }
    break;
  }


  /* ------------------------------- wdb length ------------------------ */

  if (dev->wdb_len <= 0)
  {
    dev->wdb_len = dev->inquiry_wdb_len; 
    if (dev->wdb_len <= 0)
    {
      DBG(DBG_error,"ERROR: wdb-length not given\n");
      return(1);
    }
  }

  if (dev->wdb_len > used_WDB_size)
  {
    DBG(DBG_warning,"WARNING:window descriptor block too long, will be shortned!\n");
    dev->wdb_len = used_WDB_size;
  }

  /* ----------------------------- cbhs-range ----------------------------- */

  dev->threshold   = umax_cbhs_correct(dev->inquiry_threshold_min,  dev->threshold , dev->inquiry_threshold_max);
  dev->contrast    = umax_cbhs_correct(dev->inquiry_contrast_min,   dev->contrast  , dev->inquiry_contrast_max);
  dev->brightness  = umax_cbhs_correct(dev->inquiry_brightness_min, dev->brightness, dev->inquiry_brightness_max);

  dev->highlight_r = umax_cbhs_correct(dev->inquiry_highlight_min, dev->highlight_r, dev->inquiry_highlight_max);
  dev->highlight_g = umax_cbhs_correct(dev->inquiry_highlight_min, dev->highlight_g, dev->inquiry_highlight_max);
  dev->highlight_b = umax_cbhs_correct(dev->inquiry_highlight_min, dev->highlight_b, dev->inquiry_highlight_max);

  dev->shadow_r    = umax_cbhs_correct(dev->inquiry_shadow_min, dev->shadow_r, dev->inquiry_shadow_max-1);
  dev->shadow_g    = umax_cbhs_correct(dev->inquiry_shadow_min, dev->shadow_g, dev->inquiry_shadow_max-1);
  dev->shadow_b    = umax_cbhs_correct(dev->inquiry_shadow_min, dev->shadow_b, dev->inquiry_shadow_max-1);

  if (dev->shadow_r >= dev->highlight_r)
  {
    dev->shadow_r = dev->highlight_r-1;
  }
  if (dev->shadow_g >= dev->highlight_g)
  {
    dev->shadow_g = dev->highlight_g-1;
  }
  if (dev->shadow_b >= dev->highlight_b)
  {
    dev->shadow_b = dev->highlight_b-1;
  }

  /* ----------------------- quality calibration and preview -------------- */

  if (dev->inquiry_preview == 0)
  {
    if (dev->preview)
    {
      DBG(DBG_warning, "WARNING: fast preview function not supported by scanner\n");
      dev->preview = 0;
    }
  }

  /* always set calibration lines because we also need this value if the scanner
     requeires calibration by driver */
  dev->calib_lines = dev->inquiry_max_calib_lines;

  if (dev->force_quality_calibration)
  {
    dev->quality = 1; /* always use quality calibration */
  }
  else if (dev->inquiry_quality_ctrl == 0)
  {
    if (dev->quality)
    {
      DBG(DBG_warning, "WARNING: quality calibration not supported by scanner\n");
      dev->quality = 0;
    }
  }
  else
  {
    if (dev->preview != 0)
    {
      DBG(DBG_info, "quality calibration disabled in preview mode\n");
      dev->quality = 0; /* do not use quality calibration in preview mode */
    }
  }

  /* --------------------------- lamp intensity control ------------------- */

  if (dev->inquiry_lamp_ctrl == 0)
  {
    if (dev->c_density || dev->s_density)
    {
      DBG(DBG_warning, "WARNING: scanner doesn't support lamp intensity control\n");
    }
    dev->c_density = dev->s_density = 0;
  }


  /* --------------------------- reverse (negative) ----------------------- */

  if (dev->reverse != 0)
  {
    if ( (dev->colormode == LINEART)     || (dev->colormode == HALFTONE) ||
         (dev->colormode == RGB_LINEART) || (dev->colormode == RGB_HALFTONE) )
    {
      if (dev->inquiry_reverse == 0)
      {
         DBG(DBG_error, "ERROR: reverse for bi-level-image not supported\n");
         return(1);
      }
    }
    else
    { dev->reverse = 0; }
  }

  if (dev->reverse_multi != 0)
  {
    if ((dev->colormode == RGB) || (dev->colormode == GRAYSCALE) )
    {
      if (dev->inquiry_reverse_multi == 0)
      {
         DBG(DBG_error, "ERROR: reverse for multi-level-image not supported\n");
         return(1);
      }
    }
    else
    {
      dev->reverse_multi = 0;
    }
  }

  /* ----------------------------- analog gamma ---------------------------- */

  if (dev->inquiry_analog_gamma == 0)
  {
    if (dev->analog_gamma_r + dev->analog_gamma_g + dev->analog_gamma_b != 0)
    {
      DBG(DBG_warning,"WARNING: analog gamma correction not supported by scanner!\n");
    }
    dev->analog_gamma_r = dev->analog_gamma_g = dev->analog_gamma_b = 0;
  }

  /* ---------------------------- digital gamma ---------------------------- */

  if ( (dev->digital_gamma_r == 0) || (dev->digital_gamma_g == 0) ||
       (dev->digital_gamma_b == 0) )
  {
    if (dev->inquiry_gamma_dwload == 0)
    {
      DBG(DBG_warning, "WARNING: gamma download not available\n");
      dev->digital_gamma_r = dev->digital_gamma_g = dev->digital_gamma_b = 15;
    }
  }

  /* ---------------------------- speed and smear  ------------------------- */
  
  if (dev->slow == 1)
  {
    dev->WD_speed = WD_speed_slow;
  }
  else
  {
    dev->WD_speed = WD_speed_fast;
  }

  if (dev->smear == 1)
  {
    dev->WD_speed += WD_speed_smear;
  }

  /* ---------------------- test bits per pixel  --------------------------- */
  
  if ( ( (dev->inquiry_GIB | 1) & dev->gamma_input_bits_code) == 0 )
  {
    DBG(DBG_warning,"WARNING: selected gamma input bits not supported, gamma ignored\n");
    dev->gamma_input_bits_code = 1;
    dev->digital_gamma_r = dev->digital_gamma_g = dev->digital_gamma_b = 15;
  }

  if ( ( (dev->inquiry_GOB | 1) & dev->bits_per_pixel_code) == 0 )
  {
    DBG(DBG_error,"ERROR: selected bits per pixel not supported\n");
    return(1);
  }
  
  /* ----------------------- scan mode dependencies ------------------------ */

  switch(dev->colormode)
  {
    case LINEART:							       /* ------------ LINEART ------------- */
    case RGB_LINEART:						       /* ---------- RGB_LINEART ----------- */
      dev->use_exposure_time_min = dev->inquiry_exposure_time_l_min;

      if (dev->module == WD_module_flatbed) 
      {
        dev->use_exposure_time_def_r = dev->inquiry_exposure_time_l_fb_def;
      }
      else
      {
        dev->use_exposure_time_def_r = dev->inquiry_exposure_time_l_uta_def;
      }

      if (dev->inquiry_lineart == 0)
      {
        DBG(DBG_error,"ERROR: lineart mode not supported by scanner\n");
       return(1);
      }
     break;

    case HALFTONE:							 /* ----------- HALFTONE------------ */
    case RGB_HALFTONE:							 /* --------- RGB_HALFTONE---------- */
      dev->use_exposure_time_min = dev->inquiry_exposure_time_h_min;
      if (dev->module == WD_module_flatbed) 
      {
        dev->use_exposure_time_def_r = dev->inquiry_exposure_time_h_fb_def;
      }
      else
      {
        dev->use_exposure_time_def_r = dev->inquiry_exposure_time_h_uta_def;
      }

      if (dev->inquiry_halftone == 0)
      {
        DBG(DBG_error,"ERROR: halftone mode not supported by scanner\n");
        return(1);
      }
     break;

    case GRAYSCALE:						       /* ---------- GRAYSCALE ------------- */
      dev->use_exposure_time_min = dev->inquiry_exposure_time_g_min;

      if (dev->module == WD_module_flatbed) 
      {
        dev->use_exposure_time_def_r = dev->inquiry_exposure_time_g_fb_def;
      }
      else
      {
        dev->use_exposure_time_def_r = dev->inquiry_exposure_time_g_uta_def;
      }

      if (dev->inquiry_gray == 0)
      {
        DBG(DBG_error, "ERROR: grayscale mode not supported by scanner\n");
       return(1);
      }
     break;

    case RGB:							       /* ----------------- COLOR ---------- */
      dev->use_exposure_time_min = dev->inquiry_exposure_time_c_min;
      if (dev->module == WD_module_flatbed) 
      {
        dev->use_exposure_time_def_r = dev->inquiry_exposure_time_c_fb_def_r;
        dev->use_exposure_time_def_g = dev->inquiry_exposure_time_c_fb_def_g;
        dev->use_exposure_time_def_b = dev->inquiry_exposure_time_c_fb_def_b;
      }
      else
      {
        dev->use_exposure_time_def_r = dev->inquiry_exposure_time_c_uta_def_r;
        dev->use_exposure_time_def_g = dev->inquiry_exposure_time_c_uta_def_g;
        dev->use_exposure_time_def_b = dev->inquiry_exposure_time_c_uta_def_b;
      }

      if (dev->inquiry_color == 0)
      {
        DBG(DBG_error,"ERROR: color mode not supported by scanner\n");
       return(1);
      }

      if (dev->inquiry_one_pass_color)
      {
        DBG(DBG_info,"using one pass scanning mode\n");

        if (dev->inquiry_color_order & IN_color_ordering_pixel)
        {
          DBG(DBG_info,"scanner uses color-pixel-ordering\n");
        }
        else if (dev->inquiry_color_order & IN_color_ordering_line_no_ccd)
        {
          dev->CCD_distance = 0;
          dev->do_color_ordering = 1;
          DBG(DBG_info,"scanner uses color-line-ordering without CCD-distance\n");
        }
        else if (dev->inquiry_color_order & IN_color_ordering_line_w_ccd)
        {
          dev->CCD_distance = dev->inquiry_CCD_line_distance;
          dev->do_color_ordering = 1;
          switch (dev->inquiry_fb_uta_color_arrangement)		     /* define color order for line ordering */
          {
            case 1:
              dev->CCD_color[0] = CCD_color_green;

              dev->CCD_color[1] = CCD_color_blue;
              dev->CCD_color[2] = CCD_color_green;

              dev->CCD_color[3] = CCD_color_blue;
              dev->CCD_color[4] = CCD_color_red;
              dev->CCD_color[5] = CCD_color_green;

              dev->CCD_color[6] = CCD_color_blue;
              dev->CCD_color[7] = CCD_color_red;

              dev->CCD_color[8] = CCD_color_red;
             break;

            case 2:
              dev->CCD_color[0] = CCD_color_blue;

              dev->CCD_color[1] = CCD_color_green;
              dev->CCD_color[2] = CCD_color_blue;

              dev->CCD_color[3] = CCD_color_green;
              dev->CCD_color[4] = CCD_color_red;
              dev->CCD_color[5] = CCD_color_blue;

              dev->CCD_color[6] = CCD_color_green;
              dev->CCD_color[7] = CCD_color_red;

              dev->CCD_color[8] = CCD_color_red;
             break;

            case 3:
              dev->CCD_color[0] = CCD_color_red;

              dev->CCD_color[1] = CCD_color_blue;
              dev->CCD_color[2] = CCD_color_red;
 
              dev->CCD_color[3] = CCD_color_blue;
              dev->CCD_color[4] = CCD_color_green;
              dev->CCD_color[5] = CCD_color_red;

              dev->CCD_color[6] = CCD_color_blue;
              dev->CCD_color[7] = CCD_color_green;

              dev->CCD_color[8] = CCD_color_green;
             break;

            case 4:										 /* may be wrong !!! */
              dev->CCD_color[0] = CCD_color_red;

              dev->CCD_color[1] = CCD_color_green;
              dev->CCD_color[2] = CCD_color_red;

              dev->CCD_color[3] = CCD_color_green;
              dev->CCD_color[4] = CCD_color_red;
              dev->CCD_color[5] = CCD_color_blue;

              dev->CCD_color[6] = CCD_color_green;
              dev->CCD_color[7] = CCD_color_blue;

              dev->CCD_color[8] = CCD_color_blue;
             break;

            case 32:						    /* not defined from UMAX, for Astra 600S */
              dev->CCD_color[0] = CCD_color_green;

              dev->CCD_color[1] = CCD_color_green;
              dev->CCD_color[2] = CCD_color_blue;

              dev->CCD_color[3] = CCD_color_green;
              dev->CCD_color[4] = CCD_color_red;
              dev->CCD_color[5] = CCD_color_blue;

              dev->CCD_color[6] = CCD_color_red;
              dev->CCD_color[7] = CCD_color_blue;

              dev->CCD_color[8] = CCD_color_red;
             break;

            case 33:						    /* not defined from UMAX, for Astra 610S */
              dev->CCD_color[0] = CCD_color_red;

              dev->CCD_color[1] = CCD_color_red;
              dev->CCD_color[2] = CCD_color_blue;

              dev->CCD_color[3] = CCD_color_red;
              dev->CCD_color[4] = CCD_color_green;
              dev->CCD_color[5] = CCD_color_blue;

              dev->CCD_color[6] = CCD_color_green;
              dev->CCD_color[7] = CCD_color_blue;
 
              dev->CCD_color[8] = CCD_color_green;
             break;

            default:
              dev->CCD_color[0] = CCD_color_green;
 
              dev->CCD_color[1] = CCD_color_blue;
              dev->CCD_color[2] = CCD_color_green;

              dev->CCD_color[3] = CCD_color_blue;
              dev->CCD_color[4] = CCD_color_red;
              dev->CCD_color[5] = CCD_color_green;

              dev->CCD_color[6] = CCD_color_blue;
              dev->CCD_color[7] = CCD_color_red;

              dev->CCD_color[8] = CCD_color_red;
          }
          DBG(DBG_info,"scanner uses color-line-ordering with CCD-distance of %d lines\n", dev->CCD_distance);
        }
        else
        { 
          DBG(DBG_error,"ERROR: color-ordering-type not supported \n");
         return(1);
        }
      }
      else 
      {
        DBG(DBG_info,"using three pass scanning mode\n");
        dev->three_pass=1;
      }
     break;
  } /* switch */

  /* ----------------------------- color ordering  ------------------------ */

  if (dev->do_color_ordering != 0)
  {
    if ( (dev->colormode != RGB) || (dev->three_pass != 0) )
    {
      dev->do_color_ordering = 0; /* color ordering not necessery */
    }
  }

 return(0);
}


/* ------------------------------------------------------------ UMAX GET INQUIRY VALUES -------------------- */


static void umax_get_inquiry_values(Umax_Device *dev)
{
 unsigned char * inquiry_block;

  DBG(DBG_proc,"get_inquiry_values\n");

  inquiry_block   = dev->buffer[0];
  dev->inquiry_len = get_inquiry_additional_length(dev->buffer[0])+5;
  dev->cbhs_range  = dev->inquiry_cbhs = get_inquiry_CBHS(inquiry_block);

  if (dev->cbhs_range > IN_CBHS_255)
  {
    dev->cbhs_range = IN_CBHS_255;
  }

  if (dev->cbhs_range == IN_CBHS_50)
  {
    dev->inquiry_contrast_min   = 103;	      /* minimum value for c */
    dev->inquiry_contrast_max   = 153;	      /* maximum value for c */
    dev->inquiry_brightness_min = 78;	      /* minimum value for b */
    dev->inquiry_brightness_max = 178;	      /* maximum value for b */
    dev->inquiry_threshold_min  = 78;	      /* minimum value for t */
    dev->inquiry_threshold_max  = 178;	      /* maximum value for t */
    dev->inquiry_highlight_min  = 1;	      /* minimum value for h */
    dev->inquiry_highlight_max  = 50;	      /* maximum value for h */
    dev->inquiry_shadow_min     = 0;	      /* minimum value for s */
    dev->inquiry_shadow_max     = 49;	      /* maximum value for s */ 
  }

  get_inquiry_vendor( (char *)inquiry_block, dev->vendor);  dev->vendor[8]  ='\0';
  get_inquiry_product((char *)inquiry_block, dev->product); dev->product[16]='\0';
  get_inquiry_version((char *)inquiry_block, dev->version); dev->version[4] ='\0';

  dev->inquiry_batch_scan       = get_inquiry_fw_batch_scan(inquiry_block);
  dev->inquiry_quality_ctrl     = get_inquiry_fw_quality(inquiry_block);
  dev->inquiry_preview          = get_inquiry_fw_fast_preview(inquiry_block);
  dev->inquiry_lamp_ctrl        = get_inquiry_fw_lamp_int_cont(inquiry_block);
  dev->inquiry_calibration      = get_inquiry_fw_calibration(inquiry_block);
  dev->inquiry_transavail       = get_inquiry_transavail(inquiry_block);
  dev->inquiry_adfmode          = get_inquiry_scanmode(inquiry_block);

  if (dev->inquiry_len<=0x8f)
  {
    DBG(DBG_warning, "WARNING: inquiry return block is unexpected short.\n");
  }

  dev->inquiry_uta              = get_inquiry_sc_uta(inquiry_block);
  dev->inquiry_adf              = get_inquiry_sc_adf(inquiry_block);

  dev->inquiry_one_pass_color   = get_inquiry_sc_one_pass_color(inquiry_block);
  dev->inquiry_three_pass_color = get_inquiry_sc_three_pass_color(inquiry_block);
  dev->inquiry_color            = get_inquiry_sc_color(inquiry_block);
  dev->inquiry_gray             = get_inquiry_sc_gray(inquiry_block);
  dev->inquiry_halftone         = get_inquiry_sc_halftone(inquiry_block);
  dev->inquiry_lineart          = get_inquiry_sc_lineart(inquiry_block);

  dev->inquiry_exposure_adj              = get_inquiry_fw_adjust_exposure_tf(inquiry_block);
  dev->inquiry_exposure_time_step_unit   = get_inquiry_exposure_time_step_unit(inquiry_block);
  dev->inquiry_exposure_time_max         = get_inquiry_exposure_time_max(inquiry_block);

             /* --- lineart --- */
  dev->inquiry_exposure_time_l_min       = get_inquiry_exposure_time_lhg_min(inquiry_block);
  dev->inquiry_exposure_time_l_fb_def    = get_inquiry_exposure_time_lh_def_fb(inquiry_block);
  dev->inquiry_exposure_time_l_uta_def   = get_inquiry_exposure_time_lh_def_uta(inquiry_block);

             /* --- halftone --- */
  dev->inquiry_exposure_time_h_min       = get_inquiry_exposure_time_lhg_min(inquiry_block);
  dev->inquiry_exposure_time_h_fb_def    = get_inquiry_exposure_time_lh_def_fb(inquiry_block);
  dev->inquiry_exposure_time_h_uta_def   = get_inquiry_exposure_time_lh_def_uta(inquiry_block);

             /* --- grayscale --- */
  dev->inquiry_exposure_time_g_min       = get_inquiry_exposure_time_lhg_min(inquiry_block);
  dev->inquiry_exposure_time_g_fb_def    = get_inquiry_exposure_time_gray_def_fb(inquiry_block);
  dev->inquiry_exposure_time_g_uta_def   = get_inquiry_exposure_time_gray_def_uta(inquiry_block);

             /* --- color --- */
  dev->inquiry_exposure_time_c_min       = get_inquiry_exposure_time_color_min(inquiry_block);
  dev->inquiry_exposure_time_c_fb_def_r  = get_inquiry_exposure_time_def_r_fb(inquiry_block);
  dev->inquiry_exposure_time_c_fb_def_g  = get_inquiry_exposure_time_def_g_fb(inquiry_block);
  dev->inquiry_exposure_time_c_fb_def_b  = get_inquiry_exposure_time_def_g_fb(inquiry_block);
  dev->inquiry_exposure_time_c_uta_def_r = get_inquiry_exposure_time_def_r_uta(inquiry_block);
  dev->inquiry_exposure_time_c_uta_def_g = get_inquiry_exposure_time_def_g_uta(inquiry_block);
  dev->inquiry_exposure_time_c_uta_def_b = get_inquiry_exposure_time_def_b_uta(inquiry_block);


  dev->inquiry_dor           = get_inquiry_sc_double_res(inquiry_block);
  dev->inquiry_reverse       = get_inquiry_sc_bi_image_reverse(inquiry_block);
  dev->inquiry_reverse_multi = get_inquiry_sc_multi_image_reverse(inquiry_block);
  dev->inquiry_shadow        = 1 - get_inquiry_sc_no_shadow(inquiry_block);
  dev->inquiry_highlight     = 1 - get_inquiry_sc_no_highlight(inquiry_block);
  dev->inquiry_analog_gamma  = get_inquiry_analog_gamma(inquiry_block);
  dev->inquiry_lineart_order = get_inquiry_lineart_order(inquiry_block);

  dev->inquiry_lens_cal_in_doc_pos  = get_inquiry_manual_focus(inquiry_block);
  dev->inquiry_manual_focus         = get_inquiry_manual_focus(inquiry_block);
  dev->inquiry_sel_uta_lens_cal_pos = get_inquiry_manual_focus(inquiry_block);

  dev->inquiry_gamma_dwload  = get_inquiry_gamma_download_available(inquiry_block);

  if (get_inquiry_gamma_type_2(inquiry_block) != 0)
  {
    dev->inquiry_gamma_DCF = 2;
  }

  dev->inquiry_GIB           = get_inquiry_gib(inquiry_block);
  dev->inquiry_GOB           = get_inquiry_gob(inquiry_block);
  dev->inquiry_color_order   = get_inquiry_color_order(inquiry_block);
  dev->inquiry_vidmem        = get_inquiry_max_vidmem(inquiry_block);

  /* optical resolution = [0x73] * 100 + [0x94] , 0x94 is not always defined */
  dev->inquiry_optical_res = 100 * get_inquiry_max_opt_res(inquiry_block);
  if (dev->inquiry_len > 0x94)
  {
    dev->inquiry_optical_res += get_inquiry_optical_resolution_residue(inquiry_block);
  }

  /* x resolution = [0x74] * 100 + [0x95] , 0x95 is not always defined */
  dev->inquiry_x_res = 100 * get_inquiry_max_x_res(inquiry_block);
  if (dev->inquiry_len > 0x95)
  {
    dev->inquiry_x_res+= get_inquiry_x_resolution_residue(inquiry_block);
  };

  /* y resolution = [0x75] * 100 + [0x96] , 0x96 is not always defined */
  dev->inquiry_y_res = 100 * get_inquiry_max_y_res(inquiry_block);
  if (dev->inquiry_len > 0x96)
  {
    dev->inquiry_y_res+= get_inquiry_y_resolution_residue(inquiry_block);
  }


  /* optical resolution = [0x83] * 100 + [0xa0] , 0xa0 is not always defined */
  dev->inquiry_dor_optical_res = 100 * get_inquiry_dor_max_opt_res(inquiry_block);
  if (dev->inquiry_len > 0xa0)
  {
    dev->inquiry_dor_optical_res += get_inquiry_dor_optical_resolution_residue(inquiry_block);
  }

  /* x resolution = [0x84] * 100 + [0xa1] , 0xa1 is not always defined */
  dev->inquiry_dor_x_res = 100 * get_inquiry_dor_max_x_res(inquiry_block);
  if (dev->inquiry_len > 0xa1)
  {
    dev->inquiry_dor_x_res+= get_inquiry_dor_x_resolution_residue(inquiry_block);
  }

  /* y resolution = [0x85] * 100 + [0xa2] , 0xa2 is not always defined */
  dev->inquiry_dor_y_res = 100 * get_inquiry_dor_max_y_res(inquiry_block);
  if (dev->inquiry_len > 0xa2)
  {
    dev->inquiry_dor_y_res+= get_inquiry_dor_y_resolution_residue(inquiry_block);
  }

  if (dev->inquiry_dor) /* DOR mode available ? */
  {
    /* if DOR resolutions are not defined, use double of standard resolution */

    if (dev->inquiry_dor_optical_res == 0)
    {
      dev->inquiry_dor_optical_res = dev->inquiry_optical_res * 2;
    }

    if (dev->inquiry_dor_x_res == 0)
    {
      dev->inquiry_dor_x_res = dev->inquiry_x_res * 2;
    }

    if (dev->inquiry_dor_y_res == 0)
    {
      dev->inquiry_dor_y_res = dev->inquiry_y_res * 2;
    }
  }

  dev->inquiry_fb_width   = (double)get_inquiry_fb_max_scan_width(inquiry_block)  * 0.01;
  dev->inquiry_fb_length  = (double)get_inquiry_fb_max_scan_length(inquiry_block) * 0.01;

  dev->inquiry_uta_width  = (double)get_inquiry_uta_max_scan_width(inquiry_block)  * 0.01;
  dev->inquiry_uta_length = (double)get_inquiry_uta_max_scan_length(inquiry_block) * 0.01;
  dev->inquiry_uta_x_off  = (double)get_inquiry_uta_x_original_point(inquiry_block) * 0.01;
  dev->inquiry_uta_y_off  = (double)get_inquiry_uta_y_original_point(inquiry_block) * 0.01;

  dev->inquiry_dor_width  = (double)get_inquiry_dor_max_scan_width(inquiry_block)  * 0.01;
  dev->inquiry_dor_length = (double)get_inquiry_dor_max_scan_length(inquiry_block) * 0.01;
  dev->inquiry_dor_x_off  = (double)get_inquiry_dor_x_original_point(inquiry_block) * 0.01;
  dev->inquiry_dor_y_off  = (double)get_inquiry_dor_y_original_point(inquiry_block) * 0.01;

  dev->inquiry_max_warmup_time          = get_inquiry_lamp_warmup_maximum_time(inquiry_block) * 2;

  dev->inquiry_wdb_len                  = get_inquiry_wdb_length(inquiry_block);

  /* it is not guaranteed that the following values are in the inquiry return block */

  /* 0x9a */
  if (dev->inquiry_len<=0x9a)
  {
    return;
  }
  dev->inquiry_max_calib_lines          = get_inquiry_max_calibration_data_lines(inquiry_block);

  /* 0x9b */
  if (dev->inquiry_len<=0x9b)
  {
    return;
  }
  dev->inquiry_fb_uta_color_arrangement = get_inquiry_fb_uta_line_arrangement_mode(inquiry_block);

  /* 0x9c */
  if (dev->inquiry_len<=0x9c)
  {
    return;
  }
  dev->inquiry_adf_color_arrangement    = get_inquiry_adf_line_arrangement_mode(inquiry_block);

  /* 0x9d */
  if (dev->inquiry_len<=0x9d)
  {
    return;
  } 
  dev->inquiry_CCD_line_distance        = get_inquiry_CCD_line_distance(inquiry_block);

  return;
}


/* ------------------------------------------------------------ UMAX CALCULATE ANALOG GAMMA ---------------- */


static int umax_calculate_analog_gamma(double value)
{
 int gamma;

  if (value < 1.0)
   { value=1.0; }

  if (value > 2.0)
   { value=2.0; }

  gamma=0;						       /* select gamma_value from analog_gamma_table */
  while (value>analog_gamma_table[gamma])
  {
    gamma++;
  } 

  if (gamma)
  {
    if ((analog_gamma_table[gamma-1] + analog_gamma_table[gamma]) /2 > value)
    {
      gamma--;
    }
  }
  
 return(gamma);
}

/* ------------------------------------------------------------ UMAX OUTPUT IMAGE DATA  -------------------- */

static void umax_output_image_data(Umax_Device *dev, FILE *fp, unsigned int data_to_read, int bufnr)
{
    if (dev->do_color_ordering == 0)							   /* pixel ordering */
    {
      if ((dev->inquiry_lineart_order) && (dev->colormode == LINEART)) /* lineart with LSB first */
      {
       unsigned int i, j;
       int new, old;

        for (i=0; i<data_to_read; i++)
        {
          old = dev->buffer[bufnr][i];    
          new = 0;
          for (j=0; j<8; j++)  /* reverse bit order of 1 byte */
          {
            new = (new << 1) + (old & 1);
            old = old >> 1;
          }
          dev->buffer[bufnr][i]=new;
        }
      }
      fwrite(dev->buffer[bufnr], 1, data_to_read, fp);
    }
    else										    /* line ordering */
    {
     unsigned char *linesource = dev->buffer[bufnr];
     unsigned char *pixelsource;
     int bytes = 1;
     int lines;
     int i;

      if (dev->bits_per_pixel_code != 1)							  /* >24 bpp */
      {
        bytes = 2;
      }

      lines = data_to_read / (dev->width_in_pixels * bytes);

      for(i=0; i<lines; i++)
      {
        umax_order_line(dev, linesource);
        linesource += dev->width_in_pixels * bytes;

        pixelsource = umax_get_pixel_line(dev);
        if (pixelsource != NULL)
        {
          fwrite(pixelsource, bytes, dev->width_in_pixels * 3, fp);
        }
      }
    }
}

/* ------------------------------------------------------------ UMAX READER PROCESS ------------------------ */


static int umax_reader_process(Umax_Device *dev, FILE *fp, unsigned int image_size)
{
 int status;
 int bytes        = 1;
 int queue_filled = 0;
 unsigned int bufnr_queue  = 0;
 unsigned int bufnr_read   = 0;
 unsigned int data_left_to_read  = image_size;
 unsigned int data_left_to_queue = image_size;
 unsigned int data_to_read;
 unsigned int data_to_queue;

  dev->row_bufsize = dev->bufsize;
  umax_trim_rowbufsize(dev);								     /* trim bufsize */

  if (dev->bits_per_pixel_code != 1) /* >24 bpp */
  {
    bytes = 2;
  }

  DBG(DBG_read,"reading %u bytes in blocks of %u bytes\n", image_size, dev->row_bufsize);

  if (dev->pixelbuffer != NULL)								   /* buffer exists? */
  {
    free(dev->pixelbuffer);
    dev->pixelbuffer = NULL;
  }

  if (dev->do_color_ordering != 0)
  {
    DBG(DBG_info,"ordering from line-order to pixel-order\n");

    dev->pixelline_max = 3 *  dev->CCD_distance * dev->scale_y + 2;

    dev->pixelbuffer = malloc(dev->width_in_pixels * dev->pixelline_max * bytes * 3);

    if (dev->pixelbuffer == NULL) /* NO MEMORY */
    {
      return -1;
    }
  }

  WAIT_SCANNER;

  do
  {
    if (data_left_to_queue)
    {
      data_to_queue = (data_left_to_queue < dev->row_bufsize) ? data_left_to_queue : dev->row_bufsize;

      /* umax_get_data_buffer_status(dev); */

      status = umax_queue_read_image_data_req(dev, data_to_queue, bufnr_queue);

      if (status == 0) /* no error but nothing queued */
      {
        continue;
      }

      if (status == -1) /* error */
      {
        DBG(DBG_error,"ERROR: umax_reader_process: unable to queue read image data request!\n");
        free(dev->pixelbuffer);
        dev->pixelbuffer = NULL;
        return(-1);
      }

      data_left_to_queue -= data_to_queue;
      DBG(DBG_read, "umax_reader_process: read image data queued for buffer[%d] \n", bufnr_queue);

      bufnr_queue++;
      if (bufnr_queue >= dev->scsi_maxqueue)
      {
        bufnr_queue = 0;
        queue_filled = 1; /* ok, we can start to read the queued buffers - if not already started */
      }

      if (!data_left_to_queue)
      {
        queue_filled = 1; /* ok, we can start to read the queued buffer(s) - all read requests are send */
      }
    }

    if (queue_filled) /* queue filled, ok we can read data */
    {
      status = umax_wait_queued_image_data(dev, bufnr_read);

      if (status == -1)
      {
        DBG(DBG_error,"ERROR: umax_reader_process: unable to get image data from scanner!\n");
        free(dev->pixelbuffer);
        dev->pixelbuffer = NULL;
        return(-1);
      }

      data_to_read = dev->length_read[bufnr_read]; /* number of bytes in buffer */
      umax_output_image_data(dev, fp, data_to_read, bufnr_read);

      data_left_to_read -= data_to_read;
      DBG(DBG_read, "umax_reader_process: buffer of %d bytes read; %d bytes to go\n", data_to_read, data_left_to_read);

      /* if we did not get all requested data increase data_left_to_queue so that we get all needed data */
      if (dev->length_read[bufnr_read] != dev->length_queued[bufnr_read])
      {
        data_left_to_queue += dev->length_queued[bufnr_read] - dev->length_read[bufnr_read];
      }

      bufnr_read++;
      if (bufnr_read >= dev->scsi_maxqueue)
      {
        bufnr_read = 0;
      }
    }
  } while (data_left_to_read);

  free(dev->pixelbuffer);
  dev->pixelbuffer = NULL;

 return 0;
}


/* ------------------------------------------------------------ UMAX INITIALIZE VALUES --------------------- */


static void umax_initialize_values(Umax_Device *dev)	      /* called each time before setting scan-values */
{										 /* Initialize dev structure */
  DBG(DBG_proc,"initialize_values\n");

  dev->three_pass            = 0;						  /* 1 if threepas_mode only */
  dev->row_len               = -1;
  dev->max_value             = 255;							    /* maximum value */

  dev->wdb_len               = 0;
  dev->width_in_pixels       = 0;						     /* scan width in pixels */
  dev->length_in_pixels      = 0;						    /* scan length in pixels */
  dev->scanwidth             = 0;			           /* width in inch at x_coordinate_base dpi */
  dev->scanlength            = 0;				  /* length in inch at y_coordinate_base dpi */
  dev->x_resolution          = 0;
  dev->y_resolution          = 0;
  dev->upper_left_x          = 0;							   /* at 1200pt/inch */
  dev->upper_left_y          = 0;							   /* at 1200pt/inch */
  dev->bytes_per_color       = 0;						     /* bytes for each color */

  dev->bits_per_pixel        = 8;						 /* number of bits per pixel */
  dev->bits_per_pixel_code   = 1;			    /* 1 =  8/24 bpp,  2 =  9/27 bpp,  4 = 10/30 bpp */
  dev->gamma_input_bits_code = 1;			    /* 8 = 12/36 bpp, 16 = 14/42 bpp, 32 = 16/48 bpp */
  dev->set_auto              = 0;								   /* 0 or 1 */
  dev->preview               = 0;							    /* 1 for preview */
  dev->quality               = 0;						      /* quality calibration */
  dev->warmup                = 0;							       /* warmup-bit */
  dev->fix_focus_position    = 0;						       /* fix focus position */
  dev->lens_cal_in_doc_pos   = 0;				    /* lens calibration in document position */
  dev->disable_pre_focus     = 0;							/* disable pre focus */
  dev->holder_focus_pos_0mm  = 0;				    /* 0.6mm <-> 0.0mm holder focus position */
  dev->manual_focus          = 0;					       /* automatic <-> manual focus */
  dev->colormode             = 0;				      /* LINEART, HALFTONE, GRAYSCALE or RGB */
  dev->adf                   = 0;						   /* 1 if adf shall be used */
  dev->uta                   = 0;						   /* 1 if uta shall be used */
  dev->module                = WD_module_flatbed;
  dev->cbhs_range            = WD_CBHS_255;
  dev->dor                   = 0;
  dev->halftone              = WD_halftone_8x8_1;
  dev->reverse               = 0;
  dev->reverse_multi         = 0;
  dev->calibration           = 0;

  dev->exposure_time_calibration_r = 0;						 /* use this for calibration */
  dev->exposure_time_calibration_g = 0;						 /* use this for calibration */
  dev->exposure_time_calibration_b = 0;						 /* use this for calibration */
  dev->exposure_time_scan_r        = 0;							/* use this for scan */
  dev->exposure_time_scan_g        = 0;							/* use this for scan */
  dev->exposure_time_scan_b        = 0;							/* use this for scan */

  dev->c_density           = WD_lamp_c_density_auto;				 /* calibration lamp density */
  dev->s_density           = WD_lamp_s_density_auto;				   /* next scan lamp density */

  dev->threshold           = 128;					       /* threshold for lineart mode */
  dev->brightness          = 128;					     /* brightness for halftone mode */
  dev->contrast            = 128;					       /* contrast for halftone mode */
  dev->highlight_r         = 255;						       /* highlight gray/red */
  dev->highlight_g         = 255;							  /* highlight green */
  dev->highlight_b         = 255;						  	   /* highlight blue */
  dev->shadow_r            = 0;							     	  /* shadow gray/red */
  dev->shadow_g            = 0;								     /* shadow green */
  dev->shadow_b            = 0;								      /* shadow blue */

  dev->digital_gamma_r     = WD_gamma_normal;
  dev->digital_gamma_g     = WD_gamma_normal;
  dev->digital_gamma_b     = WD_gamma_normal;

  dev->analog_gamma_r      = 0;					     /* analog gamma for red and gray to 1.0 */
  dev->analog_gamma_g      = 0;						    /* analog gamma for green to 1.0 */
  dev->analog_gamma_b      = 0;						     /* analog gamma for blue to 1.0 */


  dev->pixelline_ready[0]  = 0;					      /* reset all values for color ordering */
  dev->pixelline_ready[1]  = 0;
  dev->pixelline_ready[2]  = 0;
  dev->pixelline_next[0]   = 0;
  dev->pixelline_next[1]   = 0;
  dev->pixelline_next[2]   = 0;
  dev->pixelline_del[0]    = 1;
  dev->pixelline_del[1]    = 1;
  dev->pixelline_del[2]    = 1;
  dev->pixelline_optic[0]  = 1;
  dev->pixelline_optic[1]  = 1;
  dev->pixelline_optic[2]  = 1;
  dev->pixelline_max       = 0;        
  dev->pixelline_opt_res   = 0;        
  dev->pixelline_read      = 0;        
  dev->pixelline_written   = 0;        
  dev->CCD_distance        = 0;        

  dev->calib_lines         = 0;							/* request calibration lines */
  dev->do_calibration      = 0;							 /* no calibration by driver */
  dev->do_color_ordering   = 0;						  /* no line- to pixel-mode ordering */

  dev->button0_pressed     = 0;						      /* reset button 0 pressed flag */
  dev->button1_pressed     = 0;						      /* reset button 1 pressed flag */
  dev->button2_pressed     = 0;						      /* reset button 2 pressed flag */
}


/* ------------------------------------------------------------ UMAX INIT ---------------------------------- */


static void umax_init(Umax_Device *dev)		     /* umax_init is called once while driver-initialization */
{
  DBG(DBG_proc,"init\n");

  dev->devicename  = NULL;
  dev->pixelbuffer = NULL;

  /* config file or predefined settings */
  if (dev->connection_type == SANE_UMAX_SCSI)
  {
    dev->request_scsi_maxqueue  = umax_scsi_maxqueue;
  }
  else /* SANE_UMAX_USB, USB does not support command queueing */
  {
    DBG(DBG_info2, "setting request_scsi_maxqueue = 1 for USB connection\n");
    dev->request_scsi_maxqueue  = 1;	  
  }

  dev->request_preview_lines          = umax_preview_lines;
  dev->request_scan_lines             = umax_scan_lines;
  dev->handle_bad_sense_error         = umax_handle_bad_sense_error;
  dev->execute_request_sense          = umax_execute_request_sense;
  dev->scsi_buffer_size_min           = umax_scsi_buffer_size_min;
  dev->scsi_buffer_size_max           = umax_scsi_buffer_size_max;
  dev->force_preview_bit_rgb          = umax_force_preview_bit_rgb;
  dev->slow                           = umax_slow;
  dev->smear                          = umax_smear;
  dev->calibration_area               = umax_calibration_area;
  dev->calibration_width_offset       = umax_calibration_width_offset;
  dev->calibration_width_offset_batch = umax_calibration_width_offset_batch;
  dev->calibration_bytespp            = umax_calibration_bytespp;
  dev->exposure_time_rgb_bind         = umax_exposure_time_rgb_bind;
  dev->invert_shading_data            = umax_invert_shading_data;
  dev->lamp_control_available         = umax_lamp_control_available;
  dev->gamma_lsb_padded               = umax_gamma_lsb_padded;

  DBG(DBG_info, "request_scsi_maxqueue          = %d\n", dev->request_scsi_maxqueue);
  DBG(DBG_info, "request_preview_lines          = %d\n", dev->request_preview_lines);
  DBG(DBG_info, "request_scan_lines             = %d\n", dev->request_scan_lines);
  DBG(DBG_info, "handle_bad_sense_error         = %d\n", dev->handle_bad_sense_error);
  DBG(DBG_info, "execute_request_sense          = %d\n", dev->execute_request_sense);
  DBG(DBG_info, "scsi_buffer_size_min           = %d\n", dev->scsi_buffer_size_min);
  DBG(DBG_info, "scsi_buffer_size_max           = %d\n", dev->scsi_buffer_size_max);
  DBG(DBG_info, "force_preview_bit_rgb          = %d\n", dev->force_preview_bit_rgb);
  DBG(DBG_info, "slow                           = %d\n", dev->slow);
  DBG(DBG_info, "smear                          = %d\n", dev->smear);
  DBG(DBG_info, "calibration_area               = %d\n", dev->calibration_area);
  DBG(DBG_info, "calibration_width_offset       = %d\n", dev->calibration_width_offset);
  DBG(DBG_info, "calibration_width_offset_batch = %d\n", dev->calibration_width_offset_batch);
  DBG(DBG_info, "calibration_bytespp            = %d\n", dev->calibration_bytespp);
  DBG(DBG_info, "exposure_time_rgb_bind         = %d\n", dev->exposure_time_rgb_bind);
  DBG(DBG_info, "invert_shading_data            = %d\n", dev->invert_shading_data);
  DBG(DBG_info, "lamp_control_available         = %d\n", dev->lamp_control_available);


  dev->inquiry_len                       = 0;
  dev->inquiry_wdb_len                   = -1;
  dev->inquiry_optical_res               = -1;
  dev->inquiry_x_res                     = -1;
  dev->inquiry_y_res                     = -1;
  dev->inquiry_fb_width                  = -1;
  dev->inquiry_fb_length                 = -1;
  dev->inquiry_uta_width                 = -1;
  dev->inquiry_uta_length                = -1;
  dev->inquiry_dor_width                 = -1;
  dev->inquiry_dor_length                = -1;
  dev->inquiry_exposure_adj              = 0;
  dev->inquiry_exposure_time_step_unit   = -1;				  /* exposure time unit in micro sec */
  dev->inquiry_exposure_time_max         = -1;					    /* exposure time maximum */
  dev->inquiry_exposure_time_l_min       = -1;			       /*  exposure time minimum for lineart */
  dev->inquiry_exposure_time_l_fb_def    = -1;			/* exposure time default for lineart flatbed */
  dev->inquiry_exposure_time_l_uta_def   = -1;			    /* exposure time default for lineart uta */
  dev->inquiry_exposure_time_h_min       = -1;			      /*  exposure time minimum for halftone */
  dev->inquiry_exposure_time_h_fb_def    = -1;		       /* exposure time default for halftone flatbed */
  dev->inquiry_exposure_time_h_uta_def   = -1;			   /* exposure time default for halftone uta */
  dev->inquiry_exposure_time_g_min       = -1;			     /*  exposure time minimum for grayscale */
  dev->inquiry_exposure_time_g_fb_def    = -1;		      /* exposure time default for grayscale flatbed */
  dev->inquiry_exposure_time_g_uta_def   = -1;			  /* exposure time default for grayscale uta */
  dev->inquiry_exposure_time_c_min       = -1;				  /* exposure time minimum for color */
  dev->inquiry_exposure_time_c_fb_def_r  = -1;		      /* exposure time default for color flatbed red */
  dev->inquiry_exposure_time_c_fb_def_g  = -1;		    /* exposure time default for color flatbed green */
  dev->inquiry_exposure_time_c_fb_def_b  = -1;		     /* exposure time default for color flatbed blue */
  dev->inquiry_exposure_time_c_uta_def_r = -1;			  /* exposure time default for color uta red */
  dev->inquiry_exposure_time_c_uta_def_g = -1;			/* exposure time default for color uta green */
  dev->inquiry_exposure_time_c_uta_def_b = -1;			 /* exposure time default for color uta blue */
  dev->inquiry_max_warmup_time           = 0;					      /* maximum warmup time */
  dev->inquiry_cbhs                      = WD_CBHS_255;
  dev->inquiry_contrast_min              = 1;					      /* minimum value for c */
  dev->inquiry_contrast_max              = 255;					      /* maximum value for c */
  dev->inquiry_brightness_min            = 1;					      /* minimum value for b */
  dev->inquiry_brightness_max            = 255;					      /* maximum value for b */
  dev->inquiry_threshold_min             = 1;					      /* minimum value for t */
  dev->inquiry_threshold_max             = 255;					      /* maximum value for t */
  dev->inquiry_highlight_min             = 1;					      /* minimum value for h */
  dev->inquiry_highlight_max             = 255;					      /* maximum value for h */
  dev->inquiry_shadow_min                = 0;					      /* minimum value for s */
  dev->inquiry_shadow_max                = 254;					      /* maximum value for s */ 
  dev->inquiry_quality_ctrl              = 0;
  dev->inquiry_preview                   = 0;
  dev->inquiry_lamp_ctrl                 = 0;
  dev->inquiry_transavail                = 0;
  dev->inquiry_uta                       = 0;
  dev->inquiry_adfmode                   = 0;
  dev->inquiry_adf                       = 0;
  dev->inquiry_dor                       = 0;
  dev->inquiry_reverse                   = 0;
  dev->inquiry_reverse_multi             = 0;
  dev->inquiry_analog_gamma              = 0;
  dev->inquiry_gamma_dwload              = 0;
  dev->inquiry_one_pass_color            = 0;
  dev->inquiry_three_pass_color          = 0;
  dev->inquiry_color                     = 0;
  dev->inquiry_gray                      = 0;
  dev->inquiry_halftone                  = 0;
  dev->inquiry_lineart                   = 0;
  dev->inquiry_calibration               = 1;
  dev->inquiry_shadow                    = 0;
  dev->inquiry_highlight                 = 0;
  dev->inquiry_gamma_DCF                 = -1;
  dev->inquiry_max_calib_lines           = 66;	 /* most scanners use 66 lines, so lets define it as default */

  dev->common_xy_resolutions             = 0;

  dev->x_coordinate_base = 1200;						/* these are the 1200pt/inch */
  dev->y_coordinate_base = 1200;						/* these are the 1200pt/inch */

  dev->button0_pressed   = 0;						      /* reset button 0 pressed flag */
  dev->button1_pressed   = 0;						      /* reset button 1 pressed flag */
  dev->button2_pressed   = 0;						      /* reset button 2 pressed flag */

  dev->pause_for_color_calibration = 0;			/* pause between start_scan and do_calibration in ms */
  dev->pause_for_gray_calibration  = 0;			/* pause between start_scan and do_calibration in ms */
  dev->pause_after_calibration     = 0;			 /* pause between do_calibration and read data in ms */
  dev->pause_after_reposition      = -1;	    /* pause after repostion scanner in ms, -1 = do not wait */
  dev->pause_for_moving            = 0;			         /* pause for moving scanhead over full area */

  if (umax_test_little_endian() == SANE_TRUE)
  {
    dev->low_byte_first = 1;					        /* in 2 byte mode send lowbyte first */
    DBG(DBG_info, "backend runs on little endian machine\n");
  }
  else
  {
    dev->low_byte_first = 0;					       /* in 2 byte mode send highbyte first */
    DBG(DBG_info, "backend runs on big endian machine\n");
  }

#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
  DBG(DBG_info,"variable scsi buffer size (usage of sanei_scsi_open_extended)\n");
#else
  DBG(DBG_info,"fixed scsi buffer size = %d bytes\n", sanei_scsi_max_request_size);
#endif
}


/* ------------------------------------------------------------ MAX STRING SIZE ---------------------------- */


static size_t max_string_size(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;
}


/* ------------------------------------------------------------ DO CANCEL ---------------------------------- */


static SANE_Status do_cancel(Umax_Scanner *scanner)
{
 int pid, status;

  DBG(DBG_sane_proc,"do_cancel\n");

  scanner->scanning = SANE_FALSE;

  if (scanner->reader_pid > 0)
  {
    DBG(DBG_sane_info,"killing reader_process\n");

    sanei_thread_kill(scanner->reader_pid);
    pid = sanei_thread_waitpid(scanner->reader_pid, &status);

    if (pid < 0)
    {
      DBG(DBG_sane_info, "do_cancel: sanei_thread_waitpid failed, already terminated ? (%s)\n", strerror(errno));
    }
    else
    {
      DBG(DBG_sane_info, "do_cancel: reader_process terminated with status: %s\n", sane_strstatus(status));
    }

    scanner->reader_pid = 0;

    if (scanner->device->pixelbuffer != NULL)					      /* pixelbuffer exists? */
    {
      free(scanner->device->pixelbuffer);						 /* free pixelbuffer */
      scanner->device->pixelbuffer = NULL;
    }
  }

  sanei_scsi_req_flush_all(); /* flush SCSI queue, when we do not do this then sanei_scsi crashes next time */

  if (scanner->device->sfd != -1) /* make sure we have a working filedescriptor */
  {
    umax_give_scanner(scanner->device); /* reposition and release scanner */
    DBG(DBG_sane_info,"closing scannerdevice filedescriptor\n");
    umax_scsi_close(scanner->device);
  }

  scanner->device->three_pass_color = 1; /* reset color in color scanning */

 return SANE_STATUS_CANCELLED;
}


/* ------------------------------------------------------------ ATTACH SCANNER ----------------------------- */


static SANE_Status attach_scanner(const char *devicename, Umax_Device **devp, int connection_type)
{
 Umax_Device *dev;
 int i;

  DBG(DBG_sane_proc,"attach_scanner: %s, connection_type %d\n", devicename, connection_type);

  for (dev = first_dev; dev; dev = dev->next) /* search is scanner already is listed in devicelist */
  {
    if (strcmp(dev->sane.name, devicename) == 0) /* scanner is already listed */
    {
      if (devp)
      {
        *devp = dev; /* return pointer to device */
      }
     return SANE_STATUS_GOOD;
    }
  }

  /* scanner has not been attached yet */

  dev = malloc( sizeof(*dev) );
  if (!dev)
  {
     return SANE_STATUS_NO_MEM;
  }
  memset(dev, '\0', sizeof(Umax_Device)); /* clear structure */

  /* If connection type is not known (==0) then try to open the device as an USB device. */
  /* If it fails, try the SCSI method. */

#ifdef UMAX_ENABLE_USB
  dev->connection_type = connection_type; /* 0 = unknown, 1=scsi, 2=usb */

  if (dev->connection_type != SANE_UMAX_SCSI)
  {
    dev->bufsize = 16384; /* 16KB */
    DBG(DBG_info, "attach_scanner: opening usb device %s\n", devicename);

    if (sanei_umaxusb_open(devicename, &dev->sfd, sense_handler, dev) == SANE_STATUS_GOOD)
    {
      dev->connection_type = SANE_UMAX_USB;
    }
    else /* opening usb device failed */
    {
      if (dev->connection_type == SANE_UMAX_USB) /* we know it is not a scsi device: error */
      {
        DBG(DBG_error, "ERROR: attach_scanner: opening usb device %s failed\n", devicename);
        free(dev);
       return SANE_STATUS_INVAL;
      }

      DBG(DBG_info, "attach_scanner: failed to open %s as usb device\n", devicename);
    }
  }
#else
  dev->connection_type = SANE_UMAX_SCSI;
#endif

  if (dev->connection_type != SANE_UMAX_USB) /* not an USB device, then try as SCSI */
  {
#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
    dev->bufsize = 16384; /* 16KB */
    DBG(DBG_info, "attach_scanner: opening scsi device %s\n", devicename);

    if (sanei_scsi_open_extended(devicename, &dev->sfd, sense_handler, dev, (int *) &dev->bufsize) != 0)
    {
      DBG(DBG_error, "ERROR: attach_scanner: opening scsi device %s failed\n", devicename);
      free(dev);
     return SANE_STATUS_INVAL;
    }

    if (dev->bufsize < 4096) /* < 4KB */
    {
      DBG(DBG_error, "ERROR: attach_scanner: sanei_scsi_open_extended returned too small scsi buffer\n");
      umax_scsi_close(dev);
      free(dev);
     return SANE_STATUS_NO_MEM;
    }

    DBG(DBG_info, "attach_scanner: sanei_scsi_open_extended returned scsi buffer size = %d\n", dev->bufsize);
#else
    dev->bufsize = sanei_scsi_max_request_size;

    if (sanei_scsi_open(devicename, dev, sense_handler, dev) != 0)
    {
      DBG(DBG_error, "ERROR: attach_scanner: opening scsi device %s failed\n", devicename);
      free(dev);
     return SANE_STATUS_INVAL;
    }
#endif
    dev->connection_type = SANE_UMAX_SCSI; /* set connection type (may have been unknown == 0) */
  }

  DBG(DBG_info, "attach_scanner: allocating SCSI buffer[0]\n");
  dev->buffer[0] = malloc(dev->bufsize);								/* allocate buffer */

  for (i=1; i<SANE_UMAX_SCSI_MAXQUEUE; i++)
  {
    dev->buffer[i] = NULL;
  }

  if (!dev->buffer[0]) /* malloc failed */
  {
    DBG(DBG_error, "ERROR: attach scanner: could not allocate buffer[0]\n");
    umax_scsi_close(dev);
    free(dev);
    return SANE_STATUS_NO_MEM;
  }

  dev->scsi_maxqueue = 1; /* only one buffer outside the reader process */

  umax_init(dev);									 /* preset values in structure dev */
  umax_initialize_values(dev);										   /* reset values */

  dev->devicename = strdup(devicename);

  if (umax_identify_scanner(dev) != 0)
  {
    DBG(DBG_error, "ERROR: attach_scanner: scanner-identification failed\n");
    umax_scsi_close(dev);
    free(dev->buffer[0]);
    free(dev);
    return SANE_STATUS_INVAL;
  }

  if (dev->slow == -1) /* option is not predefined in umax.conf and not by backend */
  {
    dev->slow = 0;
  }

  if (dev->smear == -1) /* option is not predefined in umax.conf and not by backend */
  {
    dev->smear = 0;
  }

  if (dev->invert_shading_data == -1) /* nothing defined in umax.conf and not by backend */
  {
    dev->invert_shading_data = 0;
  }

  if (dev->gamma_lsb_padded == -1) /* nothing defined in umax.conf and not by backend */
  {
    dev->gamma_lsb_padded = 0;
  }

  umax_get_inquiry_values(dev);
  umax_print_inquiry(dev);
  DBG(DBG_inquiry,"\n");
  DBG(DBG_inquiry,"==================== end of inquiry ====================\n");
  DBG(DBG_inquiry,"\n");

  umax_scsi_close(dev);

  dev->sane.name   = dev->devicename;
  dev->sane.vendor = dev->vendor;
  dev->sane.model  = dev->product;
  dev->sane.type   = "flatbed scanner"; 

  if (strcmp(dev->sane.model,"PSD ") == 0)
  {
    dev->sane.type = "page scanner";
  }

  dev->x_range.min               = SANE_FIX(0);
  dev->x_range.quant             = SANE_FIX(0);
  dev->x_range.max               = SANE_FIX(dev->inquiry_fb_width  * MM_PER_INCH);

  dev->y_range.min               = SANE_FIX(0);
  dev->y_range.quant             = SANE_FIX(0);
  dev->y_range.max               = SANE_FIX(dev->inquiry_fb_length * MM_PER_INCH);

#if UMAX_RESOLUTION_PERCENT_STEP
  dev->x_dpi_range.min           = SANE_FIX(dev->inquiry_optical_res/100);
  dev->x_dpi_range.quant         = SANE_FIX(dev->inquiry_optical_res/100);
#else
  dev->x_dpi_range.min           = SANE_FIX(5);
  dev->x_dpi_range.quant         = SANE_FIX(5);
#endif
  dev->x_dpi_range.max           = SANE_FIX(dev->inquiry_x_res);

#if UMAX_RESOLUTION_PERCENT_STEP
  dev->y_dpi_range.min           = SANE_FIX(dev->inquiry_optical_res/100);
  dev->y_dpi_range.quant         = SANE_FIX(dev->inquiry_optical_res/100);
#else
  dev->y_dpi_range.min           = SANE_FIX(5);
  dev->y_dpi_range.quant         = SANE_FIX(5);
#endif
  dev->y_dpi_range.max           = SANE_FIX(dev->inquiry_y_res);

  dev->analog_gamma_range.min    = SANE_FIX(1.0);
  dev->analog_gamma_range.quant  = SANE_FIX(0.01);
  dev->analog_gamma_range.max    = SANE_FIX(2.0);

  DBG(DBG_info,"x_range.max     = %f\n", SANE_UNFIX(dev->x_range.max));
  DBG(DBG_info,"y_range.max     = %f\n", SANE_UNFIX(dev->y_range.max));
  DBG(DBG_info,"x_dpi_range.max = %f\n", SANE_UNFIX(dev->x_dpi_range.max));
  DBG(DBG_info,"y_dpi_range.max = %f\n", SANE_UNFIX(dev->y_dpi_range.max));

  ++num_devices;
  dev->next = first_dev;
  first_dev = dev;

  if (devp)
  {
    *devp = dev;
  }

 return SANE_STATUS_GOOD;
}


/* ------------------------------------------------------------ READER PROCESS SIGTERM HANDLER  ------------ */


static RETSIGTYPE reader_process_sigterm_handler(int signal)
{
  DBG(DBG_sane_info,"reader_process: terminated by signal %d\n", signal);

  sanei_scsi_req_flush_all(); /* flush SCSI queue */

  _exit (SANE_STATUS_GOOD);
}


/* ------------------------------------------------------------ READER PROCESS ----------------------------- */


static int reader_process(void *data) /* executed as a child process or as thread */
{
 Umax_Scanner *scanner = (Umax_Scanner *)data;
 FILE *fp;
 int status;
 unsigned int data_length;
 struct SIGACTION act;
 unsigned int i;

  if (sanei_thread_is_forked())
  {
    DBG(DBG_sane_proc,"reader_process started (forked)\n");
    close(scanner->pipe_read_fd);
    scanner->pipe_read_fd = -1;

    /* sanei_scsi crashes when the scsi commands are not flushed, done in reader_process_sigterm_handler */
    memset(&act, 0, sizeof (act));						   /* define SIGTERM-handler */
    act.sa_handler = reader_process_sigterm_handler;
    sigaction(SIGTERM, &act, 0);
  }
  else
  {
    DBG(DBG_sane_proc,"reader_process started (as thread)\n");
  }


  scanner->device->scsi_maxqueue = scanner->device->request_scsi_maxqueue;

  if (scanner->device->request_scsi_maxqueue > 1)
  {
    for (i = 1; i<SANE_UMAX_SCSI_MAXQUEUE; i++)
    {
      if (scanner->device->buffer[i])
      {
        DBG(DBG_info, "reader_process: freeing SCSI buffer[%d]\n", i);
        free(scanner->device->buffer[i]);									     /* free buffer */
        scanner->device->buffer[i] = NULL;
      }
    }

    for (i = 1; i<scanner->device->request_scsi_maxqueue; i++)
    {
      DBG(DBG_info, "reader_process: allocating SCSI buffer[%d]\n", i);
      scanner->device->buffer[i]  = malloc(scanner->device->bufsize);			  /* allocate buffer */

      if (!scanner->device->buffer[i]) /* malloc failed */
      {
        DBG(DBG_warning, "WARNING: reader_process: only allocated %d/%d scsi buffers\n", i, scanner->device->request_scsi_maxqueue);
        scanner->device->scsi_maxqueue = i;
        break; /* leave for loop */
      }
    }
  }

  data_length = scanner->params.lines * scanner->params.bytes_per_line;

  fp = fdopen(scanner->pipe_write_fd, "w");
  if (!fp)
  {
    return SANE_STATUS_IO_ERROR;
  } 

  DBG(DBG_sane_info,"reader_process: starting to READ data\n");

  status = umax_reader_process(scanner->device, fp, data_length);
  fclose(fp); /* close write end of pipe */

  for (i = 1; i<scanner->device->request_scsi_maxqueue; i++)
  {
    if (scanner->device->buffer[i])
    {
      DBG(DBG_info, "reader_process: freeing SCSI buffer[%d]\n", i);
      free(scanner->device->buffer[i]);									     /* free buffer */
      scanner->device->buffer[i] = NULL;
    }
  }
  DBG(DBG_sane_info,"reader_process: finished reading data\n");

 return status;
}


/* ------------------------------------------------------------ INIT OPTIONS ------------------------------- */


static SANE_Status init_options(Umax_Scanner *scanner)
{
 int i;
 int scan_modes;
 int bit_depths;

  DBG(DBG_sane_proc,"init_options\n");

  memset(scanner->opt, 0, sizeof (scanner->opt));
  memset(scanner->val, 0, sizeof (scanner->val));

  for (i = 0; i < NUM_OPTIONS; ++i)
  {
    scanner->opt[i].size = sizeof (SANE_Word);
    scanner->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  }

  scanner->opt[OPT_NUM_OPTS].name  = SANE_NAME_NUM_OPTIONS; /* empty string */
  scanner->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
  scanner->opt[OPT_NUM_OPTS].desc  = SANE_DESC_NUM_OPTIONS;
  scanner->opt[OPT_NUM_OPTS].type  = SANE_TYPE_INT;
  scanner->opt[OPT_NUM_OPTS].cap   = SANE_CAP_SOFT_DETECT;
  scanner->val[OPT_NUM_OPTS].w     = NUM_OPTIONS;

  /* "Mode" group: */
  scanner->opt[OPT_MODE_GROUP].title = SANE_I18N("Scan Mode");
  scanner->opt[OPT_MODE_GROUP].desc  = "";
  scanner->opt[OPT_MODE_GROUP].type  = SANE_TYPE_GROUP;
  scanner->opt[OPT_MODE_GROUP].cap   = 0;
  scanner->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  scan_modes = -1;

  if (scanner->device->inquiry_lineart)
  {
    scan_mode_list[++scan_modes] = LINEART_STR;
  }

  if (scanner->device->inquiry_halftone)
  {
    scan_mode_list[++scan_modes]= HALFTONE_STR;
  }

  if (scanner->device->inquiry_gray)
  {
    scan_mode_list[++scan_modes]= GRAY_STR;
  }

  if (scanner->device->inquiry_color)
  {
/*  
    if (scanner->device->inquiry_lineart)
    { scan_mode_list[++scan_modes]= COLOR_LINEART_STR; }

    if (scanner->device->inquiry_halftone) 
    { scan_mode_list[++scan_modes]= COLOR_HALFTONE_STR; }
*/
    scan_mode_list[++scan_modes]= COLOR_STR; 
  }

  scan_mode_list[scan_modes + 1] = 0; 

  {
   int i=0;
    source_list[i++]= FLB_STR;

    if (scanner->device->inquiry_adfmode)
    {
      source_list[i++] = ADF_STR;
    }

    if (scanner->device->inquiry_transavail)
    {
      source_list[i++] = UTA_STR;
    }

    source_list[i] = 0;
  }
  
  /* scan mode */
  scanner->opt[OPT_MODE].name  = SANE_NAME_SCAN_MODE;
  scanner->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
  scanner->opt[OPT_MODE].desc  = SANE_DESC_SCAN_MODE;
  scanner->opt[OPT_MODE].type  = SANE_TYPE_STRING;
  scanner->opt[OPT_MODE].size  = max_string_size((SANE_String_Const *) scan_mode_list);
  scanner->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  scanner->opt[OPT_MODE].constraint.string_list = (SANE_String_Const *) scan_mode_list;
  scanner->val[OPT_MODE].s     = (SANE_Char*)strdup(scan_mode_list[0]);

  /* source */
  scanner->opt[OPT_SOURCE].name  = SANE_NAME_SCAN_SOURCE;
  scanner->opt[OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE;
  scanner->opt[OPT_SOURCE].desc  = SANE_DESC_SCAN_SOURCE;
  scanner->opt[OPT_SOURCE].type  = SANE_TYPE_STRING;
  scanner->opt[OPT_SOURCE].size  = max_string_size(source_list);
  scanner->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  scanner->opt[OPT_SOURCE].constraint.string_list = source_list;
  scanner->val[OPT_SOURCE].s     = (SANE_Char*)strdup(source_list[0]);
  
  /* x-resolution */
  scanner->opt[OPT_X_RESOLUTION].name  = SANE_NAME_SCAN_RESOLUTION;
  scanner->opt[OPT_X_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  scanner->opt[OPT_X_RESOLUTION].desc  = SANE_DESC_SCAN_RESOLUTION;
  scanner->opt[OPT_X_RESOLUTION].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_X_RESOLUTION].unit  = SANE_UNIT_DPI;
  scanner->opt[OPT_X_RESOLUTION].constraint_type  = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_X_RESOLUTION].constraint.range = &scanner->device->x_dpi_range;
  scanner->val[OPT_X_RESOLUTION].w     = 100 << SANE_FIXED_SCALE_SHIFT;

  /* y-resolution */
  scanner->opt[OPT_Y_RESOLUTION].name  = SANE_NAME_SCAN_Y_RESOLUTION;
  scanner->opt[OPT_Y_RESOLUTION].title = SANE_TITLE_SCAN_Y_RESOLUTION;
  scanner->opt[OPT_Y_RESOLUTION].desc  = SANE_DESC_SCAN_Y_RESOLUTION;
  scanner->opt[OPT_Y_RESOLUTION].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_Y_RESOLUTION].unit  = SANE_UNIT_DPI;
  scanner->opt[OPT_Y_RESOLUTION].constraint_type  = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_Y_RESOLUTION].constraint.range = &scanner->device->y_dpi_range;
  scanner->val[OPT_Y_RESOLUTION].w     = 100 << SANE_FIXED_SCALE_SHIFT;
  scanner->opt[OPT_Y_RESOLUTION].cap  |= SANE_CAP_INACTIVE;

  /* bind resolution */
  scanner->opt[OPT_RESOLUTION_BIND].name  = SANE_NAME_RESOLUTION_BIND;
  scanner->opt[OPT_RESOLUTION_BIND].title = SANE_TITLE_RESOLUTION_BIND;
  scanner->opt[OPT_RESOLUTION_BIND].desc  = SANE_DESC_RESOLUTION_BIND;
  scanner->opt[OPT_RESOLUTION_BIND].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_RESOLUTION_BIND].w     = SANE_TRUE;
  if (scanner->device->common_xy_resolutions)	/* disable bind if x and y res have to be the same */
  {
    scanner->opt[OPT_RESOLUTION_BIND].cap  |= SANE_CAP_INACTIVE;
  }


  /* negative */
  scanner->opt[OPT_NEGATIVE].name  = SANE_NAME_NEGATIVE;
  scanner->opt[OPT_NEGATIVE].title = SANE_TITLE_NEGATIVE;
  scanner->opt[OPT_NEGATIVE].desc  = SANE_DESC_NEGATIVE;
  scanner->opt[OPT_NEGATIVE].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_NEGATIVE].w     = SANE_FALSE;

  if (scanner->device->inquiry_reverse_multi == 0)
  {
    scanner->opt[OPT_NEGATIVE].cap  |= SANE_CAP_INACTIVE;
  }

  /* ------------------------------ */

  /* "Geometry" group: */
  scanner->opt[OPT_GEOMETRY_GROUP].title = SANE_I18N("Geometry");
  scanner->opt[OPT_GEOMETRY_GROUP].desc  = "";
  scanner->opt[OPT_GEOMETRY_GROUP].type  = SANE_TYPE_GROUP;
  scanner->opt[OPT_GEOMETRY_GROUP].cap   = SANE_CAP_ADVANCED;
  scanner->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* top-left x */
  scanner->opt[OPT_TL_X].name  = SANE_NAME_SCAN_TL_X;
  scanner->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
  scanner->opt[OPT_TL_X].desc  = SANE_DESC_SCAN_TL_X;
  scanner->opt[OPT_TL_X].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_TL_X].unit  = SANE_UNIT_MM;
  scanner->opt[OPT_TL_X].constraint_type  = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_TL_X].constraint.range = &(scanner->device->x_range);
  scanner->val[OPT_TL_X].w     = 0;

  /* top-left y */
  scanner->opt[OPT_TL_Y].name  = SANE_NAME_SCAN_TL_Y;
  scanner->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
  scanner->opt[OPT_TL_Y].desc  = SANE_DESC_SCAN_TL_Y;
  scanner->opt[OPT_TL_Y].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_TL_Y].unit  = SANE_UNIT_MM;
  scanner->opt[OPT_TL_Y].constraint_type  = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_TL_Y].constraint.range = &(scanner->device->y_range);
  scanner->val[OPT_TL_Y].w     = 0;

  /* bottom-right x */
  scanner->opt[OPT_BR_X].name  = SANE_NAME_SCAN_BR_X;
  scanner->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
  scanner->opt[OPT_BR_X].desc  = SANE_DESC_SCAN_BR_X;
  scanner->opt[OPT_BR_X].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_BR_X].unit  = SANE_UNIT_MM;
  scanner->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_BR_X].constraint.range = &(scanner->device->x_range);
  scanner->val[OPT_BR_X].w     = scanner->device->x_range.max;

  /* bottom-right y */
  scanner->opt[OPT_BR_Y].name  = SANE_NAME_SCAN_BR_Y;
  scanner->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
  scanner->opt[OPT_BR_Y].desc  = SANE_DESC_SCAN_BR_Y;
  scanner->opt[OPT_BR_Y].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_BR_Y].unit  = SANE_UNIT_MM;
  scanner->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_BR_Y].constraint.range = &(scanner->device->y_range);
  scanner->val[OPT_BR_Y].w     = scanner->device->y_range.max;

  /* ------------------------------ */


  /* "Enhancement" group: */
  scanner->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N("Enhancement");
  scanner->opt[OPT_ENHANCEMENT_GROUP].desc  = "";
  scanner->opt[OPT_ENHANCEMENT_GROUP].type  = SANE_TYPE_GROUP;
  scanner->opt[OPT_ENHANCEMENT_GROUP].cap   = 0;
  scanner->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;


  /* bit depth */
  bit_depths = 0;

  if (scanner->device->inquiry_GOB & 1)
  {
    bit_depth_list[++bit_depths] = 8;
  }

  if (scanner->device->inquiry_GOB & 2)
  {
    bit_depth_list[++bit_depths] = 9;
  }

  if (scanner->device->inquiry_GOB & 4)
  {
    bit_depth_list[++bit_depths] = 10;
  }

  if (scanner->device->inquiry_GOB & 8)
  {
    bit_depth_list[++bit_depths] = 12;
  }

  if (scanner->device->inquiry_GOB & 16)
  {
    bit_depth_list[++bit_depths] = 14;
  }

  if (scanner->device->inquiry_GOB & 32)
  {
    bit_depth_list[++bit_depths] = 16;
  }

  bit_depth_list[0] = bit_depths;

  scanner->opt[OPT_BIT_DEPTH].name  = SANE_NAME_BIT_DEPTH;
  scanner->opt[OPT_BIT_DEPTH].title = SANE_TITLE_BIT_DEPTH;
  scanner->opt[OPT_BIT_DEPTH].desc  = SANE_DESC_BIT_DEPTH;
  scanner->opt[OPT_BIT_DEPTH].type  = SANE_TYPE_INT;
  scanner->opt[OPT_BIT_DEPTH].unit  = SANE_UNIT_BIT;
  scanner->opt[OPT_BIT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  scanner->opt[OPT_BIT_DEPTH].constraint.word_list = bit_depth_list;
  scanner->val[OPT_BIT_DEPTH].w     = bit_depth_list[1];


  /* quality-calibration */
  scanner->opt[OPT_QUALITY].name  = SANE_NAME_QUALITY_CAL;
  scanner->opt[OPT_QUALITY].title = SANE_TITLE_QUALITY_CAL;
  scanner->opt[OPT_QUALITY].desc  = SANE_DESC_QUALITY_CAL;
  scanner->opt[OPT_QUALITY].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_QUALITY].w     = SANE_FALSE;

  if ((scanner->device->inquiry_quality_ctrl == 0) || (scanner->device->force_quality_calibration) )
  {
    scanner->opt[OPT_QUALITY].cap  |= SANE_CAP_INACTIVE;
  }
  else /* enable quality calibration when available */
  {
    scanner->val[OPT_QUALITY].w = SANE_TRUE;
  }


  /* double optical resolution */
  scanner->opt[OPT_DOR].name  = SANE_NAME_DOR;
  scanner->opt[OPT_DOR].title = SANE_TITLE_DOR;
  scanner->opt[OPT_DOR].desc  = SANE_DESC_DOR;
  scanner->opt[OPT_DOR].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_DOR].w     = SANE_FALSE;

  if (scanner->device->inquiry_dor == 0)
  {
    scanner->opt[OPT_DOR].cap  |= SANE_CAP_INACTIVE;
  }


  /* warmup */
  scanner->opt[OPT_WARMUP].name  = SANE_NAME_WARMUP;
  scanner->opt[OPT_WARMUP].title = SANE_TITLE_WARMUP;
  scanner->opt[OPT_WARMUP].desc  = SANE_DESC_WARMUP;
  scanner->opt[OPT_WARMUP].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_WARMUP].w     = SANE_FALSE;

  if (scanner->device->inquiry_max_warmup_time == 0)
  {
    scanner->opt[OPT_WARMUP].cap  |= SANE_CAP_INACTIVE;
  }

  scanner->opt[OPT_RGB_BIND].name  = SANE_NAME_RGB_BIND;
  scanner->opt[OPT_RGB_BIND].title = SANE_TITLE_RGB_BIND;
  scanner->opt[OPT_RGB_BIND].desc  = SANE_DESC_RGB_BIND;
  scanner->opt[OPT_RGB_BIND].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_RGB_BIND].w     = SANE_FALSE;

  /* brightness */
  scanner->opt[OPT_BRIGHTNESS].name  = SANE_NAME_BRIGHTNESS;
  scanner->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
  scanner->opt[OPT_BRIGHTNESS].desc  = SANE_DESC_BRIGHTNESS;
  scanner->opt[OPT_BRIGHTNESS].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_BRIGHTNESS].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_BRIGHTNESS].constraint.range = &percentage_range;
  scanner->val[OPT_BRIGHTNESS].w     = 0;

  /* contrast */
  scanner->opt[OPT_CONTRAST].name  = SANE_NAME_CONTRAST;
  scanner->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
  scanner->opt[OPT_CONTRAST].desc  = SANE_DESC_CONTRAST;
  scanner->opt[OPT_CONTRAST].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_CONTRAST].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_CONTRAST].constraint.range = &percentage_range;
  scanner->val[OPT_CONTRAST].w     = 0;


  /* threshold */
  scanner->opt[OPT_THRESHOLD].name  = SANE_NAME_THRESHOLD;
  scanner->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD;
  scanner->opt[OPT_THRESHOLD].desc  = SANE_DESC_THRESHOLD;
  scanner->opt[OPT_THRESHOLD].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_THRESHOLD].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_THRESHOLD].constraint.range = &percentage_range_100;
  scanner->val[OPT_THRESHOLD].w     = SANE_FIX(50);


  /* ------------------------------ */


  /* highlight, white level */
  scanner->opt[OPT_HIGHLIGHT].name  = SANE_NAME_HIGHLIGHT;
  scanner->opt[OPT_HIGHLIGHT].title = SANE_TITLE_HIGHLIGHT;
  scanner->opt[OPT_HIGHLIGHT].desc  = SANE_DESC_HIGHLIGHT;
  scanner->opt[OPT_HIGHLIGHT].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_HIGHLIGHT].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_HIGHLIGHT].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_HIGHLIGHT].constraint.range = &percentage_range_100;
  scanner->val[OPT_HIGHLIGHT].w     = SANE_FIX(100);

  scanner->opt[OPT_HIGHLIGHT_R].name  = SANE_NAME_HIGHLIGHT_R;
  scanner->opt[OPT_HIGHLIGHT_R].title = SANE_TITLE_HIGHLIGHT_R;
  scanner->opt[OPT_HIGHLIGHT_R].desc  = SANE_DESC_HIGHLIGHT_R;
  scanner->opt[OPT_HIGHLIGHT_R].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_HIGHLIGHT_R].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_HIGHLIGHT_R].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_HIGHLIGHT_R].constraint.range = &percentage_range_100;
  scanner->val[OPT_HIGHLIGHT_R].w     = SANE_FIX(100);

  scanner->opt[OPT_HIGHLIGHT_G].name  = SANE_NAME_HIGHLIGHT_G;
  scanner->opt[OPT_HIGHLIGHT_G].title = SANE_TITLE_HIGHLIGHT_G;
  scanner->opt[OPT_HIGHLIGHT_G].desc  = SANE_DESC_HIGHLIGHT_G;
  scanner->opt[OPT_HIGHLIGHT_G].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_HIGHLIGHT_G].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_HIGHLIGHT_G].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_HIGHLIGHT_G].constraint.range = &percentage_range_100;
  scanner->val[OPT_HIGHLIGHT_G].w     = SANE_FIX(100);

  scanner->opt[OPT_HIGHLIGHT_B].name  = SANE_NAME_HIGHLIGHT_B;
  scanner->opt[OPT_HIGHLIGHT_B].title = SANE_TITLE_HIGHLIGHT_B;
  scanner->opt[OPT_HIGHLIGHT_B].desc  = SANE_DESC_HIGHLIGHT_B;
  scanner->opt[OPT_HIGHLIGHT_B].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_HIGHLIGHT_B].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_HIGHLIGHT_B].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_HIGHLIGHT_B].constraint.range = &percentage_range_100;
  scanner->val[OPT_HIGHLIGHT_B].w     = SANE_FIX(100);


  /* shadow, black level */
  scanner->opt[OPT_SHADOW].name  = SANE_NAME_SHADOW;
  scanner->opt[OPT_SHADOW].title = SANE_TITLE_SHADOW;
  scanner->opt[OPT_SHADOW].desc  = SANE_DESC_SHADOW;
  scanner->opt[OPT_SHADOW].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_SHADOW].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_SHADOW].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_SHADOW].constraint.range = &percentage_range_100;
  scanner->val[OPT_SHADOW].w     = 0;

  scanner->opt[OPT_SHADOW_R].name  = SANE_NAME_SHADOW_R;
  scanner->opt[OPT_SHADOW_R].title = SANE_TITLE_SHADOW_R;
  scanner->opt[OPT_SHADOW_R].desc  = SANE_DESC_SHADOW_R;
  scanner->opt[OPT_SHADOW_R].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_SHADOW_R].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_SHADOW_R].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_SHADOW_R].constraint.range = &percentage_range_100;
  scanner->val[OPT_SHADOW_R].w     = 0;

  scanner->opt[OPT_SHADOW_G].name  = SANE_NAME_SHADOW_G;
  scanner->opt[OPT_SHADOW_G].title = SANE_TITLE_SHADOW_G;
  scanner->opt[OPT_SHADOW_G].desc  = SANE_DESC_SHADOW_G;
  scanner->opt[OPT_SHADOW_G].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_SHADOW_G].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_SHADOW_G].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_SHADOW_G].constraint.range = &percentage_range_100;
  scanner->val[OPT_SHADOW_G].w     = 0;

  scanner->opt[OPT_SHADOW_B].name  = SANE_NAME_SHADOW_B;
  scanner->opt[OPT_SHADOW_B].title = SANE_TITLE_SHADOW_B;
  scanner->opt[OPT_SHADOW_B].desc  = SANE_DESC_SHADOW_B;
  scanner->opt[OPT_SHADOW_B].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_SHADOW_B].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_SHADOW_B].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_SHADOW_B].constraint.range = &percentage_range_100;
  scanner->val[OPT_SHADOW_B].w     = 0;



  /* analog-gamma */
  scanner->opt[OPT_ANALOG_GAMMA].name  = SANE_NAME_ANALOG_GAMMA;
  scanner->opt[OPT_ANALOG_GAMMA].title = SANE_TITLE_ANALOG_GAMMA;
  scanner->opt[OPT_ANALOG_GAMMA].desc  = SANE_DESC_ANALOG_GAMMA;
  scanner->opt[OPT_ANALOG_GAMMA].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_ANALOG_GAMMA].unit  = SANE_UNIT_NONE;
  scanner->opt[OPT_ANALOG_GAMMA].constraint_type  = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_ANALOG_GAMMA].constraint.range = &(scanner->device->analog_gamma_range);
  scanner->val[OPT_ANALOG_GAMMA].w     = 1 << SANE_FIXED_SCALE_SHIFT;

  scanner->opt[OPT_ANALOG_GAMMA_R].name  = SANE_NAME_ANALOG_GAMMA_R;
  scanner->opt[OPT_ANALOG_GAMMA_R].title = SANE_TITLE_ANALOG_GAMMA_R;
  scanner->opt[OPT_ANALOG_GAMMA_R].desc  = SANE_DESC_ANALOG_GAMMA_R;
  scanner->opt[OPT_ANALOG_GAMMA_R].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_ANALOG_GAMMA_R].unit  = SANE_UNIT_NONE;
  scanner->opt[OPT_ANALOG_GAMMA_R].constraint_type  = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_ANALOG_GAMMA_R].constraint.range = &(scanner->device->analog_gamma_range);
  scanner->val[OPT_ANALOG_GAMMA_R].w     = 1 << SANE_FIXED_SCALE_SHIFT;

  scanner->opt[OPT_ANALOG_GAMMA_G].name  = SANE_NAME_ANALOG_GAMMA_G;
  scanner->opt[OPT_ANALOG_GAMMA_G].title = SANE_TITLE_ANALOG_GAMMA_G;
  scanner->opt[OPT_ANALOG_GAMMA_G].desc  = SANE_DESC_ANALOG_GAMMA_G;
  scanner->opt[OPT_ANALOG_GAMMA_G].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_ANALOG_GAMMA_G].unit  = SANE_UNIT_NONE;
  scanner->opt[OPT_ANALOG_GAMMA_G].constraint_type  = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_ANALOG_GAMMA_G].constraint.range = &(scanner->device->analog_gamma_range);
  scanner->val[OPT_ANALOG_GAMMA_G].w     = 1 << SANE_FIXED_SCALE_SHIFT;

  scanner->opt[OPT_ANALOG_GAMMA_B].name  = SANE_NAME_ANALOG_GAMMA_B;
  scanner->opt[OPT_ANALOG_GAMMA_B].title = SANE_TITLE_ANALOG_GAMMA_B;
  scanner->opt[OPT_ANALOG_GAMMA_B].desc  = SANE_DESC_ANALOG_GAMMA_B;
  scanner->opt[OPT_ANALOG_GAMMA_B].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_ANALOG_GAMMA_B].unit  = SANE_UNIT_NONE;
  scanner->opt[OPT_ANALOG_GAMMA_B].constraint_type  = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_ANALOG_GAMMA_B].constraint.range = &(scanner->device->analog_gamma_range);
  scanner->val[OPT_ANALOG_GAMMA_B].w     = 1 << SANE_FIXED_SCALE_SHIFT;


  /* custom-gamma table */
  scanner->opt[OPT_CUSTOM_GAMMA].name  = SANE_NAME_CUSTOM_GAMMA;
  scanner->opt[OPT_CUSTOM_GAMMA].title = SANE_TITLE_CUSTOM_GAMMA;
  scanner->opt[OPT_CUSTOM_GAMMA].desc  = SANE_DESC_CUSTOM_GAMMA;
  scanner->opt[OPT_CUSTOM_GAMMA].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_CUSTOM_GAMMA].w     = SANE_TRUE;

  /* grayscale gamma vector */
  scanner->opt[OPT_GAMMA_VECTOR].name  = SANE_NAME_GAMMA_VECTOR;
  scanner->opt[OPT_GAMMA_VECTOR].title = SANE_TITLE_GAMMA_VECTOR;
  scanner->opt[OPT_GAMMA_VECTOR].desc  = SANE_DESC_GAMMA_VECTOR;
  scanner->opt[OPT_GAMMA_VECTOR].type  = SANE_TYPE_INT;
  scanner->opt[OPT_GAMMA_VECTOR].unit  = SANE_UNIT_NONE;
  scanner->opt[OPT_GAMMA_VECTOR].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->val[OPT_GAMMA_VECTOR].wa    = scanner->gamma_table[0];
  scanner->opt[OPT_GAMMA_VECTOR].constraint.range = &scanner->output_range;
  scanner->opt[OPT_GAMMA_VECTOR].size  = scanner->gamma_length * sizeof(SANE_Word);

  scanner->output_range.min   = 0;

  if (bit_depth_list[1] == 8)
  {
    scanner->output_range.max = 255;    /* 8 bits/pixel */
  }
  else
  {
    scanner->output_range.max = 65535;  /* 9-16 bits/pixel */
  }

  scanner->output_range.quant = 0;

  /* red gamma vector */
  scanner->opt[OPT_GAMMA_VECTOR_R].name  = SANE_NAME_GAMMA_VECTOR_R;
  scanner->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R;
  scanner->opt[OPT_GAMMA_VECTOR_R].desc  = SANE_DESC_GAMMA_VECTOR_R;
  scanner->opt[OPT_GAMMA_VECTOR_R].type  = SANE_TYPE_INT;
  scanner->opt[OPT_GAMMA_VECTOR_R].unit  = SANE_UNIT_NONE;
  scanner->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->val[OPT_GAMMA_VECTOR_R].wa    = scanner->gamma_table[1];
  scanner->opt[OPT_GAMMA_VECTOR_R].constraint.range = &(scanner->gamma_range);
  scanner->opt[OPT_GAMMA_VECTOR_R].size  = scanner->gamma_length * sizeof(SANE_Word);

  /* green gamma vector */
  scanner->opt[OPT_GAMMA_VECTOR_G].name  = SANE_NAME_GAMMA_VECTOR_G;
  scanner->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G;
  scanner->opt[OPT_GAMMA_VECTOR_G].desc  = SANE_DESC_GAMMA_VECTOR_G;
  scanner->opt[OPT_GAMMA_VECTOR_G].type  = SANE_TYPE_INT;
  scanner->opt[OPT_GAMMA_VECTOR_G].unit  = SANE_UNIT_NONE;
  scanner->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->val[OPT_GAMMA_VECTOR_G].wa    = scanner->gamma_table[2];
  scanner->opt[OPT_GAMMA_VECTOR_G].constraint.range = &(scanner->gamma_range);
  scanner->opt[OPT_GAMMA_VECTOR_G].size  = scanner->gamma_length * sizeof(SANE_Word);

  /* blue gamma vector */
  scanner->opt[OPT_GAMMA_VECTOR_B].name  = SANE_NAME_GAMMA_VECTOR_B;
  scanner->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B;
  scanner->opt[OPT_GAMMA_VECTOR_B].desc  = SANE_DESC_GAMMA_VECTOR_B;
  scanner->opt[OPT_GAMMA_VECTOR_B].type  = SANE_TYPE_INT;
  scanner->opt[OPT_GAMMA_VECTOR_B].unit  = SANE_UNIT_NONE;
  scanner->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->val[OPT_GAMMA_VECTOR_B].wa    = scanner->gamma_table[3];
  scanner->opt[OPT_GAMMA_VECTOR_B].constraint.range = &(scanner->gamma_range);
  scanner->opt[OPT_GAMMA_VECTOR_B].size  = scanner->gamma_length * sizeof(SANE_Word);

  /* halftone dimension */
  scanner->opt[OPT_HALFTONE_DIMENSION].name  = SANE_NAME_HALFTONE_DIMENSION;
  scanner->opt[OPT_HALFTONE_DIMENSION].title = SANE_TITLE_HALFTONE_DIMENSION;
  scanner->opt[OPT_HALFTONE_DIMENSION].desc  = SANE_DESC_HALFTONE_DIMENSION;
  scanner->opt[OPT_HALFTONE_DIMENSION].type  = SANE_TYPE_INT;
  scanner->opt[OPT_HALFTONE_DIMENSION].unit  = SANE_UNIT_PIXEL;
  scanner->opt[OPT_HALFTONE_DIMENSION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  scanner->opt[OPT_HALFTONE_DIMENSION].constraint.word_list = pattern_dim_list;
  scanner->val[OPT_HALFTONE_DIMENSION].w     = pattern_dim_list[1];

  /* halftone pattern */
  scanner->opt[OPT_HALFTONE_PATTERN].name  = SANE_NAME_HALFTONE_PATTERN;
  scanner->opt[OPT_HALFTONE_PATTERN].title = SANE_TITLE_HALFTONE_PATTERN;
  scanner->opt[OPT_HALFTONE_PATTERN].desc  = SANE_DESC_HALFTONE_PATTERN;
  scanner->opt[OPT_HALFTONE_PATTERN].type  = SANE_TYPE_INT;
  scanner->opt[OPT_HALFTONE_PATTERN].size  = 0;
  scanner->opt[OPT_HALFTONE_PATTERN].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_HALFTONE_PATTERN].constraint.range = &u8_range;
  scanner->val[OPT_HALFTONE_PATTERN].wa    = scanner->halftone_pattern;


  /* ------------------------------ */


  /* "Advanced" group: */
  scanner->opt[OPT_ADVANCED_GROUP].title = SANE_I18N("Advanced");
  scanner->opt[OPT_ADVANCED_GROUP].desc  = "";
  scanner->opt[OPT_ADVANCED_GROUP].type  = SANE_TYPE_GROUP;
  scanner->opt[OPT_ADVANCED_GROUP].cap   = SANE_CAP_ADVANCED;
  scanner->opt[OPT_ADVANCED_GROUP].constraint_type = SANE_CONSTRAINT_NONE;


  /* ------------------------------ */


  /* select exposure time */
  scanner->opt[OPT_SELECT_EXPOSURE_TIME].name  = SANE_NAME_SELECT_EXPOSURE_TIME;
  scanner->opt[OPT_SELECT_EXPOSURE_TIME].title = SANE_TITLE_SELECT_EXPOSURE_TIME;
  scanner->opt[OPT_SELECT_EXPOSURE_TIME].desc  = SANE_DESC_SELECT_EXPOSURE_TIME;
  scanner->opt[OPT_SELECT_EXPOSURE_TIME].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_SELECT_EXPOSURE_TIME].w     = SANE_FALSE;

  /* select calibration exposure time */
  scanner->opt[OPT_SELECT_CAL_EXPOSURE_TIME].name  = SANE_UMAX_NAME_SELECT_CALIBRATON_EXPOSURE_TIME;
  scanner->opt[OPT_SELECT_CAL_EXPOSURE_TIME].title = SANE_UMAX_TITLE_SELECT_CALIBRATION_EXPOSURE_TIME;
  scanner->opt[OPT_SELECT_CAL_EXPOSURE_TIME].desc  = SANE_UMAX_DESC_SELECT_CALIBRATION_EXPOSURE_TIME;
  scanner->opt[OPT_SELECT_CAL_EXPOSURE_TIME].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_SELECT_CAL_EXPOSURE_TIME].w     = SANE_FALSE;
  scanner->opt[OPT_SELECT_CAL_EXPOSURE_TIME].cap  |= SANE_CAP_INACTIVE;

  /* calibration exposure time */
  scanner->opt[OPT_CAL_EXPOS_TIME].name  = SANE_NAME_CAL_EXPOS_TIME;
  scanner->opt[OPT_CAL_EXPOS_TIME].title = SANE_TITLE_CAL_EXPOS_TIME;
  scanner->opt[OPT_CAL_EXPOS_TIME].desc  = SANE_DESC_CAL_EXPOS_TIME;
  scanner->opt[OPT_CAL_EXPOS_TIME].type  = SANE_TYPE_INT;
  scanner->opt[OPT_CAL_EXPOS_TIME].unit  = SANE_UNIT_MICROSECOND;
  scanner->opt[OPT_CAL_EXPOS_TIME].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_CAL_EXPOS_TIME].constraint.range = &(scanner->exposure_time_range);
  scanner->val[OPT_CAL_EXPOS_TIME].w     = scanner->device->inquiry_exposure_time_g_fb_def *
                                           scanner->device->inquiry_exposure_time_step_unit;
  scanner->opt[OPT_CAL_EXPOS_TIME].cap  |= SANE_CAP_INACTIVE;

  /* calibration exposure time red */
  scanner->opt[OPT_CAL_EXPOS_TIME_R].name  = SANE_NAME_CAL_EXPOS_TIME_R;
  scanner->opt[OPT_CAL_EXPOS_TIME_R].title = SANE_TITLE_CAL_EXPOS_TIME_R;
  scanner->opt[OPT_CAL_EXPOS_TIME_R].desc  = SANE_DESC_CAL_EXPOS_TIME_R;
  scanner->opt[OPT_CAL_EXPOS_TIME_R].type  = SANE_TYPE_INT;
  scanner->opt[OPT_CAL_EXPOS_TIME_R].unit  = SANE_UNIT_MICROSECOND;
  scanner->opt[OPT_CAL_EXPOS_TIME_R].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_CAL_EXPOS_TIME_R].constraint.range = &(scanner->exposure_time_range);
  scanner->val[OPT_CAL_EXPOS_TIME_R].w     = scanner->device->inquiry_exposure_time_c_fb_def_r *
                                             scanner->device->inquiry_exposure_time_step_unit;
  scanner->opt[OPT_CAL_EXPOS_TIME_R].cap  |= SANE_CAP_INACTIVE;

  /* calibration exposure time green */
  scanner->opt[OPT_CAL_EXPOS_TIME_G].name  = SANE_NAME_CAL_EXPOS_TIME_G;
  scanner->opt[OPT_CAL_EXPOS_TIME_G].title = SANE_TITLE_CAL_EXPOS_TIME_G;
  scanner->opt[OPT_CAL_EXPOS_TIME_G].desc  = SANE_DESC_CAL_EXPOS_TIME_G;
  scanner->opt[OPT_CAL_EXPOS_TIME_G].type  = SANE_TYPE_INT;
  scanner->opt[OPT_CAL_EXPOS_TIME_G].unit  = SANE_UNIT_MICROSECOND;
  scanner->opt[OPT_CAL_EXPOS_TIME_G].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_CAL_EXPOS_TIME_G].constraint.range = &(scanner->exposure_time_range);
  scanner->val[OPT_CAL_EXPOS_TIME_G].w     = scanner->device->inquiry_exposure_time_c_fb_def_g *
                                             scanner->device->inquiry_exposure_time_step_unit;
  scanner->opt[OPT_CAL_EXPOS_TIME_G].cap  |= SANE_CAP_INACTIVE;

  /* calibration exposure time blue */
  scanner->opt[OPT_CAL_EXPOS_TIME_B].name  = SANE_NAME_CAL_EXPOS_TIME_B;
  scanner->opt[OPT_CAL_EXPOS_TIME_B].title = SANE_TITLE_CAL_EXPOS_TIME_B;
  scanner->opt[OPT_CAL_EXPOS_TIME_B].desc  = SANE_DESC_CAL_EXPOS_TIME_B;
  scanner->opt[OPT_CAL_EXPOS_TIME_B].type  = SANE_TYPE_INT;
  scanner->opt[OPT_CAL_EXPOS_TIME_B].unit  = SANE_UNIT_MICROSECOND;
  scanner->opt[OPT_CAL_EXPOS_TIME_B].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_CAL_EXPOS_TIME_B].constraint.range = &(scanner->exposure_time_range);
  scanner->val[OPT_CAL_EXPOS_TIME_B].w     = scanner->device->inquiry_exposure_time_c_fb_def_b *
                                             scanner->device->inquiry_exposure_time_step_unit;
  scanner->opt[OPT_CAL_EXPOS_TIME_B].cap  |= SANE_CAP_INACTIVE;

  /* scan exposure time */
  scanner->opt[OPT_SCAN_EXPOS_TIME].name  = SANE_NAME_SCAN_EXPOS_TIME;
  scanner->opt[OPT_SCAN_EXPOS_TIME].title = SANE_TITLE_SCAN_EXPOS_TIME;
  scanner->opt[OPT_SCAN_EXPOS_TIME].desc  = SANE_DESC_SCAN_EXPOS_TIME;
  scanner->opt[OPT_SCAN_EXPOS_TIME].type  = SANE_TYPE_INT;
  scanner->opt[OPT_SCAN_EXPOS_TIME].unit  = SANE_UNIT_MICROSECOND;
  scanner->opt[OPT_SCAN_EXPOS_TIME].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_SCAN_EXPOS_TIME].constraint.range = &(scanner->exposure_time_range);
  scanner->val[OPT_SCAN_EXPOS_TIME].w     = scanner->device->inquiry_exposure_time_g_fb_def *
                                            scanner->device->inquiry_exposure_time_step_unit;
  scanner->opt[OPT_SCAN_EXPOS_TIME].cap  |= SANE_CAP_INACTIVE;

  /* scan exposure time red */
  scanner->opt[OPT_SCAN_EXPOS_TIME_R].name  = SANE_NAME_SCAN_EXPOS_TIME_R;
  scanner->opt[OPT_SCAN_EXPOS_TIME_R].title = SANE_TITLE_SCAN_EXPOS_TIME_R;
  scanner->opt[OPT_SCAN_EXPOS_TIME_R].desc  = SANE_DESC_SCAN_EXPOS_TIME_R;
  scanner->opt[OPT_SCAN_EXPOS_TIME_R].type  = SANE_TYPE_INT;
  scanner->opt[OPT_SCAN_EXPOS_TIME_R].unit  = SANE_UNIT_MICROSECOND;
  scanner->opt[OPT_SCAN_EXPOS_TIME_R].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_SCAN_EXPOS_TIME_R].constraint.range = &(scanner->exposure_time_range);
  scanner->val[OPT_SCAN_EXPOS_TIME_R].w     = scanner->device->inquiry_exposure_time_c_fb_def_r *
                                              scanner->device->inquiry_exposure_time_step_unit;
  scanner->opt[OPT_SCAN_EXPOS_TIME_R].cap  |= SANE_CAP_INACTIVE;

  /* scan exposure time green */
  scanner->opt[OPT_SCAN_EXPOS_TIME_G].name  = SANE_NAME_SCAN_EXPOS_TIME_G;
  scanner->opt[OPT_SCAN_EXPOS_TIME_G].title = SANE_TITLE_SCAN_EXPOS_TIME_G;
  scanner->opt[OPT_SCAN_EXPOS_TIME_G].desc  = SANE_DESC_SCAN_EXPOS_TIME_G;
  scanner->opt[OPT_SCAN_EXPOS_TIME_G].type  = SANE_TYPE_INT;
  scanner->opt[OPT_SCAN_EXPOS_TIME_G].unit  = SANE_UNIT_MICROSECOND;
  scanner->opt[OPT_SCAN_EXPOS_TIME_G].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_SCAN_EXPOS_TIME_G].constraint.range = &(scanner->exposure_time_range);
  scanner->val[OPT_SCAN_EXPOS_TIME_G].w     = scanner->device->inquiry_exposure_time_c_fb_def_g *
                                              scanner->device->inquiry_exposure_time_step_unit;
  scanner->opt[OPT_SCAN_EXPOS_TIME_G].cap  |= SANE_CAP_INACTIVE;

  /* scan exposure time blue */
  scanner->opt[OPT_SCAN_EXPOS_TIME_B].name  = SANE_NAME_SCAN_EXPOS_TIME_B;
  scanner->opt[OPT_SCAN_EXPOS_TIME_B].title = SANE_TITLE_SCAN_EXPOS_TIME_B;
  scanner->opt[OPT_SCAN_EXPOS_TIME_B].desc  = SANE_DESC_SCAN_EXPOS_TIME_B;
  scanner->opt[OPT_SCAN_EXPOS_TIME_B].type  = SANE_TYPE_INT;
  scanner->opt[OPT_SCAN_EXPOS_TIME_B].unit  = SANE_UNIT_MICROSECOND;
  scanner->opt[OPT_SCAN_EXPOS_TIME_B].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_SCAN_EXPOS_TIME_B].constraint.range = &(scanner->exposure_time_range);
  scanner->val[OPT_SCAN_EXPOS_TIME_B].w     = scanner->device->inquiry_exposure_time_c_fb_def_b *
                                              scanner->device->inquiry_exposure_time_step_unit;
  scanner->opt[OPT_SCAN_EXPOS_TIME_B].cap  |= SANE_CAP_INACTIVE;

  if (scanner->device->inquiry_exposure_adj == 0)
  {
    scanner->opt[OPT_SELECT_EXPOSURE_TIME].cap   |= SANE_CAP_INACTIVE;
  }


  /* ------------------------------ */


  /* select calibration lamp density */
  scanner->opt[OPT_SELECT_LAMP_DENSITY].name  = SANE_NAME_SELECT_LAMP_DENSITY;
  scanner->opt[OPT_SELECT_LAMP_DENSITY].title = SANE_TITLE_SELECT_LAMP_DENSITY;
  scanner->opt[OPT_SELECT_LAMP_DENSITY].desc  = SANE_DESC_SELECT_LAMP_DENSITY;
  scanner->opt[OPT_SELECT_LAMP_DENSITY].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_SELECT_LAMP_DENSITY].w     = SANE_FALSE;

  /* calibration lamp density */
  scanner->opt[OPT_CAL_LAMP_DEN].name  = SANE_NAME_CAL_LAMP_DEN;
  scanner->opt[OPT_CAL_LAMP_DEN].title = SANE_TITLE_CAL_LAMP_DEN;
  scanner->opt[OPT_CAL_LAMP_DEN].desc  = SANE_DESC_CAL_LAMP_DEN;
  scanner->opt[OPT_CAL_LAMP_DEN].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_CAL_LAMP_DEN].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_CAL_LAMP_DEN].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_CAL_LAMP_DEN].constraint.range = &percentage_range_100;
  scanner->val[OPT_CAL_LAMP_DEN].w     = SANE_FIX(50);
  scanner->opt[OPT_CAL_LAMP_DEN].cap  |= SANE_CAP_INACTIVE;

  /* scan lamp density */
  scanner->opt[OPT_SCAN_LAMP_DEN].name  = SANE_NAME_SCAN_LAMP_DEN;
  scanner->opt[OPT_SCAN_LAMP_DEN].title = SANE_TITLE_SCAN_LAMP_DEN;
  scanner->opt[OPT_SCAN_LAMP_DEN].desc  = SANE_DESC_SCAN_LAMP_DEN;
  scanner->opt[OPT_SCAN_LAMP_DEN].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_SCAN_LAMP_DEN].unit  = SANE_UNIT_PERCENT;
  scanner->opt[OPT_SCAN_LAMP_DEN].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_SCAN_LAMP_DEN].constraint.range = &percentage_range_100;
  scanner->val[OPT_SCAN_LAMP_DEN].w     = SANE_FIX(50);
  scanner->opt[OPT_SCAN_LAMP_DEN].cap  |= SANE_CAP_INACTIVE;

  if (scanner->device->inquiry_lamp_ctrl == 0)
  {
    scanner->opt[OPT_SELECT_LAMP_DENSITY].cap  |= SANE_CAP_INACTIVE;
  }

  /* ------------------------------ */

  /* disable pre focus */
  scanner->opt[OPT_DISABLE_PRE_FOCUS].name  = "disable-pre-focus";
  scanner->opt[OPT_DISABLE_PRE_FOCUS].title = SANE_I18N("Disable pre focus");
  scanner->opt[OPT_DISABLE_PRE_FOCUS].desc  = SANE_I18N("Do not calibrate focus");
  scanner->opt[OPT_DISABLE_PRE_FOCUS].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_DISABLE_PRE_FOCUS].w     = SANE_FALSE;

  if (scanner->device->inquiry_manual_focus == 0)
  {
    scanner->opt[OPT_DISABLE_PRE_FOCUS].cap  |= SANE_CAP_INACTIVE;
  }

  /* manual pre focus */
  scanner->opt[OPT_MANUAL_PRE_FOCUS].name  = "manual-pre-focus";
  scanner->opt[OPT_MANUAL_PRE_FOCUS].title = SANE_I18N("Manual pre focus");
  scanner->opt[OPT_MANUAL_PRE_FOCUS].desc  = "";
  scanner->opt[OPT_MANUAL_PRE_FOCUS].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_MANUAL_PRE_FOCUS].w     = SANE_FALSE;

  if (scanner->device->inquiry_manual_focus == 0)
  {
    scanner->opt[OPT_MANUAL_PRE_FOCUS].cap  |= SANE_CAP_INACTIVE;
  }

  /* fix focus position */
  scanner->opt[OPT_FIX_FOCUS_POSITION].name  = "fix-focus-position";
  scanner->opt[OPT_FIX_FOCUS_POSITION].title = SANE_I18N("Fix focus position");
  scanner->opt[OPT_FIX_FOCUS_POSITION].desc  = "";
  scanner->opt[OPT_FIX_FOCUS_POSITION].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_FIX_FOCUS_POSITION].w     = SANE_FALSE;

  if (scanner->device->inquiry_manual_focus == 0)
  {
    scanner->opt[OPT_FIX_FOCUS_POSITION].cap  |= SANE_CAP_INACTIVE;
  }

  /* lens calibration in doc position */
  scanner->opt[OPT_LENS_CALIBRATION_DOC_POS].name  = "lens-calibration-in-doc-position";
  scanner->opt[OPT_LENS_CALIBRATION_DOC_POS].title = SANE_I18N("Lens calibration in doc position");
  scanner->opt[OPT_LENS_CALIBRATION_DOC_POS].desc  = SANE_I18N("Calibrate lens focus in document position");
  scanner->opt[OPT_LENS_CALIBRATION_DOC_POS].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_LENS_CALIBRATION_DOC_POS].w     = SANE_FALSE;

  if (scanner->device->inquiry_lens_cal_in_doc_pos == 0)
  {
    scanner->opt[OPT_LENS_CALIBRATION_DOC_POS].cap  |= SANE_CAP_INACTIVE;
  }

  /* 0mm holder focus position */
  scanner->opt[OPT_HOLDER_FOCUS_POS_0MM].name  = "holder-focus-position-0mm";
  scanner->opt[OPT_HOLDER_FOCUS_POS_0MM].title = SANE_I18N("Holder focus position 0mm");
  scanner->opt[OPT_HOLDER_FOCUS_POS_0MM].desc  = SANE_I18N("Use 0mm holder focus position instead of 0.6mm");
  scanner->opt[OPT_HOLDER_FOCUS_POS_0MM].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_HOLDER_FOCUS_POS_0MM].w     = SANE_FALSE;

  if (scanner->device->inquiry_sel_uta_lens_cal_pos == 0)
  {
    scanner->opt[OPT_HOLDER_FOCUS_POS_0MM].cap  |= SANE_CAP_INACTIVE;
  }

  /* ------------------------------ */

  /* lamp on */
  scanner->opt[OPT_LAMP_ON].name  = "lamp-on";
  scanner->opt[OPT_LAMP_ON].title = SANE_I18N("Lamp on");
  scanner->opt[OPT_LAMP_ON].desc  = SANE_I18N("Turn on scanner lamp");
  scanner->opt[OPT_LAMP_ON].type  = SANE_TYPE_BUTTON;
  scanner->opt[OPT_LAMP_ON].unit  = SANE_UNIT_NONE;
  scanner->opt[OPT_LAMP_ON].size  = 0;
  scanner->opt[OPT_LAMP_ON].constraint_type  = SANE_CONSTRAINT_NONE;
  scanner->val[OPT_LAMP_ON].w     = 0;

  if (!scanner->device->lamp_control_available)		/* lamp state can not be controlled by driver */
  {
    scanner->opt[OPT_LAMP_ON].cap  |= SANE_CAP_INACTIVE;
  }

  /* ------------------------------ */

  /* lamp off */
  scanner->opt[OPT_LAMP_OFF].name  = "lamp-off";
  scanner->opt[OPT_LAMP_OFF].title = SANE_I18N("Lamp off");
  scanner->opt[OPT_LAMP_OFF].desc  = SANE_I18N("Turn off scanner lamp");
  scanner->opt[OPT_LAMP_OFF].type  = SANE_TYPE_BUTTON;
  scanner->opt[OPT_LAMP_OFF].unit  = SANE_UNIT_NONE;
  scanner->opt[OPT_LAMP_OFF].size  = 0;
  scanner->opt[OPT_LAMP_OFF].constraint_type  = SANE_CONSTRAINT_NONE;
  scanner->val[OPT_LAMP_OFF].w     = 0;

  if (!scanner->device->lamp_control_available)		/* lamp state can not be controlled by driver */
  {
    scanner->opt[OPT_LAMP_OFF].cap  |= SANE_CAP_INACTIVE;
  }

  /* ------------------------------ */

  /* lamp off at exit */
  scanner->opt[OPT_LAMP_OFF_AT_EXIT].name  = "lamp-off-at-exit";
  scanner->opt[OPT_LAMP_OFF_AT_EXIT].title = SANE_I18N("Lamp off at exit");
  scanner->opt[OPT_LAMP_OFF_AT_EXIT].desc  = SANE_I18N("Turn off lamp when program exits");
  scanner->opt[OPT_LAMP_OFF_AT_EXIT].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_LAMP_OFF_AT_EXIT].w     = SANE_FALSE;

  if (!scanner->device->lamp_control_available)		/* lamp state can not be controlled by driver */
  {
    scanner->opt[OPT_LAMP_OFF_AT_EXIT].cap  |= SANE_CAP_INACTIVE;
  }

  /* ------------------------------ */

  /* batch-scan-start */
  scanner->opt[OPT_BATCH_SCAN_START].name  = SANE_NAME_BATCH_SCAN_START;
  scanner->opt[OPT_BATCH_SCAN_START].title = SANE_TITLE_BATCH_SCAN_START;
  scanner->opt[OPT_BATCH_SCAN_START].desc  = SANE_DESC_BATCH_SCAN_START;
  scanner->opt[OPT_BATCH_SCAN_START].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_BATCH_SCAN_START].w     = SANE_FALSE;

  /* batch-scan-loop */
  scanner->opt[OPT_BATCH_SCAN_LOOP].name  = SANE_NAME_BATCH_SCAN_LOOP;
  scanner->opt[OPT_BATCH_SCAN_LOOP].title = SANE_TITLE_BATCH_SCAN_LOOP;
  scanner->opt[OPT_BATCH_SCAN_LOOP].desc  = SANE_DESC_BATCH_SCAN_LOOP;
  scanner->opt[OPT_BATCH_SCAN_LOOP].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_BATCH_SCAN_LOOP].w     = SANE_FALSE;

  /* batch-scan-end */
  scanner->opt[OPT_BATCH_SCAN_END].name  = SANE_NAME_BATCH_SCAN_END;
  scanner->opt[OPT_BATCH_SCAN_END].title = SANE_TITLE_BATCH_SCAN_END;
  scanner->opt[OPT_BATCH_SCAN_END].desc  = SANE_DESC_BATCH_SCAN_END;
  scanner->opt[OPT_BATCH_SCAN_END].type  = SANE_TYPE_BOOL;
  scanner->val[OPT_BATCH_SCAN_END].w     = SANE_FALSE;

  /* batch-scan-next-y */
  scanner->opt[OPT_BATCH_NEXT_TL_Y].name  = SANE_NAME_BATCH_NEXT_TL_Y;
  scanner->opt[OPT_BATCH_NEXT_TL_Y].title = SANE_TITLE_BATCH_NEXT_TL_Y;
  scanner->opt[OPT_BATCH_NEXT_TL_Y].desc  = SANE_DESC_BATCH_NEXT_TL_Y;
  scanner->opt[OPT_BATCH_NEXT_TL_Y].type  = SANE_TYPE_FIXED;
  scanner->opt[OPT_BATCH_NEXT_TL_Y].unit  = SANE_UNIT_MM;
  scanner->opt[OPT_BATCH_NEXT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_BATCH_NEXT_TL_Y].constraint.range = &(scanner->device->y_range);
  scanner->val[OPT_BATCH_NEXT_TL_Y].w     = 0xFFFF; /* mark value as not touched */

  if (scanner->device->inquiry_batch_scan == 0)
  {
    scanner->opt[OPT_BATCH_SCAN_START].cap  |= SANE_CAP_INACTIVE;
    scanner->opt[OPT_BATCH_SCAN_LOOP].cap   |= SANE_CAP_INACTIVE;
    scanner->opt[OPT_BATCH_SCAN_END].cap    |= SANE_CAP_INACTIVE;
    scanner->opt[OPT_BATCH_NEXT_TL_Y].cap      |= SANE_CAP_INACTIVE;
  }

  /* ------------------------------ */

#ifdef UMAX_CALIBRATION_MODE_SELECTABLE
  /* calibration mode */
  scanner->opt[OPT_CALIB_MODE].name  = "calibrationmode";
  scanner->opt[OPT_CALIB_MODE].title = SANE_I18N("Calibration mode");
  scanner->opt[OPT_CALIB_MODE].desc  = SANE_I18N("Define calibration mode");
  scanner->opt[OPT_CALIB_MODE].type  = SANE_TYPE_STRING;
  scanner->opt[OPT_CALIB_MODE].size  = max_string_size(calibration_list);
  scanner->opt[OPT_CALIB_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  scanner->opt[OPT_CALIB_MODE].constraint.string_list = calibration_list;
  scanner->val[OPT_CALIB_MODE].s     = (SANE_Char*)strdup(calibration_list[0]);

  if (scanner->device->inquiry_calibration == 0)
  {
    scanner->opt[OPT_CALIB_MODE].cap  |= SANE_CAP_INACTIVE;
  }
#endif

  /* preview */
  scanner->opt[OPT_PREVIEW].name  = SANE_NAME_PREVIEW;
  scanner->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW;
  scanner->opt[OPT_PREVIEW].desc  = SANE_DESC_PREVIEW;
  scanner->opt[OPT_PREVIEW].type  = SANE_TYPE_BOOL;
  scanner->opt[OPT_PREVIEW].cap   = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  scanner->val[OPT_PREVIEW].w     = SANE_FALSE;

  sane_control_option(scanner, OPT_MODE, SANE_ACTION_SET_VALUE,
                      (SANE_String *) scan_mode_list[scan_modes], NULL );
  
 return SANE_STATUS_GOOD;
}


/* ------------------------------------------------------------ ATTACH ONE SCSI ----------------------------- */

/* callback function for sanei_config_attach_matching_devices(dev_name, attach_one_scsi) */
static SANE_Status attach_one_scsi(const char *name)
{
  attach_scanner(name, 0, SANE_UMAX_SCSI);
 return SANE_STATUS_GOOD;
}

/* ------------------------------------------------------------ ATTACH ONE USB ------------------------------ */

/* callback function for sanei_usb_attach_matching_devices(dev_name, attach_one_usb) */
static SANE_Status attach_one_usb(const char *name)
{
  attach_scanner(name, 0, SANE_UMAX_USB);
 return SANE_STATUS_GOOD;
}

/* ------------------------------------------------------------ UMAX TEST CONFIGURE OPTION ------------------ */

static SANE_Status umax_test_configure_option(const char *option_str, char *test_name, int *test_value, int test_min, int test_max)
/* returns with 1 if option was found, 0 if option was not found */
{
 const char *value_str;
 char *end_ptr;
 int value;

  if (strncmp(option_str, test_name, strlen(test_name)) == 0)
  {
    value_str = sanei_config_skip_whitespace(option_str+strlen(test_name));

    errno = 0;
    value = strtol(value_str, &end_ptr, 10);
    if (end_ptr == value_str || errno) 
    {
      DBG(DBG_error, "ERROR: invalid value \"%s\" for option %s in %s\n", value_str, test_name, UMAX_CONFIG_FILE);
    }
    else
    {
      if (value < test_min)
      {
        DBG(DBG_error, "ERROR: value \"%d\" is too small for option %s in %s\n", value, test_name, UMAX_CONFIG_FILE);
        value = test_min;
      }
      else if (value > test_max)
      {
        DBG(DBG_error, "ERROR: value \"%d\" is too large for option %s in %s\n", value, test_name, UMAX_CONFIG_FILE);
        value = test_max;
      }

      *test_value = value;

      DBG(DBG_info, "option %s = %d\n", test_name, *test_value);
    }
    return 1;
  }
 return 0;
}

/* ------------------------------------------------------------ SANE INIT ---------------------------------- */


SANE_Status sane_init(SANE_Int *version_code, SANE_Auth_Callback authorize)
{
 char config_line[PATH_MAX];
 const char *option_str;
 size_t len;
 FILE *fp;

  /* we have to initialize these global variables here because sane_init can be called several times */
  num_devices  = 0;
  devlist      = NULL;
  first_dev    = NULL;
  first_handle = NULL;

  DBG_INIT();

  DBG(DBG_sane_init,"sane_init\n");
  DBG(DBG_error,"This is sane-umax version %d.%d build %d\n", V_MAJOR, V_MINOR, BUILD);
#ifdef UMAX_ENABLE_USB
  DBG(DBG_error,"compiled with USB support for Astra 2200\n");
#else
  DBG(DBG_error,"no USB support for Astra 2200\n");
#endif
  DBG(DBG_error,"(C) 1997-2002 by Oliver Rauch\n");
  DBG(DBG_error,"EMAIL: Oliver.Rauch@rauch-domain.de\n");

  if (version_code)
  {
    *version_code = SANE_VERSION_CODE(V_MAJOR, V_MINOR, BUILD);
  }

  frontend_authorize_callback = authorize; /* store frontend authorize callback */

  sanei_thread_init(); /* must be called before any other sanei_thread call */

#ifdef UMAX_ENABLE_USB
  sanei_usb_init();
#endif

  fp = sanei_config_open(UMAX_CONFIG_FILE);
  if (!fp) 
  {
    /* no config-file: try /dev/scanner and /dev/usbscanner. */
    attach_scanner("/dev/scanner",    0, SANE_UMAX_SCSI);
#ifdef UMAX_ENABLE_USB
    attach_scanner("/dev/usbscanner", 0, SANE_UMAX_USB);
#endif
   return SANE_STATUS_GOOD;
  }

  DBG(DBG_info, "reading configure file %s\n", UMAX_CONFIG_FILE);

  while(sanei_config_read(config_line, sizeof(config_line), fp))
  {
    if (config_line[0] == '#')
    {
      continue; /* ignore line comments */
    }

    if (strncmp(config_line, "option", 6) == 0)
    {
      option_str = sanei_config_skip_whitespace(config_line+6);

      if      (umax_test_configure_option(option_str, "scsi-maxqueue",                  &umax_scsi_maxqueue, 1, SANE_UMAX_SCSI_MAXQUEUE));
      else if (umax_test_configure_option(option_str, "scsi-buffer-size-min",           &umax_scsi_buffer_size_min,   4096, 1048576));
      else if (umax_test_configure_option(option_str, "scsi-buffer-size-max",           &umax_scsi_buffer_size_max,   4096, 1048576));
      else if (umax_test_configure_option(option_str, "preview-lines",                  &umax_preview_lines,             1,   65535));
      else if (umax_test_configure_option(option_str, "scan-lines",                     &umax_scan_lines,                1,   65535));
      else if (umax_test_configure_option(option_str, "handle-bad-sense-error",         &umax_handle_bad_sense_error,    0,   3));
      else if (umax_test_configure_option(option_str, "execute-request-sense",          &umax_execute_request_sense,     0,   1));
      else if (umax_test_configure_option(option_str, "force-preview-bit-rgb",          &umax_force_preview_bit_rgb,     0,   1));
      else if (umax_test_configure_option(option_str, "slow-speed",                     &umax_slow,                     -1,   1));
      else if (umax_test_configure_option(option_str, "care-about-smearing",            &umax_smear,                    -1,   1));
      else if (umax_test_configure_option(option_str, "calibration-full-ccd",           &umax_calibration_area,         -1,   1));
      else if (umax_test_configure_option(option_str, "calibration-width-offset-batch", &umax_calibration_width_offset_batch, -99999, 65535));
      else if (umax_test_configure_option(option_str, "calibration-width-offset",       &umax_calibration_width_offset, -99999, 65535));
      else if (umax_test_configure_option(option_str, "calibration-bytes-pixel",        &umax_calibration_bytespp,      -1,   2));
      else if (umax_test_configure_option(option_str, "exposure-time-rgb-bind",         &umax_exposure_time_rgb_bind,   -1,   1));
      else if (umax_test_configure_option(option_str, "invert-shading-data",            &umax_invert_shading_data,      -1,   1));
      else if (umax_test_configure_option(option_str, "lamp-control-available",         &umax_lamp_control_available,    0,   1));
      else if (umax_test_configure_option(option_str, "gamma-lsb-padded",               &umax_gamma_lsb_padded,         -1,   1));
      else if (umax_test_configure_option(option_str, "connection-type",                &umax_connection_type,           1,   2));
      else
      {
        DBG(DBG_error,"ERROR: unknown option \"%s\" in %s\n", option_str, UMAX_CONFIG_FILE);
      }
      continue;
    }
    else if (strncmp(config_line, "scsi", 4) == 0)
    {
      DBG(DBG_info,"sanei_config_attach_matching_devices(%s)\n", config_line);
      sanei_config_attach_matching_devices(config_line, attach_one_scsi);
      continue;
    }
    else if (strncmp(config_line, "usb", 3) == 0)
    {
#ifdef UMAX_ENABLE_USB
      DBG(DBG_info,"sanei_usb_attach_matching_devices(%s)\n", config_line);
      sanei_usb_attach_matching_devices(config_line, attach_one_usb);
#else
      DBG(DBG_info,"USB not supported, ignoring config line: %s\n", config_line);
#endif
      continue;
    }

    len = strlen(config_line);

    if (!len) /* ignore empty lines */
    {
      continue;
    }

    /* umax_connection_type is set by umax.conf: 1=scsi, 2=usb */
    attach_scanner(config_line, 0, umax_connection_type); /* try to open as devicename */
  }

  DBG(DBG_info, "finished reading configure file\n");

  fclose(fp);

 return SANE_STATUS_GOOD;
}


/* ------------------------------------------------------------ SANE EXIT ---------------------------------- */


void sane_exit(void)
{
 Umax_Device *dev, *next;

  DBG(DBG_sane_init,"sane_exit\n");

  for (dev = first_dev; dev; dev = next)
  {
    next = dev->next;
    free(dev->devicename);
    free(dev);
  }

  if (devlist)
  {
    free(devlist);
  }
}


/* ------------------------------------------------------------ SANE GET DEVICES --------------------------- */


SANE_Status sane_get_devices(const SANE_Device ***device_list, SANE_Bool local_only)
{
 Umax_Device *dev;
 int i;

  DBG(DBG_sane_init,"sane_get_devices(local_only = %d)\n", local_only);

  if (devlist)
  {
    free(devlist);
  }

  devlist = malloc((num_devices + 1) * sizeof (devlist[0]));
  if (!devlist)
  {
    return SANE_STATUS_NO_MEM;
  }

  i = 0;

  for (dev = first_dev; i < num_devices; dev = dev->next)
  {
    devlist[i++] = &dev->sane;
  }

  devlist[i++] = 0;

  *device_list = devlist;

 return SANE_STATUS_GOOD;
}


/* ------------------------------------------------------------ SANE OPEN ---------------------------------- */


SANE_Status sane_open(SANE_String_Const devicename, SANE_Handle *handle)
{
 Umax_Device *dev;
 SANE_Status status;
 Umax_Scanner *scanner;
 unsigned int i, j;

  DBG(DBG_sane_init,"sane_open\n");

  if (devicename[0])   /* search for devicename */
  {
    DBG(DBG_sane_info,"sane_open: devicename=%s\n", devicename);

    for (dev = first_dev; dev; dev = dev->next)
    {
      if (strcmp(dev->sane.name, devicename) == 0)
      {
        break; /* device found, exit for loop */
      }
    }

    if (!dev) /* no device found */
    {
      status = attach_scanner(devicename, &dev, 0 /* connection-type not known */); /* try to open devicename and set dev */
      if (status != SANE_STATUS_GOOD)
      {
        return status;
      }
    }
  }
  else
  {
    DBG(DBG_sane_info,"sane_open: no devicename, opening first device\n");
    dev = first_dev; 							/* empty devicename -> use first device */
  }

  if (!dev) /* no device found */
  {
    return SANE_STATUS_INVAL;
  }

  scanner = malloc(sizeof (*scanner));
  if (!scanner)
  {
    return SANE_STATUS_NO_MEM;
  }

  memset(scanner, 0, sizeof (*scanner));

  scanner->device = dev;

  if (scanner->device->inquiry_GIB & 32)
  {
    scanner->gamma_length = 65536;							    /* 16 bits input */
    DBG(DBG_sane_info, "Using 16 bits for gamma input\n");
  }
  else if (scanner->device->inquiry_GIB & 16)
  {
    scanner->gamma_length = 16384;							    /* 14 bits input */
    DBG(DBG_sane_info, "Using 14 bits for gamma input\n");
  }
  else if (scanner->device->inquiry_GIB & 8)
  {
    scanner->gamma_length = 4096;							    /* 12 bits input */
    DBG(DBG_sane_info, "Using 12 bits for gamma input\n");
  }
  else if (scanner->device->inquiry_GIB & 4)
  {
    scanner->gamma_length = 1024;							    /* 10 bits input */
    DBG(DBG_sane_info, "Using 10 bits for gamma input\n");
  }
  else if (scanner->device->inquiry_GIB & 2)
  {
    scanner->gamma_length = 512;							     /* 9 bits input */
    DBG(DBG_sane_info, "Using 9 bits for gamma input\n");
  }
  else
  {
    scanner->gamma_length = 256;							     /* 8 bits input */
    DBG(DBG_sane_info, "Using 8 bits for gamma input\n");
  }

  scanner->output_bytes = 1;								    /* 8 bits output */

  scanner->gamma_range.min   = 0;
  scanner->gamma_range.max   = scanner->gamma_length-1;
  scanner->gamma_range.quant = 0;

  scanner->gamma_table[0] = (SANE_Int *) malloc(scanner->gamma_length * sizeof(SANE_Int));
  scanner->gamma_table[1] = (SANE_Int *) malloc(scanner->gamma_length * sizeof(SANE_Int));
  scanner->gamma_table[2] = (SANE_Int *) malloc(scanner->gamma_length * sizeof(SANE_Int));
  scanner->gamma_table[3] = (SANE_Int *) malloc(scanner->gamma_length * sizeof(SANE_Int));

  for (j = 0; j < scanner->gamma_length; ++j)			     /* gamma_table[0] : converts GIB to GOB */
  {
    scanner->gamma_table[0][j] = j * scanner->device->max_value / scanner->gamma_length;
  }

  for (i = 1; i < 4; ++i)			 /* gamma_table[1,2,3] : doesn't convert anything (GIB->GIB) */
  {
    for (j = 0; j < scanner->gamma_length; ++j)
    {
      scanner->gamma_table[i][j] = j;
    }
  }

  scanner->exposure_time_range.min   = scanner->device->inquiry_exposure_time_c_min *
                                       scanner->device->inquiry_exposure_time_step_unit;
  scanner->exposure_time_range.quant = scanner->device->inquiry_exposure_time_step_unit;
  scanner->exposure_time_range.max   = scanner->device->inquiry_exposure_time_max *
                                       scanner->device->inquiry_exposure_time_step_unit;

  init_options(scanner);

  scanner->next = first_handle;			    /* insert newly opened handle into list of open handles: */
  first_handle = scanner;

  *handle = scanner;
 return SANE_STATUS_GOOD;
}


/* ------------------------------------------------------------ SANE CLOSE --------------------------------- */


void sane_close(SANE_Handle handle)
{
 Umax_Scanner *prev, *scanner;

  DBG(DBG_sane_init,"sane_close\n");

  if (!first_handle)
  {
    DBG(DBG_error, "ERROR: sane_close: no handles opened\n");
    return;
  }

								 /* remove handle from list of open handles: */
  prev = 0;

  for (scanner = first_handle; scanner; scanner = scanner->next)
  {
    if (scanner == handle)
    {
      break;
    }

    prev = scanner;
  }
  
  if (!scanner)
  {
    DBG(DBG_error, "ERROR: sane_close: invalid handle %p\n", handle);
    return;								 /* oops, not a handle we know about */
  }

  if (scanner->scanning)						      /* stop scan if still scanning */
  {
    do_cancel(handle);
  } 

  if (scanner->device->lamp_control_available)			   /* lamp state can be controlled by driver */
  {
    if (scanner->val[OPT_LAMP_OFF_AT_EXIT].w)			      /* turn off scanner lamp on sane_close */
    {
      umax_set_lamp_status(handle, 0 /* lamp off */);
    }
  }

  if (prev)
  {
    prev->next = scanner->next;
  }
  else
  {
    first_handle = scanner->next;
  }

  free(scanner->gamma_table[0]);						 /* free custom gamma tables */
  free(scanner->gamma_table[1]);
  free(scanner->gamma_table[2]);
  free(scanner->gamma_table[3]);

  free(scanner->device->buffer[0]);			  /* free buffer allocated by umax_initialize_values */
  scanner->device->buffer[0]  = NULL;
  scanner->device->bufsize = 0;

  free(scanner);									     /* free scanner */
}


/* ------------------------------------------------------------ SANE GET OPTION DESCRIPTOR ----------------- */


const SANE_Option_Descriptor *sane_get_option_descriptor(SANE_Handle handle, SANE_Int option)
{
 Umax_Scanner *scanner = handle;

  DBG(DBG_sane_option,"sane_get_option_descriptor %d\n", option);

  if ((unsigned) option >= NUM_OPTIONS)
  {
    return 0;
  }

 return scanner->opt + option;
}

/* ------------------------------------------------------------ UMAX SET MAX GEOMETRY ---------------------- */

static void umax_set_max_geometry(Umax_Scanner *scanner)
{

  if (scanner->val[OPT_DOR].w)
  {
    scanner->device->x_range.min = SANE_FIX(scanner->device->inquiry_dor_x_off * MM_PER_INCH);
    scanner->device->x_range.max = SANE_FIX( (scanner->device->inquiry_dor_x_off + scanner->device->inquiry_dor_width)  * MM_PER_INCH);
    scanner->device->y_range.min = SANE_FIX(scanner->device->inquiry_dor_y_off * MM_PER_INCH);
    scanner->device->y_range.max = SANE_FIX( (scanner->device->inquiry_dor_y_off + scanner->device->inquiry_dor_length) * MM_PER_INCH);

    scanner->device->x_dpi_range.max = SANE_FIX(scanner->device->inquiry_dor_x_res);
    scanner->device->y_dpi_range.max = SANE_FIX(scanner->device->inquiry_dor_y_res);      
  }
  else if ( (strcmp(scanner->val[OPT_SOURCE].s, FLB_STR) == 0) || (strcmp(scanner->val[OPT_SOURCE].s, ADF_STR) == 0) )
  {
    scanner->device->x_range.min = 0;
    scanner->device->x_range.max = SANE_FIX(scanner->device->inquiry_fb_width  * MM_PER_INCH);
    scanner->device->y_range.min = 0;
    scanner->device->y_range.max = SANE_FIX(scanner->device->inquiry_fb_length * MM_PER_INCH);

    scanner->device->x_dpi_range.max = SANE_FIX(scanner->device->inquiry_x_res);
    scanner->device->y_dpi_range.max = SANE_FIX(scanner->device->inquiry_y_res);      
  }
  else if (strcmp(scanner->val[OPT_SOURCE].s, UTA_STR) == 0)
  {
    scanner->device->x_range.min = SANE_FIX(scanner->device->inquiry_uta_x_off  * MM_PER_INCH);
    scanner->device->x_range.max = SANE_FIX( (scanner->device->inquiry_uta_x_off + scanner->device->inquiry_uta_width) * MM_PER_INCH);
    scanner->device->y_range.min = SANE_FIX(scanner->device->inquiry_uta_y_off  * MM_PER_INCH);
    scanner->device->y_range.max = SANE_FIX( ( scanner->device->inquiry_uta_y_off + scanner->device->inquiry_uta_length) * MM_PER_INCH);

    scanner->device->x_dpi_range.max = SANE_FIX(scanner->device->inquiry_x_res);
    scanner->device->y_dpi_range.max = SANE_FIX(scanner->device->inquiry_y_res);      
  }

  DBG(DBG_info,"x_range     = [%f .. %f]\n", SANE_UNFIX(scanner->device->x_range.min), SANE_UNFIX(scanner->device->x_range.max));
  DBG(DBG_info,"y_range     = [%f .. %f]\n", SANE_UNFIX(scanner->device->y_range.min), SANE_UNFIX(scanner->device->y_range.max));
  DBG(DBG_info,"x_dpi_range = [1 .. %f]\n", SANE_UNFIX(scanner->device->x_dpi_range.max));
  DBG(DBG_info,"y_dpi_range = [1 .. %f]\n", SANE_UNFIX(scanner->device->y_dpi_range.max));

  /* make sure geometry selection is in bounds */

  if ( scanner->val[OPT_TL_X].w < scanner->device->x_range.min)
  {
    scanner->val[OPT_TL_X].w = scanner->device->x_range.min;
  }
 
  if (scanner->val[OPT_TL_Y].w < scanner->device->y_range.min)
  {
    scanner->val[OPT_TL_Y].w = scanner->device->y_range.min;
  }

  if ( scanner->val[OPT_BR_X].w > scanner->device->x_range.max)
  {
    scanner->val[OPT_BR_X].w = scanner->device->x_range.max;
  }
 
  if (scanner->val[OPT_BR_Y].w > scanner->device->y_range.max)
  {
    scanner->val[OPT_BR_Y].w = scanner->device->y_range.max;
  }
}

/* ------------------------------------------------------------ SANE CONTROL OPTION ------------------------ */


SANE_Status sane_control_option(SANE_Handle handle, SANE_Int option, SANE_Action action,
                                void *val, SANE_Int *info)
{
 Umax_Scanner *scanner = handle;
 SANE_Status status;
 SANE_Word w, cap;
 SANE_String_Const name;

  if (info)
  {
    *info = 0;
  }

  if (scanner->scanning)
  {
    return SANE_STATUS_DEVICE_BUSY;
  }

  if ((unsigned) option >= NUM_OPTIONS)
  {
    return SANE_STATUS_INVAL;
  }

  cap = scanner->opt[option].cap;
  if (!SANE_OPTION_IS_ACTIVE (cap))
  {
    return SANE_STATUS_INVAL;
  }

  name = scanner->opt[option].name;
  if (!name)
  {
    name = "(no name)";
  }

  if (action == SANE_ACTION_GET_VALUE)
  {
    DBG(DBG_sane_option,"get %s [#%d]\n", name, option);

    switch (option)
    {
       /* word options: */
      case OPT_NUM_OPTS:
      case OPT_RESOLUTION_BIND:
      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_PREVIEW:
      case OPT_BIT_DEPTH:
      case OPT_NEGATIVE:
      case OPT_BATCH_SCAN_START:
      case OPT_BATCH_SCAN_LOOP:
      case OPT_BATCH_SCAN_END:
      case OPT_BATCH_NEXT_TL_Y:
      case OPT_QUALITY:
      case OPT_DOR:
      case OPT_WARMUP:
      case OPT_RGB_BIND:
      case OPT_ANALOG_GAMMA:
      case OPT_ANALOG_GAMMA_R:
      case OPT_ANALOG_GAMMA_G:
      case OPT_ANALOG_GAMMA_B:
      case OPT_BRIGHTNESS:
      case OPT_CONTRAST:
      case OPT_THRESHOLD:
      case OPT_HIGHLIGHT:
      case OPT_HIGHLIGHT_R:
      case OPT_HIGHLIGHT_G:
      case OPT_HIGHLIGHT_B:
      case OPT_SHADOW:
      case OPT_SHADOW_R:
      case OPT_SHADOW_G:
      case OPT_SHADOW_B:
      case OPT_CUSTOM_GAMMA:
      case OPT_HALFTONE_DIMENSION:
      case OPT_SELECT_EXPOSURE_TIME:
      case OPT_SELECT_CAL_EXPOSURE_TIME:
      case OPT_CAL_EXPOS_TIME:
      case OPT_CAL_EXPOS_TIME_R:
      case OPT_CAL_EXPOS_TIME_G:
      case OPT_CAL_EXPOS_TIME_B:
      case OPT_SCAN_EXPOS_TIME:
      case OPT_SCAN_EXPOS_TIME_R:
      case OPT_SCAN_EXPOS_TIME_G:
      case OPT_SCAN_EXPOS_TIME_B:
      case OPT_CAL_LAMP_DEN:
      case OPT_SCAN_LAMP_DEN:
      case OPT_DISABLE_PRE_FOCUS:
      case OPT_MANUAL_PRE_FOCUS:
      case OPT_FIX_FOCUS_POSITION:
      case OPT_LENS_CALIBRATION_DOC_POS:
      case OPT_HOLDER_FOCUS_POS_0MM: 
      case OPT_LAMP_OFF_AT_EXIT:
      case OPT_SELECT_LAMP_DENSITY:
        *(SANE_Word *) val = scanner->val[option].w;
       return SANE_STATUS_GOOD;

      /* word-array options: */
      case OPT_GAMMA_VECTOR:
      case OPT_GAMMA_VECTOR_R:
      case OPT_GAMMA_VECTOR_G:
      case OPT_GAMMA_VECTOR_B:
      case OPT_HALFTONE_PATTERN:
        memcpy (val, scanner->val[option].wa, scanner->opt[option].size);
       return SANE_STATUS_GOOD;

      /* string options: */
      case OPT_SOURCE:
      /* fall through */
      case OPT_MODE:
      /* fall through */
#ifdef UMAX_CALIBRATION_MODE_SELECTABLE
      case OPT_CALIB_MODE:
      /* fall through */
#endif
        strcpy (val, scanner->val[option].s);
       return SANE_STATUS_GOOD;
    }
  }
  else if (action == SANE_ACTION_SET_VALUE)
  {
    switch (scanner->opt[option].type)
    {
      case SANE_TYPE_INT:
        DBG(DBG_sane_option, "set %s [#%d] to %d\n", name, option, *(SANE_Word *) val);
       break;

      case SANE_TYPE_FIXED:
        DBG(DBG_sane_option, "set %s [#%d] to %f\n", name, option, SANE_UNFIX(*(SANE_Word *) val));
       break;

      case SANE_TYPE_STRING:
        DBG(DBG_sane_option, "set %s [#%d] to %s\n", name, option, (char *) val);
       break;

      case SANE_TYPE_BOOL:
        DBG(DBG_sane_option, "set %s [#%d] to %d\n", name, option, *(SANE_Word *) val);
       break;

      default:
        DBG(DBG_sane_option, "set %s [#%d]\n", name, option);
    }

    if (!SANE_OPTION_IS_SETTABLE(cap))
    {
      DBG(DBG_error, "could not set option, not settable\n");
     return SANE_STATUS_INVAL;
    }

    status = sanei_constrain_value(scanner->opt+option, val, info);
    if (status != SANE_STATUS_GOOD)
    {
      DBG(DBG_error, "could not set option, invalid value\n");
     return status;
    }

    switch (option)
    {
      /* (mostly) side-effect-free word options: */
      case OPT_X_RESOLUTION:
      case OPT_Y_RESOLUTION:
      case OPT_TL_X:
      case OPT_TL_Y:
      case OPT_BR_X:
      case OPT_BR_Y:
        if (info)
        {
          *info |= SANE_INFO_RELOAD_PARAMS;
        }
        /* fall through */
      case OPT_NUM_OPTS:
      case OPT_NEGATIVE:
      case OPT_BATCH_SCAN_START:
      case OPT_BATCH_SCAN_LOOP:
      case OPT_BATCH_SCAN_END:
      case OPT_BATCH_NEXT_TL_Y:
      case OPT_QUALITY:
      case OPT_WARMUP:
      case OPT_PREVIEW:
      case OPT_ANALOG_GAMMA:
      case OPT_ANALOG_GAMMA_R:
      case OPT_ANALOG_GAMMA_G:
      case OPT_ANALOG_GAMMA_B:
      case OPT_BRIGHTNESS:
      case OPT_CONTRAST:
      case OPT_THRESHOLD:
      case OPT_HIGHLIGHT:
      case OPT_HIGHLIGHT_R:
      case OPT_HIGHLIGHT_G:
      case OPT_HIGHLIGHT_B:
      case OPT_SHADOW:
      case OPT_SHADOW_R:
      case OPT_SHADOW_G:
      case OPT_SHADOW_B:
      case OPT_CAL_EXPOS_TIME:
      case OPT_CAL_EXPOS_TIME_R:
      case OPT_CAL_EXPOS_TIME_G:
      case OPT_CAL_EXPOS_TIME_B:
      case OPT_SCAN_EXPOS_TIME:
      case OPT_SCAN_EXPOS_TIME_R:
      case OPT_SCAN_EXPOS_TIME_G:
      case OPT_SCAN_EXPOS_TIME_B:
      case OPT_CAL_LAMP_DEN:
      case OPT_SCAN_LAMP_DEN:
      case OPT_DISABLE_PRE_FOCUS:
      case OPT_MANUAL_PRE_FOCUS:
      case OPT_FIX_FOCUS_POSITION:
      case OPT_LENS_CALIBRATION_DOC_POS:
      case OPT_HOLDER_FOCUS_POS_0MM: 
      case OPT_LAMP_OFF_AT_EXIT:
        scanner->val[option].w = *(SANE_Word *) val;
       return SANE_STATUS_GOOD;

      case OPT_DOR:
      {
        if (scanner->val[option].w != *(SANE_Word *) val)
        {
          scanner->val[option].w = *(SANE_Word *) val; /* update valure for umax_set_max_geometry */

          if (info)
          {
            *info |= SANE_INFO_RELOAD_PARAMS;
            *info |= SANE_INFO_RELOAD_OPTIONS;
          }

          DBG(DBG_info,"sane_control_option: set DOR = %d\n", scanner->val[option].w);
          umax_set_max_geometry(scanner); 
        }
       return SANE_STATUS_GOOD;
      }

      case OPT_BIT_DEPTH:
      {
        if (scanner->val[option].w != *(SANE_Word *) val)
        {
          scanner->val[option].w = *(SANE_Word *) val;

          if (info)
          {
            *info |= SANE_INFO_RELOAD_OPTIONS;
          }

          scanner->output_range.min   = 0;
          scanner->output_range.quant = 0;

          if (scanner->val[option].w == 8)						       /* 8 bit mode */
          {
            scanner->output_bytes  = 1;							    /* 1 bytes output */
            scanner->output_range.max = 255;
          }
          else										      /* > 8 bit mode */
          {
            scanner->output_bytes  = 2;							    /* 2 bytes output */

            if (scanner->device->gamma_lsb_padded) /* e.g. astra 1220s need lsb padded data */
            {
              scanner->output_range.max   = (int) pow(2, scanner->val[option].w) - 1;
            }
            else
            {
              scanner->output_range.max = 65535; /* define maxval for msb padded data */
            }
          }


          if (info)
          {
            *info |= SANE_INFO_RELOAD_PARAMS;
          }
        }
       return SANE_STATUS_GOOD;
      }

      case OPT_RGB_BIND:
      {
        if (scanner->val[option].w != *(SANE_Word *) val)
        {
          scanner->val[option].w = *(SANE_Word *) val;
          if (info)
          {
            *info |= SANE_INFO_RELOAD_OPTIONS;
          }

          umax_set_rgb_bind(scanner);
        }
       return SANE_STATUS_GOOD;
      }

      case OPT_RESOLUTION_BIND:
      {
        if (scanner->val[option].w != *(SANE_Word *) val)
	{
          scanner->val[option].w = *(SANE_Word *) val;

          if (info)
          {
            *info |= SANE_INFO_RELOAD_OPTIONS;
          }
          if (scanner->val[option].w == SANE_FALSE)
          { /* don't bind */
            scanner->opt[OPT_Y_RESOLUTION].cap &= ~SANE_CAP_INACTIVE;
            scanner->opt[OPT_X_RESOLUTION].title = SANE_TITLE_SCAN_X_RESOLUTION;
            scanner->opt[OPT_X_RESOLUTION].name  = SANE_NAME_SCAN_X_RESOLUTION;
            scanner->opt[OPT_X_RESOLUTION].desc  = SANE_DESC_SCAN_X_RESOLUTION;
          }
          else
          { /* bind */
            scanner->opt[OPT_Y_RESOLUTION].cap |= SANE_CAP_INACTIVE;
            scanner->opt[OPT_X_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
            scanner->opt[OPT_X_RESOLUTION].name  = SANE_NAME_SCAN_RESOLUTION;
            scanner->opt[OPT_X_RESOLUTION].desc  = SANE_DESC_SCAN_RESOLUTION;
          }
	}
       return SANE_STATUS_GOOD;
      }

      case OPT_SELECT_EXPOSURE_TIME:
      case OPT_SELECT_CAL_EXPOSURE_TIME:
      {
        if (scanner->val[option].w != *(SANE_Word *) val)
	{
          scanner->val[option].w = *(SANE_Word *) val;

          if (info)
          {
            *info |= SANE_INFO_RELOAD_OPTIONS;
          }

          if (scanner->val[OPT_SELECT_EXPOSURE_TIME].w == SANE_FALSE)
	  {
            scanner->opt[OPT_SELECT_CAL_EXPOSURE_TIME].cap |= SANE_CAP_INACTIVE;

            scanner->opt[OPT_CAL_EXPOS_TIME].cap    |= SANE_CAP_INACTIVE;
            scanner->opt[OPT_CAL_EXPOS_TIME_R].cap  |= SANE_CAP_INACTIVE;
            scanner->opt[OPT_CAL_EXPOS_TIME_G].cap  |= SANE_CAP_INACTIVE;
            scanner->opt[OPT_CAL_EXPOS_TIME_B].cap  |= SANE_CAP_INACTIVE;

            scanner->opt[OPT_SCAN_EXPOS_TIME].cap   |= SANE_CAP_INACTIVE;
            scanner->opt[OPT_SCAN_EXPOS_TIME_R].cap |= SANE_CAP_INACTIVE;
            scanner->opt[OPT_SCAN_EXPOS_TIME_G].cap |= SANE_CAP_INACTIVE;
            scanner->opt[OPT_SCAN_EXPOS_TIME_B].cap |= SANE_CAP_INACTIVE;
	  }
	  else /* exposure time selection active */
	  {
            scanner->opt[OPT_SELECT_CAL_EXPOSURE_TIME].cap &= ~SANE_CAP_INACTIVE;

            if ( (strcmp(scanner->val[OPT_MODE].s, COLOR_STR) != 0) ||
	         (scanner->val[OPT_RGB_BIND].w == SANE_TRUE) ||
                 (scanner->device->exposure_time_rgb_bind) ) /* RGB bind */
            {
              if (scanner->val[OPT_SELECT_CAL_EXPOSURE_TIME].w)
              {
                scanner->opt[OPT_CAL_EXPOS_TIME].cap &= ~SANE_CAP_INACTIVE;
              }
              else
              {
                scanner->opt[OPT_CAL_EXPOS_TIME].cap |= SANE_CAP_INACTIVE;
              }

              scanner->opt[OPT_SCAN_EXPOS_TIME].cap &= ~SANE_CAP_INACTIVE;
            }
            else /* no RGB bind */
            {
              if (scanner->val[OPT_SELECT_CAL_EXPOSURE_TIME].w)
              {
                scanner->opt[OPT_CAL_EXPOS_TIME_R].cap &= ~SANE_CAP_INACTIVE;
                scanner->opt[OPT_CAL_EXPOS_TIME_G].cap &= ~SANE_CAP_INACTIVE;
                scanner->opt[OPT_CAL_EXPOS_TIME_B].cap &= ~SANE_CAP_INACTIVE;
              }
              else
              {
                scanner->opt[OPT_CAL_EXPOS_TIME_R].cap |= SANE_CAP_INACTIVE;
                scanner->opt[OPT_CAL_EXPOS_TIME_G].cap |= SANE_CAP_INACTIVE;
                scanner->opt[OPT_CAL_EXPOS_TIME_B].cap |= SANE_CAP_INACTIVE;
              }

              scanner->opt[OPT_SCAN_EXPOS_TIME_R].cap &= ~SANE_CAP_INACTIVE;
              scanner->opt[OPT_SCAN_EXPOS_TIME_G].cap &= ~SANE_CAP_INACTIVE;
              scanner->opt[OPT_SCAN_EXPOS_TIME_B].cap &= ~SANE_CAP_INACTIVE;
            }
	  }
	}
       return SANE_STATUS_GOOD;
      }

      case OPT_SELECT_LAMP_DENSITY:
      {
        if (scanner->val[option].w != *(SANE_Word *) val)
	{
          scanner->val[option].w = *(SANE_Word *) val;

          if (info)
          {
            *info |= SANE_INFO_RELOAD_OPTIONS;
          }
          if (scanner->val[option].w == SANE_FALSE)
	  {
            scanner->opt[OPT_CAL_LAMP_DEN].cap  |= SANE_CAP_INACTIVE;
            scanner->opt[OPT_SCAN_LAMP_DEN].cap |= SANE_CAP_INACTIVE;
	  }
	  else
	  {
            scanner->opt[OPT_CAL_LAMP_DEN].cap  &= ~SANE_CAP_INACTIVE;
            scanner->opt[OPT_SCAN_LAMP_DEN].cap &= ~SANE_CAP_INACTIVE;
	  }
	}
       return SANE_STATUS_GOOD;
      }

      /* side-effect-free word-array options: */
      case OPT_HALFTONE_PATTERN:
      case OPT_GAMMA_VECTOR:
      case OPT_GAMMA_VECTOR_R:
      case OPT_GAMMA_VECTOR_G:
      case OPT_GAMMA_VECTOR_B:
        memcpy (scanner->val[option].wa, val, scanner->opt[option].size);
       return SANE_STATUS_GOOD;

      /* single string-option with side-effect: */
      case OPT_SOURCE:
      {
        if (scanner->val[option].s)
        {
          free(scanner->val[option].s);
        }
        scanner->val[option].s = (SANE_Char *) strdup(val); /* update string for umax_set_max_geometry */

        DBG(DBG_info,"sane_control_option: set SOURCE = %s\n", (SANE_Char *) val);
        umax_set_max_geometry(scanner);

        if (info)
        {
          *info |= SANE_INFO_RELOAD_PARAMS;
          *info |= SANE_INFO_RELOAD_OPTIONS;
        }
       return SANE_STATUS_GOOD;
      }
      break;

      /* side-effect-free single-string options: */
#ifdef UMAX_CALIBRATION_MODE_SELECTABLE
      case OPT_CALIB_MODE:
      /* fall through */
#endif
      {
        if (scanner->val[option].s)
        {
          free (scanner->val[option].s);
        }
        scanner->val[option].s = (SANE_Char*)strdup(val);
       return SANE_STATUS_GOOD;
      }

      /* options with side-effects: */

      case OPT_CUSTOM_GAMMA:
      {
        w = *(SANE_Word *) val;
        if (w == scanner->val[OPT_CUSTOM_GAMMA].w) { return SANE_STATUS_GOOD; } 

        scanner->val[OPT_CUSTOM_GAMMA].w = w;
        if (w)									   /* use custom_gamma_table */
        {
           const char *mode = scanner->val[OPT_MODE].s;

           if ( (strcmp(mode, LINEART_STR) == 0) ||
                (strcmp(mode, HALFTONE_STR) == 0) ||
                (strcmp(mode, GRAY_STR) == 0) )
           { scanner->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE; }
           else if (strcmp(mode, COLOR_STR) == 0)
           {
             scanner->opt[OPT_GAMMA_VECTOR].cap   &= ~SANE_CAP_INACTIVE;
             scanner->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE;
             scanner->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE;
             scanner->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE;
           }
        }
        else								     /* don't use custom_gamma_table */
        {
          scanner->opt[OPT_GAMMA_VECTOR].cap   |= SANE_CAP_INACTIVE;
          scanner->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
          scanner->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
          scanner->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
        }
        if (info)
        {
          *info |= SANE_INFO_RELOAD_OPTIONS;
        }
       return SANE_STATUS_GOOD;
      }

      case OPT_MODE:
      {
       int halftoning;

        if (scanner->val[option].s)
        {
          free (scanner->val[option].s);
        }

        scanner->val[option].s = (SANE_Char*)strdup(val);

        if (info)
        {
          *info |=SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
        }

        scanner->opt[OPT_NEGATIVE].cap           |= SANE_CAP_INACTIVE; 

        scanner->opt[OPT_BIT_DEPTH].cap          |= SANE_CAP_INACTIVE;

        scanner->opt[OPT_CUSTOM_GAMMA].cap       |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_GAMMA_VECTOR].cap       |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_GAMMA_VECTOR_R].cap     |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_GAMMA_VECTOR_G].cap     |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_GAMMA_VECTOR_B].cap     |= SANE_CAP_INACTIVE;

        scanner->opt[OPT_CONTRAST].cap           |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_BRIGHTNESS].cap         |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_THRESHOLD].cap          |= SANE_CAP_INACTIVE;

        scanner->opt[OPT_RGB_BIND].cap           |= SANE_CAP_INACTIVE;

        scanner->opt[OPT_ANALOG_GAMMA].cap       |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_ANALOG_GAMMA_R].cap     |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_ANALOG_GAMMA_G].cap     |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_ANALOG_GAMMA_B].cap     |= SANE_CAP_INACTIVE;

        scanner->opt[OPT_HIGHLIGHT].cap          |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_HIGHLIGHT_R].cap        |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_HIGHLIGHT_G].cap        |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_HIGHLIGHT_B].cap        |= SANE_CAP_INACTIVE;

        scanner->opt[OPT_SHADOW].cap             |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_SHADOW_R].cap           |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_SHADOW_G].cap           |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_SHADOW_B].cap           |= SANE_CAP_INACTIVE;

        scanner->opt[OPT_CAL_EXPOS_TIME].cap     |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_CAL_EXPOS_TIME_R].cap   |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_CAL_EXPOS_TIME_G].cap   |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_CAL_EXPOS_TIME_B].cap   |= SANE_CAP_INACTIVE;

        scanner->opt[OPT_SCAN_EXPOS_TIME].cap    |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_SCAN_EXPOS_TIME_R].cap  |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_SCAN_EXPOS_TIME_G].cap  |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_SCAN_EXPOS_TIME_B].cap  |= SANE_CAP_INACTIVE;

        scanner->opt[OPT_HALFTONE_DIMENSION].cap |= SANE_CAP_INACTIVE;
        scanner->opt[OPT_HALFTONE_PATTERN].cap   |= SANE_CAP_INACTIVE;


        halftoning = (strcmp(val, HALFTONE_STR) == 0 || strcmp(val, COLOR_HALFTONE_STR) == 0);

        if (halftoning || strcmp(val, LINEART_STR) == 0 || strcmp(val, COLOR_LINEART_STR) == 0)
        {										    /* one bit modes */
          if (scanner->device->inquiry_reverse)
          {
            scanner->opt[OPT_NEGATIVE].cap  &= ~SANE_CAP_INACTIVE;
          }

          if (halftoning)
          {										 /* halftoning modes */
            scanner->opt[OPT_CONTRAST].cap   &= ~SANE_CAP_INACTIVE;
            scanner->opt[OPT_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE;

	    if (scanner->device->inquiry_highlight)
            {
              scanner->opt[OPT_HIGHLIGHT].cap &= ~SANE_CAP_INACTIVE;
            }

	    if (scanner->device->inquiry_shadow)
            {
              scanner->opt[OPT_SHADOW].cap &= ~SANE_CAP_INACTIVE;
            }

/* disable halftone pattern download options */
#if 0
            scanner->opt[OPT_HALFTONE_DIMENSION].cap &= ~SANE_CAP_INACTIVE;

            if (scanner->val[OPT_HALFTONE_DIMENSION].w)
            {
              scanner->opt[OPT_HALFTONE_PATTERN].cap &= ~SANE_CAP_INACTIVE;
            }
#endif

            if (scanner->val[OPT_SELECT_EXPOSURE_TIME].w == SANE_TRUE)
            {
              scanner->opt[OPT_CAL_EXPOS_TIME].cap   &= ~SANE_CAP_INACTIVE; 
              scanner->opt[OPT_SCAN_EXPOS_TIME].cap  &= ~SANE_CAP_INACTIVE;
            }

            scanner->exposure_time_range.min = scanner->device->inquiry_exposure_time_h_min
	                                       * scanner->device->inquiry_exposure_time_step_unit;
          }
	  else
	  {										    /* lineart modes */
            scanner->opt[OPT_THRESHOLD].cap  &= ~SANE_CAP_INACTIVE;

            if (scanner->val[OPT_SELECT_EXPOSURE_TIME].w == SANE_TRUE)
            {
              scanner->opt[OPT_CAL_EXPOS_TIME].cap   &= ~SANE_CAP_INACTIVE;
              scanner->opt[OPT_SCAN_EXPOS_TIME].cap   &= ~SANE_CAP_INACTIVE;
            }

            scanner->exposure_time_range.min = scanner->device->inquiry_exposure_time_l_min
	                                       * scanner->device->inquiry_exposure_time_step_unit;
	  }
        }
        else
        {								   /* multi-bit modes(gray or color) */
          scanner->opt[OPT_BIT_DEPTH].cap &= ~SANE_CAP_INACTIVE;

          if (scanner->device->inquiry_highlight)
          {
            scanner->opt[OPT_HIGHLIGHT].cap &= ~SANE_CAP_INACTIVE;
          }

          if (scanner->device->inquiry_shadow)
          {
            scanner->opt[OPT_SHADOW].cap &= ~SANE_CAP_INACTIVE;
          }

          if (scanner->device->inquiry_reverse_multi)
          {
            scanner->opt[OPT_NEGATIVE].cap &= ~SANE_CAP_INACTIVE; 
          }

          if (scanner->device->inquiry_gamma_dwload)
          {
            scanner->opt[OPT_CUSTOM_GAMMA].cap &= ~SANE_CAP_INACTIVE;
          }
          else
          {
            scanner->val[OPT_CUSTOM_GAMMA].w = SANE_FALSE;
          }

          if (scanner->device->inquiry_analog_gamma)
          {
            scanner->opt[OPT_ANALOG_GAMMA].cap &= ~SANE_CAP_INACTIVE;
          }

          if (scanner->val[OPT_SELECT_EXPOSURE_TIME].w == SANE_TRUE)
          {
            scanner->opt[OPT_CAL_EXPOS_TIME].cap  &= ~SANE_CAP_INACTIVE;
            scanner->opt[OPT_SCAN_EXPOS_TIME].cap &= ~SANE_CAP_INACTIVE;
          }

          if (strcmp(val, COLOR_STR) == 0)
          {
            if ( (scanner->device->inquiry_analog_gamma) ||
                 (scanner->device->inquiry_highlight)    ||
                 (scanner->device->inquiry_shadow)       ||
                 (scanner->device->inquiry_exposure_adj) )
            {
              scanner->opt[OPT_RGB_BIND].cap &= ~SANE_CAP_INACTIVE;
            }

            scanner->exposure_time_range.min = scanner->device->inquiry_exposure_time_c_min
	                                       * scanner->device->inquiry_exposure_time_step_unit;
          }
	  else /* grayscale */
	  {
            scanner->exposure_time_range.min = scanner->device->inquiry_exposure_time_g_min
	                                       * scanner->device->inquiry_exposure_time_step_unit;
	  }
	}

        umax_set_rgb_bind(scanner);

        if (scanner->val[OPT_CUSTOM_GAMMA].w)
        {
          if (strcmp(val, GRAY_STR) == 0)
          {
            scanner->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
          }
          else if (strcmp(val, COLOR_STR) == 0)
          {
            scanner->opt[OPT_GAMMA_VECTOR].cap   &= ~SANE_CAP_INACTIVE;
            scanner->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE;
            scanner->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE;
            scanner->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE;
          }
	}
       return SANE_STATUS_GOOD;
      }

      case OPT_HALFTONE_DIMENSION: /* halftone pattern dimension affects halftone pattern option: */
      {
        unsigned dim = *(SANE_Word *) val;

         scanner->val[option].w = dim;

         if (info)
         {
           *info |= SANE_INFO_RELOAD_OPTIONS;
         }

         scanner->opt[OPT_HALFTONE_PATTERN].cap |= SANE_CAP_INACTIVE;

         if (dim > 0)
         {
           scanner->opt[OPT_HALFTONE_PATTERN].cap &= ~SANE_CAP_INACTIVE;
           scanner->opt[OPT_HALFTONE_PATTERN].size = dim * sizeof (SANE_Word);
         }
        return SANE_STATUS_GOOD;
      }

      case OPT_LAMP_ON:
      {
        if (!umax_set_lamp_status(handle, 1 /* lamp on */))
        {
          return SANE_STATUS_GOOD;
        }
        else
        {
          return SANE_STATUS_UNSUPPORTED;
        }
      }

      case OPT_LAMP_OFF:
      {
        if (!umax_set_lamp_status(handle, 0 /* lamp off */))
        {
          return SANE_STATUS_GOOD;
        }
        else
        {
          return SANE_STATUS_UNSUPPORTED;
        }
      }
    }
  } /* else */
 return SANE_STATUS_INVAL;
}


/* ------------------------------------------------------------ SANE GET PARAMETERS ------------------------ */


SANE_Status sane_get_parameters(SANE_Handle handle, SANE_Parameters *params)
{
 Umax_Scanner *scanner = handle;
 const char *mode;

  DBG(DBG_sane_info,"sane_get_parameters\n");

  if (!scanner->scanning)
  {								  /* not scanning, so lets use recent values */
    double width, length, x_dpi, y_dpi;

    memset(&scanner->params, 0, sizeof (scanner->params));

    width  = SANE_UNFIX(scanner->val[OPT_BR_X].w - scanner->val[OPT_TL_X].w);
    length = SANE_UNFIX(scanner->val[OPT_BR_Y].w - scanner->val[OPT_TL_Y].w);
    x_dpi  = SANE_UNFIX(scanner->val[OPT_X_RESOLUTION].w);
    y_dpi  = SANE_UNFIX(scanner->val[OPT_Y_RESOLUTION].w);

    if ( (scanner->val[OPT_RESOLUTION_BIND].w == SANE_TRUE) || (scanner->val[OPT_PREVIEW].w == SANE_TRUE) )
    {
      y_dpi = x_dpi;
    }

    if (x_dpi > 0.0 && y_dpi > 0.0 && width > 0.0 && length > 0.0)
    {
      double x_dots_per_mm = x_dpi / MM_PER_INCH;
      double y_dots_per_mm = y_dpi / MM_PER_INCH;

      scanner->params.pixels_per_line = width *  x_dots_per_mm;
      scanner->params.lines           = length * y_dots_per_mm;
    }
  }

  mode = scanner->val[OPT_MODE].s;

  if (strcmp(mode, LINEART_STR) == 0 || strcmp(mode, HALFTONE_STR) == 0)
  {
    scanner->params.format         = SANE_FRAME_GRAY;
    scanner->params.bytes_per_line = (scanner->params.pixels_per_line + 7) / 8;
    scanner->params.depth          = 1;
  }
  else if (strcmp(mode, GRAY_STR) == 0)
  {
    scanner->params.format         = SANE_FRAME_GRAY;
    scanner->params.bytes_per_line = scanner->params.pixels_per_line * scanner->output_bytes;
    scanner->params.depth          = 8 * scanner->output_bytes;
  }
  else if (strcmp(mode, COLOR_LINEART_STR) == 0 || strcmp(mode, COLOR_HALFTONE_STR) == 0 )
  {
    if (scanner->device->inquiry_one_pass_color)
    {
      scanner->device->three_pass = 0;
      scanner->params.format         = SANE_FRAME_RGB;
      scanner->params.bytes_per_line = 3 * scanner->params.pixels_per_line;
      scanner->params.depth          = 8;
    }
    else										 /* three pass color */
    {
      scanner->device->three_pass = 1;
      scanner->params.format         = SANE_FRAME_RED + scanner->device->three_pass_color - 1;
      scanner->params.bytes_per_line = scanner->params.pixels_per_line;
      scanner->params.depth          = 8;
    }
  }
  else												      /* RGB */
  {
    if (scanner->device->inquiry_one_pass_color)
    {
      scanner->device->three_pass = 0;
      scanner->params.format         = SANE_FRAME_RGB;
      scanner->params.bytes_per_line = 3 * scanner->params.pixels_per_line * scanner->output_bytes;
      scanner->params.depth          = 8 * scanner->output_bytes;
    }
    else										 /* three pass color */
    {
      scanner->device->three_pass = 1;
      scanner->params.format         = SANE_FRAME_RED + scanner->device->three_pass_color - 1;
      scanner->params.bytes_per_line = scanner->params.pixels_per_line * scanner->output_bytes;
      scanner->params.depth          = 8 * scanner->output_bytes;
    }
  }

  scanner->params.last_frame = (scanner->params.format != SANE_FRAME_RED && scanner->params.format != SANE_FRAME_GREEN);

  if (params)
  {
    *params = scanner->params;
  }

 return SANE_STATUS_GOOD;
}


/* ------------------------------------------------------------ SANE START --------------------------------- */


SANE_Status sane_start(SANE_Handle handle)
{
 Umax_Scanner *scanner = handle;
 const char *mode;
 double xbasedots, ybasedots;
 const char *scan_source;
 int pause;
 int status;
 int fds[2];

  DBG(DBG_sane_init,"sane_start\n");

  mode = scanner->val[OPT_MODE].s;

  if (scanner->device->sfd == -1)   /* first call, don`t run this routine again on multi frame or multi image scan */
  {
    umax_initialize_values(scanner->device);								    /* reset values */

    scanner->device->three_pass_color = 1;

    /* test for adf and uta */
    scan_source = scanner->val[OPT_SOURCE].s;

    if (strcmp(scan_source, UTA_STR) == 0)
    {
      if ( (scanner->device->inquiry_uta != 0) && (scanner->device->inquiry_transavail != 0) )
      {
        scanner->device->uta = 1;
      }
      else
      {
        DBG(DBG_error,"ERROR: Transparency Adapter not available\n");
        umax_scsi_close(scanner->device);
       return SANE_STATUS_INVAL;
      }
    }
    else /* Test if ADF is selected */
    {
      scanner->device->uta = 0;

      if (strcmp(scan_source, ADF_STR) == 0)
      {
        if ( (scanner->device->inquiry_adf) && (scanner->device->inquiry_adfmode) )
        {
          scanner->device->adf = 1;
        }
        else
        {
          DBG(DBG_error,"ERROR: Automatic Document Feeder not available\n");
         umax_scsi_close(scanner->device);
         return SANE_STATUS_INVAL;
        }
      }
      else
      {
        scanner->device->adf = 0;
      }
    }

    if (scanner->device->inquiry_GIB & 32)						/* 16 bit input mode */
    {
      scanner->device->gamma_input_bits_code = 32;
      DBG(DBG_sane_info, "Using 16 bits for gamma input\n");
    }
    else if (scanner->device->inquiry_GIB & 16)						/* 14 bit input mode */
    {
      scanner->device->gamma_input_bits_code = 16;
      DBG(DBG_sane_info, "Using 14 bits for gamma input\n");
    }
    else if (scanner->device->inquiry_GIB & 8)						/* 12 bit input mode */
    {
      scanner->device->gamma_input_bits_code = 8;
      DBG(DBG_sane_info, "Using 12 bits for gamma input\n");
    }
    else if (scanner->device->inquiry_GIB & 4)						/* 10 bit input mode */
    {
      scanner->device->gamma_input_bits_code = 4;
      DBG(DBG_sane_info, "Using 10 bits for gamma input\n");
    }
    else if (scanner->device->inquiry_GIB & 2)						 /* 9 bit input mode */
    {
      scanner->device->gamma_input_bits_code = 2;
      DBG(DBG_sane_info, "Using 9 bits for gamma input\n");
    }
    else										 /* 8 bit input mode */
    {
      scanner->device->gamma_input_bits_code = 1;
      DBG(DBG_sane_info, "Using 8 bits for gamma input\n");
    }

    if (scanner->val[OPT_BIT_DEPTH].w == 16)					       /* 16 bit output mode */
    {
      scanner->device->bits_per_pixel      = 16;
      scanner->device->bits_per_pixel_code = 32;
      scanner->device->max_value      = 65535;
      DBG(DBG_sane_info,"Using 16 bits for output\n");
    }
    else if (scanner->val[OPT_BIT_DEPTH].w == 14)				       /* 14 bit output mode */
    {
      scanner->device->bits_per_pixel      = 14;
      scanner->device->bits_per_pixel_code = 16;
      scanner->device->max_value      = 16383;
      DBG(DBG_sane_info,"Using 14 bits for output\n");
    }
    else if (scanner->val[OPT_BIT_DEPTH].w == 12)				       /* 12 bit output mode */
    {
      scanner->device->bits_per_pixel      = 12;
      scanner->device->bits_per_pixel_code = 8;
      scanner->device->max_value      = 4095;
      DBG(DBG_sane_info,"Using 12 bits for output\n");
    }
    else if (scanner->val[OPT_BIT_DEPTH].w == 10)				       /* 10 bit output mode */
    {
      scanner->device->bits_per_pixel      = 10;
      scanner->device->bits_per_pixel_code = 4;
      scanner->device->max_value      = 1023;
      DBG(DBG_sane_info,"Using 10 bits for output\n");
    }
    else if (scanner->val[OPT_BIT_DEPTH].w == 9)				        /* 9 bit output mode */
    {
      scanner->device->bits_per_pixel      = 9;
      scanner->device->bits_per_pixel_code = 2;
      scanner->device->max_value      = 511;
      DBG(DBG_sane_info,"Using 9 bits for output\n");
    }
    else										/* 8 bit output mode */
    {
      scanner->device->bits_per_pixel      = 8;
      scanner->device->bits_per_pixel_code = 1;
      scanner->device->max_value      = 255;
      DBG(DBG_sane_info,"Using 8 bits for output\n");
    }

    scanner->device->reverse = scanner->device->reverse_multi = scanner->val[OPT_NEGATIVE].w;

    scanner->device->threshold         = P_100_TO_255(scanner->val[OPT_THRESHOLD].w);
    scanner->device->brightness        = P_200_TO_255(scanner->val[OPT_BRIGHTNESS].w);
    scanner->device->contrast          = P_200_TO_255(scanner->val[OPT_CONTRAST].w);

    scanner->device->batch_scan        = ( scanner->val[OPT_BATCH_SCAN_START].w ||
                                           scanner->val[OPT_BATCH_SCAN_LOOP].w ||
                                           scanner->val[OPT_BATCH_SCAN_END].w );
    scanner->device->batch_end         = scanner->val[OPT_BATCH_SCAN_END].w;
    scanner->device->batch_next_tl_y   = SANE_UNFIX(scanner->val[OPT_BATCH_NEXT_TL_Y].w) * scanner->device->y_coordinate_base / MM_PER_INCH;

    if (scanner->val[OPT_BATCH_NEXT_TL_Y].w == 0xFFFF)  /* option not set: use br_y => scanhead stops at end of batch area */
    {
      scanner->device->batch_next_tl_y = SANE_UNFIX(scanner->val[OPT_BR_Y].w) * scanner->device->y_coordinate_base / MM_PER_INCH;
    }

    if ((scanner->device->batch_scan) && !scanner->val[OPT_BATCH_SCAN_START].w)
    {
      scanner->device->calibration = 9; /* no calibration - otherwise the scanhead will go into calibration position */
    }
    else
    {
      scanner->device->calibration = 0; /* calibration defined by image type */
    }

    scanner->device->quality           = scanner->val[OPT_QUALITY].w;
    scanner->device->dor               = scanner->val[OPT_DOR].w;
    scanner->device->preview           = scanner->val[OPT_PREVIEW].w;
    scanner->device->warmup            = scanner->val[OPT_WARMUP].w;

    scanner->device->fix_focus_position   = scanner->val[OPT_FIX_FOCUS_POSITION].w;
    scanner->device->lens_cal_in_doc_pos  = scanner->val[OPT_LENS_CALIBRATION_DOC_POS].w;
    scanner->device->disable_pre_focus    = scanner->val[OPT_DISABLE_PRE_FOCUS].w;
    scanner->device->holder_focus_pos_0mm = scanner->val[OPT_HOLDER_FOCUS_POS_0MM].w;
    scanner->device->manual_focus         = scanner->val[OPT_MANUAL_PRE_FOCUS].w;

    scanner->device->analog_gamma_r =
    scanner->device->analog_gamma_g =
    scanner->device->analog_gamma_b = umax_calculate_analog_gamma(SANE_UNFIX(scanner->val[OPT_ANALOG_GAMMA].w));

    scanner->device->highlight_r =
    scanner->device->highlight_g = 
    scanner->device->highlight_b = P_100_TO_255(scanner->val[OPT_HIGHLIGHT].w);

    scanner->device->shadow_r =
    scanner->device->shadow_g = 
    scanner->device->shadow_b = P_100_TO_255(scanner->val[OPT_SHADOW].w);

    if (scanner->val[OPT_SELECT_EXPOSURE_TIME].w == SANE_TRUE)
    {
      if (scanner->val[OPT_SELECT_CAL_EXPOSURE_TIME].w) /* separate calibration exposure time */
      {
        scanner->device->exposure_time_calibration_r =
        scanner->device->exposure_time_calibration_g =
        scanner->device->exposure_time_calibration_b = scanner->val[OPT_CAL_EXPOS_TIME].w;
      }
      else /* same exposure times for calibration as for scanning */
      {
        scanner->device->exposure_time_calibration_r =
        scanner->device->exposure_time_calibration_g =
        scanner->device->exposure_time_calibration_b = scanner->val[OPT_SCAN_EXPOS_TIME].w;
      }

      scanner->device->exposure_time_scan_r =
      scanner->device->exposure_time_scan_g =
      scanner->device->exposure_time_scan_b = scanner->val[OPT_SCAN_EXPOS_TIME].w;
    }

    if (scanner->val[OPT_SELECT_LAMP_DENSITY].w == SANE_TRUE)
    {
      scanner->device->c_density = P_100_TO_254(scanner->val[OPT_CAL_LAMP_DEN].w);
      scanner->device->s_density = P_100_TO_254(scanner->val[OPT_SCAN_LAMP_DEN].w);
    }

    if (strcmp(mode, LINEART_STR) == 0)
    {
      scanner->device->colormode = LINEART;
    }
    else if (strcmp(mode, HALFTONE_STR) == 0)
    {
      scanner->device->colormode = HALFTONE;
    }
    else if (strcmp(mode, GRAY_STR) == 0)
    {
      scanner->device->colormode = GRAYSCALE;
    }
    else if (strcmp(mode, COLOR_LINEART_STR) == 0)
    {
      scanner->device->colormode = RGB_LINEART;
    }
    else if (strcmp(mode, COLOR_HALFTONE_STR) == 0)
    {
      scanner->device->colormode = RGB_HALFTONE;
    }
    else if (strcmp(mode, COLOR_STR) == 0)
    {
      scanner->device->colormode = RGB;
      if (scanner->val[OPT_RGB_BIND].w == SANE_FALSE)
      {
        scanner->device->analog_gamma_r =
                 umax_calculate_analog_gamma( SANE_UNFIX(scanner->val[OPT_ANALOG_GAMMA_R].w) );
        scanner->device->analog_gamma_g =
                 umax_calculate_analog_gamma( SANE_UNFIX(scanner->val[OPT_ANALOG_GAMMA_G].w) );
        scanner->device->analog_gamma_b =
                 umax_calculate_analog_gamma( SANE_UNFIX(scanner->val[OPT_ANALOG_GAMMA_B].w) );

        scanner->device->highlight_r = P_100_TO_255(scanner->val[OPT_HIGHLIGHT_R].w);
        scanner->device->highlight_g = P_100_TO_255(scanner->val[OPT_HIGHLIGHT_G].w);
        scanner->device->highlight_b = P_100_TO_255(scanner->val[OPT_HIGHLIGHT_B].w);

        scanner->device->shadow_r = P_100_TO_255(scanner->val[OPT_SHADOW_R].w);
        scanner->device->shadow_g = P_100_TO_255(scanner->val[OPT_SHADOW_G].w);
        scanner->device->shadow_b = P_100_TO_255(scanner->val[OPT_SHADOW_B].w);

        if ((scanner->val[OPT_SELECT_EXPOSURE_TIME].w == SANE_TRUE) && (!scanner->device->exposure_time_rgb_bind))
        {
          if (scanner->val[OPT_SELECT_CAL_EXPOSURE_TIME].w) /* separate calibration exposure time */
          {
            scanner->device->exposure_time_calibration_r = scanner->val[OPT_CAL_EXPOS_TIME_R].w;
            scanner->device->exposure_time_calibration_g = scanner->val[OPT_CAL_EXPOS_TIME_G].w;
            scanner->device->exposure_time_calibration_b = scanner->val[OPT_CAL_EXPOS_TIME_B].w;
          }
          else /* same exposure times for calibration as for scanning */
          {
            scanner->device->exposure_time_calibration_r = scanner->val[OPT_SCAN_EXPOS_TIME_R].w;
            scanner->device->exposure_time_calibration_g = scanner->val[OPT_SCAN_EXPOS_TIME_G].w;
            scanner->device->exposure_time_calibration_b = scanner->val[OPT_SCAN_EXPOS_TIME_B].w;
          }

          scanner->device->exposure_time_scan_r = scanner->val[OPT_SCAN_EXPOS_TIME_R].w;
          scanner->device->exposure_time_scan_g = scanner->val[OPT_SCAN_EXPOS_TIME_G].w;
          scanner->device->exposure_time_scan_b = scanner->val[OPT_SCAN_EXPOS_TIME_B].w;
        }
      }
    }

    if (scanner->device->force_preview_bit_rgb != 0)          /* in RGB-mode set preview bit, eg. for UMAX S6E */
    {
      if (scanner->device->colormode == RGB)
      {
        DBG(DBG_sane_info,"setting preview bit = 1 (option force-preview-bit-rgb)\n");
        scanner->device->preview = SANE_TRUE;
      }
    }


#ifdef UMAX_CALIBRATION_MODE_SELECTABLE
    if (strcmp(scanner->val[OPT_CALIB_MODE].s, CALIB_MODE_0000) == 0)
    {
      scanner->device->calibration = 0;
    }
    else if (strcmp(scanner->val[OPT_CALIB_MODE].s, CALIB_MODE_1111) == 0)
    {
      scanner->device->calibration = 15;
    }
    else if (strcmp(scanner->val[OPT_CALIB_MODE].s, CALIB_MODE_1110) == 0)
    {
      scanner->device->calibration = 14;
    }
    else if (strcmp(scanner->val[OPT_CALIB_MODE].s, CALIB_MODE_1101) == 0)
    {
      scanner->device->calibration = 13;
    }
    else if (strcmp(scanner->val[OPT_CALIB_MODE].s, CALIB_MODE_1010) == 0)
    {
      scanner->device->calibration = 10;
    }
    else if (strcmp(scanner->val[OPT_CALIB_MODE].s, CALIB_MODE_1001) == 0)
    {
      scanner->device->calibration = 9;
    }
#endif

    /* get and set geometric values for scanning */
    scanner->device->x_resolution = SANE_UNFIX(scanner->val[OPT_X_RESOLUTION].w);
    scanner->device->y_resolution = SANE_UNFIX(scanner->val[OPT_Y_RESOLUTION].w);

    if ( (scanner->val[OPT_RESOLUTION_BIND].w == SANE_TRUE) || (scanner->val[OPT_PREVIEW].w == SANE_TRUE) )
    {
      scanner->device->y_resolution = scanner->device->x_resolution;
    }

    xbasedots = scanner->device->x_coordinate_base / MM_PER_INCH;
    ybasedots = scanner->device->y_coordinate_base / MM_PER_INCH;

#if 0
    scanner->device->upper_left_x = ((int) (SANE_UNFIX(scanner->val[OPT_TL_X].w) * xbasedots)) & 65534;
    scanner->device->upper_left_y = ((int) (SANE_UNFIX(scanner->val[OPT_TL_Y].w) * ybasedots)) & 65534;

    scanner->device->scanwidth    = ((int)((SANE_UNFIX(scanner->val[OPT_BR_X].w - scanner->val[OPT_TL_X].w)) * xbasedots)) & 65534;
    scanner->device->scanlength   = ((int)((SANE_UNFIX(scanner->val[OPT_BR_Y].w - scanner->val[OPT_TL_Y].w)) * ybasedots)) & 65534;
#endif

    scanner->device->upper_left_x = (int) (SANE_UNFIX(scanner->val[OPT_TL_X].w) * xbasedots);
    scanner->device->upper_left_y = (int) (SANE_UNFIX(scanner->val[OPT_TL_Y].w) * ybasedots);

    scanner->device->scanwidth    = (int)((SANE_UNFIX(scanner->val[OPT_BR_X].w - scanner->val[OPT_TL_X].w)) * xbasedots);
    scanner->device->scanlength   = (int)((SANE_UNFIX(scanner->val[OPT_BR_Y].w - scanner->val[OPT_TL_Y].w)) * ybasedots);


    if (umax_check_values(scanner->device) != 0)
    {
      DBG(DBG_error,"ERROR: invalid scan-values\n");
      scanner->scanning = SANE_FALSE;
     return SANE_STATUS_INVAL;
    }

    /* The scanner defines a x-origin-offset for DOR mode, this offset is used for the */
    /* x range in this backend, so the frontend/user knows the correct positions related to */
    /* scanner's surface. But the scanner wants x values from origin 0 instead */
    /* of the x-origin defined by the scanner`s inquiry */
    if (scanner->device->dor != 0) /* dor mode active */
    {
      DBG(DBG_info,"substracting DOR x-origin-offset from upper left x\n");
      scanner->device->upper_left_x -= scanner->device->inquiry_dor_x_off * scanner->device->x_coordinate_base; /* correct DOR x-origin */

      if (scanner->device->upper_left_x < 0) /* rounding errors may create a negative value */
      {
        scanner->device->upper_left_x = 0; /* but negative values are not allowed */
      }
    }

    scanner->params.bytes_per_line  = scanner->device->row_len;
    scanner->params.pixels_per_line = scanner->device->width_in_pixels; 
    scanner->params.lines           = scanner->device->length_in_pixels;


    /* set exposure times */
    if ( scanner->device->inquiry_exposure_adj )
    {
      umax_calculate_exposure_time(scanner->device, scanner->device->use_exposure_time_def_r, &scanner->device->exposure_time_calibration_r);
      umax_calculate_exposure_time(scanner->device, scanner->device->use_exposure_time_def_g, &scanner->device->exposure_time_calibration_g);
      umax_calculate_exposure_time(scanner->device, scanner->device->use_exposure_time_def_b, &scanner->device->exposure_time_calibration_b);

      umax_calculate_exposure_time(scanner->device, scanner->device->use_exposure_time_def_r, &scanner->device->exposure_time_scan_r);
      umax_calculate_exposure_time(scanner->device, scanner->device->use_exposure_time_def_g, &scanner->device->exposure_time_scan_g);
      umax_calculate_exposure_time(scanner->device, scanner->device->use_exposure_time_def_b, &scanner->device->exposure_time_scan_b);
    }
    else
    {
      scanner->device->exposure_time_calibration_r = scanner->device->exposure_time_calibration_g = scanner->device->exposure_time_calibration_b =
      scanner->device->exposure_time_scan_r = scanner->device->exposure_time_scan_g = scanner->device->exposure_time_scan_b = 0;
    }


    scanner->scanning = SANE_TRUE;
    sane_get_parameters(scanner, 0);

    DBG(DBG_sane_info,"x_resolution (dpi)      = %u\n", scanner->device->x_resolution);
    DBG(DBG_sane_info,"y_resolution (dpi)      = %u\n", scanner->device->y_resolution);
    DBG(DBG_sane_info,"x_coordinate_base (dpi) = %u\n", scanner->device->x_coordinate_base);
    DBG(DBG_sane_info,"y_coordinate_base (dpi) = %u\n", scanner->device->y_coordinate_base);
    DBG(DBG_sane_info,"upper_left_x (xbase)    = %d\n", scanner->device->upper_left_x);
    DBG(DBG_sane_info,"upper_left_y (ybase)    = %d\n", scanner->device->upper_left_y);
    DBG(DBG_sane_info,"scanwidth    (xbase)    = %u\n", scanner->device->scanwidth);
    DBG(DBG_sane_info,"scanlength   (ybase)    = %u\n", scanner->device->scanlength);
    DBG(DBG_sane_info,"width in pixels         = %u\n", scanner->device->width_in_pixels);
    DBG(DBG_sane_info,"length in pixels        = %u\n", scanner->device->length_in_pixels);
    DBG(DBG_sane_info,"bits per pixel/color    = %u\n", scanner->device->bits_per_pixel);
    DBG(DBG_sane_info,"bytes per line          = %d\n", scanner->params.bytes_per_line);
    DBG(DBG_sane_info,"pixels_per_line         = %d\n", scanner->params.pixels_per_line);
    DBG(DBG_sane_info,"lines                   = %d\n", scanner->params.lines);
    DBG(DBG_sane_info,"negative                = %d\n", scanner->device->reverse);
    DBG(DBG_sane_info,"threshold  (lineart)    = %d\n", scanner->device->threshold);
    DBG(DBG_sane_info,"brightness (halftone)   = %d\n", scanner->device->brightness);
    DBG(DBG_sane_info,"contrast   (halftone)   = %d\n", scanner->device->contrast);

    DBG(DBG_sane_info,"analog_gamma            = %d %d %d\n",
            scanner->device->analog_gamma_r,
            scanner->device->analog_gamma_g,
            scanner->device->analog_gamma_b);
    DBG(DBG_sane_info,"highlight               = %d %d %d\n",
            scanner->device->highlight_r,
            scanner->device->highlight_g,
            scanner->device->highlight_b);
    DBG(DBG_sane_info,"shadow                  = %d %d %d\n",
            scanner->device->shadow_r,
            scanner->device->shadow_g,
            scanner->device->shadow_b);
    DBG(DBG_sane_info,"calibrat. exposure time = %d %d %d\n",
            scanner->device->exposure_time_calibration_r,
            scanner->device->exposure_time_calibration_g,
            scanner->device->exposure_time_calibration_b);
    DBG(DBG_sane_info,"scan exposure time      = %d %d %d\n",
            scanner->device->exposure_time_scan_r,
            scanner->device->exposure_time_scan_g,
            scanner->device->exposure_time_scan_b);

#ifdef UMAX_CALIBRATION_MODE_SELECTABLE
    DBG(DBG_sane_info,"calibration             = %s\n", scanner->val[OPT_CALIB_MODE].s);
#endif
    DBG(DBG_sane_info,"calibration mode number = %d\n", scanner->device->calibration);

    DBG(DBG_sane_info,"batch scan              = %d\n", scanner->device->batch_scan);
    DBG(DBG_sane_info,"batch end               = %d\n", scanner->device->batch_end);
    DBG(DBG_sane_info,"batch next top left y   = %d\n", scanner->device->batch_next_tl_y);
    DBG(DBG_sane_info,"quality calibration     = %d\n", scanner->device->quality);
    DBG(DBG_sane_info,"warm up                 = %d\n", scanner->device->warmup);
    DBG(DBG_sane_info,"fast preview function   = %d\n", scanner->device->preview);
    DBG(DBG_sane_info,"DOR                     = %d\n", scanner->device->dor);
    DBG(DBG_sane_info,"ADF                     = %d\n", scanner->device->adf);
    DBG(DBG_sane_info,"manual focus            = %d\n", scanner->device->manual_focus);
    DBG(DBG_sane_info,"fix focus position      = %d\n", scanner->device->fix_focus_position);
    DBG(DBG_sane_info,"disable pre focus       = %d\n", scanner->device->disable_pre_focus);
    DBG(DBG_sane_info,"lens cal in doc pos     = %d\n", scanner->device->lens_cal_in_doc_pos);
    DBG(DBG_sane_info,"holder focus pos 0mm    = %d\n", scanner->device->holder_focus_pos_0mm);

    if (scanner->val[OPT_PREVIEW].w) /* preview mode */
    {
      scanner->device->lines_max = scanner->device->request_preview_lines;
    }
    else /* scan mode */
    {
      scanner->device->lines_max = scanner->device->request_scan_lines;
    }

#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
    {
     unsigned int scsi_bufsize  = 0;

      scsi_bufsize = scanner->device->width_in_pixels * scanner->device->lines_max;

      if (scsi_bufsize == 0) /* no scsi buffer size, take scanner buffer size */
      {
        scsi_bufsize = scanner->device->inquiry_vidmem;
      }

      if (scsi_bufsize < scanner->device->scsi_buffer_size_min) /* make sure buffer has at least minimum size */
      {
        scsi_bufsize = scanner->device->scsi_buffer_size_min;
      }
      else if (scsi_bufsize > scanner->device->scsi_buffer_size_max) /* make sure buffer does not exceed maximum size */
      {
        scsi_bufsize = scanner->device->scsi_buffer_size_max;
      }

      if (umax_scsi_open_extended(scanner->device->sane.name, scanner->device, sense_handler,
                                   scanner->device, (int *) &scsi_bufsize) != 0)
      {
        DBG(DBG_error, "ERROR: sane_start: open failed\n");
        scanner->scanning = SANE_FALSE;
        return SANE_STATUS_INVAL;
      }

      if (scsi_bufsize < scanner->device->scsi_buffer_size_min) /* minimum size must be available */
      {
        DBG(DBG_error, "ERROR: sane_start: umax_scsi_open_extended returned too small scsi buffer\n");
        umax_scsi_close((scanner->device));
        scanner->scanning = SANE_FALSE;
        return SANE_STATUS_NO_MEM;
      }
      DBG(DBG_info, "sane_start: umax_scsi_open_extended returned scsi buffer size = %d\n", scsi_bufsize);

      if (scsi_bufsize < scanner->device->width_in_pixels) /* print warning when buffer is smaller than one scanline */
      {
        DBG(DBG_warning, "WARNING: sane_start: scsi buffer is smaller than one scanline\n");
      }

      if (scsi_bufsize != scanner->device->bufsize)
      {
        DBG(DBG_info, "sane_start: buffer size has changed, reallocating buffer\n");

        if (scanner->device->buffer[0])
        {
          DBG(DBG_info, "sane_start: freeing SCSI buffer[0]\n");
          free(scanner->device->buffer[0]);									     /* free buffer */
        }

        scanner->device->bufsize = scsi_bufsize;

        DBG(DBG_info, "sane_start: allocating SCSI buffer[0]\n");
        scanner->device->buffer[0]  = malloc(scanner->device->bufsize);					  /* allocate buffer */

        if (!scanner->device->buffer[0]) /* malloc failed */
        {
          DBG(DBG_error, "ERROR: sane_start: could not allocate buffer[0]\n");
          umax_scsi_close(scanner->device);
          scanner->device->bufsize = 0;
          scanner->scanning = SANE_FALSE;
         return SANE_STATUS_NO_MEM;
        }
      }
    }
#else
    if ( umax_scsi_open(scanner->device->sane.name, scanner->device, sense_handler,
                         scanner->device) != SANE_STATUS_GOOD )
    {
      scanner->scanning = SANE_FALSE;
      DBG(DBG_error, "ERROR: sane_start: open of %s failed:\n", scanner->device->sane.name);
      return SANE_STATUS_INVAL;
    }

    /* there is no need to reallocate the buffer because the size is fixed */
#endif

    /* grab scanner */
    if (umax_grab_scanner(scanner->device))
    {
      umax_scsi_close(scanner->device);
      scanner->scanning = SANE_FALSE;
      DBG(DBG_warning,"WARNING: unable to reserve scanner: device busy\n");
     return SANE_STATUS_DEVICE_BUSY;
    }

/* halftone pattern download is not ready in this version */
#if 0
										     /* send halftonepattern */
    if ( (strcmp(mode, HALFTONE_STR) == 0) || (strcmp(mode, COLOR_HALFTONE_STR) == 0) )
    {
      umax_send_halftone_pattern(scanner->device, (char *) &(scanner->halftone_pattern[0]),
                                 scanner->val[OPT_HALFTONE_DIMENSION].w );
      scanner->device->halftone = WD_halftone_download;
    }									      /* end of send halftonepattern */
#endif
 
  } /* ------------ end of first call -------------- */


  /* send gammacurves */
  if (scanner->val[OPT_CUSTOM_GAMMA].w == SANE_TRUE)
  {
    if (strcmp(mode, COLOR_STR) == 0)
    {
      if (scanner->device->three_pass == 0)					      /* one pass color scan */
      {
       unsigned int i, dest, color, value;
       char *gamma;

        gamma = malloc( (size_t) (3 * scanner->gamma_length * scanner->output_bytes) );
        if (gamma == NULL)
        {
          DBG(DBG_warning,"WARNING: not able to allocate memory for gamma table, gamma ignored !!!\n");
        }
        else
        {
          dest=0;
          for(color=1; color <= 3; color++)
          {
            for(i=0; i < scanner->gamma_length; i++)
            {
              value = scanner->gamma_table[color][i];
              if (scanner->output_bytes == 2)
              {
                gamma[dest++] = scanner->gamma_table[0][value] / 256;
              }
              gamma[dest++] = (scanner->gamma_table[0][value] & 255);
            }
          }

          DBG(DBG_sane_info,"sending 3 * %d bytes of gamma data for RGB\n",
              scanner->gamma_length * scanner->output_bytes);

          umax_send_gamma_data(scanner->device, &gamma[0], 3);
          scanner->device->digital_gamma_r =
          scanner->device->digital_gamma_g =
          scanner->device->digital_gamma_b = WD_gamma_download;
          free(gamma);
        }
      }
      else									    /* three pass color scan */
      {
       unsigned int i, dest, color, value;
       char *gamma;

        gamma = malloc( (size_t) (scanner->gamma_length * scanner->output_bytes) );
        if (gamma == NULL)
        {
          DBG(DBG_warning,"not able to allocate memory for gamma table, gamma ignored !!!\n");
        }
        else
        {
          dest  = 0;
          color = scanner->device->three_pass_color;

          for(i = 0; i < scanner->gamma_length; i++)
          {
            value = scanner->gamma_table[color][i];

            if (scanner->output_bytes == 2)
            {
              gamma[dest++] = scanner->gamma_table[0][value] / 256;
            }
            gamma[dest++] = (scanner->gamma_table[0][value] & 255);
          }

          DBG(DBG_sane_info,"sending %d bytes of gamma data for color %d\n",
              scanner->gamma_length * scanner->output_bytes, color);

          umax_send_gamma_data(scanner->device, &gamma[0], 1);
          scanner->device->digital_gamma_r =
          scanner->device->digital_gamma_g =
          scanner->device->digital_gamma_b = WD_gamma_download;
          free(gamma);
        }
      }
    }
    else if (strcmp(mode, GRAY_STR) == 0) /* grayscale scan */
    {
     unsigned int i, dest;
     char *gamma;

      gamma = malloc( (size_t) (scanner->gamma_length * scanner->output_bytes) );
      if (gamma == NULL)
      {
        DBG(DBG_warning,"WARNING: not able to allocate memory for gamma table, gamma ignored !!!\n");
      }
      else
      {
        dest=0;
        for(i=0; i < scanner->gamma_length; i++)
        {
            if (scanner->output_bytes == 2)
            {
              gamma[dest++] = scanner->gamma_table[0][i] / 256;
            }
            gamma[dest++] = (scanner->gamma_table[0][i] & 255);
        }

        DBG(DBG_sane_info,"sending %d bytes of gamma data for gray\n",
            scanner->gamma_length * scanner->output_bytes);

        umax_send_gamma_data(scanner->device, &gamma[0], 1);
        scanner->device->digital_gamma_r = WD_gamma_download;
	free(gamma);
      }
    }
  }										  /* end of send gammacurves */

  if ( scanner->device->three_pass_color > WD_wid_red) /* three pass scan, not first pass */
  {
    umax_reposition_scanner(scanner->device);
  }

  umax_set_window_param(scanner->device);
  status = umax_start_scan(scanner->device);
  if (status) /* errror */
  {
    umax_give_scanner(scanner->device); /* reposition and release scanner */
    return status;
  }

  pause = scanner->device->pause_for_color_calibration;

  if (scanner->device->colormode != RGB)
  {
    pause = scanner->device->pause_for_gray_calibration;
  }

  if (pause) /* Astra 2400S needs this pause (7sec in color, 4sec in gray mode) */
  {
    DBG(DBG_info2,"pause for calibration %d msec ...\n", pause);
    usleep(((long) pause) * 1000); /* time in ms */
    DBG(DBG_info2,"pause done\n");
  }

  status = umax_do_calibration(scanner->device);
  if (status) /* errror */
  {
    umax_give_scanner(scanner->device); /* reposition and release scanner */
    return status;
  }

  if (scanner->device->pause_after_calibration) /* may be usefull */
  {
    DBG(DBG_info2,"pause after calibration %d msec ...\n", scanner->device->pause_after_calibration);
    usleep(((long) scanner->device->pause_after_calibration) * 1000); /* time in ms */
    DBG(DBG_info2,"pause done\n");
  }


  if (pipe(fds) < 0)
  {
    DBG(DBG_error,"ERROR: could not create pipe\n");
    scanner->scanning = SANE_FALSE;
    umax_give_scanner(scanner->device); /* reposition and release scanner */
    umax_scsi_close(scanner->device);
   return SANE_STATUS_IO_ERROR;
  }

  scanner->pipe_read_fd  = fds[0]; 
  scanner->pipe_write_fd = fds[1];

  /* start reader_process, deponds on OS if fork() or threads are used */
  scanner->reader_pid = sanei_thread_begin(reader_process, (void *) scanner);

  if (scanner->reader_pid < 0)
  {
    DBG(DBG_error, "ERROR: sanei_thread_begin failed (%s)\n", strerror(errno));
    scanner->scanning = SANE_FALSE;
    umax_give_scanner(scanner->device); /* reposition and release scanner */
    umax_scsi_close(scanner->device);
   return SANE_STATUS_NO_MEM; /* any other reason than no memory possible ? */
  }

  if (sanei_thread_is_forked())
  {
    close(scanner->pipe_write_fd);
    scanner->pipe_write_fd = -1;
  }

 return SANE_STATUS_GOOD;
}


/* ------------------------------------------------------------ SANE READ ---------------------------------- */


SANE_Status sane_read(SANE_Handle handle, SANE_Byte *buf, SANE_Int max_len, SANE_Int *len)
{
 Umax_Scanner *scanner = handle;
 ssize_t nread;

  *len = 0;

  nread = read(scanner->pipe_read_fd, buf, max_len);

  DBG(DBG_sane_info, "sane_read: read %ld bytes\n", (long) nread);

  if (!(scanner->scanning)) /* OOPS, not scanning */
  {
    return do_cancel(scanner);
  }

  if (nread < 0)
  {
    if (errno == EAGAIN)
    {
      DBG(DBG_sane_info, "sane_read: EAGAIN\n");
      return SANE_STATUS_GOOD;
    }
    else
    {
      do_cancel(scanner); /* we had an error, stop scanner */
     return SANE_STATUS_IO_ERROR;
    }
  }

  *len = nread;

  if (nread == 0) /* EOF */
  {
    if ( (scanner->device->three_pass == 0) ||
         (scanner->device->colormode <= RGB_LINEART) ||
         (++(scanner->device->three_pass_color) > 3) )
    {
      do_cancel(scanner);
    }

    DBG(DBG_sane_proc,"closing read end of pipe\n");

    if (scanner->pipe_read_fd >= 0)
    {
      close(scanner->pipe_read_fd);
      scanner->pipe_read_fd = -1;
    }

    return SANE_STATUS_EOF;    
  }

 return SANE_STATUS_GOOD;
}


/* ------------------------------------------------------------ SANE CANCEL -------------------------------- */


void sane_cancel(SANE_Handle handle)
{
 Umax_Scanner *scanner = handle;

  DBG(DBG_sane_init,"sane_cancel\n");

  if (scanner->scanning)
  {
    do_cancel(scanner);
  }
}


/* ------------------------------------------------------------ SANE SET IO MODE --------------------------- */


SANE_Status sane_set_io_mode(SANE_Handle handle, SANE_Bool non_blocking)
{
 Umax_Scanner *scanner = handle;

  DBG(DBG_sane_init,"sane_set_io_mode: non_blocking=%d\n", non_blocking);

  if (!scanner->scanning) { return SANE_STATUS_INVAL; }

  if (fcntl(scanner->pipe_read_fd, F_SETFL, non_blocking ? O_NONBLOCK : 0) < 0)
  {
    return SANE_STATUS_IO_ERROR;
  }  
 return SANE_STATUS_GOOD;  
}


/* ------------------------------------------------------------ SANE GET SELECT FD ------------------------- */


SANE_Status sane_get_select_fd(SANE_Handle handle, SANE_Int *fd)
{
 Umax_Scanner *scanner = handle;

  DBG(DBG_sane_init,"sane_get_select_fd\n");

  if (!scanner->scanning)
  {
    return SANE_STATUS_INVAL;
  }

  *fd = scanner->pipe_read_fd;

 return SANE_STATUS_GOOD;
}

/* ------------------------------------------------------------ EOF ---------------------------------------- */
