/* ------------------------------------------------------------------------- */
/* sane - Scanner Access Now Easy.
   coolscan.c , version  0.4.4

   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 COOLSCAN flatbed scanners.  */

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


/* SANE-FLOW-DIAGRAMM

   - sane_init() : initialize backend, attach scanners
   . - sane_get_devices() : query list of scanner-devices
   . - sane_open() : open a particular scanner-device
   . . - sane_set_io_mode : set blocking-mode
   . . - sane_get_select_fd : get scanner-fd
   . . - sane_get_option_descriptor() : get option informations
   . . - 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)
   . .
   . . - sane_cancel() : cancel operation
   . - sane_close() : close opened scanner-device
   - sane_exit() : terminate use of backend
 */

#ifdef _AIX
# include "lalloca.h"		/* MUST come first for AIX! */
#endif

#include "../include/sane/config.h"
#include "lalloca.h"

#include <errno.h>
#include <math.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <unistd.h>

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

#include "../include/sane/sanei_config.h"
#define COOLSCAN_CONFIG_FILE "coolscan.conf"
#include "../include/sane/sanei_backend.h"

#include "coolscan.h"
#include "coolscan-scsidef.h"


#ifndef PATH_MAX
#define PATH_MAX       1024
#endif

/* ------------------------------------------------------------------------- */
static const SANE_Int resolution_list[] =
{
  25,
  2700, 1350, 900, 675, 540, 450, 385, 337, 300, 270, 245, 225, 207,
  192, 180, 168, 158, 150, 142, 135, 128, 122, 117, 112, 108
};


#define coolscan_do_scsi_open(dev, fd, handler) sanei_scsi_open(dev, fd, handler)
#define coolscan_do_scsi_close(fd)              sanei_scsi_close(fd)

#define	COOLSCAN_MAX_RETRY	25


static SANE_Status sense_handler (int scsi_fd, unsigned char * result, void *arg);
static int coolscan_check_values (Coolscan_t * s);
static int get_internal_info (Coolscan_t *);
static void coolscan_get_inquiry_values (Coolscan_t *);
static void hexdump (int level, char *comment, unsigned char *p, int l);
static int swap_res (Coolscan_t * s);
/* --------------------------- COOLSCAN_DO_SCSI_CMD  ----------------------- */
static int
do_scsi_cmd (int fd, unsigned char *cmd, int cmd_len, unsigned char *out, size_t out_len)
{
  int ret;
  size_t ol = out_len;

  hexdump (20, "", cmd, cmd_len);

  ret = sanei_scsi_cmd (fd, cmd, cmd_len, out, &ol);
  if ((out_len != 0) && (out_len != ol))
    {
      DBG (1, "sanei_scsi_cmd: asked %lu bytes, got %lu\n",
	   (u_long) out_len, (u_long) ol);
    }
  if (ret)
    {
      DBG (1, "sanei_scsi_cmd: returning 0x%08x\n", ret);
    }
  DBG (10, "sanei_scsi_cmd: returning %lu bytes:\n", (u_long) ol);
  if (out != NULL && out_len != 0)
    hexdump (15, "", out, (out_len > 0x60) ? 0x60 : out_len);

  return ret;
}


static int
request_sense_parse (unsigned char *sensed_data)
{
  int ret, sense, asc, ascq;
  sense = get_RS_sense_key (sensed_data);
  asc = get_RS_ASC (sensed_data);
  ascq = get_RS_ASCQ (sensed_data);

  ret = SANE_STATUS_IO_ERROR;

  switch (sense)
    {
    case 0x0:
      DBG (5, "\t%d/%d/%d: Scanner ready\n", sense, asc, ascq);
      return SANE_STATUS_GOOD;

    case 0x1:
      if ((0x37 == asc) && (0x00 == ascq)) {
	DBG (1, "\t%d/%d/%d: Rounded Parameter\n", sense, asc, ascq);
        ret = SANE_STATUS_GOOD;
      }
      else if ((0x61 == asc) && (0x02 == ascq))
	DBG (1, "\t%d/%d/%d: Out Of Focus\n", sense, asc, ascq);
      else
	DBG (1, "\tUnknown - Sense=%d, ASC=%d, ASCQ=%d\n", sense, asc, ascq);
      break;

    case 0x2:
      if ((0x4 == asc) && (0x1 == ascq)) {
	DBG (10, "\t%d/%d/%d: Logical unit is in process of becoming ready\n",
	     sense, asc, ascq);
	ret = SANE_STATUS_DEVICE_BUSY;
      }
      else if ((0x3A == asc) && (0x00 == ascq))
	{
	  DBG (1, "\t%d/%d/%d: No Diapo inserted\n", sense, asc, ascq);
          ret = SANE_STATUS_GOOD;
	}
      else if ((0x60 == asc) && (0x00 == ascq))
	DBG (1, "\t%d/%d/%d: Lamp Failure\n", sense, asc, ascq);
      else
	{
	  DBG (1, "\tUnknown - Sense=%d, ASC=%d, ASCQ=%d\n", sense, asc, ascq);
          ret = SANE_STATUS_GOOD;
	}
      break;

    case 0x3:
      if ((0x3b == asc) && (0xe == ascq))
	DBG (1, "\t%d/%d/%d: Medium source element empty\n", sense, asc, ascq);
      else if ((0x53 == asc) && (0x00 == ascq))
	DBG (1, "\t%d/%d/%d: Media Load of Eject Failed\n", sense, asc, ascq);
      else
	DBG (1, "\tUnknown - Sense=%d, ASC=%d, ASCQ=%d\n", sense, asc, ascq);
      break;

    case 0x4:
      if ((0x15 == asc) && (0x1 == ascq))
	DBG (1, "\t%d/%d/%d: Mechanical Positioning Error\n", sense, asc, ascq);
      else
	DBG (1, "\tUnknown - Sense=%d, ASC=%d, ASCQ=%d\n", sense, asc, ascq);
      break;

    case 0x5:
      if ((0x00 == asc) && (0x5 == ascq))
	DBG (1, "\t%d/%d/%d: End-Of-Data Detected\n", sense, asc, ascq);
      else if ((0x1a == asc) && (0x00 == ascq))
	DBG (1, "\t%d/%d/%d: Parameter List Length Error\n", sense, asc, ascq);
      else if ((0x20 == asc) && (0x00 == ascq))
	DBG (1, "\t%d/%d/%d: Invalid Command Operation Code\n", sense, asc, ascq);
      else if ((0x24 == asc) && (0x00 == ascq))
	DBG (1, "\t%d/%d/%d: Invalid Field In CDB\n", sense, asc, ascq);
      else if ((0x25 == asc) && (0x00 == ascq))
	DBG (1, "\t%d/%d/%d: Logical Unit Not Supported\n", sense, asc, ascq);
      else if ((0x26 == asc) && (0x00 == ascq))
	DBG (1, "\t%d/%d/%d: Invalid Field in Parameter List\n", sense, asc, ascq);
      else if ((0x2c == asc) && (0x00 == ascq))
	DBG (1, "\t%d/%d/%d: Command Sequence Error\n", sense, asc, ascq);
      else if ((0x39 == asc) && (0x00 == ascq))
	DBG (1, "\t%d/%d/%d: Saving Parameters Not Supported\n", sense, asc, ascq);
      else if ((0x3d == asc) && (0x00 == ascq))
	DBG (1, "\t%d/%d/%d: Invalid Bits In Identify Message\n", sense, asc, ascq);
      else
	DBG (1, "\tUnknown - Sense=%d, ASC=%d, ASCQ=%d\n", sense, asc, ascq);
      break;

    case 0x6:
      if ((0x29 == asc) && (0x0 == ascq))
	DBG (1, "\t%d/%d/%d: Power On, Reset, or Bus Device Reset Occured\n", sense, asc, ascq);
      else if ((0x2a == asc) && (0x1 == ascq))
	DBG (1, "\t%d/%d/%d: Mode Parameters Changed\n", sense, asc, ascq);
      else
	DBG (1, "\tUnknown - Sense=%d, ASC=%d, ASCQ=%d\n", sense, asc, ascq);
      break;

    case 0xb:
      if ((0x43 == asc) && (0x0 == ascq))
	DBG (1, "\t%d/%d/%d: Message Error\n", sense, asc, ascq);
      else if ((0x47 == asc) && (0x0 == ascq))
	DBG (1, "\t%d/%d/%d: SCSI Parity Error\n", sense, asc, ascq);
      else if ((0x48 == asc) && (0x0 == ascq))
	DBG (1, "\t%d/%d/%d: Initiator Detected Error Message Received\n", sense, asc, ascq);
      else if ((0x49 == asc) && (0x0 == ascq))
	DBG (1, "\t%d/%d/%d: Invalid Message Error\n", sense, asc, ascq);
      else if ((0x4e == asc) && (0x0 == ascq))
	DBG (1, "\t%d/%d/%d: Overlapped Commands Attempted\n", sense, asc, ascq);
      else
	DBG (1, "\tUnknown - Sense=%d, ASC=%d, ASCQ=%d\n", sense, asc, ascq);
      break;

    default:
      DBG (1, "\tUnknown - Sense=%d, ASC=%d, ASCQ=%d\n", sense, asc, ascq);
      break;
    }				/* switch */
  return ret;
}

/* 
 *  wait_scanner should spin until TEST_UNIT_READY returns 0 (GOOD)
 *  returns 0 on success,
 *  returns -1 on error.  
 */
static int
wait_scanner (Coolscan_t * s)
{
  int ret = -1;
  int cnt = 0;
  DBG (10, "wait_scanner: Testing if scanner is ready\n");

  while (ret != 0)
    {
      ret = do_scsi_cmd (s->sfd, test_unit_ready.cmd,
			 test_unit_ready.size, 0, 0);

      if (ret == SANE_STATUS_DEVICE_BUSY)
	{
	  usleep (500000);	/* wait 0.5 seconds */
	  if (cnt++ > 40)
	    {			/* 20 sec. max (prescan takes up to 15 sec. */
	      DBG (1, "wait_scanner: scanner does NOT get ready\n");
	      return -1;
	    }
	}
      else if (ret == SANE_STATUS_GOOD)
	{
	  DBG (10, "wait_scanner: scanner is ready\n");
	  return ret;
	}
      else
	{
	  DBG (1, "wait_scanner: test unit ready failed (%s)\n",
	       sane_strstatus (ret));
	}
    }
  return 0;
}

/* ------------------------- COOLSCAN GRAB SCANNER ----------------------------- */


/* coolscan_grab_scanner should go through the following command sequence:
 * TEST UNIT READY
 *     CHECK CONDITION  \
 * REQUEST SENSE         > These should be handled automagically by
 *     UNIT ATTENTION   /  the kernel if they happen (powerup/reset)
 * TEST UNIT READY
 *     GOOD
 * RESERVE UNIT
 *     GOOD
 * 
 * It is then responsible for installing appropriate signal handlers
 * to call emergency_give_scanner() if user aborts.
 */

static int
coolscan_grab_scanner (Coolscan_t * s)
{
  int ret;

  DBG (10, "grabbing scanner\n");

  wait_scanner (s);		/* wait for scanner ready, if not print 
				   sense and return 1 */
  ret = do_scsi_cmd (s->sfd, reserve_unit.cmd, reserve_unit.size, NULL, 0);
  if (ret)
    return ret;

  DBG (10, "scanner reserved\n");
  return 0;
}

/* 
 * Convert a size in ilu to the units expected by the scanner
 */

static int
resDivToVal (int res_div)
{ 
  if (res_div < 1 || res_div > resolution_list[0])
    { 
      DBG (1, "Invalid resolution divisor %d \n", res_div);
      return 2700;
    }
  else
    {
      return resolution_list[res_div];
    }
}

static int
resValToDiv (int res_val)
{
  int res_div;
  int max_res = resolution_list[0];
  for (res_div = 1; res_div <= max_res; res_div++)
    {
      if (resolution_list[res_div] == res_val)
	break;
    }
  if (res_div > max_res)
    {
      DBG (1, "Invalid resolution value\n");
      return 1;
    }
  else
    {
      return res_div;
    }
}
/* 
 * use mode select to force a mesurement divisor of 2700
 */
static unsigned char mode_select[] =
{
  MODE_SELECT, 0x10, 0, 0, 20, 0,
  0, 0, 0, 8,
  0, 0, 0, 0, 0, 0, 0, 1,
  3, 6, 0, 0, 0xA, 0x8C, 0, 0};

static int
select_MUD (Coolscan_t * s)
{
  return do_scsi_cmd (s->sfd, mode_select, 26, NULL, 0);
}

static int
coolscan_autofocus_LS30 (Coolscan_t * s)
{
  int x, y;

  wait_scanner(s);
  memcpy(s->buffer, autofocusLS30.cmd, autofocusLS30.size);
  memcpy(s->buffer+ autofocusLS30.size, autofocuspos, 9);

  x = s->xmaxpix - (s->brx + s->tlx) / 2;
  y = (s->bry + s->tly) / 2;

  DBG (10, "Attempting AutoFocus at x=%d, y=%d\n", x, y);

  do_scsi_cmd (s->sfd, s->buffer,
	       autofocusLS30.size  + 9, NULL, 0);
  /* Trashes when used in combination with scsi-driver AM53C974.o  */
  do_scsi_cmd (s->sfd, command_c1.cmd,
	       command_c1.size, NULL, 0);
  
  DBG (10, "\tWaiting end of Autofocus\n");
  wait_scanner (s);
  DBG (10, "AutoFocused.\n");
  return 0;
}

static int
coolscan_autofocus (Coolscan_t * s)
{
  int x, y;

  if(s->LS>=2)
    { return coolscan_autofocus_LS30(s);
    }

  wait_scanner(s);
  memcpy(s->buffer, autofocus.cmd, autofocus.size);

  x = s->xmaxpix - (s->brx + s->tlx) / 2;
  y = (s->bry + s->tly) / 2;

  DBG (10, "Attempting AutoFocus at x=%d, y=%d\n", x, y);

  set_AF_XPoint (s->buffer, x);
  set_AF_YPoint (s->buffer, y);

  set_AF_transferlength (s->buffer, 0); /* should be 8 !*/
  do_scsi_cmd (s->sfd, s->buffer,
	       autofocus.size  + AF_Point_length, NULL, 0);

  sleep(5);		 	/* autofocus takes a minimum of 5 sec. */

  DBG (10, "\tWaiting end of Autofocus\n");
  wait_scanner (s);
  DBG (10, "AutoFocused.\n");
  return 0;
}

/*
   static int
   coolscan_abort_scan (Coolscan_t * s)
   {
   int ret;

   DBG (5, "Aborting scan...\n");
   ret = do_scsi_cmd (s->sfd, sabort.cmd, sabort.size, NULL, 0);
   if (ret)
   DBG (5, "Scan Aborted\n");
   else
   DBG (5, "Not scanning\n");
   return 0;
   }
 */
static int
coolscan_mode_sense (Coolscan_t * s)
{
  int ret, len;

  DBG (10, "Mode Sense...\n");
  len = 12;
  set_MS_DBD (mode_sense.cmd, 1);
  set_MS_len (mode_sense.cmd, len);
  ret = do_scsi_cmd (s->sfd, mode_sense.cmd, mode_sense.size,
		     s->buffer, len);

  if (ret == 0)
    {
      s->MUD = get_MS_MUD (s->buffer);
      DBG (10, "\tMode Sensed (MUD is %d)\n", s->MUD);
    }
  return ret;
}

static int
coolscan_object_discharge (Coolscan_t * s)
{
  int ret;

  DBG (10, "Trying to discharge object...\n");

  memcpy (s->buffer, object_position.cmd, object_position.size);
  set_OP_autofeed (s->buffer, OP_Discharge);
  ret = do_scsi_cmd (s->sfd, s->buffer,
		     object_position.size, NULL, 0);
  wait_scanner (s);
  DBG (10, "Object discharged.\n");
  return ret;
}

static int
coolscan_object_feed (Coolscan_t * s)
{
  int ret;
  DBG (10, "Trying to feed object...\n");
  if (!s->asf)
    {
      DBG (10, "\tAutofeeder not present.\n");
      return 0;
    }
  memcpy (s->buffer, object_position.cmd, object_position.size);
  set_OP_autofeed (s->buffer, OP_Feed);
  ret = do_scsi_cmd (s->sfd, s->buffer,
		     object_position.size, NULL, 0);
  wait_scanner (s);
  DBG (10, "Object fed.\n");
  return ret;
}

/* coolscan_give_scanner should go through the following sequence:
 * OBJECT POSITION DISCHARGE
 *     GOOD
 * RELEASE UNIT
 *     GOOD
 */
static int
coolscan_give_scanner (Coolscan_t * s)
{
  DBG (10, "trying to release scanner ...\n");
  coolscan_object_discharge (s);
  wait_scanner (s);
  do_scsi_cmd (s->sfd, release_unit.cmd, release_unit.size, NULL, 0);
  DBG (10, "scanner released\n");
  return 0;
}


static int
coolscan_set_window_param_LS20 (Coolscan_t * s, int prescan)
{
  unsigned char buffer_r[max_WDB_size];
  int ret;

  wait_scanner (s);
  memset (buffer_r, '\0', max_WDB_size);	/* clear buffer */
  memcpy (buffer_r, window_descriptor_block.cmd,
	  window_descriptor_block.size);	/* copy preset data */

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

  set_WD_negative (buffer_r, s->negative);	/* Negative/positive slide */

  if (prescan)
    {
      set_WD_scanmode (buffer_r, WD_Prescan);
    }
  else
    {
      set_WD_scanmode (buffer_r, WD_Scan);

      /* geometry */
      set_WD_Xres (buffer_r, resDivToVal (s->x_nres));	/* x resolution in dpi */
      set_WD_Yres (buffer_r, resDivToVal (s->y_nres));	/* y resolution in dpi */

      /* the coolscan  uses the upper right corner as the origin of coordinates */
      /* xmax and ymax are given in 1200 dpi */
      set_WD_ULX (buffer_r, (s->xmaxpix - s->brx));
      set_WD_ULY (buffer_r, s->tly);	/* upper_edge y */
      set_WD_width (buffer_r, (s->brx - s->tlx + 1));
      set_WD_length (buffer_r, (s->bry - s->tly + 1));

      /* BTC */
      if (s->brightness == 128)
	{
	  set_WD_brightness (buffer_r, 0);
	}
      else
	{
	  set_WD_brightness (buffer_r, s->brightness);	/* brightness */
	}

      if (s->contrast == 128)
	{
	  set_WD_contrast (buffer_r, 0);
	}
      else
	{
	  set_WD_contrast (buffer_r, s->contrast);	/* contrast */
	}

      /* scanmode */
      if (s->colormode == GREYSCALE)
	set_WD_composition (buffer_r, WD_comp_grey);	/* GRAY composition */
      else
	set_WD_composition (buffer_r, WD_comp_rgb_full);	/* RGB composition */

      set_WD_dropoutcolor (buffer_r, s->dropoutcolor);	/* Which color to scan with when grayscale scan */
      set_WD_transfermode (buffer_r, WD_LineSequence);
      set_WD_gammaselection (buffer_r, s->gammaselection);	/* monitor/linear */

      set_WD_shading (buffer_r, WD_Shading_ON);		/* default for non-manufacturing */

      if (1 == s->LS)
	{			/* Analog gamma reserved on LS-1000 */
	  set_WD_analog_gamma_R (buffer_r, 0);
	  set_WD_analog_gamma_G (buffer_r, 0);
	  set_WD_analog_gamma_R (buffer_r, 0);
	}
      else
	{
	  /* Quote spec: "It is recomended that analog gamma bits 5, 4 and 3 be
	   * set to 1 (OFF) when the object type of byte 48 is positive and the
	   * gamma specificateion of byte 51 is linear, and to 0 (ON) in all
	   * other cases." */
	  /*
	  int foo;
	  if ((buffer_r[48] == WD_Positive) && (buffer_r[51] == WD_Linear))
	    foo = WD_Analog_Gamma_OFF;
	  else
	    foo = WD_Analog_Gamma_ON;
	  set_WD_analog_gamma_R (buffer_r, foo);
	  set_WD_analog_gamma_G (buffer_r, foo);
	  set_WD_analog_gamma_B (buffer_r, foo);
	  */
	  set_WD_analog_gamma_R (buffer_r, s->analog_gamma_r);
	  set_WD_analog_gamma_G (buffer_r, s->analog_gamma_g);
	  set_WD_analog_gamma_B (buffer_r, s->analog_gamma_b);
	  if (s->gamma_bind)
	    {
	      set_WD_LUT_R (buffer_r, 1);
	      set_WD_LUT_G (buffer_r, 1);
	      set_WD_LUT_B (buffer_r, 1);
	    }
	  else
	    {
	      set_WD_LUT_R (buffer_r, 1);
	      set_WD_LUT_G (buffer_r, 2);
	      set_WD_LUT_B (buffer_r, 3);
	    }
	}
      set_WD_averaging (buffer_r, s->averaging);

      set_WD_brightness_R (buffer_r, s->brightness_R);
      set_WD_brightness_G (buffer_r, s->brightness_G);
      set_WD_brightness_B (buffer_r, s->brightness_B);

      set_WD_contrast_R (buffer_r, s->contrast_R);
      set_WD_contrast_G (buffer_r, s->contrast_G);
      set_WD_contrast_B (buffer_r, s->contrast_B);

      set_WD_exposure_R (buffer_r, s->exposure_R);
      set_WD_exposure_G (buffer_r, s->exposure_G);
      set_WD_exposure_B (buffer_r, s->exposure_B);
      set_WD_shift_R (buffer_r, s->shift_R);
      set_WD_shift_G (buffer_r, s->shift_G);
      set_WD_shift_B (buffer_r, s->shift_B);

 
      /* FIXME: LUT-[RGB] */
      /* FIXME: stop on/off */
    }

  DBG (10, "\tx_nres=%d, y_nres=%d, upper left-x=%d, upper left-y=%d\n",
       s->x_nres, s->y_nres, s->tlx, s->tly);
  DBG (10, "\twindow width=%d, MUD=%d, brx=%d\n",
       s->brx - s->tlx, s->MUD, s->brx);
  DBG (10, "\tcolormode=%d, bits per pixel=%d\n",
       s->colormode, s->bits_per_color);
  DBG (10, "\tnegative=%d, dropoutcolor=%d, preview=%d, transfermode=%d, gammasel=%d\n",
       s->negative, s->dropoutcolor, s->preview, s->transfermode,
       s->gammaselection);

  /* prepare SCSI-BUFFER */
  memcpy (s->buffer, set_window.cmd, set_window.size);	/* SET-WINDOW cmd */
  memcpy ((s->buffer + set_window.size),	/* add WPDB */
	  window_parameter_data_block.cmd,
	  window_parameter_data_block.size);
  set_WPDB_wdblen ((s->buffer + set_window.size), used_WDB_size);	/* set WD_len */
  memcpy (s->buffer + set_window.size + window_parameter_data_block.size,
	  buffer_r, window_descriptor_block.size);

  hexdump (15, "Window set", buffer_r, s->wdb_len);

  set_SW_xferlen (s->buffer, (window_parameter_data_block.size +
			      window_descriptor_block.size));

  ret = do_scsi_cmd (s->sfd, s->buffer, set_window.size +
		     window_parameter_data_block.size +
		     window_descriptor_block.size,
		     NULL, 0);
  DBG (10, "window set.\n");
  return ret;
}

static int
coolscan_set_window_param_LS30 (Coolscan_t * s, int wid, int prescan)
{
  unsigned char buffer_r[max_WDB_size];
  int ret;

  wait_scanner (s);
  memset (buffer_r, '\0', max_WDB_size);	/* clear buffer */
  memcpy (buffer_r, window_descriptor_block_LS30.cmd,
	  window_descriptor_block_LS30.size);	/* copy preset data */

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

  /* geometry */
  set_WD_Xres (buffer_r, resDivToVal (s->x_nres));	/* x resolution in dpi */
  set_WD_Yres (buffer_r, resDivToVal (s->y_nres));	/* y resolution in dpi */

  if (prescan)
    {
      set_WD_scanmode_LS30 (buffer_r, WD_Prescan);
      set_WD_Xres (buffer_r, resDivToVal (1));	/* x res. in dpi */
      set_WD_Yres (buffer_r, resDivToVal (1));	/* y res. in dpi */
      buffer_r[0x29]=0x81;
      buffer_r[0x2a]=0x04;
      buffer_r[0x2b]=0x02;
      buffer_r[0x2c]=0x01;
      buffer_r[0x2d]=0xff;
      buffer_r[0x30]=0x00;
      buffer_r[0x31]=0x00;
      buffer_r[0x32]=0x00;
      buffer_r[0x33]=0x00;
      set_WD_width (buffer_r,(2592));
      set_WD_length (buffer_r,(3894));
    }
  else
    {
      set_WD_scanmode_LS30 (buffer_r, WD_Scan);

      /* the coolscan LS-30 uses the upper left corner 
	 as the origin of coordinates */
      /* xmax and ymax are given in 1200 dpi */
      set_WD_ULX (buffer_r, s->tlx);
      set_WD_ULY (buffer_r, s->tly);	/* upper_edge y */
      set_WD_width (buffer_r, (s->brx - s->tlx+1));
      set_WD_length (buffer_r, (s->bry - s->tly+1));

      /* BTC */
      if (s->brightness == 128)
	{
	  buffer_r[0x32]=0x00;
	}
      else
	{
	  buffer_r[0x32]=s->brightness;	/* brightness */
	}

      if (s->contrast == 128)
	{
	  buffer_r[0x33]=0x00;
	}
      else
	{
	  buffer_r[0x33]=s->contrast;	/* contrast */
	}

      /* scanmode */
      if (s->colormode == GREYSCALE)
	set_WD_composition (buffer_r, WD_comp_grey);	/* GRAY composition */
      else
	set_WD_composition (buffer_r, WD_comp_rgb_full);	/* RGB composition */

      set_WD_composition (buffer_r, WD_comp_rgb_full);  /* allways RGB composition */

      /* Bits per pixel */
      set_WD_bitsperpixel(buffer_r, s->bits_per_color);

      buffer_r[0x29]=0x81;
      buffer_r[0x2a]=0x01;
      buffer_r[0x2b]=0x02;
      buffer_r[0x2c]=0x01;
      buffer_r[0x2d]=0xff;
      buffer_r[0x30]=0x00;

    }
    set_WD_negative_LS30(buffer_r, s->negative);	/* Negative/positive slide */

    switch(wid)
    { case 1:  set_gain_LS30(buffer_r,(s->exposure_R*s->pretv_r)/50);	
                 break;
      case 2:  set_gain_LS30(buffer_r,(s->exposure_G*s->pretv_g)/50);	
                 break;
      case 3:  set_gain_LS30(buffer_r,(s->exposure_B*s->pretv_b)/50);	
                 break;
    }

  DBG (10, "\texpo_r=%d, expo_g=%d, expob=%d\n",
       s->exposure_R, s->exposure_G, s->exposure_B);
  DBG (10, "\tpre_r=%d, pre_g=%d, preb=%d\n",
       s->pretv_r, s->pretv_g, s->pretv_b);
  DBG (10, "\tx_nres=%d, y_nres=%d, upper left-x=%d, upper left-y=%d\n",
       s->x_nres, s->y_nres, s->tlx, s->tly);
  DBG (10, "\twindow width=%d, MUD=%d, brx=%d\n",
       s->brx - s->tlx, s->MUD, s->brx);
  DBG (10, "\tcolormode=%d, bits per pixel=%d\n",
       s->colormode, s->bits_per_color);
  DBG (10, "\tnegative=%d, dropoutcolor=%d, preview=%d, transfermode=%d, gammasel=%d\n",
       s->negative, s->dropoutcolor, s->preview, s->transfermode,
       s->gammaselection);

  /* prepare SCSI-BUFFER */
  memcpy (s->buffer, set_window.cmd, set_window.size);	/* SET-WINDOW cmd */
  memcpy ((s->buffer + set_window.size),	/* add WPDB */
	  window_parameter_data_block.cmd,
	  window_parameter_data_block.size);
  set_WPDB_wdblen ((s->buffer + set_window.size), used_WDB_size_LS30);	/* set WD_len */
  memcpy (s->buffer + set_window.size + window_parameter_data_block.size,
	  buffer_r, window_descriptor_block_LS30.size);

  hexdump (15, "Window set", buffer_r, s->wdb_len);

  set_SW_xferlen (s->buffer, (window_parameter_data_block.size +
			      window_descriptor_block_LS30.size));

  ret = do_scsi_cmd (s->sfd, s->buffer, set_window.size +
		     window_parameter_data_block.size +
		     window_descriptor_block_LS30.size,
		     NULL, 0);
  DBG (10, "window set.\n");
  return ret;
}

static int
coolscan_set_window_param (Coolscan_t * s, int prescan)
{
  int ret;
  ret=0;
  DBG (10, "set_window_param\n");
  
  if(s->LS<2)                   /* distinquish between old and new scanners */
  { ret=coolscan_set_window_param_LS20 (s,prescan);
  }
  else
  {  do_scsi_cmd (s->sfd,commande1.cmd,commande1.size,s->buffer,0x0d);
     wait_scanner (s);
     wait_scanner (s);
     coolscan_set_window_param_LS30(s,1,prescan);
     ret=coolscan_set_window_param_LS30(s,2,prescan);
     ret=coolscan_set_window_param_LS30(s,3,prescan);     
     if(s->colormode&0x08)
     { ret=coolscan_set_window_param_LS30(s,9,prescan);
     }
  }
  return ret;
}


/* 
 * The only purpose of get_window is debugging. None of the return parameters
 * is currently used. 
 */
static int
coolscan_get_window_param_LS30 (Coolscan_t * s, int wid,int prescanok)
{
  int translen;
  unsigned char *buf;

  DBG (10, "GET_WINDOW_PARAM\n");
  /*  wait_scanner (s); */

  translen = window_parameter_data_block.size + window_descriptor_block_LS30.size;

  /* prepare SCSI-BUFFER */
  memset (s->buffer, '\0', max_WDB_size);	/* clear buffer */

  set_SW_xferlen (get_window.cmd, translen);	/* Transfer length */
  get_window.cmd[5]= wid;                     	/* window identifier */

  hexdump (15, "Get window cmd", get_window.cmd, get_window.size);
  do_scsi_cmd (s->sfd, get_window.cmd, get_window.size,
		     s->buffer, translen);

  buf = s->buffer + window_parameter_data_block.size;
  hexdump (10, "Window get", buf, 117);

  s->brightness = buf[0x32];	/* brightness */
  s->contrast = buf[0x33];	/* contrast */
  DBG (10, "\tbrightness=%d, contrast=%d\n", s->brightness, s->contrast);

  /* Useful? */
  s->bits_per_color = get_WD_bitsperpixel (buf);	/* bits/pixel (8) */

  DBG (10, "\tcolormode=%d, bits per pixel=%d\n",
       s->colormode, s->bits_per_color);

  if(prescanok)
  { switch(wid)
    { case 1: s->pretv_r = get_gain_LS30(buf);	
              break;
      case 2: s->pretv_g = get_gain_LS30(buf);	
              break;
      case 3: s->pretv_b = get_gain_LS30(buf);	
            break;
    }
  }

  /* Should this one be set at all, here? */
  s->transfermode = get_WD_transfermode (buf);

  s->gammaselection = get_WD_gammaselection (buf);	/* monitor/linear */
  DBG (10, "\tpre_r=%d, pre_g=%d, preb=%d\n",
       s->pretv_r, s->pretv_g, s->pretv_b);

  DBG (5, "\tnegative=%d, dropoutcolor=%d, preview=%d, transfermode=%d, gammasel=%d\n",
       s->negative, s->dropoutcolor, s->preview, s->transfermode,
       s->gammaselection);

  DBG (10, "get_window_param - return\n");
  return 0;
}

/* 
 * The only purpose of get_window is debugging. None of the return parameters
 * is currently used. 
 */
static int
coolscan_get_window_param_LS20 (Coolscan_t * s)
{
  int translen;
  unsigned char *buf;

  DBG (10, "GET_WINDOW_PARAM\n");
  wait_scanner (s);

  translen = window_parameter_data_block.size + window_descriptor_block.size;

  /* prepare SCSI-BUFFER */
  memset (s->buffer, '\0', max_WDB_size);	/* clear buffer */

  set_SW_xferlen (get_window.cmd, translen);	/* Transfer length */

  hexdump (15, "Get window cmd", get_window.cmd, get_window.size);
  do_scsi_cmd (s->sfd, get_window.cmd, get_window.size,
		     s->buffer, translen);

  buf = s->buffer + window_parameter_data_block.size;
  hexdump (10, "Window get", buf, 117);

  /* BTC */
  s->brightness = get_WD_brightness (buf);	/* brightness */
  s->contrast = get_WD_contrast (buf);	/* contrast */
  DBG (10, "\tbrightness=%d, contrast=%d\n", s->brightness, s->contrast);

  if (WD_comp_gray == get_WD_composition (buf))
    s->colormode = GREYSCALE;
  else
    s->colormode = RGB;

  /* Useful? */
  s->bits_per_color = get_WD_bitsperpixel (buf);	/* bits/pixel (8) */

  DBG (10, "\tcolormode=%d, bits per pixel=%d\n",
       s->colormode, s->bits_per_color);


  s->dropoutcolor = get_WD_dropoutcolor (buf);	/* Which color to scan with when grayscale scan */

  /* Should this one be set at all, here? */
  s->transfermode = get_WD_transfermode (buf);

  s->gammaselection = get_WD_gammaselection (buf);	/* monitor/linear */

  DBG (5, "\tnegative=%d, dropoutcolor=%d, preview=%d, transfermode=%d, gammasel=%d\n",
       s->negative, s->dropoutcolor, s->preview, s->transfermode,
       s->gammaselection);

  /* Should this one be set at all? */
  s->shading = get_WD_shading (buf);
  s->averaging = get_WD_averaging (buf);
  DBG (10, "get_window_param - return\n");
  return 0;
}

/* 
 * The only purpose of get_window is debugging. None of the return parameters
 * is currently used. 
 */
static int
coolscan_get_window_param (Coolscan_t * s, int prescanok)
{
  int ret;
  DBG (10, "get_window_param\n");

  ret=0;  
  if(s->LS<2)                   /* distinquish between old and new scanners */
  { ret=coolscan_get_window_param_LS20 (s);
  }
  else
  {  
     ret=coolscan_get_window_param_LS30(s,1,prescanok);
     ret=coolscan_get_window_param_LS30(s,2,prescanok);
     ret=coolscan_get_window_param_LS30(s,3,prescanok);
     if(s->colormode&0x08)
     { ret=coolscan_get_window_param_LS30(s,9,prescanok);
     }
  }
  return ret;
}

static int
coolscan_start_scanLS30 (Coolscan_t * s)
{ int channels;
  DBG (10, "starting scan\n");

  channels=1;
  memcpy (s->buffer, scan.cmd, scan.size);
  switch(s->colormode)
    {  case RGB:
       case GREYSCALE:	 
	       channels=s->buffer[4]=0x03; /* window 1 */
               s->buffer[6]=0x01; /* window 1 */
	       s->buffer[7]=0x02; /* window 2 */
	       s->buffer[8]=0x03; /* window 3 */  
	       
              break;      
       case RGBI:
	       channels=s->buffer[4]=0x04; /* window 1 */
               s->buffer[6]=0x01; /* window 1 */
	       s->buffer[7]=0x02; /* window 2 */
	       s->buffer[8]=0x03; /* window 3 */  
	       s->buffer[9]=0x09; /* window 3 */  
              break; 
       case IRED:
	       channels=s->buffer[4]=0x01; /* window 1 */
	       s->buffer[8]=0x09; /* window 3 */  
              break; 
    }

  return do_scsi_cmd (s->sfd, s->buffer, scan.size+channels, NULL, 0);
}

static int
coolscan_start_scan (Coolscan_t * s)
{
  DBG (10, "starting scan\n");
  if(s->LS>=2)
    { return coolscan_start_scanLS30(s);
    }
  return do_scsi_cmd (s->sfd, scan.cmd, scan.size, NULL, 0);
}


static int
prescan (Coolscan_t * s)
{
  int ret;

  DBG (10, "Starting prescan...\n");
  if(s->LS<2)
  {  coolscan_set_window_param (s, 1);
  }
  else
  { 
     do_scsi_cmd (s->sfd,commande1.cmd,commande1.size,s->buffer,0x0d);
     wait_scanner (s);
     wait_scanner (s);
     coolscan_set_window_param_LS30 (s,1,1);
     coolscan_set_window_param_LS30 (s,2,1);
     coolscan_set_window_param_LS30 (s,3,1);
  
  }
  ret = coolscan_start_scan(s);

  sleep(8);			/* prescan takes a minimum of 10 sec. */
  wait_scanner (s);
  DBG (10, "Prescan done\n");
  return ret;
}

static SANE_Status
do_prescan_now (Coolscan_t * scanner)
{

  DBG (10, "do_prescan_now \n");
  if (scanner->scanning == SANE_TRUE)
    return SANE_STATUS_DEVICE_BUSY;

  if (scanner->sfd < 0)
    {				/* first call */
      if (sanei_scsi_open (scanner->sane.name,
			   &(scanner->sfd),
			   sense_handler, 0) != SANE_STATUS_GOOD)
	{
	  DBG (1, "do_prescan_now: open of %s failed:\n",
	       scanner->sane.name);
	  return SANE_STATUS_INVAL;
	}
    }
  scanner->scanning = SANE_TRUE;


  if (coolscan_check_values (scanner) != 0)
    {				/* Verify values */
      DBG (1, "ERROR: invalid scan-values\n");
      scanner->scanning = SANE_FALSE;
      coolscan_give_scanner (scanner);
      sanei_scsi_close (scanner->sfd);
      scanner->sfd = -1;
      return SANE_STATUS_INVAL;
    }

  if (coolscan_grab_scanner (scanner))
    {
      sanei_scsi_close (scanner->sfd);
      scanner->sfd = -1;
      DBG (5, "WARNING: unable to reserve scanner: device busy\n");
      scanner->scanning = SANE_FALSE;
      return SANE_STATUS_DEVICE_BUSY;
    }

  prescan (scanner);  
  if(scanner->LS<2)
    {	get_internal_info(scanner);
    }
  coolscan_get_window_param (scanner,1);
  scanner->scanning = SANE_FALSE;
  coolscan_give_scanner (scanner);
  return SANE_STATUS_GOOD;
}


static int
send_one_LUT (Coolscan_t * s, SANE_Word * LUT, int reg)
{
  int i;
  short lutval;
  short bytesperval;
  unsigned char *gamma, *gamma_p;
  unsigned short *gamma_s;

  DBG (10, "send LUT\n");

  if(s->LS<2)
  { set_S_datatype_code (send.cmd, R_user_reg_gamma);
    bytesperval=1;
  }
  else
  {
    send.cmd[0x02]=3;
    send.cmd[0x05]=1;
    bytesperval=2;
  }

  set_S_xfer_length (send.cmd, s->lutlength*bytesperval);
  set_S_datatype_qual_upper (send.cmd, reg);

  gamma = alloca (send.size + s->lutlength*2);
  memcpy (gamma, send.cmd, send.size);
  if(s->LS<2)  
  { gamma_p = &gamma[send.size];
    for (i = 0; i < s->lutlength; i++)
    {
      if (LUT[i] > 255)
	LUT[i] = 255;		/* broken gtk */
      *gamma_p++ = (unsigned char) LUT[i];
    }
  }
  else if(s->LS==2)  
  { gamma_s = (unsigned short*)( &gamma[send.size]);
    for (i = 0; i < s->lutlength; i++)
    {
       if(s->negative)
       {
         lutval=(unsigned short)(LUT[(s->lutlength-i)]);  
       }
       else    
       {
	 lutval=(unsigned short)(LUT[i]);      
       }     
       if (LUT[i] >= s->max_lut_val)
       LUT[i] = s->max_lut_val-1;	          	/* broken gtk */
       if(s->low_byte_first)                                /* if on little endian machine: */
       {
         lutval=((lutval&0x00ff)<<8)+((lutval&0xff00)>>8); /* inverse byteorder */       
       }
       *gamma_s++ = lutval;   
    }
  }
  else if(s->LS==3)  
  { gamma_s = (unsigned short*)( &gamma[send.size]);
    for (i = 0; i < s->lutlength; i++)
    {
       if(s->negative)
       {
         lutval=(unsigned short)(LUT[s->lutlength-i]);  
       }
       else    
       {
	 lutval=(unsigned short)(LUT[i]);      
       }     
       if (LUT[i] >= s->max_lut_val)
       LUT[i] = s->max_lut_val-1;	          	    /* broken gtk */
       if(s->low_byte_first)                                /* if on little endian machine: */
       {  lutval=((lutval&0x00ff)<<8)+((lutval&0xff00)>>8); /* inverse byteorder */ 
       }
       *gamma_s++ = lutval;   
    }
  }
  return do_scsi_cmd (s->sfd, gamma, send.size + s->lutlength*bytesperval, NULL, 0);
}


static int
send_LUT (Coolscan_t * s)
{
  wait_scanner (s);
  if (s->gamma_bind)
    {
      send_one_LUT (s, s->gamma, S_DQ_Reg1);
      if(s->LS>=2)
	{      send_one_LUT (s, s->gamma, S_DQ_Reg2);
	       send_one_LUT (s, s->gamma, S_DQ_Reg3);
               if(s->colormode&0x08)
	       { send_one_LUT (s, s->gamma, S_DQ_Reg9);
	       }   

	}
    }
  else
    {
      send_one_LUT (s, s->gamma_r, S_DQ_Reg1);
      send_one_LUT (s, s->gamma_g, S_DQ_Reg2);
      send_one_LUT (s, s->gamma_b, S_DQ_Reg3);
      if(s->colormode&0x08)
      { send_one_LUT (s, s->gamma_r, S_DQ_Reg9);
      }   
    }
  return 0;
}


static int
coolscan_read_data_block (Coolscan_t * s, unsigned int datatype, unsigned int length)
{
  int r;

  DBG (10, "read_data_block (type= %x length = %d)\n",datatype,length);
  /*wait_scanner(s); */

  set_R_datatype_code (sread.cmd, datatype);
  sread.cmd[4]=00;
  sread.cmd[5]=00;
  set_R_xfer_length (sread.cmd, length);

  r = do_scsi_cmd (s->sfd, sread.cmd, sread.size, s->buffer, length);
  return ((r != 0) ? -1 : (int) length);
}


static void
coolscan_do_inquiry (Coolscan_t * s)
{
  int size;

  DBG (10, "do_inquiry\n");
  memset (s->buffer, '\0', 256);	/* clear buffer */
  size = 36;			/* Hardcoded, and as specified by Nikon */
  /* then get inquiry with actual size */
  set_inquiry_return_size (inquiry.cmd, size);
  do_scsi_cmd (s->sfd, inquiry.cmd, inquiry.size, s->buffer, size);
}

static int
coolscan_identify_scanner (Coolscan_t * s)
{
  unsigned char vendor[9];
  unsigned char product[0x11];
  unsigned char version[5];
  unsigned char *pp;
  int i;

  vendor[8] = product[0x10] = version[4] = 0;
  DBG (10, "identify_scanner\n");
  coolscan_do_inquiry (s);	/* get inquiry */
  if (get_inquiry_periph_devtype (s->buffer) != IN_periph_devtype_scanner)
    {
      DBG (5, "identify_scanner: not a scanner\n");
      return 1;
    }				/* no, continue searching */

  coolscan_get_inquiry_values (s);

  get_inquiry_vendor ((char *)s->buffer, (char *)vendor);
  get_inquiry_product ((char *)s->buffer, (char *)product);
  get_inquiry_version ((char *)s->buffer, (char *)version);

  if (strncmp ("Nikon   ", (char *)vendor, 8))
    {
      DBG (5, "identify_scanner: \"%s\" isn't a Nikon product\n", vendor);
      return 1;
    }				/* Not a Nikon product */

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

  pp = &product[0x10];
  product[0x10] = ' ';
  while (*(pp - 1) == ' ')
    {
      *pp-- = '\0';
    }				/* leave one blank at the end! */

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

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

  /* look for scanners that do not give all inquiry-informations */
  /* and if possible use driver-known inquiry-data  */
  if (get_inquiry_additional_length (s->buffer) >= 0x1f)
    {
      /* Now identify full supported scanners */
      for (i = 0; i < known_scanners; i++)
	{
	  if (!strncmp ((char *)product, scanner_str[i], strlen (scanner_str[i])))
	    {
	      s->LS = i;
	      return 0;
	    }
	}
      if (s->cont)
	return 0;
      else
	return 1;
    }
  else
    return 1;
}

static int
pixels_per_line (Coolscan_t * s)
{
  int pic_dot;
  if(s->LS<2)
  {  pic_dot = (s->brx - s->tlx + s->x_nres) / s->x_nres; 
  }
  else
  { pic_dot = (s->brx - s->tlx + 1) / s->x_nres; 
  }
  DBG (10, "pic_dot=%d\n", pic_dot);
  return pic_dot;
}

static int
lines_per_scan (Coolscan_t * s)
{
  int pic_line;
  if(s->LS<2)
  { pic_line = (s->bry - s->tly + s->y_nres) / s->y_nres; 
  }
  else
  { pic_line = (( s->bry - s->tly + 1.0 )  / s->y_nres); 
  }
  DBG (10, "pic_line=%d\n", pic_line);
  return pic_line;
}

static int
scan_bytes_per_line (Coolscan_t * s)
{ int bpl;
  switch(s->colormode)
    {  case RGB:
       case GREYSCALE:
              bpl=pixels_per_line (s) * 3;
              if(s->bits_per_color>8) bpl=bpl*2;
              return bpl;
              break;      
       case RGBI:
       case IRED:
              bpl=pixels_per_line (s) * 4;
              if(s->bits_per_color>8) bpl=bpl*2;
              return bpl;
              break; 
    }
    return 0;
}

static int
write_bytes_per_line (Coolscan_t * s)
{ int bpl;
  switch(s->colormode)
    {  case RGB:
              bpl=pixels_per_line (s) * 3;
              if(s->bits_per_color>8) bpl=bpl*2;
              return bpl;
              break;      
       case RGBI:
              bpl=pixels_per_line (s) * 4;
              if(s->bits_per_color>8) bpl=bpl*2;
              return bpl;
              break; 
       case IRED:
       case GREYSCALE:
              bpl= pixels_per_line (s) ;
              if(s->bits_per_color>8) bpl=bpl*2;
              return bpl;
              break; 
    }
    return 0;
}


static void
coolscan_trim_rowbufsize (Coolscan_t * s)
{
  unsigned int row_len;
  row_len = scan_bytes_per_line (s);
  s->row_bufsize = (s->row_bufsize < row_len) ? s->row_bufsize
    : s->row_bufsize - (s->row_bufsize % row_len);
  DBG (10, "trim_bufsize to %d\n", s->row_bufsize);
}

static int
coolscan_check_values (Coolscan_t * s)
{
  DBG (10, "check_values\n");
  /* -------------------------- asf --------------------------------- */
  if (s->asf != 0)
    {
      if (s->autofeeder == 0)
	{
	  DBG (1, "ERROR: ASF-MODE NOT SUPPORTED BY SCANNER, ABORTING\n");
	  return (1);
	}
    }

  return (0);
}

/* test_little_endian */

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

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

static int
get_inquiery_part_LS30 (Coolscan_t * s, unsigned char part)
{ 
  int size;
  int ret;

  /* Get length of reponse */
  inquiry.cmd[1]=0x01;
  inquiry.cmd[2]=part;
  size=4;
  set_inquiry_return_size (inquiry.cmd, size);
  ret = do_scsi_cmd (s->sfd, inquiry.cmd, inquiry.size,
		     s->buffer, size);
  size=get_inquiry_length(s->buffer); 
  size+=4;
  /* then get inquiry with actual size */
  set_inquiry_return_size (inquiry.cmd, size);
  ret = do_scsi_cmd (s->sfd, inquiry.cmd, inquiry.size,
		     s->buffer, size);
  return size;
}

static int
coolscan_read_var_data_block (Coolscan_t * s,int datatype)
{
  int r;
  int size;

  DBG (10, "read_data_block (type= %x)\n",datatype);
  /*wait_scanner(s); */

  sread.cmd[2]=datatype;
  sread.cmd[4]=00;
  sread.cmd[5]=03;
  size=6;
  set_R_xfer_length (sread.cmd, size);
  r = do_scsi_cmd (s->sfd, sread.cmd, sread.size,
		     s->buffer, size);
  size=s->buffer[5]; 
  set_R_xfer_length (sread.cmd, size);
  r = do_scsi_cmd (s->sfd, sread.cmd, sread.size,
		     s->buffer, size);
  return ((r != 0) ? -1 : size);
}

static int
get_inquiery_LS30 (Coolscan_t * s)
{ 
  unsigned char part;
  unsigned char parts[5];
  int size;
  int i;

  /* Get vector of inquiery parts */
  size=get_inquiery_part_LS30(s, (unsigned char) 0);
  /* Get the parts of inquiery */  
  for(i=0;i<5;i++)
  { parts[i]=((unsigned char *)s->buffer)[4+11+i];
  }
  for(i=0;i<5;i++)
  { part=parts[i];
    size=get_inquiery_part_LS30 (s, part);
    switch(part)
    {  case 0x0c1:/* max size and resolution */                   
                    s->adbits = 8;
                    s->outputbits = 8;
		    s->maxres = getnbyte(s->buffer+0x12,2)-1;
		    s->xmaxpix = getnbyte(s->buffer+0x53,2)-1;
		    s->ymaxpix = getnbyte(s->buffer+0x3c,2)-1;
                  break;
       case 0x0d1:
                  break;
       case 0x0e1:
                  break;
       case 0x0f0:
                  break;
       case 0x0f8:
                  break;
    }  
  }

  /* get windows */
  coolscan_get_window_param_LS30 (s,0,0);
   s->xmax = get_WD_width(s->buffer);
   s->ymax = get_WD_length(s->buffer);
  coolscan_get_window_param_LS30 (s,1,0);
  coolscan_get_window_param_LS30 (s,2,0);
  coolscan_get_window_param_LS30 (s,3,0);
  coolscan_get_window_param_LS30 (s,4,0);
  coolscan_get_window_param_LS30 (s,9,0);

  s->analoggamma = 0;
  return 1;
}

static int
get_feeder_type_LS30 (Coolscan_t * s)
{ 
  int size;
  unsigned char *ptr;
  int ima;

  /* find out about Film-strip-feeder or Mount-Feeder */
  size=get_inquiery_part_LS30(s, (unsigned char) 1);
  if(strncmp((char *)s->buffer+5,"Strip",5)==0)
  { s->feeder=STRIP_FEEDER;
    s->autofeeder = 1;
  }
  if(strncmp((char *)s->buffer+5,"Mount",5)==0)
  { s->feeder=MOUNT_FEEDER;
  }
  /* find out about Film-strip-feeder positions*/
  if(s->feeder==STRIP_FEEDER)
  { size=coolscan_read_var_data_block (s,(int)0x88);
    if(size>=4)
    { s->numima=s->buffer[3];
      if(s->numima>6) s->numima=6; /* limit to 6 images for now */
      if(s->numima>(size-4)/16) s->numima=(size-4)/16;
      ptr=s->buffer+4;
      for(ima=0;ima<s->numima;ima++)
      {  s->ipos[ima].start=getnbyte(ptr,4);
         s->ipos[ima].offset=getnbyte(ptr+4,4);
         s->ipos[ima].end=getnbyte(ptr+8,4);
         s->ipos[ima].height=getnbyte(ptr+12,4);
	 ptr+=16;
      }
    }
    s->posima=0;
  }
  return 1;
}


static int
get_internal_info_LS20 (Coolscan_t * s)
{
  int ret;

  DBG (10, "get_internal_info\n");
  wait_scanner (s);
  memset (s->buffer, '\0', DI_length);	/* clear buffer */

  set_R_datatype_code (sread.cmd, R_device_internal_info);
  set_R_datatype_qual_upper (sread.cmd, R_DQ_none);
  set_R_xfer_length (sread.cmd, DI_length);
  /* then get inquiry with actual size */
  ret = do_scsi_cmd (s->sfd, sread.cmd, sread.size,
		     s->buffer, DI_length);

  s->adbits = get_DI_ADbits (s->buffer);
  s->outputbits = get_DI_Outputbits (s->buffer);
  s->maxres = get_DI_MaxResolution (s->buffer);
  s->xmax = get_DI_Xmax (s->buffer);
  s->ymax = get_DI_Ymax (s->buffer);
  s->xmaxpix = get_DI_Xmaxpixel (s->buffer);
  s->ymaxpix = get_DI_Ymaxpixel (s->buffer);
  s->ycurrent = get_DI_currentY (s->buffer);
  s->currentfocus = get_DI_currentFocus (s->buffer);
  s->currentscanpitch = get_DI_currentscanpitch (s->buffer);
  s->autofeeder = get_DI_autofeeder (s->buffer);
  s->analoggamma = get_DI_analoggamma (s->buffer);
  s->derr[0] = get_DI_deviceerror0 (s->buffer);
  s->derr[1] = get_DI_deviceerror1 (s->buffer);
  s->derr[2] = get_DI_deviceerror2 (s->buffer);
  s->derr[3] = get_DI_deviceerror3 (s->buffer);
  s->derr[4] = get_DI_deviceerror4 (s->buffer);
  s->derr[5] = get_DI_deviceerror5 (s->buffer);
  s->derr[6] = get_DI_deviceerror6 (s->buffer);
  s->derr[7] = get_DI_deviceerror7 (s->buffer);
  s->wbetr_r = get_DI_WBETR_R (s->buffer);
  s->webtr_g = get_DI_WBETR_G (s->buffer);
  s->webtr_b = get_DI_WBETR_B (s->buffer);
  s->pretv_r = get_DI_PRETV_R (s->buffer);
  s->pretv_g = get_DI_PRETV_G (s->buffer);
  s->pretv_r = get_DI_PRETV_R (s->buffer);
  s->cetv_r = get_DI_CETV_R (s->buffer);
  s->cetv_g = get_DI_CETV_G (s->buffer);
  s->cetv_b = get_DI_CETV_B (s->buffer);
  s->ietu_r = get_DI_IETU_R (s->buffer);
  s->ietu_g = get_DI_IETU_G (s->buffer);
  s->ietu_b = get_DI_IETU_B (s->buffer);
  s->limitcondition = get_DI_limitcondition (s->buffer);
  s->offsetdata_r = get_DI_offsetdata_R (s->buffer);
  s->offsetdata_g = get_DI_offsetdata_G (s->buffer);
  s->offsetdata_b = get_DI_offsetdata_B (s->buffer);
  get_DI_poweron_errors (s->buffer, s->power_on_errors);

  DBG (10,
       "\tadbits=%d\toutputbits=%d\tmaxres=%d\txmax=%d\tymax=%d\n"
       "\txmaxpix=%d\tymaxpix=%d\tycurrent=%d\tcurrentfocus=%d\n"
       "\tautofeeder=%s\tanaloggamma=%s\tcurrentscanpitch=%d\n",
       s->adbits, s->outputbits, s->maxres, s->xmax, s->ymax,
       s->xmaxpix, s->ymaxpix, s->ycurrent, s->currentfocus,
       s->autofeeder ? "Yes" : "No", s->analoggamma ? "Yes" : "No",
       s->currentscanpitch);
  DBG (10,
       "\tWhite balance exposure time var [RGB]=\t%d %d %d\n"
       "\tPrescan result exposure time var [RGB]=\t%d %d %d\n"
       "\tCurrent exposure time var.[RGB]=\t%d %d %d\n"
       "\tInternal exposure time unit[RGB]=\t%d %d %d\n",
       s->wbetr_r, s->webtr_g, s->webtr_b, s->pretv_r, s->pretv_g,
       s->pretv_r, s->cetv_r, s->cetv_g, s->cetv_b, s->ietu_r,
       s->ietu_g, s->ietu_b);
  DBG (10,
       "\toffsetdata_[rgb]=\t0x%x 0x%x 0x%x\n"
       "\tlimitcondition=0x%x\n"
       "\tdevice error code = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n"
       "\tpower-on errors = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
       s->offsetdata_r, s->offsetdata_g, s->offsetdata_b,
       s->limitcondition, 
       s->derr[0], s->derr[1], s->derr[2], s->derr[3], s->derr[4],
       s->derr[5], s->derr[6], s->derr[7],
       s->power_on_errors[0], s->power_on_errors[1],
       s->power_on_errors[2], s->power_on_errors[3],
       s->power_on_errors[4], s->power_on_errors[5],
       s->power_on_errors[6], s->power_on_errors[7]);

  return ret;
}

static int
get_internal_info (Coolscan_t * s)
{
  int ret;

  DBG (10, "get_internal_info\n");
  
  if(s->LS<2)                   /* distinquish between old and new scanners */
  { ret=get_internal_info_LS20 (s);
  }
  else
  { ret=get_inquiery_LS30 (s);
  }
  return ret;
}

static void
coolscan_get_inquiry_values (Coolscan_t * s)
{
  unsigned char *inquiry_block;

  DBG (10, "get_inquiry_values\n");

  inquiry_block = (unsigned char *) s->buffer;
  s->inquiry_len = 36;

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

  if (s->inquiry_len < 36)
    {
      DBG (1, "WARNING: inquiry return block is unexpected short (%d instead of 36).\n", s->inquiry_len);
    }
  s->inquiry_wdb_len = 117;
  return;
}

static void
coolscan_initialize_values (Coolscan_t * s)
{
  int i;
  DBG (10, "initialize_values\n");
  /* Initialize us structure */
  if(s->LS<2)                   /* LS-20 or LS-10000 */
  {  select_MUD (s);		/* must be before mode_sense - not for LS-30*/
     coolscan_mode_sense (s);	/* Obtain MUD (Measurement Unit Divisor) */
     get_internal_info (s);	/* MUST be called first. */
     s->wdb_len = 117;
  }
  if(s->LS>=2)                  /* LS-30 */
  {
    get_inquiery_LS30(s);	/* Info about scanner*/ 
    select_MUD (s);		/* must be before mode_sense */
    get_feeder_type_LS30(s);
    s->wdb_len = 117;
  }

  s->cont = 0;			/* do not continue if scanner is unknown */
  s->verbose = 2;		/* 1=verbose,2=very verbose */


  s->x_nres = s->y_nres = 2;	/* 2 => 1350 dpi */
  s->x_p_nres = s->y_p_nres = 9;	/* 9 => 300 dpi */
  s->tlx = 0;
  s->tly = 0;
  s->brx = s->xmaxpix;		/* 2700 / 1200; */
  s->bry = s->ymaxpix;		/* 2700 / 1200; */


  s->set_auto = 0;		/* Always 0 on Nikon LS-{100|2}0 */
  s->preview = 0;		/* 1 for preview */
  s->colormode = RGB;		/* GREYSCALE or RGB */
  s->colormode_p = RGB;		/* GREYSCALE or RGB for preview*/
  s->asf = 0;			/* 1 if asf shall be used */
  s->gammaselection = WD_Linear;

  s->brightness = 128;
  s->brightness_R = 128;
  s->brightness_G = 128;
  s->brightness_B = 128;
  s->contrast = 128;
  s->contrast_R = 128;
  s->contrast_G = 128;
  s->contrast_B = 128;

  s->exposure_R = 50;
  s->exposure_G = 50;
  s->exposure_B = 50;

  s->pretv_r=40000;
  s->pretv_g=40000;  
  s->pretv_b=40000;

  s->shift_R = 128;
  s->shift_G = 128;
  s->shift_B = 128;

  s->ired_red=60;
  s->ired_green=1;
  s->ired_blue=1;

  s->prescan = 1;
  s->bits_per_color = 8;
  s->rgb_control = 0;
  s->gamma_bind = 1;
  switch(s->LS)
  {  case 0:s->lutlength=2048;
            s->max_lut_val=256;
            break;
     case 1:s->lutlength=512;
            s->max_lut_val=512;
            break;
     case 2:s->lutlength=1024;
            s->max_lut_val=1024;
            break;
     case 3:s->lutlength=4096;
            s->max_lut_val=4096;
            break;
  }
  for (i = 0; i < s->lutlength; i++)
  {
     s->gamma[i] =((short)((((double)i)/s->lutlength)*s->max_lut_val));
     s->gamma_r[i] = s->gamma[i];
     s->gamma_g[i] = s->gamma[i];
     s->gamma_b[i] = s->gamma[i];
  } 

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

static void
hexdump (int level, char *comment, unsigned char *p, int l)
{
  int i;
  char line[128];
  char *ptr;

  DBG (level, "%s\n", comment);
  ptr = line;
  for (i = 0; i < l; i++, p++)
    {
      if ((i % 16) == 0)
	{
	  if (ptr != line)
	    {
	      *ptr = '\0';
	      DBG (level, "%s\n", line);
	      ptr = line;
	    }
	  sprintf (ptr, "%3.3d:", i);
	  ptr += 4;
	}
      sprintf (ptr, " %2.2x", *p);
      ptr += 3;
    }
  *ptr = '\0';
  DBG (level, "%s\n", line);
}


static SANE_Status
sense_handler (int scsi_fd, unsigned char * result, void *arg)
{
  scsi_fd = scsi_fd;
  arg = arg;

  if (result[0] != 0x70)
    {
      return SANE_STATUS_IO_ERROR;	/* we only know about this one  */
    }
  return request_sense_parse(result);
 
}


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


/* ilu per mm */

#define length_quant SANE_UNFIX(SANE_FIX(MM_PER_INCH / 2700.0))
#define mmToIlu(mm) ((mm) / length_quant)
#define iluToMm(ilu) ((ilu) * length_quant)

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

static const char negativeStr[] = "Negative";
static const char positiveStr[] = "Positive";
static SANE_String_Const type_list[] =
{
  positiveStr,
  negativeStr,
  0
};

static const char colorStr[] = SANE_VALUE_SCAN_MODE_COLOR;
static const char grayStr[] = SANE_VALUE_SCAN_MODE_GRAY;
static const char rgbiStr[] = "RGBI";
static const char iredStr[] = "Infrared";

static SANE_String_Const scan_mode_list_LS20[] =
{
  colorStr,
  grayStr,
  NULL
};

static SANE_String_Const scan_mode_list_LS30[] =
{
  colorStr,
  grayStr,
#ifdef HAS_IRED	     
  rgbiStr,
#endif /* HAS_IRED */
  NULL
};

static SANE_Int bit_depth_list[9];

static const char neverStr[] = "never";
static const char previewStr[] = "before preview";
static const char scanStr[] = "before scan";
static const char preandscanStr[] = "before preview and scan";
static SANE_String_Const autofocus_mode_list[] =
{
  neverStr,
  previewStr,
  scanStr,
  preandscanStr,
  NULL
};

static SANE_String_Const source_list[4] =
{NULL, NULL, NULL, NULL};

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


static const SANE_Range gamma_range_9 = 
{
  0,				/* minimum */
  511,				/* maximum */
  1				/* quantization */
};

static const SANE_Range gamma_range_10 = 
{
  0,				/* minimum */
  1023,				/* maximum */
  1				/* quantization */
};

static const SANE_Range gamma_range_12 = 
{
  0,				/* minimum */
  4096,				/* maximum */
  1				/* quantization */
};

static const SANE_Range brightness_range =
{
  -5,
  +5,
  1
};

static const SANE_Range contrast_range =
{
  -5,
  +5,
  0
};

static const SANE_Range exposure_range =
{
  24,
  400,
  2
};

static const SANE_Range shift_range =
{
  -15,
  +15,
  0
};

static int num_devices;
static Coolscan_t *first_dev;


static size_t
max_string_size (const SANE_String_Const strings[])
{
  size_t size, max_size = 0;
  int i;

  for (i = 0; strings[i]; ++i)
    {
      size = strlen (strings[i]) + 1;
      if (size > max_size)
	max_size = size;
    }
  return max_size;
}

static SANE_Status
do_eof (Coolscan_t * scanner)
{
  DBG (10, "do_eof\n");

  if (scanner->pipe >= 0)
    {
      close (scanner->pipe);
      scanner->pipe = -1;
    }
  return SANE_STATUS_EOF;
}

static SANE_Status
do_cancel (Coolscan_t * scanner)
{
  DBG (10, "do_cancel\n");
  swap_res (scanner);
  scanner->scanning = SANE_FALSE;

  do_eof (scanner);		/* close pipe and reposition scanner */

  if (scanner->reader_pid != -1)
    {
      int exit_status;

      DBG (10, "do_cancel: kill reader_process\n");

      /* ensure child knows it's time to stop: */
      sanei_thread_kill (scanner->reader_pid);
      while (sanei_thread_waitpid(scanner->reader_pid, &exit_status) !=
                                                        scanner->reader_pid );
      scanner->reader_pid = -1;
    }

  if (scanner->sfd >= 0)
    {
      coolscan_give_scanner (scanner);
      DBG (10, "do_cancel: close filedescriptor\n");
      sanei_scsi_close (scanner->sfd);
      scanner->sfd = -1;
    }

  return SANE_STATUS_CANCELLED;
}

static SANE_Status
attach_scanner (const char *devicename, Coolscan_t ** devp)
{
  Coolscan_t *dev;
  int sfd;

  DBG (10, "attach_scanner: %s\n", devicename);

  for (dev = first_dev; dev; dev = dev->next)
    {
      if (strcmp (dev->sane.name, devicename) == 0)
	{
	  if (devp)
	    {
	      *devp = dev;
	    }
	  DBG (5, "attach_scanner: scanner already attached (is ok)!\n");
	  return SANE_STATUS_GOOD;
	}
    }

  DBG (10, "attach_scanner: opening %s\n", devicename);
  if (sanei_scsi_open (devicename, &sfd, sense_handler, 0) != 0)
    {
      DBG (1, "attach_scanner: open failed\n");
      return SANE_STATUS_INVAL;
    }

  if (NULL == (dev = malloc (sizeof (*dev))))
    return SANE_STATUS_NO_MEM;

	
  dev->row_bufsize = (sanei_scsi_max_request_size < (64 * 1024)) ?
  	sanei_scsi_max_request_size : 64 * 1024;
 
  if ((dev->buffer = malloc (dev->row_bufsize)) == NULL)
/*  if ((dev->buffer = malloc (sanei_scsi_max_request_size)) == NULL)*/
    return SANE_STATUS_NO_MEM;

  if ((dev->obuffer = malloc (dev->row_bufsize)) == NULL)
    return SANE_STATUS_NO_MEM;

  dev->devicename = strdup (devicename);
  dev->sfd = sfd;

  /* Nikon manual: Step 1 */
  if (coolscan_identify_scanner (dev) != 0)
    {
      DBG (1, "attach_scanner: scanner-identification failed\n");
      sanei_scsi_close (dev->sfd);
      free (dev->buffer);
      free (dev);
      return SANE_STATUS_INVAL;
    }

  /* Get MUD (via mode_sense), internal info (via get_internal_info), and
   * initialize values */
  coolscan_initialize_values (dev);

  /* Why? */
  sanei_scsi_close (dev->sfd);
  dev->sfd = -1;

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

  dev->x_range.min = SANE_FIX (0);
  dev->x_range.quant = SANE_FIX (length_quant);
  dev->x_range.max = SANE_FIX ((double) ((dev->xmaxpix) * length_quant));

  dev->y_range.min = SANE_FIX (0.0);
  dev->y_range.quant = SANE_FIX (length_quant);
  dev->y_range.max = SANE_FIX ((double) ((dev->ymaxpix) * length_quant));

  /* ...and this?? */
  dev->dpi_range.min = SANE_FIX (108);
  dev->dpi_range.quant = SANE_FIX (0);
  dev->dpi_range.max = SANE_FIX (dev->maxres);
  DBG (10, "attach: dev->dpi_range.max = %f\n",
       SANE_UNFIX (dev->dpi_range.max));

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

  if (devp)
    {
      *devp = dev;
    }

  DBG (10, "attach_scanner done\n");

  return SANE_STATUS_GOOD;
}

static SANE_Status
attach_one (const char *devName)
{
  return attach_scanner(devName, 0);
}

static RETSIGTYPE
sigterm_handler (int signal)
{
  signal = signal;
  sanei_scsi_req_flush_all ();	/* flush SCSI queue */
  _exit (SANE_STATUS_GOOD);
}


typedef struct Color_correct_s
{ int sum;           /* number of pixels summed so far */
  double sumr;          /* sum of red pixel values*/
  double sumi;          /* sum of infrared pixel values*/
  double sumri;         /* sum of red*ired pixel values*/
  double sumii;         /* sum of ired*ired pixel values*/
  double sumrr;         /* sum of ired*ired pixel values*/
  int  mr;         /* factor between red and ired values (*256) */ 
  int  br;         /* offset of ired values */ 
} ColorCorrect;

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

  function:   RGBIfix

  taks:       Correct the infrared channel
      
  import:     unsigned char * rgbimat - RGBI - matrix from scanner
              int size - number of pixels to correct
	      int *lutr - lookup table for red correction
	      int *lutg - lookup table for red correction
	      int *lutb - lookup table for red correction
	      int *lutr - lookup table for red correction

  export:     unsigned char * orgbimat - RGBI - corrected matrix
                  
  written by: Andreas RICK   19.6.1999
                           
  ----------------------------------------------------------------*/

static int Calc_fix_LUT(Coolscan_t * s)
{ int uselutr,uselutg,uselutb,useluti;    
/*  static int irmulr= -34*25; */
   int irmulr= -64*25;
   int irmulg= -1*25;
   int irmulb= -0*25;
   int irmuli= 256*25;
   int div;
   int i;

    irmulr=s->ired_red*(25);
    irmulg=s->ired_green*(25);
    irmulb=s->ired_blue*(25);
    irmuli=25*256;
  
  if(s->LS==2) /* TODO: right conversion factors for 10 and 12 bit */
    { div=4;
    }
  else  if(s->LS==3) 
    { div=16;
    }
  else
    { return 0;
    }

  memset(s->lutr, 0,256*4);
  memset(s->lutg, 0,256*4);
  memset(s->lutb, 0,256*4);
  memset(s->luti, 0,256*4);

  for(i=0;i<s->lutlength;i++)
  {  if(s->gamma_bind)
     { uselutr=uselutg=uselutb=useluti=s->gamma[i]/div;
     }
     else
     { uselutr=s->gamma_r[i]/div;
       uselutg=s->gamma_g[i]/div;
       uselutb=s->gamma_b[i]/div;
       useluti=s->gamma_r[i]/div;
     }
     s->lutr[uselutr]=(int)(irmulr*pow((double)i,(double)0.333333)); 
     s->lutg[uselutg]=(int)(irmulg*pow((double)i,(double)0.333333)); 
     s->lutb[uselutb]=(int)(irmulb*pow((double)i,(double)0.333333)); 
     s->luti[useluti]=(int)(irmuli*pow((double)i,(double)0.333333)); 
     if(uselutr<255)
     { if(s->lutr[uselutr+1]==0) s->lutr[uselutr+1]=s->lutr[uselutr];
     }
     if(uselutg<255)
     { if(s->lutg[uselutg+1]==0) s->lutg[uselutg+1]=s->lutg[uselutg];
     }
     if(uselutb<255)
     { if(s->lutb[uselutb+1]==0) s->lutb[uselutb+1]=s->lutb[uselutb];
     }
     if(useluti<255)
     { if(s->luti[useluti+1]==0) s->luti[useluti+1]=s->luti[useluti];
     }
  }
  /* DEBUG
  for(i=0;i<255;i++)
  { fprintf(stderr,"%d %d %d %d\n"
	    ,s->lutr[i],s->lutg[i],s->lutb[i],s->luti[i]);
  }
  */
  return 1;
}



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

  function:   RGBIfix

  taks:       Correct the infrared channel
      
  import:     unsigned char * rgbimat - RGBI - matrix from scanner
              int size - number of pixels to correct
	      int *lutr - lookup table for red correction
	      int *lutg - lookup table for red correction
	      int *lutb - lookup table for red correction
	      int *lutr - lookup table for red correction

  export:     unsigned char * orgbimat - RGBI - corrected matrix
                  
  written by: Andreas RICK   19.6.1999
                           
  ----------------------------------------------------------------*/

static int RGBIfix(Coolscan_t * scanner,
	   unsigned char* rgbimat,
	   unsigned char* orgbimat, 	   
	    int size,
	    int *lutr,
	    int *lutg,
	    int *lutb,
	    int *luti)
	    
{
  unsigned char *pr,*pg,*pb,*pi;
  unsigned char *opr,*opg,*opb,*opi;

   int r,g,b,i;
   int ii;
   int x;
   for(x=0;x<size;x++)
   {
        pr=rgbimat+x*4;
	pg=pr+1; 
	pb=pg+1;
	pi=pb+1;	
        opr=orgbimat+x*4;
	opg=opr+1; 
	opb=opg+1;
	opi=opb+1;
	r=lutr[(*pr)];
	g=lutg[(*pg)];
	b=lutb[(*pb)];	
	i=luti[(*pi)];	
	ii= i-r-g-b;
	(*opr)=(*pr);
	(*opg)=(*pg);
	(*opb)=(*pb);	
	if(ii<0)ii=0;
	if(ii>255*256)ii=255*256;
	if(scanner->negative)
	{
	    (*opi)=(unsigned char)(255-(ii>>8));	
	}
	else
	{
	  (*opi)=(unsigned char)(ii>>8);	
	}
   }
   return 1;
}

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

  function:   RGBIfix16

  taks:       Correct the infrared channel for 16 bit images
              (doesn't do anything for now)
      
  import:     unsigned char * rgbimat - RGBI - matrix from scanner
              int size - number of pixels to correct
	      int *lutr - lookup table for red correction
	      int *lutg - lookup table for red correction
	      int *lutb - lookup table for red correction
	      int *lutr - lookup table for red correction

  export:     unsigned char * orgbimat - RGBI - corrected matrix
                  
  written by: Andreas RICK   19.6.1999
                           
  ----------------------------------------------------------------*/

static int RGBIfix16(Coolscan_t * scanner,
	      unsigned short* rgbimat,
	      unsigned short* orgbimat, 	   
	      int size,
	      int *lutr,
	      int *lutg,
	      int *lutb,
	      int *luti)
	    
{  
  unsigned short *pr,*pg,*pb,*pi;
  unsigned short *opr,*opg,*opb,*opi;
  int x;

  scanner = scanner; lutr = lutr; lutg = lutg; lutb = lutb; luti = luti;

   for(x=0;x<size;x++)
   {
        pr=rgbimat+x*4;
	pg=pr+1; 
	pb=pg+1;
	pi=pb+1;	
        opr=orgbimat+x*4;
	opg=opr+1; 
	opb=opg+1;
	opi=opb+1;
	(*opr)=(((*pr)&0x00ff)<<8)+(((*pr)&0xff00)>>8); 
      	(*opg)=(((*pg)&0x00ff)<<8)+(((*pg)&0xff00)>>8); 
	(*opb)=(((*pb)&0x00ff)<<8)+(((*pb)&0xff00)>>8); 	
	(*opi)=(((*pi)&0x00ff)<<8)+(((*pi)&0xff00)>>8); 
   }
   return 1;
}


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

  function:   rgb2g

  taks:       Convert RGB data to grey
      
  import:     unsigned char * rgbimat - RGB - matrix from scanner
              int size - size of input data (num pixel)

  export:     unsigned char * gomat - Grey matrix
                  
  written by: Andreas RICK   13.7.1999
                           
  ----------------------------------------------------------------*/
#define RtoG ((int)(0.27*256))
#define GtoG ((int)(0.54*256))
#define BtoG ((int)(0.19*256))

static int rgb2g(unsigned char* rgbimat,unsigned char* gomat, 	   
	  int size)
	    
{  unsigned char *pr,*pg,*pb;
   unsigned char *opg;

   int g;
   int x;
   for(x=0;x<size;x++)
   {
        pr=rgbimat+x*3;
	pg=pr+1; 
	pb=pg+1;
        opg=gomat+x;
	g= RtoG*(*pr) + GtoG*(*pg) + BtoG*(*pb);
	(*opg)=(unsigned char)(g>>8);	
   }
   return 1;
}


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

  function:   RGBIfix1

  taks:       Correct the infrared channel.
              The input image data is the output of scaning
	      with LUT. To calculate the original values
	      the lutr and luti is applied.
	      The infrared values is corrected by:

	      Ir=mr*lutr(r)+luti(i)
      
  import:     unsigned char * rgbimat - RGBI - matrix from scanner
              int size - number of pixels to correct
 	      ColorCorrect *cc,
	      int *lutr - lookup table for red correction
	      int *luti - lookup table for ired correction

  export:     unsigned char * orgbimat - RGBI - corrected matrix
                  
  written by: Andreas RICK   3.7.1999
                           
  ----------------------------------------------------------------*/
#if 0
static int RGBIfix1(unsigned char* rgbimat,unsigned char* orgbimat, 	   
	    int size,
	    int *lutr,
	    int *lutg,
	    int *lutb,
	    int *luti)
	    
{  unsigned char *pr,*pg,*pb,*pi;
   unsigned char *opr,*opg,*opb,*opi;
   ColorCorrect cc;
   int r,i;
   static int thresi=100;
   int ii;
   int x;

   lutg = lutg; lutb = lutb;

   /* calculate regression between r and ir */
   cc.sum=0;
   cc.sumr=cc.sumii=cc.sumrr=cc.sumi=cc.sumri=0.0;
   for(x=0;x<size;x++)
   {    pr=rgbimat+x*4;
	pi=pr+3;	
	r=lutr[(*pr)];
	i=luti[(*pi)];
	/*	r=(*pr);
		i=(*pi); */
	if((*pi)>thresi)
        { cc.sum++;
          cc.sumr+=r;
          cc.sumii+=(i*i);
          cc.sumrr+=(r*r);
          cc.sumi+=i;
          cc.sumri+=(i*r);
	}
   }
   if((cc.sumii!=0)&&(cc.sum!=0))
   { double dn,dz,dm;
     dz=(cc.sumri-cc.sumr*cc.sumi/cc.sum);
     dn=(cc.sumrr-cc.sumr*cc.sumr/cc.sum);
     DBG (2, "Reg:dz:%e dn:%e\n",dz,dn);
     if(dn!=0)
     {  dm=(dz/dn);
        cc.mr=(int)(dm*1024);
     }
     else
     { cc.mr=0;
       dm=0;
     }
     cc.br=(int)((cc.sumi-dm*cc.sumr)/cc.sum);
   }
   else
   { cc.mr=0;
   }
   DBG (2, "Regression: size:%d I=%d/1024*R b:%d s:%d sr:%e si:%e sii:%e sri:%e  srr:%e\n",
	size,cc.mr,cc.br,cc.sum,cc.sumr,cc.sumi,cc.sumii,cc.sumri,cc.sumrr);
   for(x=0;x<size;x++)
   {

        pr=rgbimat+x*4;
	pg=pr+1; 
	pb=pg+1;
	pi=pb+1;	
        opr=orgbimat+x*4;
	opg=opr+1; 
	opb=opg+1;
	opi=opb+1;
	r=lutr[(*pr)];
	i=luti[(*pi)];
	/*	r=(*pr);
		i=(*pi); */
	ii= ((i-((r*cc.mr)>>10)-cc.br)>>2) +128;
	(*opr)=(*pr);
	(*opg)=(*pg);
	(*opb)=(*pb);	
	if(ii<0) ii=0;
	if(ii>255) ii=255;
	(*opi)=(unsigned char)(ii);	
   }
   return 1;
}

#endif
/* This function is executed as a child process. */
static int
reader_process (void *data )
{
  int status;
  unsigned int i;
  unsigned char h;
  unsigned int data_left;
  unsigned int data_to_read;
  unsigned int data_to_write;
  FILE *fp;
  sigset_t sigterm_set, ignore_set;
  struct SIGACTION act;
  unsigned int bpl, linesPerBuf, lineOffset;
  unsigned char r_data, g_data, b_data;
  unsigned int j, line;
  Coolscan_t * scanner = (Coolscan_t*)data;

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

      sigfillset ( &ignore_set );
      sigdelset  ( &ignore_set, SIGTERM );
#if defined (__APPLE__) && defined (__MACH__)
      sigdelset  ( &ignore_set, SIGUSR2 );
#endif
      sigprocmask( SIG_SETMASK, &ignore_set, 0 );

      memset (&act, 0, sizeof (act));
      sigaction (SIGTERM, &act, 0);
    }
  else
    {
      DBG (10, "reader_process started (as thread)\n");
    }

  sigemptyset (&sigterm_set);
  sigaddset (&sigterm_set, SIGTERM);

  fp = fdopen ( scanner->reader_fds, "w");
  if (!fp)
    {
      DBG (1, "reader_process: couldn't open pipe!\n");
      return 1;
    }

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

  data_left = scan_bytes_per_line (scanner) *
    lines_per_scan (scanner);
  
  /*scanner->row_bufsize = sanei_scsi_max_request_size;*/
  coolscan_trim_rowbufsize (scanner);	/* trim bufsize */

  DBG (10, "reader_process: reading %u bytes in blocks of %u bytes\n",
       data_left, scanner->row_bufsize);

  memset (&act, 0, sizeof (act));
  act.sa_handler = sigterm_handler;
  sigaction (SIGTERM, &act, 0);
  /* wait_scanner(scanner); */
  do
    {
      data_to_read = (data_left < scanner->row_bufsize) ?
	data_left : scanner->row_bufsize;

      data_to_write=data_to_read;

      status = coolscan_read_data_block (scanner
					 ,R_datatype_imagedata,data_to_read);
      if (status == 0)
	{
	  continue;
	}
      if (status == -1)
	{
	  DBG (1, "reader_process: unable to get image data from scanner!\n");
	  fclose (fp);
	  return (-1);
	}
    
      if (scanner->LS == 1) {	/* mirror image for LS-1000 */
	  bpl = scan_bytes_per_line(scanner);
	  linesPerBuf = data_to_read / bpl;
	  
	  for (line = 0, lineOffset = 0; line < linesPerBuf; 
	       line++, lineOffset += bpl ) {
	      
	      if (scanner->colormode == RGB) {
		  for (j = 0; j < bpl/2 ; j += 3) {
		      r_data=scanner->buffer[lineOffset + j];
		      g_data=scanner->buffer[lineOffset + j + 1];
		      b_data=scanner->buffer[lineOffset + j + 2];
		      
		      scanner->buffer[lineOffset + j] = 
			  scanner->buffer[lineOffset + bpl -1 - j - 2 ];
		      scanner->buffer[lineOffset + j + 1] = 
			  scanner->buffer[lineOffset + bpl -1 - j - 1 ];
		      scanner->buffer[lineOffset + j + 2] = 
			  scanner->buffer[lineOffset + bpl -1 - j ];
		  
		      scanner->buffer[lineOffset + bpl -1 - j - 2 ] = r_data;
		      scanner->buffer[lineOffset + bpl -1 - j - 1] = g_data;
		      scanner->buffer[lineOffset + bpl -1 - j] = b_data;
		  }
	      }
	      else {
		  for (j = 0; j < bpl/2; j++) {
		      r_data=scanner->buffer[lineOffset + j];
		      scanner->buffer[lineOffset + j] = 
			  scanner->buffer[lineOffset + bpl - 1 - j];
		      scanner->buffer[lineOffset + bpl - 1 - j] = r_data;
		  }
	      }
	  }	  
      }    
      if(scanner->colormode==RGBI) 
      {  /* Correct Infrared Channel */
	if(scanner->bits_per_color>8)
	{
	  RGBIfix16(scanner, (unsigned short * ) scanner->buffer, 
		    (unsigned short * )scanner->obuffer,
		 data_to_read/8,scanner->lutr,
		 scanner->lutg,scanner->lutb,scanner->luti);
	}
	else
	{
	  RGBIfix(scanner,scanner->buffer,scanner->obuffer,
		 data_to_read/4,scanner->lutr,
		 scanner->lutg,scanner->lutb,scanner->luti);
	}
      }
      else if((scanner->colormode==GREYSCALE)&&(scanner->LS>=2))
      {  /* Convert to Grey */
	 data_to_write/=3;
	 rgb2g(scanner->buffer,scanner->obuffer,data_to_write);
      }
      else
      { /* or just copy */
	memcpy (scanner->obuffer, scanner->buffer,data_to_read);
      }     
      if((!scanner->low_byte_first)&&(scanner->bits_per_color>8))
	{  for(i=0;i<data_to_write;i++) /* inverse byteorder */        
           { h=scanner->obuffer[i];
             scanner->obuffer[i]=scanner->obuffer[i+1];
	     i++;
  	     scanner->obuffer[i]=h;
	   }
	}
      fwrite (scanner->obuffer, 1, data_to_write, fp);
      fflush (fp);      
      data_left -= data_to_read;
      DBG (10, "reader_process: buffer of %d bytes read; %d bytes to go\n",
	   data_to_read, data_left);
    }
  while (data_left);

  fclose (fp);

  DBG (10, "reader_process: finished reading data\n");

  return 0;
}

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

  DBG (10, "init_options\n");

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

  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].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;

  /* "Mode" group: */
  scanner->opt[OPT_MODE_GROUP].title = "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 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;
  if(scanner->LS<2)
  { scanner->opt[OPT_MODE].size = max_string_size (scan_mode_list_LS20);
    scanner->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
    scanner->opt[OPT_MODE].constraint.string_list = scan_mode_list_LS20;
  }
  else
  { scanner->opt[OPT_MODE].size = max_string_size (scan_mode_list_LS30);
    scanner->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
    scanner->opt[OPT_MODE].constraint.string_list = scan_mode_list_LS30;
  }

  /* source */
  source_list[0] = "Slide";
  source_list[1] = "Automatic Slide Feeder";
  source_list[2] = NULL;
  if (!scanner->autofeeder)
    {
      scanner->opt[OPT_SOURCE].cap = SANE_CAP_INACTIVE;
    }

  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;

  /* negative */
  scanner->opt[OPT_TYPE].name = "type";
  scanner->opt[OPT_TYPE].title = "Film type";
  scanner->opt[OPT_TYPE].desc =
    "Select the film type (positive (slide) or negative)";
  scanner->opt[OPT_TYPE].type = SANE_TYPE_STRING;
  scanner->opt[OPT_TYPE].size = max_string_size (type_list);
  scanner->opt[OPT_TYPE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  scanner->opt[OPT_TYPE].constraint.string_list = type_list;

  scanner->opt[OPT_PRESCAN].name = "prescan";
  scanner->opt[OPT_PRESCAN].title = "Prescan";
  scanner->opt[OPT_PRESCAN].desc =
    "Perform a prescan during preview";
  scanner->opt[OPT_PRESCAN].type = SANE_TYPE_BOOL;
  scanner->opt[OPT_PRESCAN].unit = SANE_UNIT_NONE;

  scanner->opt[OPT_PRESCAN_NOW].name = "prescan now";
  scanner->opt[OPT_PRESCAN_NOW].title = "Prescan now";
  scanner->opt[OPT_PRESCAN_NOW].desc =
    "Perform a prescan now";
  scanner->opt[OPT_PRESCAN_NOW].type = SANE_TYPE_BUTTON;
  scanner->opt[OPT_PRESCAN_NOW].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_PRESCAN_NOW].size = 0;
  scanner->opt[OPT_PRESCAN_NOW].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  scanner->opt[OPT_PRESCAN_NOW].constraint_type = SANE_CONSTRAINT_NONE;
  scanner->opt[OPT_PRESCAN_NOW].constraint.string_list = 0;

 /* bit depth */
  
  bit_depths=0;
  bit_depth_list[++bit_depths] = 8;
  if (scanner->LS==2)
  {
    bit_depth_list[++bit_depths] = 10;
  }
  if (scanner->LS==3)
  {
    bit_depth_list[++bit_depths] = 12;
  }

  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;

  /* resolution */
  scanner->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  scanner->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  scanner->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
  scanner->opt[OPT_RESOLUTION].type = SANE_TYPE_INT;
  scanner->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  scanner->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  scanner->opt[OPT_RESOLUTION].constraint.word_list = resolution_list;

  scanner->opt[OPT_PREVIEW_RESOLUTION].name = "preview-resolution";
  scanner->opt[OPT_PREVIEW_RESOLUTION].title = "Preview resolution";
  scanner->opt[OPT_PREVIEW_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
  scanner->opt[OPT_PREVIEW_RESOLUTION].type = SANE_TYPE_INT;
  scanner->opt[OPT_PREVIEW_RESOLUTION].unit = SANE_UNIT_DPI;
  scanner->opt[OPT_PREVIEW_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  scanner->opt[OPT_PREVIEW_RESOLUTION].constraint.word_list = resolution_list;

  /* "Geometry" group: */
  scanner->opt[OPT_GEOMETRY_GROUP].title = "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->x_range);

  /* 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->y_range);

  /* 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->x_range);

  /* 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->y_range);


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

  /* "Enhancement" group: */
  scanner->opt[OPT_ENHANCEMENT_GROUP].title = "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;


  scanner->opt[OPT_GAMMA_BIND].name = "gamma-bind";
  scanner->opt[OPT_GAMMA_BIND].title = "Gamma bind";
  scanner->opt[OPT_GAMMA_BIND].desc =
    "Use same gamma correction for all colours";
  scanner->opt[OPT_GAMMA_BIND].type = SANE_TYPE_BOOL;
  scanner->opt[OPT_GAMMA_BIND].unit = SANE_UNIT_NONE;

  
  scanner->opt[OPT_ANALOG_GAMMA].name = "analog_gamma";
  scanner->opt[OPT_ANALOG_GAMMA].title = "Analog Gamma";
  scanner->opt[OPT_ANALOG_GAMMA].desc = "Analog Gamma";
  scanner->opt[OPT_ANALOG_GAMMA].type = SANE_TYPE_BOOL;
  scanner->opt[OPT_ANALOG_GAMMA].unit = SANE_UNIT_NONE;
  if (!scanner->analoggamma)
    {
      scanner->opt[OPT_ANALOG_GAMMA].cap = SANE_CAP_INACTIVE;
    }

  scanner->opt[OPT_AVERAGING].name = "averaging";
  scanner->opt[OPT_AVERAGING].title = "Averaging";
  scanner->opt[OPT_AVERAGING].desc = "Averaging";
  scanner->opt[OPT_AVERAGING].type = SANE_TYPE_BOOL;
  scanner->opt[OPT_AVERAGING].unit = SANE_UNIT_NONE;


  scanner->opt[OPT_RGB_CONTROL].name = "rgb-control";
  scanner->opt[OPT_RGB_CONTROL].title = "RGB control";
  scanner->opt[OPT_RGB_CONTROL].desc =
    "toggles brightness/contrast control over individual colours";
  scanner->opt[OPT_RGB_CONTROL].type = SANE_TYPE_BOOL;
  scanner->opt[OPT_RGB_CONTROL].unit = SANE_UNIT_NONE;
  if(scanner->LS>=2)
  {  scanner->opt[OPT_RGB_CONTROL].cap |= SANE_CAP_INACTIVE;
  }



  /* 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_INT;
  scanner->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
  if(scanner->LS>=2)
  {  scanner->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;
  }


  scanner->opt[OPT_R_BRIGHTNESS].name = "red-brightness";
  scanner->opt[OPT_R_BRIGHTNESS].title = "Red brightness";
  scanner->opt[OPT_R_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
  scanner->opt[OPT_R_BRIGHTNESS].type = SANE_TYPE_INT;
  scanner->opt[OPT_R_BRIGHTNESS].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_R_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_R_BRIGHTNESS].constraint.range = &brightness_range;
  scanner->opt[OPT_R_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;

  scanner->opt[OPT_G_BRIGHTNESS].name = "green-brightness";
  scanner->opt[OPT_G_BRIGHTNESS].title = "Green brightness";
  scanner->opt[OPT_G_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
  scanner->opt[OPT_G_BRIGHTNESS].type = SANE_TYPE_INT;
  scanner->opt[OPT_G_BRIGHTNESS].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_G_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_G_BRIGHTNESS].constraint.range = &brightness_range;
  scanner->opt[OPT_G_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;

  scanner->opt[OPT_B_BRIGHTNESS].name = "blue-brightness";
  scanner->opt[OPT_B_BRIGHTNESS].title = "Blue brightness";
  scanner->opt[OPT_B_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
  scanner->opt[OPT_B_BRIGHTNESS].type = SANE_TYPE_INT;
  scanner->opt[OPT_B_BRIGHTNESS].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_B_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_B_BRIGHTNESS].constraint.range = &brightness_range;
  scanner->opt[OPT_B_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;

  /* 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_INT;
  scanner->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_CONTRAST].constraint.range = &contrast_range;
  if(scanner->LS>=2)
  {  scanner->opt[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE;
  }


  scanner->opt[OPT_R_CONTRAST].name = "red-contrast";
  scanner->opt[OPT_R_CONTRAST].title = "Red contrast";
  scanner->opt[OPT_R_CONTRAST].desc = SANE_DESC_CONTRAST;
  scanner->opt[OPT_R_CONTRAST].type = SANE_TYPE_INT;
  scanner->opt[OPT_R_CONTRAST].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_R_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_R_CONTRAST].constraint.range = &contrast_range;
  scanner->opt[OPT_R_CONTRAST].cap |= SANE_CAP_INACTIVE;

  scanner->opt[OPT_G_CONTRAST].name = "green-contrast";
  scanner->opt[OPT_G_CONTRAST].title = "Green contrast";
  scanner->opt[OPT_G_CONTRAST].desc = SANE_DESC_CONTRAST;
  scanner->opt[OPT_G_CONTRAST].type = SANE_TYPE_INT;
  scanner->opt[OPT_G_CONTRAST].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_G_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_G_CONTRAST].constraint.range = &contrast_range;
  scanner->opt[OPT_G_CONTRAST].cap |= SANE_CAP_INACTIVE;

  scanner->opt[OPT_B_CONTRAST].name = "blue-contrast";
  scanner->opt[OPT_B_CONTRAST].title = "Blue contrast";
  scanner->opt[OPT_B_CONTRAST].desc = SANE_DESC_CONTRAST;
  scanner->opt[OPT_B_CONTRAST].type = SANE_TYPE_INT;
  scanner->opt[OPT_B_CONTRAST].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_B_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_B_CONTRAST].constraint.range = &contrast_range;
  scanner->opt[OPT_B_CONTRAST].cap |= SANE_CAP_INACTIVE;

  scanner->opt[OPT_EXPOSURE].name = "exposure";
  scanner->opt[OPT_EXPOSURE].title = "Exposure";
  scanner->opt[OPT_EXPOSURE].desc = "";
  scanner->opt[OPT_EXPOSURE].type = SANE_TYPE_INT;
  scanner->opt[OPT_EXPOSURE].cap &= ~SANE_CAP_INACTIVE;
  scanner->opt[OPT_EXPOSURE].unit = SANE_UNIT_PERCENT;
  scanner->opt[OPT_EXPOSURE].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_EXPOSURE].constraint.range = &exposure_range;

  scanner->opt[OPT_R_EXPOSURE].name = "red-exposure";
  scanner->opt[OPT_R_EXPOSURE].title = "Red exposure";
  scanner->opt[OPT_R_EXPOSURE].desc = "";
  scanner->opt[OPT_R_EXPOSURE].type = SANE_TYPE_INT;
  scanner->opt[OPT_R_EXPOSURE].cap |= SANE_CAP_INACTIVE;
  scanner->opt[OPT_R_EXPOSURE].unit = SANE_UNIT_PERCENT;
  scanner->opt[OPT_R_EXPOSURE].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_R_EXPOSURE].constraint.range = &exposure_range;

  scanner->opt[OPT_G_EXPOSURE].name = "green-exposure";
  scanner->opt[OPT_G_EXPOSURE].title = "Green exposure";
  scanner->opt[OPT_G_EXPOSURE].desc = "";
  scanner->opt[OPT_G_EXPOSURE].type = SANE_TYPE_INT;
  scanner->opt[OPT_G_EXPOSURE].unit = SANE_UNIT_PERCENT;
  scanner->opt[OPT_G_EXPOSURE].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_G_EXPOSURE].constraint.range = &exposure_range;
  scanner->opt[OPT_G_EXPOSURE].cap |= SANE_CAP_INACTIVE;

  scanner->opt[OPT_B_EXPOSURE].name = "blue-exposure";
  scanner->opt[OPT_B_EXPOSURE].title = "Blue exposre";
  scanner->opt[OPT_B_EXPOSURE].desc = "";
  scanner->opt[OPT_B_EXPOSURE].type = SANE_TYPE_INT;
  scanner->opt[OPT_B_EXPOSURE].unit = SANE_UNIT_PERCENT;
  scanner->opt[OPT_B_EXPOSURE].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_B_EXPOSURE].constraint.range = &exposure_range;
  scanner->opt[OPT_B_EXPOSURE].cap |= SANE_CAP_INACTIVE;
  if(scanner->LS>=2)
  {  scanner->opt[OPT_R_EXPOSURE].cap &= ~SANE_CAP_INACTIVE;
     scanner->opt[OPT_G_EXPOSURE].cap &= ~SANE_CAP_INACTIVE;
     scanner->opt[OPT_B_EXPOSURE].cap &= ~SANE_CAP_INACTIVE;
     scanner->opt[OPT_EXPOSURE].cap |= SANE_CAP_INACTIVE;
  }

  scanner->opt[OPT_R_SHIFT].name = "red-shift";
  scanner->opt[OPT_R_SHIFT].title = "Red shift";
  scanner->opt[OPT_R_SHIFT].desc = "";
  scanner->opt[OPT_R_SHIFT].type = SANE_TYPE_INT;
  scanner->opt[OPT_R_SHIFT].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_R_SHIFT].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_R_SHIFT].constraint.range = &shift_range;
  if(scanner->LS>=2)
  {  scanner->opt[OPT_R_SHIFT].cap |= SANE_CAP_INACTIVE;
  }


  scanner->opt[OPT_G_SHIFT].name = "green-shift";
  scanner->opt[OPT_G_SHIFT].title = "Green shift";
  scanner->opt[OPT_G_SHIFT].desc = "";
  scanner->opt[OPT_G_SHIFT].type = SANE_TYPE_INT;
  scanner->opt[OPT_G_SHIFT].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_G_SHIFT].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_G_SHIFT].constraint.range = &shift_range;
  if(scanner->LS>=2)
  {  scanner->opt[OPT_G_SHIFT].cap |= SANE_CAP_INACTIVE;
  }


  scanner->opt[OPT_B_SHIFT].name = "blue-shift";
  scanner->opt[OPT_B_SHIFT].title = "Blue shift";
  scanner->opt[OPT_B_SHIFT].desc = "";
  scanner->opt[OPT_B_SHIFT].type = SANE_TYPE_INT;
  scanner->opt[OPT_B_SHIFT].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_B_SHIFT].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_B_SHIFT].constraint.range = &shift_range;
  if(scanner->LS>=2)
  {  scanner->opt[OPT_B_SHIFT].cap |= SANE_CAP_INACTIVE;
  }

  /* R+G+B 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;
  if (scanner->LS == 1)
    {
      scanner->opt[OPT_GAMMA_VECTOR].cap = SANE_CAP_INACTIVE;
    }
  scanner->opt[OPT_GAMMA_VECTOR].unit = SANE_UNIT_NONE;
  switch(scanner->LS)
  {  case 0:
           scanner->opt[OPT_GAMMA_VECTOR].constraint.range = &gamma_range_8;
           scanner->lutlength=2048;
           break;
     case 1:
           scanner->opt[OPT_GAMMA_VECTOR].constraint.range = &gamma_range_9;
           scanner->lutlength=512;
           break;
     case 2:
           scanner->opt[OPT_GAMMA_VECTOR].constraint.range = &gamma_range_10;
           scanner->lutlength=1024;
           break;
     case 3:
           scanner->opt[OPT_GAMMA_VECTOR].constraint.range = &gamma_range_12;
           scanner->lutlength=4096;
           break;
  }
  scanner->opt[OPT_GAMMA_VECTOR].size = scanner->lutlength * sizeof (SANE_Word);
  scanner->opt[OPT_GAMMA_VECTOR].constraint_type = SANE_CONSTRAINT_RANGE;

  /* 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].cap |= SANE_CAP_INACTIVE;
  scanner->opt[OPT_GAMMA_VECTOR_R].unit = SANE_UNIT_NONE;
  switch(scanner->LS)
  {  case 0:
           scanner->opt[OPT_GAMMA_VECTOR_R].constraint.range = &gamma_range_8;
           scanner->lutlength=2048;
           break;
     case 1:
           scanner->opt[OPT_GAMMA_VECTOR_R].constraint.range = &gamma_range_9;
           scanner->lutlength=512;
           break;
     case 2:
           scanner->opt[OPT_GAMMA_VECTOR_R].constraint.range = &gamma_range_10;
           scanner->lutlength=1024;
           break;
     case 3:
           scanner->opt[OPT_GAMMA_VECTOR_R].constraint.range = &gamma_range_12;
           scanner->lutlength=4096;
           break;
  }
  scanner->opt[OPT_GAMMA_VECTOR_R].size = scanner->lutlength * sizeof (SANE_Word);
  scanner->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE;

  /* 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].cap |= SANE_CAP_INACTIVE;
  scanner->opt[OPT_GAMMA_VECTOR_G].unit = SANE_UNIT_NONE;
  switch(scanner->LS)
  {  case 0:
           scanner->opt[OPT_GAMMA_VECTOR_G].constraint.range = &gamma_range_8;
           scanner->lutlength=2048;
           break;
     case 1:
           scanner->opt[OPT_GAMMA_VECTOR_G].constraint.range = &gamma_range_9;
           scanner->lutlength=512;
           break;
     case 2:
           scanner->opt[OPT_GAMMA_VECTOR_G].constraint.range = &gamma_range_10;
           scanner->lutlength=1024;
           break;
     case 3:
           scanner->opt[OPT_GAMMA_VECTOR_G].constraint.range = &gamma_range_12;
           scanner->lutlength=4096;
           break;
  }
  scanner->opt[OPT_GAMMA_VECTOR_G].size = scanner->lutlength * sizeof (SANE_Word);
  scanner->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE;

  /* 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].cap |= SANE_CAP_INACTIVE;
  scanner->opt[OPT_GAMMA_VECTOR_B].unit = SANE_UNIT_NONE;
  switch(scanner->LS)
  {  case 0:
           scanner->opt[OPT_GAMMA_VECTOR_B].constraint.range = &gamma_range_8;
           scanner->lutlength=2048;
           break;
     case 1:
           scanner->opt[OPT_GAMMA_VECTOR_B].constraint.range = &gamma_range_9;
           scanner->lutlength=512;
           break;
     case 2:
           scanner->opt[OPT_GAMMA_VECTOR_B].constraint.range = &gamma_range_10;
           scanner->lutlength=1024;
           break;
     case 3:
           scanner->opt[OPT_GAMMA_VECTOR_B].constraint.range = &gamma_range_12;
           scanner->lutlength=4096;
           break;
  }
  scanner->opt[OPT_GAMMA_VECTOR_B].size = scanner->lutlength * sizeof (SANE_Word);
  scanner->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE;


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

  /* "Advanced" group: */
  scanner->opt[OPT_ADVANCED_GROUP].title = "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;

  /* 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;

  /* Autofocus */
  scanner->opt[OPT_AUTOFOCUS].name = "Autofocus";
  scanner->opt[OPT_AUTOFOCUS].title ="Autofocus";
  scanner->opt[OPT_AUTOFOCUS].desc = "When to do autofocussing";
  scanner->opt[OPT_AUTOFOCUS].type = SANE_TYPE_STRING;
  scanner->opt[OPT_AUTOFOCUS].size = max_string_size (autofocus_mode_list);
  scanner->opt[OPT_AUTOFOCUS].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  scanner->opt[OPT_AUTOFOCUS].constraint.string_list = autofocus_mode_list;

  scanner->opt[OPT_IRED_RED].name = "IRED cor. red";
  scanner->opt[OPT_IRED_RED].title ="IRED cor. red";
  scanner->opt[OPT_IRED_RED].desc = "Correction of infrared from red";
  scanner->opt[OPT_IRED_RED].type = SANE_TYPE_INT;
  scanner->opt[OPT_IRED_RED].unit = SANE_UNIT_NONE;
  scanner->opt[OPT_IRED_RED].constraint_type = SANE_CONSTRAINT_RANGE;
  scanner->opt[OPT_IRED_RED].constraint.range = &gamma_range_8;
  scanner->opt[OPT_IRED_RED].cap |= SANE_CAP_ADVANCED;
  if(scanner->LS<2)
  {  scanner->opt[OPT_IRED_RED].cap |= SANE_CAP_INACTIVE;
  }



  /*  scanner->opt[OPT_PREVIEW].cap   = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT; */
  return SANE_STATUS_GOOD;
}


SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  char dev_name[PATH_MAX];
  size_t len;
  FILE *fp;

  authorize = authorize;

  DBG_INIT ();
  sanei_thread_init ();
  
  DBG (10, "sane_init\n");
  if (version_code)
      *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, 0);

  fp = sanei_config_open (COOLSCAN_CONFIG_FILE);
  if (!fp)
    {
      attach_scanner ("/dev/scanner", 0); /* no config-file: /dev/scanner */
      return SANE_STATUS_GOOD;
    }

  while (sanei_config_read (dev_name, sizeof (dev_name), fp))
    {
      if (dev_name[0] == '#')
	continue;		/* ignore line comments */
      len = strlen (dev_name);

      if (!len)
	continue;		/* ignore empty lines */

      sanei_config_attach_matching_devices (dev_name, attach_one);
      /*attach_scanner (dev_name, 0);*/
    }
  fclose (fp);
  return SANE_STATUS_GOOD;
}

void
sane_exit (void)
{
  Coolscan_t *dev, *next;

  DBG (10, "sane_exit\n");

  for (dev = first_dev; dev; dev = next)
    {
      next = dev->next;
      free (dev->devicename);
      free (dev->buffer);
      free (dev->obuffer);
      free (dev);
    }
}

/* ----------------------------- SANE GET DEVICES -------------------------- */
SANE_Status
sane_get_devices (const SANE_Device *** device_list,
		  SANE_Bool local_only)
{

  static const SANE_Device **devlist = 0;
  Coolscan_t *dev;
  int i;

  local_only = local_only;

  DBG (10, "sane_get_devices\n");

  if (devlist)
    free (devlist);

  devlist = calloc (num_devices + 1, sizeof (devlist[0]));
  if (!devlist)
    return SANE_STATUS_NO_MEM;

  i = 0;

  for (dev = first_dev; i < num_devices; dev = dev->next)
    devlist[i++] = &dev->sane;

  devlist[i++] = 0;

  *device_list = devlist;

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  Coolscan_t *dev;
  SANE_Status status;

  DBG (10, "sane_open\n");

  if (devicename[0])
    {				/* search for devicename */
      for (dev = first_dev; dev; dev = dev->next)
	{
	  if (strcmp (dev->sane.name, devicename) == 0)
	    {
	      break;
	    }
	}

      if (!dev)
	{
	  status = attach_scanner (devicename, &dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      return status;
	    }
	}
    }
  else
    {
      dev = first_dev;		/* empty devicname -> use first device */
    }

  if (!dev)
    return SANE_STATUS_INVAL;

  dev->sfd = -1;
  dev->pipe = -1;
  dev->scanning = SANE_FALSE;

  init_options (dev);
  *handle = dev;
  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle handle)
{
  DBG (10, "sane_close\n");
  if (((Coolscan_t *) handle)->scanning)
    do_cancel (handle);
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  Coolscan_t *scanner = handle;

  DBG (10, "sane_get_option_descriptor %d\n", option);

  if ((unsigned) option >= NUM_OPTIONS)
    return 0;
  return &scanner->opt[option];
}

/*
   static void
   worddump(char *comment, SANE_Word * p, int l)
   {
   int i;
   char line[128];
   char *ptr;

   DBG (5, "%s\n", comment);
   ptr = line;
   for (i = 0; i < l; i++, p++)
   {
   if ((i % 8) == 0)
   {
   if (ptr != line)
   {
   *ptr = '\0';
   DBG (5, "%s\n", line);
   ptr = line;
   }
   sprintf (ptr, "%3.3d:", i);
   ptr += 4;
   }
   sprintf (ptr, " %4.4d", *p);
   ptr += 5;
   }
   *ptr = '\0';
   DBG (5, "%s\n", line);
   }
 */
SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val,
		     SANE_Int * info)
{
  Coolscan_t *scanner = handle;
  SANE_Status status;
  SANE_Word cap;

  if (info)
    *info = 0;

  if (scanner->scanning)
    return SANE_STATUS_DEVICE_BUSY;

  if (option >= NUM_OPTIONS)
    return SANE_STATUS_INVAL;

  cap = scanner->opt[option].cap;


  if (action == SANE_ACTION_GET_VALUE)
    {
      DBG (10, "sane_control_option %d, get value\n", option);
      switch (option)
	{
	  /* word options: */
	case OPT_TL_X:
	  *(SANE_Word *) val = SANE_FIX (iluToMm (scanner->tlx));
	  return SANE_STATUS_GOOD;

	case OPT_TL_Y:
	  *(SANE_Word *) val = SANE_FIX (iluToMm (scanner->tly));
	  return SANE_STATUS_GOOD;

	case OPT_BR_X:
	  *(SANE_Word *) val = SANE_FIX (iluToMm (scanner->brx));
	  return SANE_STATUS_GOOD;

	case OPT_BR_Y:
	  *(SANE_Word *) val = SANE_FIX (iluToMm (scanner->bry));
	  return SANE_STATUS_GOOD;

	case OPT_PREVIEW:
	  *(SANE_Word *) val = scanner->preview;
	  return SANE_STATUS_GOOD;

	case OPT_AUTOFOCUS:
	  switch(scanner->autofocus)
	    {  case AF_NEVER: strcpy (val,neverStr);
  	                 break;
	       case AF_PREVIEW:strcpy (val,previewStr);
  	                 break;             
               case AF_SCAN:if(scanner->LS>=2) strcpy (val,scanStr);
  	                 break;             
	       case AF_PREANDSCAN:if(scanner->LS>=2) strcpy (val,preandscanStr);
  	                 break;             
	    }
	  return SANE_STATUS_GOOD;

	case OPT_NUM_OPTS:
	  *(SANE_Word *) val = NUM_OPTIONS;
	  return SANE_STATUS_GOOD;

	case OPT_RESOLUTION:
	  *(SANE_Word *) val = resDivToVal (scanner->x_nres);
	  return SANE_STATUS_GOOD;

	case OPT_PREVIEW_RESOLUTION:
	  *(SANE_Word *) val = resDivToVal (scanner->x_p_nres);
	  return SANE_STATUS_GOOD;

	case OPT_BIT_DEPTH:
	  *(SANE_Word *) val = scanner->bits_per_color;
	  return SANE_STATUS_GOOD;

	case OPT_CONTRAST:
	  *(SANE_Word *) val = scanner->contrast - 128;
	  return SANE_STATUS_GOOD;

	case OPT_R_CONTRAST:
	  *(SANE_Word *) val = scanner->contrast_R - 128;
	  return SANE_STATUS_GOOD;

	case OPT_G_CONTRAST:
	  *(SANE_Word *) val = scanner->contrast_G - 128;
	  return SANE_STATUS_GOOD;

	case OPT_B_CONTRAST:
	  *(SANE_Word *) val = scanner->contrast_B - 128;
	  return SANE_STATUS_GOOD;

	case OPT_BRIGHTNESS:
	  *(SANE_Word *) val = scanner->brightness - 128;
	  return SANE_STATUS_GOOD;

	case OPT_R_BRIGHTNESS:
	  *(SANE_Word *) val = scanner->brightness_R - 128;
	  return SANE_STATUS_GOOD;

	case OPT_G_BRIGHTNESS:
	  *(SANE_Word *) val = scanner->brightness_G - 128;
	  return SANE_STATUS_GOOD;

	case OPT_B_BRIGHTNESS:
	  *(SANE_Word *) val = scanner->brightness_B - 128;
	  return SANE_STATUS_GOOD;

	case OPT_EXPOSURE:
	  *(SANE_Word *) val = scanner->exposure_R * 2;
	  return SANE_STATUS_GOOD;

	case OPT_R_EXPOSURE:
	  *(SANE_Word *) val = scanner->exposure_R * 2;
	  return SANE_STATUS_GOOD;

	case OPT_G_EXPOSURE:
	  *(SANE_Word *) val = scanner->exposure_G * 2;
	  return SANE_STATUS_GOOD;

	case OPT_B_EXPOSURE:
	  *(SANE_Word *) val = scanner->exposure_B * 2;
	  return SANE_STATUS_GOOD;

	case OPT_R_SHIFT:
	  *(SANE_Word *) val = scanner->shift_R - 128;
	  return SANE_STATUS_GOOD;

	case OPT_G_SHIFT:
	  *(SANE_Word *) val = scanner->shift_G - 128;
	  return SANE_STATUS_GOOD;

	case OPT_B_SHIFT:
	  *(SANE_Word *) val = scanner->shift_B - 128;
	  return SANE_STATUS_GOOD;


	case OPT_IRED_RED:
	  *(SANE_Word *) val = scanner->ired_red;
	  return SANE_STATUS_GOOD;

	  /* string options: */
	case OPT_TYPE:
	  strcpy (val, ((scanner->negative) ? negativeStr : positiveStr));
	  return SANE_STATUS_GOOD;

	case OPT_MODE:
	  switch(scanner->colormode)
	    {  case RGB: strcpy (val,colorStr);
  	                 break;
	       case GREYSCALE:strcpy (val,grayStr);
  	                 break;             
               case RGBI:if(scanner->LS>=2) strcpy (val,rgbiStr);
		         else strcpy (val,colorStr);
  	                 break;             
	       case IRED:if(scanner->LS>=2) strcpy (val,iredStr); 
	                 else strcpy (val,grayStr);
  	                 break;             
	    }
 	    if (info)
	    {
	      *info |= SANE_INFO_RELOAD_PARAMS;
	    }

	  return SANE_STATUS_GOOD;

	case OPT_PRESCAN:
	  *(SANE_Word *) val = (scanner->prescan) ? SANE_TRUE : SANE_FALSE;
	  return SANE_STATUS_GOOD;

	case OPT_PRESCAN_NOW:	 
	  return SANE_STATUS_GOOD;

	case OPT_RGB_CONTROL:
	  *(SANE_Word *) val = (scanner->rgb_control) ? SANE_TRUE : SANE_FALSE;
	  return SANE_STATUS_GOOD;

	case OPT_GAMMA_BIND:
	  *(SANE_Word *) val = (scanner->gamma_bind) ? SANE_TRUE : SANE_FALSE;
	  return SANE_STATUS_GOOD;

	case OPT_ANALOG_GAMMA:
	  *(SANE_Word *) val = 
	    (scanner->analog_gamma_r) ? SANE_TRUE : SANE_FALSE;
	  return SANE_STATUS_GOOD;

	case OPT_AVERAGING:
	  *(SANE_Word *) val = (scanner->averaging) ? SANE_TRUE : SANE_FALSE;
	  return SANE_STATUS_GOOD;

	case OPT_GAMMA_VECTOR:
	  memcpy (val, scanner->gamma, scanner->opt[option].size);
	  return SANE_STATUS_GOOD;
	case OPT_GAMMA_VECTOR_R:
	  memcpy (val, scanner->gamma_r, scanner->opt[option].size);
	  return SANE_STATUS_GOOD;
	case OPT_GAMMA_VECTOR_G:
	  memcpy (val, scanner->gamma_g, scanner->opt[option].size);
	  return SANE_STATUS_GOOD;
	case OPT_GAMMA_VECTOR_B:
	  memcpy (val, scanner->gamma_b, scanner->opt[option].size);
	  return SANE_STATUS_GOOD;

	case OPT_SOURCE:
	  if (strcmp (val, "Automatic Slide Feeder") == 0)
	    {
	      /* Feed/Discharge/update filename/etc */
	    }
	  else
	    {
	      /* Reset above */
	    }
	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_PARAMS;
	    }
	}

    }
  else if (action == SANE_ACTION_SET_VALUE)
    {
      DBG (10, "sane_control_option %d, set value\n", option);

      if (!SANE_OPTION_IS_ACTIVE (cap))
	return SANE_STATUS_INVAL;

      if (!SANE_OPTION_IS_SETTABLE (cap))
	return SANE_STATUS_INVAL;

      status = sanei_constrain_value (scanner->opt + option, val, info);
      if (status != SANE_STATUS_GOOD)
	return status;

      switch (option)
	{
	case OPT_GAMMA_BIND:
	  scanner->gamma_bind = (*(SANE_Word *) val == SANE_TRUE);
	  if (scanner->LS != 1)
	    {
	      if (scanner->gamma_bind)
		{
		  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
		{
		  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_ANALOG_GAMMA:
	  scanner->analog_gamma_r = scanner->analog_gamma_g =
	    scanner->analog_gamma_b = (*(SANE_Word *) val == SANE_TRUE);
	  return SANE_STATUS_GOOD;

	case OPT_AVERAGING:
	  scanner->averaging = (*(SANE_Word *) val == SANE_TRUE);
	  return SANE_STATUS_GOOD;

	case OPT_PRESCAN:
	  scanner->prescan = (*(SANE_Word *) val == SANE_TRUE);
	  return SANE_STATUS_GOOD;

	case OPT_PRESCAN_NOW:
	   do_prescan_now(scanner);  
	  return SANE_STATUS_GOOD;

	case OPT_BIT_DEPTH:
	   scanner->bits_per_color=(*(SANE_Word *)val);
 	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  return SANE_STATUS_GOOD;


	case OPT_RGB_CONTROL:
	  scanner->rgb_control = (*(SANE_Word *) val == SANE_TRUE);
	  if (scanner->rgb_control)
	    {
	      scanner->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;
	      scanner->opt[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE;
	      scanner->opt[OPT_EXPOSURE].cap |= SANE_CAP_INACTIVE;

	      scanner->opt[OPT_R_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE;
	      scanner->opt[OPT_G_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE;
	      scanner->opt[OPT_B_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE;

	      scanner->opt[OPT_R_CONTRAST].cap &= ~SANE_CAP_INACTIVE;
	      scanner->opt[OPT_G_CONTRAST].cap &= ~SANE_CAP_INACTIVE;
	      scanner->opt[OPT_B_CONTRAST].cap &= ~SANE_CAP_INACTIVE;

	      scanner->opt[OPT_R_EXPOSURE].cap &= ~SANE_CAP_INACTIVE;
	      scanner->opt[OPT_G_EXPOSURE].cap &= ~SANE_CAP_INACTIVE;
	      scanner->opt[OPT_B_EXPOSURE].cap &= ~SANE_CAP_INACTIVE;

	      scanner->contrast_R = 128;
	      scanner->contrast_G = 128;
	      scanner->contrast_B = 128;
	      scanner->brightness_R = 128;
	      scanner->brightness_G = 128;
	      scanner->brightness_B = 128;
	      scanner->exposure_R = 50;
	      scanner->exposure_G = 50;
	      scanner->exposure_B = 50;
	    }
	  else
	    {
	      scanner->opt[OPT_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE;
	      scanner->opt[OPT_CONTRAST].cap &= ~SANE_CAP_INACTIVE;
	      scanner->opt[OPT_EXPOSURE].cap &= ~SANE_CAP_INACTIVE;

	      scanner->contrast = 128;
	      scanner->brightness = 128;
	      scanner->exposure_R = 50;
	      scanner->exposure_G = 50;
	      scanner->exposure_B = 50;

	      scanner->opt[OPT_R_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;
	      scanner->opt[OPT_G_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;
	      scanner->opt[OPT_B_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;

	      scanner->opt[OPT_R_CONTRAST].cap |= SANE_CAP_INACTIVE;
	      scanner->opt[OPT_G_CONTRAST].cap |= SANE_CAP_INACTIVE;
	      scanner->opt[OPT_B_CONTRAST].cap |= SANE_CAP_INACTIVE;

	      scanner->opt[OPT_R_EXPOSURE].cap |= SANE_CAP_INACTIVE;
	      scanner->opt[OPT_G_EXPOSURE].cap |= SANE_CAP_INACTIVE;
	      scanner->opt[OPT_B_EXPOSURE].cap |= SANE_CAP_INACTIVE;
	    }
	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_OPTIONS;
	    }
	  return SANE_STATUS_GOOD;

	case OPT_RESOLUTION:
	  scanner->y_nres = scanner->x_nres =
	    resValToDiv (*(SANE_Word *) val);

	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  return SANE_STATUS_GOOD;

	case OPT_PREVIEW_RESOLUTION:
	  scanner->y_p_nres = scanner->x_p_nres =
	    resValToDiv (*(SANE_Word *) val);

	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  return SANE_STATUS_GOOD;

	case OPT_TL_X:
	  scanner->tlx = mmToIlu (SANE_UNFIX (*(SANE_Word *) val));
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;

	  return SANE_STATUS_GOOD;

	case OPT_TL_Y:
	  scanner->tly = mmToIlu (SANE_UNFIX (*(SANE_Word *) val));
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  return SANE_STATUS_GOOD;

	case OPT_BR_X:
	  scanner->brx = mmToIlu (SANE_UNFIX (*(SANE_Word *) val));
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  return SANE_STATUS_GOOD;

	case OPT_BR_Y:
	  scanner->bry = mmToIlu (SANE_UNFIX (*(SANE_Word *) val));
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  return SANE_STATUS_GOOD;

	case OPT_NUM_OPTS:
	  return SANE_STATUS_GOOD;

	case OPT_PREVIEW:
	  scanner->preview = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	case OPT_AUTOFOCUS:
	  if(strcmp(val,neverStr)==0) 
	    {    scanner->autofocus=AF_NEVER;
	    }
	  if(strcmp(val,previewStr)==0)  
	    {    scanner->autofocus=AF_PREVIEW;
	    }
	  if(strcmp(val,scanStr)==0)  
	    {    scanner->autofocus=AF_SCAN;
	    }
	  if(strcmp(val,preandscanStr)==0)  
	    {    scanner->autofocus=AF_PREANDSCAN;;
	    }
	  return SANE_STATUS_GOOD;

	case OPT_CONTRAST:
	  scanner->contrast = *(SANE_Word *) val + 128;
	  return SANE_STATUS_GOOD;
	case OPT_R_CONTRAST:
	  scanner->contrast_R = *(SANE_Word *) val + 128;
	  return SANE_STATUS_GOOD;
	case OPT_G_CONTRAST:
	  scanner->contrast_G = *(SANE_Word *) val + 128;
	  return SANE_STATUS_GOOD;
	case OPT_B_CONTRAST:
	  scanner->contrast_B = *(SANE_Word *) val + 128;
	  return SANE_STATUS_GOOD;

	case OPT_BRIGHTNESS:
	  scanner->brightness = *(SANE_Word *) val + 128;
	  return SANE_STATUS_GOOD;
	case OPT_R_BRIGHTNESS:
	  scanner->brightness_R = *(SANE_Word *) val + 128;
	  return SANE_STATUS_GOOD;
	case OPT_G_BRIGHTNESS:
	  scanner->brightness_G = *(SANE_Word *) val + 128;
	  return SANE_STATUS_GOOD;
	case OPT_B_BRIGHTNESS:
	  scanner->brightness_B = *(SANE_Word *) val + 128;
	  return SANE_STATUS_GOOD;

	case OPT_EXPOSURE:
	  scanner->exposure_R = *(SANE_Word *) val / 2;
	  scanner->exposure_G = *(SANE_Word *) val / 2;
	  scanner->exposure_B = *(SANE_Word *) val / 2;
	  return SANE_STATUS_GOOD;
	case OPT_R_EXPOSURE:
	  scanner->exposure_R = *(SANE_Word *) val / 2;
	  return SANE_STATUS_GOOD;
	case OPT_G_EXPOSURE:
	  scanner->exposure_G = *(SANE_Word *) val / 2;
	  return SANE_STATUS_GOOD;
	case OPT_B_EXPOSURE:
	  scanner->exposure_B = *(SANE_Word *) val / 2;
	  return SANE_STATUS_GOOD;

	case OPT_R_SHIFT:
	  scanner->shift_R = *(SANE_Word *) val + 128;
	  return SANE_STATUS_GOOD;
	case OPT_G_SHIFT:
	  scanner->shift_G = *(SANE_Word *) val + 128;
	  return SANE_STATUS_GOOD;
	case OPT_B_SHIFT:
	  scanner->shift_B = *(SANE_Word *) val + 128;
	  return SANE_STATUS_GOOD;

	case OPT_IRED_RED:
	  scanner->ired_red= *(SANE_Word *) val; 
	  return SANE_STATUS_GOOD;

	case OPT_SOURCE:
	  scanner->asf = (strcmp (val, "Automatic...") == 0);
	  return SANE_STATUS_GOOD;

	case OPT_TYPE:
	  scanner->negative = (strcmp (val, negativeStr) == 0);
	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_PARAMS;
	    }
	  return SANE_STATUS_GOOD;
	case OPT_MODE:
	  if(strcmp(val,colorStr)==0) 
	    {    scanner->colormode=RGB;
	         scanner->colormode_p=RGB;
	    }
	  if(strcmp(val,grayStr)==0)  
	    {    scanner->colormode=GREYSCALE;
	         scanner->colormode_p=GREYSCALE;
	    }
	  if(strcmp(val,rgbiStr)==0)
	    {    scanner->colormode=RGBI;
	         scanner->colormode_p=RGB;
	    }
	  if(strcmp(val,iredStr)==0) 
	    {    scanner->colormode=IRED;
	         scanner->colormode_p=GREYSCALE;
	    }
	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
	    }
	  return SANE_STATUS_GOOD;

	case OPT_GAMMA_VECTOR:
	  memcpy (scanner->gamma, val, scanner->opt[option].size);
	  if(scanner->LS>2)  Calc_fix_LUT(scanner);
	  return SANE_STATUS_GOOD;

	case OPT_GAMMA_VECTOR_R:
	  memcpy (scanner->gamma_r, val, scanner->opt[option].size);
	  if(scanner->LS>2)  Calc_fix_LUT(scanner);
	  return SANE_STATUS_GOOD;

	case OPT_GAMMA_VECTOR_G:
	  memcpy (scanner->gamma_g, val, scanner->opt[option].size);
	  if(scanner->LS>2)  Calc_fix_LUT(scanner);
	  return SANE_STATUS_GOOD;

	case OPT_GAMMA_VECTOR_B:
	  memcpy (scanner->gamma_b, val, scanner->opt[option].size);
	  if(scanner->LS>2)  Calc_fix_LUT(scanner);
	  return SANE_STATUS_GOOD;

	}			/* switch */
    }				/* else */
  return SANE_STATUS_INVAL;
}

SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  Coolscan_t *scanner = handle;

  DBG (10, "sane_get_parameters");
  switch(scanner->colormode)
    {  case RGB:
              params->format =  SANE_FRAME_RGB;
              break;
#ifdef HAS_IRED
       case RGBI:
              params->format =  SANE_FRAME_RGBA; 
              break; 
#endif /* HAS_RGBI */
       case GREYSCALE:
              params->format =  SANE_FRAME_GRAY;
              break;
    }

  params->depth = scanner->bits_per_color>8?16:8;
  params->pixels_per_line = pixels_per_line (scanner);
  params->lines = lines_per_scan (scanner);
  params->bytes_per_line = write_bytes_per_line (scanner);
  params->last_frame = 1;
  return SANE_STATUS_GOOD;
}
static int
swap_res (Coolscan_t * s)
{
  if (s->preview)
    {
      /* swap preview/scan resolutions */
      int xres, yres, cmode;
      xres = s->x_nres;
      yres = s->y_nres;
      s->x_nres = s->x_p_nres;
      s->y_nres = s->y_p_nres;

      s->x_p_nres = xres;
      s->y_p_nres = yres;
      cmode=s->colormode;
      s->colormode=s->colormode_p;
      s->colormode_p=cmode;
    }
  return 0;
}
SANE_Status
sane_start (SANE_Handle handle)
{
  Coolscan_t *scanner = handle;
  int fds[2];

  DBG (10, "sane_start\n");
  if (scanner->scanning == SANE_TRUE)
    return SANE_STATUS_DEVICE_BUSY;

  if (scanner->sfd < 0)
    {				/* first call */
      if (sanei_scsi_open (scanner->sane.name,
			   &(scanner->sfd),
			   sense_handler, 0) != SANE_STATUS_GOOD)
	{
	  DBG (1, "sane_start: open of %s failed:\n",
	       scanner->sane.name);
	  return SANE_STATUS_INVAL;
	}
    }
  scanner->scanning = SANE_TRUE;


  if (coolscan_check_values (scanner) != 0)
    {				/* Verify values */
      DBG (1, "ERROR: invalid scan-values\n");
      scanner->scanning = SANE_FALSE;
      coolscan_give_scanner (scanner);
      sanei_scsi_close (scanner->sfd);
      scanner->sfd = -1;
      return SANE_STATUS_INVAL;
    }

  if (coolscan_grab_scanner (scanner))
    {
      sanei_scsi_close (scanner->sfd);
      scanner->sfd = -1;
      DBG (5, "WARNING: unable to reserve scanner: device busy\n");
      scanner->scanning = SANE_FALSE;
      return SANE_STATUS_DEVICE_BUSY;
    }

  /* hoho, step 2c, -perm */
  coolscan_object_feed (scanner);

  swap_res (scanner);
  if (!scanner->preview)
  { if(scanner->autofocus & 0x02)
    {  coolscan_autofocus (scanner);
    }
  }
  else
    { 
      if(scanner->autofocus & 0x01)
      {  coolscan_autofocus (scanner);
      }
      if (scanner->prescan) {
	prescan (scanner);  
	if(scanner->LS<2)
        {	get_internal_info(scanner);
	}
        coolscan_get_window_param (scanner,1);
      }
    }
  /*read_LUT(scanner); */
  if(scanner->LS<2)
  {  send_LUT (scanner);
     coolscan_set_window_param (scanner, 0);
     coolscan_get_window_param (scanner,0);
     coolscan_start_scan (scanner);
  }
  else
  {  coolscan_set_window_param (scanner, 0);
     send_LUT (scanner);
     Calc_fix_LUT(scanner);
     coolscan_start_scan (scanner);
     wait_scanner (scanner);
     coolscan_get_window_param (scanner,0);
  }

  DBG (10, "bytes per line        = %d\n", scan_bytes_per_line (scanner));
  DBG (10, "pixels_per_line       = %d\n", pixels_per_line (scanner));
  DBG (10, "lines                 = %d\n", lines_per_scan (scanner));
  DBG (10, "negative              = %d\n", scanner->negative);
  DBG (10, "brightness (halftone) = %d\n", scanner->brightness);
  DBG (10, "contrast   (halftone) = %d\n", scanner->contrast);
  DBG (10, "fast preview function = %d\n", scanner->preview);

  /* create a pipe, fds[0]=read-fd, fds[1]=write-fd */
  if (pipe (fds) < 0)
    {
      DBG (1, "ERROR: could not create pipe\n");

      swap_res (scanner);
      scanner->scanning = SANE_FALSE;
      coolscan_give_scanner (scanner);
      sanei_scsi_close (scanner->sfd);
      scanner->sfd = -1;
      return SANE_STATUS_IO_ERROR;
    }

  scanner->pipe       = fds[0];
  scanner->reader_fds = fds[1];
  scanner->reader_pid = sanei_thread_begin( reader_process, (void*)scanner );
  if (scanner->reader_pid == -1)
    {
      DBG (1, "sane_start: sanei_thread_begin failed (%s)\n",
             strerror (errno));
      return SANE_STATUS_NO_MEM;
    }

  if (sanei_thread_is_forked ())
    {
      close (scanner->reader_fds);
      scanner->reader_fds = -1;
    }

  return SANE_STATUS_GOOD;
}


SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf,
	   SANE_Int max_len, SANE_Int * len)
{
  Coolscan_t *scanner = handle;
  ssize_t nread;

  *len = 0;

  nread = read (scanner->pipe, buf, max_len);
  DBG (10, "sane_read: read %ld bytes\n", (long) nread);

  if (!(scanner->scanning))
    {
      return do_cancel (scanner);
    }

  if (nread < 0)
    {
      if (errno == EAGAIN)
	{
	  return SANE_STATUS_GOOD;
	}
      else
	{
	  do_cancel (scanner);
	  return SANE_STATUS_IO_ERROR;
	}
    }

  *len = nread;

  if (nread == 0)
    return do_eof (scanner);	/* close pipe */

  return SANE_STATUS_GOOD;
}

void
sane_cancel (SANE_Handle handle)
{
  Coolscan_t *s = handle;

  if (s->reader_pid != -1)
    {
      sanei_thread_kill   ( s->reader_pid );
      sanei_thread_waitpid( s->reader_pid, NULL );
      s->reader_pid = -1;
    }
  swap_res (s);
  s->scanning = SANE_FALSE;
}


SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  Coolscan_t *scanner = handle;

  DBG (10, "sane_set_io_mode: non_blocking=%d\n", non_blocking);

  if (!scanner->scanning)
    return SANE_STATUS_INVAL;

  if (fcntl (scanner->pipe, F_SETFL, non_blocking ? O_NONBLOCK : 0) < 0)
    return SANE_STATUS_IO_ERROR;

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  Coolscan_t *scanner = handle;

  DBG (10, "sane_get_select_fd\n");

  if (!scanner->scanning)
    {
      return SANE_STATUS_INVAL;
    }
  *fd = scanner->pipe;

  return SANE_STATUS_GOOD;
}
