/* sane - Scanner Access Now Easy.
   Copyright (C) 2001 Stéphane Voltz <svoltz@wanadoo.fr>
   This file is part of the SANE package.

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

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

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

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

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

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

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

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

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

/* CREDITS:
   Started by being a mere copy of mustek_pp 
   by Jochen Eisinger <jochen.eisinger@gmx.net> 
   then evolved in its own thing                                       
   
   support for the 610P has been made possible thank to an hardware donation
   from William Stuart
   */


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

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <math.h>

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_config.h"


#define BACKEND_NAME	umax_pp
#include "../include/sane/sanei_backend.h"

#include "umax_pp_mid.h"
#include "umax_pp.h"

#ifdef DMALLOC
#include "dmalloc.h"
#endif

#define UMAX_PP_CONFIG_FILE "umax_pp.conf"

#define MIN(a,b)	((a) < (b) ? (a) : (b))


/* DEBUG
 * 	for debug output, set SANE_DEBUG_UMAX_PP to
 * 		0	for nothing
 * 		1	for errors
 * 		2	for warnings
 * 		3	for additional information
 * 		4	for debug information
 * 		5	for code flow protocol (there isn't any)
 * 		129	if you want to know which parameters are unused
 */

/* history:
 *  see Changelog
 */

#define UMAX_PP_BUILD	603
#define UMAX_PP_STATE	"dev"

static int num_devices = 0;
static Umax_PP_Descriptor *devlist = NULL;
static const SANE_Device **devarray = NULL;

static Umax_PP_Device *first_dev = NULL;


/* 2 Meg scan buffer */
static long int buf_size = 2048 * 1024;


static int red_gain = 0;
static int green_gain = 0;
static int blue_gain = 0;

static int red_offset = 0;
static int green_offset = 0;
static int blue_offset = 0;






static const SANE_String_Const mode_list[] = {
  SANE_I18N ("Lineart"), SANE_I18N ("Grayscale"), SANE_I18N ("Color"), 0
};

static const SANE_Range u4_range = {
  0,				/* minimum */
  15,				/* maximum */
  0				/* quantization */
};

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


#define UMAX_PP_CHANNEL_RED		0
#define UMAX_PP_CHANNEL_GREEN		1
#define UMAX_PP_CHANNEL_BLUE		2
#define UMAX_PP_CHANNEL_GRAY		1

#define UMAX_PP_STATE_SCANNING		2
#define UMAX_PP_STATE_CANCELLED		1
#define UMAX_PP_STATE_IDLE		0

#define UMAX_PP_MODE_LINEART		0
#define UMAX_PP_MODE_GRAYSCALE		1
#define UMAX_PP_MODE_COLOR		2

#define MM_PER_INCH			25.4
#define MM_TO_PIXEL(mm, res)	(SANE_UNFIX(mm) * (float )res / MM_PER_INCH)
#define PIXEL_TO_MM(px, res)	(SANE_FIX((float )(px * MM_PER_INCH / (res / 10)) / 10.0))

#define UMAX_PP_DEFAULT_PORT		"/dev/parport0"

#define UMAX_PP_RESERVE			259200

static int
parse_int_option (const char *string, char *name, long int *value, long int fallback);

/*
 * devname may be either an hardware address for direct I/O (0x378 for instance)
 * or the device name used by ppdev on linux systems        (/dev/parport0 )
 */


static SANE_Status
attach (const char *devname)
{
  Umax_PP_Descriptor *dev;
  int i;
  SANE_Status status = SANE_STATUS_GOOD;
  int ret, prt = 0, mdl;
  char model[32];
  char name[64];

  memset (name, 0, 64);

  if ((strlen (devname) < 3))
    return SANE_STATUS_INVAL;

  /* if the name begins with a slash, it's a device, else it's an addr */
  if (devname != NULL)
    {
      if ((devname[0] == '/'))
	{
	  strncpy (name, devname, 64);
	}
      else
	{
	  if ((devname[0] == '0')
	      && ((devname[1] == 'x') || (devname[1] == 'X')))
	    prt = strtol (devname + 2, NULL, 16);
	  else
	    prt = atoi (devname);
	}
    }


  for (i = 0; i < num_devices; i++)
    {
      if (devname[0] == '/')
	{
	  if (strcmp (devlist[i].ppdevice, devname) == 0)
	    return SANE_STATUS_GOOD;
	}
      else
	{
	  if (strcmp (devlist[i].port, devname) == 0)
	    return SANE_STATUS_GOOD;
	}
    }

  ret = sanei_umax_pp_attach (prt, name);
  switch (ret)
    {
    case UMAX1220P_OK:
      status = SANE_STATUS_GOOD;
      break;
    case UMAX1220P_BUSY:
      status = SANE_STATUS_DEVICE_BUSY;
      break;
    case UMAX1220P_TRANSPORT_FAILED:
      DBG (1, "attach: failed to init transport layer on %s\n", devname);
      status = SANE_STATUS_IO_ERROR;
      break;
    case UMAX1220P_PROBE_FAILED:
      DBG (1, "attach: failed to probe scanner on %s\n", devname);
      status = SANE_STATUS_IO_ERROR;
      break;
    }

  if (status != SANE_STATUS_GOOD)
    {
      DBG (2, "attach: couldn't attach to `%s' (%s)\n", devname,
	   sane_strstatus (status));
      DEBUG ();
      return status;
    }


  /* now look for the model */
  do
    {
      ret = sanei_umax_pp_model (prt, &mdl);
      if (ret != UMAX1220P_OK)
	{
	  DBG (1, "attach: waiting for busy scanner on %s\n", devname);
	}
    }
  while (ret == UMAX1220P_BUSY);

  if (ret != UMAX1220P_OK)
    {
      DBG (1, "attach: failed to recognize scanner model on %s\n", devname);
      return SANE_STATUS_IO_ERROR;
    }
  sprintf (model, "Astra %dP", mdl);


  dev = malloc (sizeof (Umax_PP_Descriptor) * (num_devices + 1));

  if (dev == NULL)
    {
      DBG (2, "attach: not enough memory for device descriptor\n");
      DEBUG ();
      return SANE_STATUS_NO_MEM;
    }

  memset (dev, 0, sizeof (Umax_PP_Descriptor) * (num_devices + 1));

  if (num_devices > 0)
    {
      memcpy (dev + 1, devlist, sizeof (Umax_PP_Descriptor) * (num_devices));
      free (devlist);
    }

  devlist = dev;
  num_devices++;

  dev->sane.name = strdup (devname);
  dev->sane.vendor = strdup ("UMAX");
  dev->sane.type = "flatbed scanner";

  if (devname[0] == '/')
    dev->ppdevice = strdup (devname);
  else
    dev->port = strdup (devname);
  dev->buf_size = buf_size;

  if (mdl > 610)
    {				/* Astra 1220, 1600 and 2000 */
      dev->max_res = 1200;
      dev->ccd_res = 600;
      dev->max_h_size = 5100;
      dev->max_v_size = 7000 - 8;	/* -8: workaround 'y overflow bug at 600 dpi' */
    }
  else
    {				/* Astra 610 */
      dev->max_res = 600;
      dev->ccd_res = 300;
      dev->max_h_size = 2550;
      dev->max_v_size = 3500;
    }
  dev->sane.model = strdup (model);


  DBG (3, "attach: device %s attached\n", devname);

  return SANE_STATUS_GOOD;
}

/*
 * walk a port list and try to attach to them
 *
 */
static SANE_Int
umax_pp_try_ports (char **ports)
{
  int i;
  int rc = SANE_STATUS_INVAL;

  if (ports != NULL)
    {
      i = 0;
      rc = SANE_STATUS_INVAL;
      while (ports[i] != NULL)
	{
	  if (rc != SANE_STATUS_GOOD)
	    {
	      DBG (3, "umax_pp_try_ports: trying port `%s'\n", ports[i]);
	      rc = attach (ports[i]);
	      if (rc != SANE_STATUS_GOOD)
		DBG (3, "init: couldn't attach to port `%s'\n", ports[i]);
	      else
		DBG (3, "init: attach to port `%s' successfull\n", ports[i]);
	    }
	  free (ports[i]);
	  i++;
	}
      free (ports);
    }
  return rc;
}

/*
 * attempt to auto detect right parallel port
 * if safe set to SANE_TRUE, no direct hardware access
 *
 */
static SANE_Int
umax_pp_auto_attach (SANE_Int safe)
{
  char **ports;
  int rc = SANE_STATUS_INVAL;

  /* safe tests: user parallel port devices */
  ports = sanei_parport_find_device ();
  if (ports != NULL)
    rc = umax_pp_try_ports (ports);

  /* try for direct hardware access */
  if ((safe != SANE_TRUE) && (rc != SANE_STATUS_GOOD))
    {
      ports = sanei_parport_find_port ();
      if (ports != NULL)
	rc = umax_pp_try_ports (ports);
    }
  return rc;
}

static SANE_Int
umax_pp_get_sync (SANE_Int dpi)
{
  /* delta between color frames */
  if (sanei_umax_pp_getastra () > 610)
    {
      switch (dpi)
	{
	case 1200:
	  return 8;
	case 600:
	  return 4;
	case 300:
	  return 2;
	case 150:
	  return 1;
	default:
	  return 0;
	}
    }
  else
    {
      switch (dpi)
	{
	case 600:
	  return 16;
	case 300:
	  return 8;	/* 8 double-checked*/
	case 150:
	  /* wrong: 2, 3, 5
	   * double-checked : 4
	   */
	  return 4;
	default:
	  return 2;	/* 2 double-checked */
	}
    }
}


static SANE_Status
init_options (Umax_PP_Device * dev)
{
  int i;

  /* sets initial option value to zero */
  memset (dev->opt, 0, sizeof (dev->opt));
  memset (dev->val, 0, sizeof (dev->val));

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

  dev->opt[OPT_NUM_OPTS].name = SANE_NAME_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
  dev->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
  dev->val[OPT_NUM_OPTS].w = NUM_OPTIONS;

  /* "Mode" group: */

  dev->opt[OPT_MODE_GROUP].title = SANE_TITLE_SCAN_MODE;
  dev->opt[OPT_MODE_GROUP].name = "";
  dev->opt[OPT_MODE_GROUP].desc = "";
  dev->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_MODE_GROUP].size = 0;
  dev->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* scan mode */
  dev->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
  dev->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
  dev->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
  dev->opt[OPT_MODE].type = SANE_TYPE_STRING;
  dev->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_MODE].size = 10;
  dev->opt[OPT_MODE].constraint.string_list = mode_list;
  dev->val[OPT_MODE].s = strdup (mode_list[1]);

  /* resolution */
  dev->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].type = SANE_TYPE_FIXED;
  dev->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  dev->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_RESOLUTION].constraint.range = &dev->dpi_range;
  dev->val[OPT_RESOLUTION].w = dev->dpi_range.min;


  /* preview */
  dev->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW;
  dev->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW;
  dev->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW;
  dev->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL;
  dev->opt[OPT_PREVIEW].size = sizeof (SANE_Word);
  dev->opt[OPT_PREVIEW].unit = SANE_UNIT_NONE;
  dev->val[OPT_PREVIEW].w = SANE_FALSE;

  /* gray preview */
  dev->opt[OPT_GRAY_PREVIEW].name = SANE_NAME_GRAY_PREVIEW;
  dev->opt[OPT_GRAY_PREVIEW].title = SANE_TITLE_GRAY_PREVIEW;
  dev->opt[OPT_GRAY_PREVIEW].desc = SANE_DESC_GRAY_PREVIEW;
  dev->opt[OPT_GRAY_PREVIEW].type = SANE_TYPE_BOOL;
  dev->opt[OPT_GRAY_PREVIEW].size = sizeof (SANE_Word);
  dev->opt[OPT_GRAY_PREVIEW].unit = SANE_UNIT_NONE;
  dev->val[OPT_GRAY_PREVIEW].w = SANE_FALSE;

  /* "Geometry" group: */

  dev->opt[OPT_GEOMETRY_GROUP].title = SANE_I18N ("Geometry");
  dev->opt[OPT_GEOMETRY_GROUP].desc = "";
  dev->opt[OPT_GEOMETRY_GROUP].name = "";
  dev->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_GEOMETRY_GROUP].size = 0;
  dev->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* top-left x */
  dev->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
  dev->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
  dev->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
  dev->opt[OPT_TL_X].type = SANE_TYPE_INT;
  dev->opt[OPT_TL_X].unit = SANE_UNIT_PIXEL;
  dev->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_TL_X].constraint.range = &dev->x_range;
  dev->val[OPT_TL_X].w = 0;

  /* top-left y */
  dev->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
  dev->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
  dev->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
  dev->opt[OPT_TL_Y].type = SANE_TYPE_INT;
  dev->opt[OPT_TL_Y].unit = SANE_UNIT_PIXEL;
  dev->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_TL_Y].constraint.range = &dev->y_range;
  dev->val[OPT_TL_Y].w = 0;

  /* bottom-right x */
  dev->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X;
  dev->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
  dev->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X;
  dev->opt[OPT_BR_X].type = SANE_TYPE_INT;
  dev->opt[OPT_BR_X].unit = SANE_UNIT_PIXEL;
  dev->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BR_X].constraint.range = &dev->x_range;
  dev->val[OPT_BR_X].w = dev->x_range.max;

  /* bottom-right y */
  dev->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
  dev->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
  dev->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
  dev->opt[OPT_BR_Y].type = SANE_TYPE_INT;
  dev->opt[OPT_BR_Y].unit = SANE_UNIT_PIXEL;
  dev->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BR_Y].constraint.range = &dev->y_range;
  dev->val[OPT_BR_Y].w = dev->y_range.max;

  /* "Enhancement" group: */

  dev->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N ("Enhancement");
  dev->opt[OPT_ENHANCEMENT_GROUP].desc = "";
  dev->opt[OPT_ENHANCEMENT_GROUP].name = "";
  dev->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_ENHANCEMENT_GROUP].size = 0;
  dev->opt[OPT_ENHANCEMENT_GROUP].cap |= SANE_CAP_ADVANCED;
  dev->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* lamp control */
  dev->opt[OPT_LAMP_CONTROL].name = "lamp-control";
  dev->opt[OPT_LAMP_CONTROL].title = SANE_I18N ("Lamp on");
  dev->opt[OPT_LAMP_CONTROL].desc = SANE_I18N ("Sets lamp on/off");
  dev->opt[OPT_LAMP_CONTROL].type = SANE_TYPE_BOOL;
  dev->opt[OPT_LAMP_CONTROL].size = sizeof (SANE_Word);
  dev->opt[OPT_LAMP_CONTROL].unit = SANE_UNIT_NONE;
  dev->val[OPT_LAMP_CONTROL].w = SANE_TRUE;
  dev->opt[OPT_LAMP_CONTROL].cap |= SANE_CAP_ADVANCED;

  /* UTA control */
  dev->opt[OPT_UTA_CONTROL].name = "UTA-control";
  dev->opt[OPT_UTA_CONTROL].title = SANE_I18N ("UTA on");
  dev->opt[OPT_UTA_CONTROL].desc = SANE_I18N ("Sets UTA on/off");
  dev->opt[OPT_UTA_CONTROL].type = SANE_TYPE_BOOL;
  dev->opt[OPT_UTA_CONTROL].size = sizeof (SANE_Word);
  dev->opt[OPT_UTA_CONTROL].unit = SANE_UNIT_NONE;
  dev->val[OPT_UTA_CONTROL].w = SANE_TRUE;
  dev->opt[OPT_UTA_CONTROL].cap |= SANE_CAP_ADVANCED | SANE_CAP_INACTIVE;

  /* custom-gamma table */
  dev->opt[OPT_CUSTOM_GAMMA].name = SANE_NAME_CUSTOM_GAMMA;
  dev->opt[OPT_CUSTOM_GAMMA].title = SANE_TITLE_CUSTOM_GAMMA;
  dev->opt[OPT_CUSTOM_GAMMA].desc = SANE_DESC_CUSTOM_GAMMA;
  dev->opt[OPT_CUSTOM_GAMMA].type = SANE_TYPE_BOOL;
  dev->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_ADVANCED;
  dev->val[OPT_CUSTOM_GAMMA].w = SANE_FALSE;

  /* grayscale gamma vector */
  dev->opt[OPT_GAMMA_VECTOR].name = SANE_NAME_GAMMA_VECTOR;
  dev->opt[OPT_GAMMA_VECTOR].title = SANE_TITLE_GAMMA_VECTOR;
  dev->opt[OPT_GAMMA_VECTOR].desc = SANE_DESC_GAMMA_VECTOR;
  dev->opt[OPT_GAMMA_VECTOR].type = SANE_TYPE_INT;
  dev->opt[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_GAMMA_VECTOR].unit = SANE_UNIT_NONE;
  dev->opt[OPT_GAMMA_VECTOR].size = 256 * sizeof (SANE_Word);
  dev->opt[OPT_GAMMA_VECTOR].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_GAMMA_VECTOR].constraint.range = &u8_range;
  dev->val[OPT_GAMMA_VECTOR].wa = &dev->gamma_table[0][0];

  /* red gamma vector */
  dev->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R;
  dev->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R;
  dev->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R;
  dev->opt[OPT_GAMMA_VECTOR_R].type = SANE_TYPE_INT;
  dev->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_GAMMA_VECTOR_R].unit = SANE_UNIT_NONE;
  dev->opt[OPT_GAMMA_VECTOR_R].size = 256 * sizeof (SANE_Word);
  dev->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_GAMMA_VECTOR_R].constraint.range = &u8_range;
  dev->val[OPT_GAMMA_VECTOR_R].wa = &dev->gamma_table[1][0];

  /* green gamma vector */
  dev->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G;
  dev->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G;
  dev->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G;
  dev->opt[OPT_GAMMA_VECTOR_G].type = SANE_TYPE_INT;
  dev->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_GAMMA_VECTOR_G].unit = SANE_UNIT_NONE;
  dev->opt[OPT_GAMMA_VECTOR_G].size = 256 * sizeof (SANE_Word);
  dev->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_GAMMA_VECTOR_G].constraint.range = &u8_range;
  dev->val[OPT_GAMMA_VECTOR_G].wa = &dev->gamma_table[2][0];

  /* blue gamma vector */
  dev->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B;
  dev->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B;
  dev->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B;
  dev->opt[OPT_GAMMA_VECTOR_B].type = SANE_TYPE_INT;
  dev->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_GAMMA_VECTOR_B].unit = SANE_UNIT_NONE;
  dev->opt[OPT_GAMMA_VECTOR_B].size = 256 * sizeof (SANE_Word);
  dev->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_GAMMA_VECTOR_B].constraint.range = &u8_range;
  dev->val[OPT_GAMMA_VECTOR_B].wa = &dev->gamma_table[3][0];

  /*  gain group */
  dev->opt[OPT_MANUAL_GAIN].name = "manual-channel-gain";
  dev->opt[OPT_MANUAL_GAIN].title = SANE_I18N ("Gain");
  dev->opt[OPT_MANUAL_GAIN].desc = SANE_I18N ("Color channels gain settings");
  dev->opt[OPT_MANUAL_GAIN].type = SANE_TYPE_BOOL;
  dev->opt[OPT_MANUAL_GAIN].cap |= SANE_CAP_ADVANCED;
  dev->val[OPT_MANUAL_GAIN].w = SANE_FALSE;

  /* gray gain */
  dev->opt[OPT_GRAY_GAIN].name = "gray-gain";
  dev->opt[OPT_GRAY_GAIN].title = SANE_I18N ("Gray gain");
  dev->opt[OPT_GRAY_GAIN].desc = SANE_I18N ("Sets gray channel gain");
  dev->opt[OPT_GRAY_GAIN].type = SANE_TYPE_INT;
  dev->opt[OPT_GRAY_GAIN].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED;
  dev->opt[OPT_GRAY_GAIN].unit = SANE_UNIT_NONE;
  dev->opt[OPT_GRAY_GAIN].size = sizeof (SANE_Int);
  dev->opt[OPT_GRAY_GAIN].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_GRAY_GAIN].constraint.range = &u4_range;
  dev->val[OPT_GRAY_GAIN].w = dev->gray_gain;

  /* red gain */
  dev->opt[OPT_RED_GAIN].name = "red-gain";
  dev->opt[OPT_RED_GAIN].title = SANE_I18N ("Red gain");
  dev->opt[OPT_RED_GAIN].desc = SANE_I18N ("Sets red channel gain");
  dev->opt[OPT_RED_GAIN].type = SANE_TYPE_INT;
  dev->opt[OPT_RED_GAIN].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED;
  dev->opt[OPT_RED_GAIN].unit = SANE_UNIT_NONE;
  dev->opt[OPT_RED_GAIN].size = sizeof (SANE_Int);
  dev->opt[OPT_RED_GAIN].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_RED_GAIN].constraint.range = &u4_range;
  dev->val[OPT_RED_GAIN].w = dev->red_gain;

  /* green gain */
  dev->opt[OPT_GREEN_GAIN].name = "green-gain";
  dev->opt[OPT_GREEN_GAIN].title = SANE_I18N ("Green gain");
  dev->opt[OPT_GREEN_GAIN].desc = SANE_I18N ("Sets green channel gain");
  dev->opt[OPT_GREEN_GAIN].type = SANE_TYPE_INT;
  dev->opt[OPT_GREEN_GAIN].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED;
  dev->opt[OPT_GREEN_GAIN].unit = SANE_UNIT_NONE;
  dev->opt[OPT_GREEN_GAIN].size = sizeof (SANE_Int);
  dev->opt[OPT_GREEN_GAIN].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_GREEN_GAIN].constraint.range = &u4_range;
  dev->val[OPT_GREEN_GAIN].w = dev->green_gain;

  /* blue gain */
  dev->opt[OPT_BLUE_GAIN].name = "blue-gain";
  dev->opt[OPT_BLUE_GAIN].title = SANE_I18N ("Blue gain");
  dev->opt[OPT_BLUE_GAIN].desc = SANE_I18N ("Sets blue channel gain");
  dev->opt[OPT_BLUE_GAIN].type = SANE_TYPE_INT;
  dev->opt[OPT_BLUE_GAIN].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED;
  dev->opt[OPT_BLUE_GAIN].unit = SANE_UNIT_NONE;
  dev->opt[OPT_BLUE_GAIN].size = sizeof (SANE_Int);
  dev->opt[OPT_BLUE_GAIN].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BLUE_GAIN].constraint.range = &u4_range;
  dev->val[OPT_BLUE_GAIN].w = dev->blue_gain;

  /*  offset group */
  dev->opt[OPT_MANUAL_OFFSET].name = "manual-offset";
  dev->opt[OPT_MANUAL_OFFSET].title = SANE_I18N ("Offset");
  dev->opt[OPT_MANUAL_OFFSET].desc =
    SANE_I18N ("Color channels offset settings");
  dev->opt[OPT_MANUAL_OFFSET].type = SANE_TYPE_BOOL;
  dev->opt[OPT_MANUAL_OFFSET].cap |= SANE_CAP_ADVANCED;
  dev->val[OPT_MANUAL_OFFSET].w = SANE_FALSE;

  /* gray offset */
  dev->opt[OPT_GRAY_OFFSET].name = "gray-offset";
  dev->opt[OPT_GRAY_OFFSET].title = SANE_I18N ("Gray offset");
  dev->opt[OPT_GRAY_OFFSET].desc = SANE_I18N ("Sets gray channel offset");
  dev->opt[OPT_GRAY_OFFSET].type = SANE_TYPE_INT;
  dev->opt[OPT_GRAY_OFFSET].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED;
  dev->opt[OPT_GRAY_OFFSET].unit = SANE_UNIT_NONE;
  dev->opt[OPT_GRAY_OFFSET].size = sizeof (SANE_Int);
  dev->opt[OPT_GRAY_OFFSET].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_GRAY_OFFSET].constraint.range = &u4_range;
  dev->val[OPT_GRAY_OFFSET].w = dev->gray_offset;

  /* red offset */
  dev->opt[OPT_RED_OFFSET].name = "red-offset";
  dev->opt[OPT_RED_OFFSET].title = SANE_I18N ("Red offset");
  dev->opt[OPT_RED_OFFSET].desc = SANE_I18N ("Sets red channel offset");
  dev->opt[OPT_RED_OFFSET].type = SANE_TYPE_INT;
  dev->opt[OPT_RED_OFFSET].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED;
  dev->opt[OPT_RED_OFFSET].unit = SANE_UNIT_NONE;
  dev->opt[OPT_RED_OFFSET].size = sizeof (SANE_Int);
  dev->opt[OPT_RED_OFFSET].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_RED_OFFSET].constraint.range = &u4_range;
  dev->val[OPT_RED_OFFSET].w = dev->red_offset;

  /* green offset */
  dev->opt[OPT_GREEN_OFFSET].name = "green-offset";
  dev->opt[OPT_GREEN_OFFSET].title = SANE_I18N ("Green offset");
  dev->opt[OPT_GREEN_OFFSET].desc = SANE_I18N ("Sets green channel offset");
  dev->opt[OPT_GREEN_OFFSET].type = SANE_TYPE_INT;
  dev->opt[OPT_GREEN_OFFSET].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED;
  dev->opt[OPT_GREEN_OFFSET].unit = SANE_UNIT_NONE;
  dev->opt[OPT_GREEN_OFFSET].size = sizeof (SANE_Int);
  dev->opt[OPT_GREEN_OFFSET].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_GREEN_OFFSET].constraint.range = &u4_range;
  dev->val[OPT_GREEN_OFFSET].w = dev->green_offset;

  /* blue offset */
  dev->opt[OPT_BLUE_OFFSET].name = "blue-offset";
  dev->opt[OPT_BLUE_OFFSET].title = SANE_I18N ("Blue offset");
  dev->opt[OPT_BLUE_OFFSET].desc = SANE_I18N ("Sets blue channel offset");
  dev->opt[OPT_BLUE_OFFSET].type = SANE_TYPE_INT;
  dev->opt[OPT_BLUE_OFFSET].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED;
  dev->opt[OPT_BLUE_OFFSET].unit = SANE_UNIT_NONE;
  dev->opt[OPT_BLUE_OFFSET].size = sizeof (SANE_Int);
  dev->opt[OPT_BLUE_OFFSET].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BLUE_OFFSET].constraint.range = &u4_range;
  dev->val[OPT_BLUE_OFFSET].w = dev->blue_offset;

  return SANE_STATUS_GOOD;
}

static int
parse_int_option (const char *string, char *name, long int *value, long int fallback)
{
  int len;
  char *end;

  len = strlen (name);
  if ((strncmp (string, name, len) == 0) && isspace (string[len]))
    {

      string += len+1;

      errno = 0;
      *value = strtol (string, &end, 0);

      if (end == string || errno)
	{
	  DBG (2, "init: invalid value `%s`, using fallback '%ld'\n", string,fallback);
	  *value = fallback;
	}
      DBG (3, "init: option %s %ld\n", name, *value);
      return 1;
    }
  return 0;
}

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  char dev_name[512];
  const char *cp;
  size_t len;
  FILE *fp;
  SANE_Status ret;
  int portdone = 0;
  long int val = 0;

  DBG_INIT ();

  if (authorize != NULL)
    {
      DBG (2, "init: SANE_Auth_Callback not supported (yet) ...\n");
    }

  if (version_code != NULL)
    *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, UMAX_PP_BUILD);

  DBG (3, "init: SANE v%s, backend v%d.%d.%d-%s\n", VERSION, V_MAJOR, V_MINOR,
       UMAX_PP_BUILD, UMAX_PP_STATE);

  fp = sanei_config_open (UMAX_PP_CONFIG_FILE);


  if (fp == NULL)
    {
      DBG (2, "init: no configuration file, using default `port %s'\n",
	   UMAX_PP_DEFAULT_PORT);

      ret = attach (UMAX_PP_DEFAULT_PORT);
      return ret;
    }

  while (sanei_config_read (dev_name, sizeof (dev_name), fp))
    {
      cp = sanei_config_skip_whitespace (dev_name);
      if (!*cp || *cp == '#')	/* ignore line comments & empty lines */
	continue;

      len = strlen (cp);

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


      if (strncmp (cp, "option", 6) == 0 && isspace (cp[6]))
	{

	  DBG (3, "init: evaluating option <%s>\n", cp);
	  cp += 7;
	  cp = sanei_config_skip_whitespace (cp);

	  if (parse_int_option(cp, "buffer", &val, buf_size))
	    {
	      if (val < 8192)
		{
		  DBG (2, "init: invalid value `%s`, falling back to %ld\n",
		       cp, buf_size);
		  val = buf_size;	/* safe fallback */
		}
	      DBG (3, "init: option buffer %ld\n", val);

	      if (num_devices == 0)
		{
		  DBG (3, "init: setting global option buffer to %ld\n", val);
		  buf_size = val;
		}
	      else
		{
		  DBG (3, "init: setting buffer to %ld for device %s\n",
		       val, devlist[0].sane.name);
		  devlist[0].buf_size = val;
		}
	    }
	  else if (parse_int_option(cp, "astra", &val, 0))
	  {
	  	if((val!=610)&&(val!=1200)&&(val!=1600)&&(val!=2000))
		{
		  val=0;
		  DBG (2, "init: invalid value `%s`, falling back to %ld\n",
		       cp, val);
		}
	      DBG (3, "init: setting global option astra to %ld\n", val);
	      sanei_umax_pp_setastra (val);
	  }
	  else if (parse_int_option(cp, "red-gain", &val, 12))
	  {
	  	if((val<0)||(val>15))
		{
		  val=12;
		  DBG (2, "init: invalid value `%s`, falling back to %ld\n",
		       cp, val);
		}
	      DBG (3, "init: setting global option red-gain to %ld\n", val);
	      red_gain = val;
	  }
	  else if (parse_int_option(cp, "green-gain", &val, 6))
	  {
	  	if((val<0)||(val>15))
		{
		  val=6;
		  DBG (2, "init: invalid value `%s`, falling back to %ld\n",
		       cp, val);
		}
	      DBG (3, "init: setting global option green-gain to %ld\n", val);
	      green_gain = val;
	  }
	  else if (parse_int_option(cp, "blue-gain", &val,12))
	  {
	  	if((val<0)||(val>15))
		{
		  val=12;
		  DBG (2, "init: invalid value `%s`, falling back to %ld\n",
		       cp, val);
		}
	      DBG (3, "init: setting global option blue-gain to %ld\n", val);
	      blue_gain = val;
	  }
	  else if (parse_int_option(cp, "red-offset", &val, 10))
	  {
	  	if((val<0)||(val>15))
		{
		  val=10;
		  DBG (2, "init: invalid value `%s`, falling back to %ld\n",
		       cp, val);
		}
	      DBG (3, "init: setting global option red-offset to %ld\n", val);
	      red_offset = val;
	  }
	  else if (parse_int_option(cp, "green-offset", &val, 10))
	  {
	  	if((val<0)||(val>15))
		{
		  val=10;
		  DBG (2, "init: invalid value `%s`, falling back to %ld\n",
		       cp, val);
		}
	      DBG (3, "init: setting global option green-offset to %ld\n", val);
	      green_offset = val;
	  }
	  else if (parse_int_option(cp, "blue-offset", &val, 10))
	  {
	  	if((val<0)||(val>15))
		{
		  val=10;
		  DBG (2, "init: invalid value `%s`, falling back to %ld\n",
		       cp, val);
		}
	      DBG (3, "init: setting global option red-offset to %ld\n", val);
	      blue_offset = val;
	  }
          else
	    DBG (2, "init: don't know what to do with option `%s'\n", cp);
	  }
      else if ((strncmp (cp, "port", 4) == 0) && isspace (cp[4]))
	{
	  /* protect ourself from buggy configuration tool such as
	   * mandrake's 9.2 control panel */
	  if (portdone)
	    {
	      DBG (2,
		   "'port' option given more than once, check your umax_pp.conf file!!\n");
	      return SANE_STATUS_INVAL;
	    }

	  cp += 5;
	  cp = sanei_config_skip_whitespace (cp);
	  portdone = 1;

	  if (*cp)
	    {
	      /* here, the argument maybe a device, an address, or special
	       * keywords 'auto' and 'safe-auto'
	       */
	      if (strcmp (cp, "safe-auto") == 0)
		{
		  /* try every device we can find */
		  if (umax_pp_auto_attach (SANE_TRUE) != SANE_STATUS_GOOD)
		    DBG (2, "init: safe-auto attach failed !");
		}
	      else if (strcmp (cp, "auto") == 0)
		{
		  /* try every port/device we can find */
		  if (umax_pp_auto_attach (SANE_FALSE) != SANE_STATUS_GOOD)
		    {
		      DBG (2, "init: auto attach failed !");
		    }
		}
	      else
		{
		  DBG (3, "init: trying port `%s'\n", cp);

		  DBG (3, "attach(%s)\n", cp);
		  if (attach (cp) != SANE_STATUS_GOOD)
		    DBG (2, "init: couldn't attach to port `%s'\n", cp);
		}
	    }
	}
      else if ((strncmp (cp, "name", 4) == 0) && isspace (cp[4]))
	{
	  cp += 5;
	  cp = sanei_config_skip_whitespace (cp);

	  if (num_devices == 0)
	    DBG (2, "init: 'name' only allowed after 'port'\n");
	  else
	    {
	      DBG (3, "init: naming device %s '%s'\n", devlist[0].port, cp);
	      free (devlist[0].sane.name);
	      devlist[0].sane.name = strdup (cp);
	    }
	}
      else if ((strncmp (cp, "model", 5) == 0) && isspace (cp[5]))
	{
	  cp += 6;
	  cp = sanei_config_skip_whitespace (cp);

	  if (num_devices == 0)
	    DBG (2, "init: 'model' only allowed after 'port'\n");
	  else
	    {
	      DBG (3, "init: device %s is a '%s'\n", devlist[0].port, cp);
	      free (devlist[0].sane.model);
	      devlist[0].sane.model = strdup (cp);
	    }
	}
      else if ((strncmp (cp, "vendor", 6) == 0) && isspace (cp[6]))
	{
	  cp += 7;
	  cp = sanei_config_skip_whitespace (cp);

	  if (num_devices == 0)
	    DBG (2, "init: 'vendor' only allowed after 'port'\n");
	  else
	    {
	      DBG (3, "init: device %s is from '%s'\n", devlist[0].port, cp);
	      free (devlist[0].sane.vendor);
	      devlist[0].sane.vendor = strdup (cp);
	    }
	}
      else
	DBG (2, "init: don't know what to do with `%s'\n", cp);
    }

  fclose (fp);

  return SANE_STATUS_GOOD;
}

void
sane_exit (void)
{
  int i;
  Umax_PP_Device *dev;

  DBG (3, "sane_exit: (...)\n");
  if (first_dev)
    DBG (3, "exit: closing open devices\n");

  while (first_dev)
    {
      dev = first_dev;
      sane_close (dev);
    }

  for (i = 0; i < num_devices; i++)
    {
      free (devlist[i].port);
      free (devlist[i].sane.name);
      free (devlist[i].sane.model);
      free (devlist[i].sane.vendor);
    }

  if (devlist != NULL)
    {
      free (devlist);
      devlist = NULL;
    }

  if (devarray != NULL)
    {
      free (devarray);
      devarray = NULL;
    }

  /* reset values */
  num_devices = 0;
  first_dev = NULL;

  red_gain = 0;
  green_gain = 0;
  blue_gain = 0;

  red_offset = 0;
  green_offset = 0;
  blue_offset = 0;

}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  int i;

  DBG (3, "get_devices\n");
  DBG (129, "unused arg: local_only = %d\n", (int) local_only);

  if (devarray != NULL)
    {
      free (devarray);
      devarray = NULL;
    }

  devarray = malloc ((num_devices + 1) * sizeof (devarray[0]));

  if (devarray == NULL)
    {
      DBG (2, "get_devices: not enough memory for device list\n");
      DEBUG ();
      return SANE_STATUS_NO_MEM;
    }

  for (i = 0; i < num_devices; i++)
    devarray[i] = &devlist[i].sane;

  devarray[num_devices] = NULL;
  *device_list = devarray;

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  Umax_PP_Device *dev;
  Umax_PP_Descriptor *desc;
  int i, j;
  int rc, prt = 0;
  char *name = NULL;

  DBG (3, "open: device `%s'\n", devicename);

  if (devicename[0])
    {
      for (i = 0; i < num_devices; i++)
	if (strcmp (devlist[i].sane.name, devicename) == 0)
	  break;

      if (i >= num_devices)
	for (i = 0; i < num_devices; i++)
	  if (strcmp (devlist[i].port, devicename) == 0)
	    break;

      if (i >= num_devices)
	{
	  DBG (2, "open: device doesn't exist\n");
	  DEBUG ();
	  return SANE_STATUS_INVAL;
	}

      desc = &devlist[i];

      if (devlist[i].ppdevice != NULL)
	{
	  if (devlist[i].ppdevice[0] == '/')
	    {
	      name = devlist[i].ppdevice;
	    }
	}
      else
	{
	  if ((devlist[i].port[0] == '0')
	      && ((devlist[i].port[1] == 'x') || (devlist[i].port[1] == 'X')))
	    prt = strtol (devlist[i].port + 2, NULL, 16);
	  else
	    prt = atoi (devlist[i].port);
	  DBG (64, "open: devlist[i].port='%s' -> port=0x%X\n",
	       devlist[i].port, prt);
	}
      rc = sanei_umax_pp_open (prt, name);
    }
  else
    {

      if (num_devices == 0)
	{
	  DBG (1, "open: no devices present\n");
	  return SANE_STATUS_INVAL;
	}

      DBG (3, "open: trying default device %s, port=%s,ppdev=%s\n",
	   devlist[0].sane.name, devlist[0].port, devlist[0].ppdevice);
      if (devlist[0].port != NULL)
	{
	  if ((devlist[0].port[0] == '0')
	      && ((devlist[0].port[1] == 'x') || (devlist[0].port[1] == 'X')))
	    prt = strtol (devlist[0].port + 2, NULL, 16);
	  else
	    prt = atoi (devlist[0].port);
	  rc = sanei_umax_pp_open (prt, NULL);
	}
      else
	{
	  rc = sanei_umax_pp_open (0, devlist[0].ppdevice);
	}
      desc = &devlist[0];
    }
  switch (rc)
    {
    case UMAX1220P_TRANSPORT_FAILED:
      if (name == NULL)
	{
	  DBG (1, "failed to init transport layer on port 0x%03X\n", prt);
	}
      else
	{
	  DBG (1, "failed to init transport layer on device %s\n", name);
	}
      return SANE_STATUS_IO_ERROR;

    case UMAX1220P_SCANNER_FAILED:
      if (name == NULL)
	{
	  DBG (1, "failed to initialize scanner on port 0x%03X\n", prt);
	}
      else
	{
	  DBG (1, "failed to initialize scanner on device %s\n", name);
	}
      return SANE_STATUS_IO_ERROR;
    case UMAX1220P_BUSY:
      if (name == NULL)
	{
	  DBG (1, "busy scanner on port 0x%03X\n", prt);
	}
      else
	{
	  DBG (1, "busy scanner on device %s\n", name);
	}
      return SANE_STATUS_DEVICE_BUSY;
    }


  dev = (Umax_PP_Device *) malloc (sizeof (*dev));

  if (dev == NULL)
    {
      DBG (2, "open: not enough memory for device descriptor\n");
      DEBUG ();
      return SANE_STATUS_NO_MEM;
    }

  memset (dev, 0, sizeof (*dev));

  dev->desc = desc;

  for (i = 0; i < 4; ++i)
    for (j = 0; j < 256; ++j)
      dev->gamma_table[i][j] = j;

  /* the extra amount of UMAX_PP_RESERVE bytes is to handle */
  /* the data needed to resync the color frames     */
  dev->buf = malloc (dev->desc->buf_size + UMAX_PP_RESERVE);
  dev->bufsize = dev->desc->buf_size;

  dev->dpi_range.min = SANE_FIX (75);
  dev->dpi_range.max = SANE_FIX (dev->desc->max_res);
  dev->dpi_range.quant = 0;

  dev->x_range.min = 0;
  dev->x_range.max = dev->desc->max_h_size;
  dev->x_range.quant = 0;

  dev->y_range.min = 0;
  dev->y_range.max = dev->desc->max_v_size;
  dev->y_range.quant = 0;

  dev->gray_gain = 0;

  /* use pre defined settings read from umax_pp.conf */
  dev->red_gain = red_gain;
  dev->green_gain = green_gain;
  dev->blue_gain = blue_gain;
  dev->red_offset = red_offset;
  dev->green_offset = green_offset;
  dev->blue_offset = blue_offset;


  if (dev->buf == NULL)
    {
      DBG (2, "open: not enough memory for scan buffer (%lu bytes)\n",
	   (long int) dev->desc->buf_size);
      DEBUG ();
      free (dev);
      return SANE_STATUS_NO_MEM;
    }

  init_options (dev);

  dev->next = first_dev;
  first_dev = dev;


  if (sanei_umax_pp_UTA () == 1)
    dev->opt[OPT_UTA_CONTROL].cap &= ~SANE_CAP_INACTIVE;

  *handle = dev;

  DBG (3, "open: success\n");

  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle handle)
{
  Umax_PP_Device *prev, *dev;
  int rc;

  DBG (3, "sane_close: ...\n");
  /* remove handle from list of open handles: */
  prev = NULL;

  for (dev = first_dev; dev; dev = dev->next)
    {
      if (dev == handle)
	break;
      prev = dev;
    }

  if (dev == NULL)
    {
      DBG (2, "close: unknown device\n");
      DEBUG ();
      return;			/* oops, not a handle we know about */
    }

  if (dev->state == UMAX_PP_STATE_SCANNING)
    sane_cancel (handle);	/* remember: sane_cancel is a macro and
				   expands to sane_umax_pp_cancel ()... */


  /* if the scanner is parking head, we wait it to finish */
  while (dev->state == UMAX_PP_STATE_CANCELLED)
    {
      DBG (2, "close: waiting scanner to park head\n");
      rc = sanei_umax_pp_status ();

      /* check if scanner busy parking */
      if (rc != UMAX1220P_BUSY)
	{
	  DBG (2, "close: scanner head parked\n");
	  dev->state = UMAX_PP_STATE_IDLE;
	}
    }

  /* then we switch off gain if needed */
  if (dev->val[OPT_LAMP_CONTROL].w == SANE_TRUE)
    {
      rc = sanei_umax_pp_lamp (0);
      if (rc == UMAX1220P_TRANSPORT_FAILED)
	{
	  DBG (1, "close: switch off gain failed (ignored....)\n");
	}
    }

  sanei_umax_pp_close ();



  if (prev != NULL)
    prev->next = dev->next;
  else
    first_dev = dev->next;

  free (dev->buf);
  DBG (3, "close: device closed\n");

  free (handle);

}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  Umax_PP_Device *dev = handle;

  if ((unsigned) option >= NUM_OPTIONS)
    {
      DBG (2, "get_option_descriptor: option %d doesn't exist\n", option);
      DEBUG ();
      return NULL;
    }

  DBG (6, "get_option_descriptor: requested option %d (%s)\n",
       option, dev->opt[option].name);

  return dev->opt + option;
}

SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val, SANE_Int * info)
{
  Umax_PP_Device *dev = handle;
  SANE_Status status;
  SANE_Word w, cap, tmpw;
  int dpi, rc;
  unsigned int i;

  DBG (6, "control_option: option %d, action %d\n", option, action);

  if (info)
    *info = 0;

  if (dev->state == UMAX_PP_STATE_SCANNING)
    {
      DBG (2, "control_option: device is scanning\n");
      DEBUG ();
      return SANE_STATUS_DEVICE_BUSY;
    }

  if ((unsigned int) option >= NUM_OPTIONS)
    {
      DBG (2, "control_option: option doesn't exist\n");
      DEBUG ();
      return SANE_STATUS_INVAL;
    }


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

  if (!SANE_OPTION_IS_ACTIVE (cap))
    {
      DBG (2, "control_option: option isn't active\n");
      DEBUG ();
      return SANE_STATUS_INVAL;
    }

  DBG (6, "control_option: option <%s>, action ... %d\n",
       dev->opt[option].name, action);

  if (action == SANE_ACTION_GET_VALUE)
    {
      DBG (6, " get value\n");
      switch (option)
	{
	  /* word options: */
	case OPT_PREVIEW:
	case OPT_GRAY_PREVIEW:
	case OPT_LAMP_CONTROL:
	case OPT_UTA_CONTROL:
	case OPT_RESOLUTION:
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	case OPT_NUM_OPTS:
	case OPT_CUSTOM_GAMMA:
	case OPT_MANUAL_GAIN:
	case OPT_GRAY_GAIN:
	case OPT_GREEN_GAIN:
	case OPT_RED_GAIN:
	case OPT_BLUE_GAIN:
	case OPT_MANUAL_OFFSET:
	case OPT_GRAY_OFFSET:
	case OPT_GREEN_OFFSET:
	case OPT_RED_OFFSET:
	case OPT_BLUE_OFFSET:

	  *(SANE_Word *) val = dev->val[option].w;
	  return SANE_STATUS_GOOD;

	  /* word-array options: */
	case OPT_GAMMA_VECTOR:
	case OPT_GAMMA_VECTOR_R:
	case OPT_GAMMA_VECTOR_G:
	case OPT_GAMMA_VECTOR_B:

	  for (i = 0; i < dev->opt[option].size / sizeof (SANE_Word); i++)
	    {
	      if (((SANE_Int *) val)[i] < 0 || ((SANE_Int *) val)[i] > 255)
		{
		  DBG (2, "Value at index %d out of range\n", i);
		  return SANE_STATUS_INVAL;
		}
	    }
	  memcpy (val, dev->val[option].wa, dev->opt[option].size);
	  return SANE_STATUS_GOOD;

	  /* string options: */
	case OPT_MODE:

	  strcpy (val, dev->val[option].s);
	  return SANE_STATUS_GOOD;
	}
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {
      DBG (6, " set value\n");

      if (!SANE_OPTION_IS_SETTABLE (cap))
	{
	  DBG (2, "control_option: option can't be set\n");
	  DEBUG ();
	  return SANE_STATUS_INVAL;
	}

      status = sanei_constrain_value (dev->opt + option, val, info);

      if (status != SANE_STATUS_GOOD)
	{
	  DBG (2, "control_option: constrain_value failed (%s)\n",
	       sane_strstatus (status));
	  DEBUG ();
	  return status;
	}

      if (option == OPT_RESOLUTION)
	{
	  DBG (16, "control_option: setting resolution to %d\n",
	       *(SANE_Int *) val);
	}
      if (option == OPT_PREVIEW)
	{
	  DBG (16, "control_option: setting preview to %d\n",
	       *(SANE_Word *) val);
	}

      switch (option)
	{
	  /* (mostly) side-effect-free word options: */
	case OPT_PREVIEW:
	case OPT_GRAY_PREVIEW:
	case OPT_TL_Y:
	case OPT_BR_Y:

	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;

	case OPT_GRAY_GAIN:
	case OPT_GREEN_GAIN:
	case OPT_RED_GAIN:
	case OPT_BLUE_GAIN:
	case OPT_GRAY_OFFSET:
	case OPT_GREEN_OFFSET:
	case OPT_RED_OFFSET:
	case OPT_BLUE_OFFSET:

	  dev->val[option].w = *(SANE_Word *) val;
	  /* sanity check */
	  if (dev->val[OPT_BR_Y].w < dev->val[OPT_TL_Y].w)
	    {
	      tmpw = dev->val[OPT_BR_Y].w;
	      dev->val[OPT_BR_Y].w = dev->val[OPT_TL_Y].w;
	      dev->val[OPT_TL_Y].w = tmpw;
	      if (info)
		*info |= SANE_INFO_INEXACT;
	      DBG (16, "control_option: swapping Y coordinates\n");
	    }
	  if (strcmp (dev->val[OPT_MODE].s, "Color") == 0)
	    {
	      dpi = (int) (SANE_UNFIX (dev->val[OPT_RESOLUTION].w));
	      if (dev->val[OPT_TL_Y].w < 2 * umax_pp_get_sync (dpi))
		{
		  DBG (16, "control_option: correcting TL_Y coordinates\n");
		  dev->val[OPT_TL_Y].w = 2 * umax_pp_get_sync (dpi);
		  if (info)
		    *info |= SANE_INFO_INEXACT;
		}
	    }
	  return SANE_STATUS_GOOD;

	  /* side-effect-free word-array options: */
	case OPT_GAMMA_VECTOR:
	case OPT_GAMMA_VECTOR_R:
	case OPT_GAMMA_VECTOR_G:
	case OPT_GAMMA_VECTOR_B:

	  memcpy (dev->val[option].wa, val, dev->opt[option].size);
	  return SANE_STATUS_GOOD;


	  /* options with side-effects: */
	case OPT_UTA_CONTROL:
	  dev->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	case OPT_LAMP_CONTROL:
	  if (dev->state != UMAX_PP_STATE_IDLE)
	    {
	      rc = sanei_umax_pp_status ();

	      /* check if scanner busy parking */
	      if (rc == UMAX1220P_BUSY)
		{
		  DBG (2, "control_option: scanner busy\n");
		  if (info)
		    *info |= SANE_INFO_RELOAD_PARAMS;
		  return SANE_STATUS_DEVICE_BUSY;
		}
	      dev->state = UMAX_PP_STATE_IDLE;
	    }
	  dev->val[option].w = *(SANE_Word *) val;
	  if (dev->val[option].w == SANE_TRUE)
	    rc = sanei_umax_pp_lamp (1);
	  else
	    rc = sanei_umax_pp_lamp (0);
	  if (rc == UMAX1220P_TRANSPORT_FAILED)
	    return SANE_STATUS_IO_ERROR;
	  return SANE_STATUS_GOOD;

	case OPT_TL_X:
	case OPT_BR_X:
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  dpi = (int) (SANE_UNFIX (dev->val[OPT_RESOLUTION].w));
	  dev->val[option].w = *(SANE_Word *) val;
	  /* coords rounded to allow 32 bit IO/transfer */
	  /* at high resolution                         */
	  if (dpi >= 600)
	    {
	      if (dev->val[option].w & 0x03)
		{
		  if (info)
		    *info |= SANE_INFO_INEXACT;
		  dev->val[option].w = dev->val[option].w & 0xFFFC;
		  *(SANE_Word *) val = dev->val[option].w;
		  DBG (16, "control_option: rounding X to %d\n",
		       *(SANE_Word *) val);
		}
	    }
	  /* sanity check */
	  if (dev->val[OPT_BR_X].w < dev->val[OPT_TL_X].w)
	    {
	      tmpw = dev->val[OPT_BR_X].w;
	      dev->val[OPT_BR_X].w = dev->val[OPT_TL_X].w;
	      dev->val[OPT_TL_X].w = tmpw;
	      if (info)
		*info |= SANE_INFO_INEXACT;
	      DBG (16, "control_option: swapping X coordinates\n");
	    }
	  return SANE_STATUS_GOOD;



	case OPT_RESOLUTION:
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  /* resolution : only have 75, 150, 300, 600 and 1200 */
	  dpi = (int) (SANE_UNFIX (*(SANE_Word *) val));
	  if ((dpi != 75)
	      && (dpi != 150)
	      && (dpi != 300) && (dpi != 600) && (dpi != 1200))
	    {
	      if (dpi <= 75)
		dpi = 75;
	      else if (dpi <= 150)
		dpi = 150;
	      else if (dpi <= 300)
		dpi = 300;
	      else if (dpi <= 600)
		dpi = 600;
	      else
		dpi = 1200;
	      if (info)
		*info |= SANE_INFO_INEXACT;
	      *(SANE_Word *) val = SANE_FIX ((SANE_Word) dpi);
	    }
	  dev->val[option].w = *(SANE_Word *) val;

	  /* correct top x and bottom x if needed */
	  if (dpi >= 600)
	    {
	      dev->val[OPT_TL_X].w = dev->val[OPT_TL_X].w & 0xFFFC;
	      dev->val[OPT_BR_X].w = dev->val[OPT_BR_X].w & 0xFFFC;
	    }
	  /* corrects top y for offset */
	  if (strcmp (dev->val[OPT_MODE].s, "Color") == 0)
	    {
	      if (dev->val[OPT_TL_Y].w < 2 * umax_pp_get_sync (dpi))
		{
		  DBG (16, "control_option: correcting TL_Y coordinates\n");
		  dev->val[OPT_TL_Y].w = 2 * umax_pp_get_sync (dpi);
		  if (info)
		    *info |= SANE_INFO_INEXACT;
		}
	    }
	  return SANE_STATUS_GOOD;

	case OPT_MANUAL_OFFSET:
	  w = *(SANE_Word *) val;

	  if (w == dev->val[OPT_MANUAL_OFFSET].w)
	    return SANE_STATUS_GOOD;	/* no change */

	  if (info)
	    *info |= SANE_INFO_RELOAD_OPTIONS;

	  dev->val[OPT_MANUAL_OFFSET].w = w;

	  if (w == SANE_TRUE)
	    {
	      const char *mode = dev->val[OPT_MODE].s;

	      if ((strcmp (mode, "Grayscale") == 0)
		  || (strcmp (mode, "Lineart") == 0))
		dev->opt[OPT_GRAY_OFFSET].cap &= ~SANE_CAP_INACTIVE;
	      else if (strcmp (mode, "Color") == 0)
		{
		  dev->opt[OPT_GRAY_OFFSET].cap |= SANE_CAP_INACTIVE;
		  dev->opt[OPT_RED_OFFSET].cap &= ~SANE_CAP_INACTIVE;
		  dev->opt[OPT_GREEN_OFFSET].cap &= ~SANE_CAP_INACTIVE;
		  dev->opt[OPT_BLUE_OFFSET].cap &= ~SANE_CAP_INACTIVE;
		}
	    }
	  else
	    {
	      dev->opt[OPT_GRAY_OFFSET].cap |= SANE_CAP_INACTIVE;
	      dev->opt[OPT_RED_OFFSET].cap |= SANE_CAP_INACTIVE;
	      dev->opt[OPT_GREEN_OFFSET].cap |= SANE_CAP_INACTIVE;
	      dev->opt[OPT_BLUE_OFFSET].cap |= SANE_CAP_INACTIVE;
	    }
	  return SANE_STATUS_GOOD;



	case OPT_MANUAL_GAIN:
	  w = *(SANE_Word *) val;

	  if (w == dev->val[OPT_MANUAL_GAIN].w)
	    return SANE_STATUS_GOOD;	/* no change */

	  if (info)
	    *info |= SANE_INFO_RELOAD_OPTIONS;

	  dev->val[OPT_MANUAL_GAIN].w = w;

	  if (w == SANE_TRUE)
	    {
	      const char *mode = dev->val[OPT_MODE].s;

	      if ((strcmp (mode, "Grayscale") == 0)
		  || (strcmp (mode, "Lineart") == 0))
		dev->opt[OPT_GRAY_GAIN].cap &= ~SANE_CAP_INACTIVE;
	      else if (strcmp (mode, "Color") == 0)
		{
		  dev->opt[OPT_GRAY_GAIN].cap |= SANE_CAP_INACTIVE;
		  dev->opt[OPT_RED_GAIN].cap &= ~SANE_CAP_INACTIVE;
		  dev->opt[OPT_GREEN_GAIN].cap &= ~SANE_CAP_INACTIVE;
		  dev->opt[OPT_BLUE_GAIN].cap &= ~SANE_CAP_INACTIVE;
		}
	    }
	  else
	    {
	      dev->opt[OPT_GRAY_GAIN].cap |= SANE_CAP_INACTIVE;
	      dev->opt[OPT_RED_GAIN].cap |= SANE_CAP_INACTIVE;
	      dev->opt[OPT_GREEN_GAIN].cap |= SANE_CAP_INACTIVE;
	      dev->opt[OPT_BLUE_GAIN].cap |= SANE_CAP_INACTIVE;
	    }
	  return SANE_STATUS_GOOD;




	case OPT_CUSTOM_GAMMA:
	  w = *(SANE_Word *) val;

	  if (w == dev->val[OPT_CUSTOM_GAMMA].w)
	    return SANE_STATUS_GOOD;	/* no change */

	  if (info)
	    *info |= SANE_INFO_RELOAD_OPTIONS;

	  dev->val[OPT_CUSTOM_GAMMA].w = w;

	  if (w == SANE_TRUE)
	    {
	      const char *mode = dev->val[OPT_MODE].s;

	      if ((strcmp (mode, "Grayscale") == 0)
		  || (strcmp (mode, "Lineart") == 0))
		{
		  dev->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
		  sanei_umax_pp_gamma (NULL, dev->val[OPT_GAMMA_VECTOR].wa,
				       NULL);
		}
	      else if (strcmp (mode, "Color") == 0)
		{
		  dev->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
		  dev->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE;
		  dev->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE;
		  dev->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE;
		  sanei_umax_pp_gamma (dev->val[OPT_GAMMA_VECTOR_R].wa,
				       dev->val[OPT_GAMMA_VECTOR_G].wa,
				       dev->val[OPT_GAMMA_VECTOR_B].wa);
		}
	    }
	  else
	    {
	      dev->opt[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE;
	      dev->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
	      dev->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
	      dev->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
	      sanei_umax_pp_gamma (NULL, NULL, NULL);
	    }

	  return SANE_STATUS_GOOD;

	case OPT_MODE:
	  {
	    char *old_val = dev->val[option].s;

	    if (old_val)
	      {
		if (strcmp (old_val, val) == 0)
		  return SANE_STATUS_GOOD;	/* no change */

		free (old_val);
	      }

	    if (info)
	      *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;

	    dev->val[option].s = strdup (val);

	    /* corrects top y for offset */
	    if (strcmp (val, "Color") == 0)
	      {
		dpi = (int) (SANE_UNFIX (dev->val[OPT_RESOLUTION].w));
		if (dev->val[OPT_TL_Y].w < 2 * umax_pp_get_sync (dpi))
		  {
		    dev->val[OPT_TL_Y].w = 2 * umax_pp_get_sync (dpi);
		    DBG (16, "control_option: correcting TL_Y coordinates\n");
		    if (info)
		      *info |= SANE_INFO_INEXACT;
		  }
	      }

	    dev->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;
	    dev->opt[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE;
	    dev->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
	    dev->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
	    dev->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
	    sanei_umax_pp_gamma (NULL, NULL, NULL);


	    if (dev->val[OPT_CUSTOM_GAMMA].w == SANE_TRUE)
	      {
		if ((strcmp (val, "Grayscale") == 0)
		    || (strcmp (val, "Lineart") == 0))
		  {
		    dev->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
		    sanei_umax_pp_gamma (NULL, dev->val[OPT_GAMMA_VECTOR].wa,
					 NULL);
		  }
		else if (strcmp (val, "Color") == 0)
		  {
		    dev->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
		    dev->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE;
		    dev->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE;
		    dev->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE;
		    sanei_umax_pp_gamma (dev->val[OPT_GAMMA_VECTOR_R].wa,
					 dev->val[OPT_GAMMA_VECTOR_G].wa,
					 dev->val[OPT_GAMMA_VECTOR_B].wa);
		  }
	      }

	    /* rebuild OPT OFFSET */
	    dev->opt[OPT_GRAY_OFFSET].cap |= SANE_CAP_INACTIVE;
	    dev->opt[OPT_RED_OFFSET].cap |= SANE_CAP_INACTIVE;
	    dev->opt[OPT_GREEN_OFFSET].cap |= SANE_CAP_INACTIVE;
	    dev->opt[OPT_BLUE_OFFSET].cap |= SANE_CAP_INACTIVE;


	    if (dev->val[OPT_MANUAL_OFFSET].w == SANE_TRUE)
	      {
		if ((strcmp (val, "Grayscale") == 0)
		    || (strcmp (val, "Lineart") == 0))
		  dev->opt[OPT_GRAY_OFFSET].cap &= ~SANE_CAP_INACTIVE;
		else if (strcmp (val, "Color") == 0)
		  {
		    dev->opt[OPT_RED_OFFSET].cap &= ~SANE_CAP_INACTIVE;
		    dev->opt[OPT_GREEN_OFFSET].cap &= ~SANE_CAP_INACTIVE;
		    dev->opt[OPT_BLUE_OFFSET].cap &= ~SANE_CAP_INACTIVE;
		  }
	      }

	    /* rebuild OPT GAIN */
	    dev->opt[OPT_GRAY_GAIN].cap |= SANE_CAP_INACTIVE;
	    dev->opt[OPT_RED_GAIN].cap |= SANE_CAP_INACTIVE;
	    dev->opt[OPT_GREEN_GAIN].cap |= SANE_CAP_INACTIVE;
	    dev->opt[OPT_BLUE_GAIN].cap |= SANE_CAP_INACTIVE;


	    if (dev->val[OPT_MANUAL_GAIN].w == SANE_TRUE)
	      {
		if ((strcmp (val, "Grayscale") == 0)
		    || (strcmp (val, "Lineart") == 0))
		  dev->opt[OPT_GRAY_GAIN].cap &= ~SANE_CAP_INACTIVE;
		else if (strcmp (val, "Color") == 0)
		  {
		    dev->opt[OPT_RED_GAIN].cap &= ~SANE_CAP_INACTIVE;
		    dev->opt[OPT_GREEN_GAIN].cap &= ~SANE_CAP_INACTIVE;
		    dev->opt[OPT_BLUE_GAIN].cap &= ~SANE_CAP_INACTIVE;
		  }
	      }

	    return SANE_STATUS_GOOD;
	  }
	}
    }


  DBG (2, "control_option: unknown action %d \n", action);
  DEBUG ();
  return SANE_STATUS_INVAL;
}


SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  Umax_PP_Device *dev = handle;
  int dpi, remain;

  memset (&(dev->params), 0, sizeof (dev->params));
  DBG (64, "sane_get_parameters\n");

  /* color/gray */
  if (strcmp (dev->val[OPT_MODE].s, "Color") != 0)
    {
      if (strcmp (dev->val[OPT_MODE].s, "Grayscale") != 0)
	dev->color = UMAX_PP_MODE_LINEART;
      else
	dev->color = UMAX_PP_MODE_GRAYSCALE;
    }
  else
    dev->color = UMAX_PP_MODE_COLOR;

  /* offset control */
  if (dev->val[OPT_MANUAL_OFFSET].w == SANE_TRUE)
    {
      if (dev->color != UMAX_PP_MODE_COLOR)
	{
	  dev->red_offset = 0;
	  dev->green_offset = (int) (dev->val[OPT_GRAY_OFFSET].w);
	  dev->blue_offset = 0;
	}
      else
	{
	  dev->red_offset = (int) (dev->val[OPT_RED_OFFSET].w);
	  dev->green_offset = (int) (dev->val[OPT_GREEN_OFFSET].w);
	  dev->blue_offset = (int) (dev->val[OPT_BLUE_OFFSET].w);
	}
    }
  else
    {
      dev->red_offset = 6;
      dev->green_offset = 6;
      dev->blue_offset = 6;
    }

  /* gain control */
  if (dev->val[OPT_MANUAL_GAIN].w == SANE_TRUE)
    {
      if (dev->color != UMAX_PP_MODE_COLOR)
	{
	  dev->red_gain = 0;
	  dev->green_gain = (int) (dev->val[OPT_GRAY_GAIN].w);
	  dev->blue_gain = 0;
	}
      else
	{
	  dev->red_gain = (int) (dev->val[OPT_RED_GAIN].w);
	  dev->green_gain = (int) (dev->val[OPT_GREEN_GAIN].w);
	  dev->blue_gain = (int) (dev->val[OPT_BLUE_GAIN].w);
	}
    }
  else
    {
      dev->red_gain = red_gain;
      dev->green_gain = green_gain;
      dev->blue_gain = blue_gain;
    }

  /* geometry */
  dev->TopX = dev->val[OPT_TL_X].w;
  dev->TopY = dev->val[OPT_TL_Y].w;
  dev->BottomX = dev->val[OPT_BR_X].w;
  dev->BottomY = dev->val[OPT_BR_Y].w;

  /* resolution : only have 75, 150, 300, 600 and 1200 */
  dpi = (int) (SANE_UNFIX (dev->val[OPT_RESOLUTION].w));
  if (dpi <= 75)
    dpi = 75;
  else if (dpi <= 150)
    dpi = 150;
  else if (dpi <= 300)
    dpi = 300;
  else if (dpi <= 600)
    dpi = 600;
  else
    dpi = 1200;
  dev->dpi = dpi;

  DBG (16, "sane_get_parameters: dpi set to %d\n", dpi);

  /* for highest resolutions , width must be aligned on 32 bit word */
  if (dpi >= 600)
    {
      remain = (dev->BottomX - dev->TopX) & 0x03;
      if (remain)
	{
	  DBG (64, "sane_get_parameters: %d-%d -> remain is %d\n",
	       dev->BottomX, dev->TopX, remain);
	  if (dev->BottomX + remain < dev->desc->max_h_size)
	    dev->BottomX += remain;
	  else
	    {
	      remain -= (dev->desc->max_h_size - dev->BottomX);
	      dev->BottomX = dev->desc->max_h_size;
	      dev->TopX -= remain;
	    }
	}
    }

  if (dev->val[OPT_PREVIEW].w == SANE_TRUE)
    {

      if (dev->val[OPT_GRAY_PREVIEW].w == SANE_TRUE)
	{
	  DBG (16, "sane_get_parameters: gray preview\n");
	  dev->color = UMAX_PP_MODE_GRAYSCALE;
	  dev->params.format = SANE_FRAME_GRAY;
	}
      else
	{
	  DBG (16, "sane_get_parameters: color preview\n");
	  dev->color = UMAX_PP_MODE_COLOR;
	  dev->params.format = SANE_FRAME_RGB;
	}

      dev->dpi = 75;
      dev->TopX = 0;
      dev->TopY = 0;
      dev->BottomX = dev->desc->max_h_size;
      dev->BottomY = dev->desc->max_v_size;
    }


  /* fill params */
  dev->params.last_frame = SANE_TRUE;
  dev->params.lines =
    ((dev->BottomY - dev->TopY) * dev->dpi) / dev->desc->ccd_res;
  if (dev->dpi >= dev->desc->ccd_res)
    dpi = dev->desc->ccd_res;
  else
    dpi = dev->dpi;
  dev->params.pixels_per_line =
    ((dev->BottomX - dev->TopX) * dpi) / dev->desc->ccd_res;
  if (dev->color == UMAX_PP_MODE_COLOR)
    {
      dev->params.bytes_per_line = dev->params.pixels_per_line * 3;
      dev->params.format = SANE_FRAME_RGB;
    }
  else
    {
      dev->params.bytes_per_line = dev->params.pixels_per_line;
      dev->params.format = SANE_FRAME_GRAY;
    }
  dev->params.depth = 8;

  /* success */
  if (params != NULL)
    memcpy (params, &(dev->params), sizeof (dev->params));
  return SANE_STATUS_GOOD;

}

SANE_Status
sane_start (SANE_Handle handle)
{
  Umax_PP_Device *dev = handle;
  int rc, autoset;
  int delta = 0, points;

  /* sanity check */
  if (dev->state == UMAX_PP_STATE_SCANNING)
    {
      DBG (2, "sane_start: device is already scanning\n");
      DEBUG ();

      return SANE_STATUS_DEVICE_BUSY;
    }

  /* if cancelled, check if head is back home */
  if (dev->state == UMAX_PP_STATE_CANCELLED)
    {
      DBG (2, "sane_start: checking if scanner is parking head .... \n");

      rc = sanei_umax_pp_status ();

      /* check if scanner busy parking */
      if (rc == UMAX1220P_BUSY)
	{
	  DBG (2, "sane_start: scanner busy\n");
	  return SANE_STATUS_DEVICE_BUSY;
	}
      dev->state = UMAX_PP_STATE_IDLE;
    }


  /* get values from options */
  sane_get_parameters (handle, NULL);

  /* sets lamp flag to TRUE */
  dev->val[OPT_LAMP_CONTROL].w = SANE_TRUE;

  /* tests if we do auto setting */
  if (dev->val[OPT_MANUAL_GAIN].w == SANE_TRUE)
    autoset = 0;
  else
    autoset = 1;


  /* call start scan */
  if (dev->color == UMAX_PP_MODE_COLOR)
    {
      delta = umax_pp_get_sync (dev->dpi);
      points = 2 * delta;
      /* first lines are 'garbage' for 610P */
      if (sanei_umax_pp_getastra () < 1210)
	points *= 2;
      DBG (64, "sane_start:umax_pp_start(%d,%d,%d,%d,%d,1,%X,%X)\n",
	   dev->TopX,
	   dev->TopY - points,
	   dev->BottomX - dev->TopX,
	   dev->BottomY - dev->TopY + points,
	   dev->dpi,
	   (dev->red_gain << 8) + (dev->green_gain << 4) +
	   dev->blue_gain,
	   (dev->red_offset << 8) + (dev->green_offset << 4) +
	   dev->blue_offset);

      rc = sanei_umax_pp_start (dev->TopX,
				dev->TopY - points,
				dev->BottomX - dev->TopX,
				dev->BottomY - dev->TopY + points,
				dev->dpi,
				2,
				autoset,
				(dev->red_gain << 8) |
				(dev->green_gain << 4) |
				dev->blue_gain,
				(dev->red_offset << 8) |
				(dev->green_offset << 4) |
				dev->blue_offset, &(dev->bpp), &(dev->tw),
				&(dev->th));
      /* we enlarged the scanning zone   */
      /* to allow reordering, we must    */
      /* substract it from real scanning */
      /* zone                            */
      dev->th -= points;
      DBG (64, "sane_start: bpp=%d,tw=%d,th=%d\n", dev->bpp, dev->tw,
	   dev->th);
    }
  else
    {
      DBG (64, "sane_start:umax_pp_start(%d,%d,%d,%d,%d,0,%X,%X)\n",
	   dev->TopX,
	   dev->TopY,
	   dev->BottomX - dev->TopX,
	   dev->BottomY - dev->TopY, dev->dpi, dev->gray_gain << 4,
	   dev->gray_offset << 4);
      rc = sanei_umax_pp_start (dev->TopX,
				dev->TopY,
				dev->BottomX - dev->TopX,
				dev->BottomY - dev->TopY,
				dev->dpi,
				1,
				autoset,
				dev->gray_gain << 4,
				dev->gray_offset << 4, &(dev->bpp),
				&(dev->tw), &(dev->th));
      DBG (64, "sane_start: bpp=%d,tw=%d,th=%d\n", dev->bpp, dev->tw,
	   dev->th);
    }

  if (rc != UMAX1220P_OK)
    {
      DBG (2, "sane_start: failure\n");
      return SANE_STATUS_IO_ERROR;
    }

  /* scan started, no bytes read */
  dev->state = UMAX_PP_STATE_SCANNING;
  dev->buflen = 0;
  dev->bufread = 0;
  dev->read = 0;

  /* leading lines for 610P aren't complete in color mode */
  /* and should be discarded                              */
  if ((sanei_umax_pp_getastra () < 1210)
      && (dev->color == UMAX_PP_MODE_COLOR))
    {
      rc =
	sanei_umax_pp_read (2 * delta * dev->tw * dev->bpp, dev->tw, dev->dpi,
			    0,
			    dev->buf + UMAX_PP_RESERVE -
			    2 * delta * dev->tw * dev->bpp);
      if (rc != UMAX1220P_OK)
	{
	  DBG (2, "sane_start: first lines discarding failed\n");
	  return SANE_STATUS_IO_ERROR;
	}
    }

  /* in case of color, we have to preload blue and green */
  /* data to allow reordering while later read           */
  if ((dev->color == UMAX_PP_MODE_COLOR) && (delta > 0))
    {
      rc =
	sanei_umax_pp_read (2 * delta * dev->tw * dev->bpp, dev->tw, dev->dpi,
			    0,
			    dev->buf + UMAX_PP_RESERVE -
			    2 * delta * dev->tw * dev->bpp);
      if (rc != UMAX1220P_OK)
	{
	  DBG (2, "sane_start: preload buffer failed\n");
	  return SANE_STATUS_IO_ERROR;
	}
    }

  /* OK .... */
  return SANE_STATUS_GOOD;

}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
	   SANE_Int * len)
{
  Umax_PP_Device *dev = handle;
  long int length;
  int last, rc;
  int x, y, nl, ll;
  SANE_Byte *lbuf;
  int max = 0;
  int min = 255;
  int delta = 0;


  /* no data until further notice */
  *len = 0;
  DBG (64, "sane_read(max_len=%d)\n", max_len);
  ll = dev->tw * dev->bpp;

  /* sanity check */
  if (dev->state == UMAX_PP_STATE_CANCELLED)
    {
      DBG (2, "sane_read: scan cancelled\n");
      DEBUG ();

      return SANE_STATUS_CANCELLED;
    }

  /* eof test */
  if (dev->read >= dev->th * ll)
    {
      DBG (2, "sane_read: end of scan reached\n");
      return SANE_STATUS_EOF;
    }

  /* read data from scanner if needed */
  if ((dev->buflen == 0) || (dev->bufread >= dev->buflen))
    {
      DBG (64, "sane_read: reading data from scanner\n");
      /* absolute number of bytes needed */
      length = ll * dev->th - dev->read;

      /* does all fit in a single last read ? */
      if (length <= dev->bufsize)
	{
	  last = 1;
	}
      else
	{
	  last = 0;
	  /* round number of scan lines */
	  length = (dev->bufsize / ll) * ll;
	}


      if (dev->color == UMAX_PP_MODE_COLOR)
	{
	  delta = umax_pp_get_sync (dev->dpi);
	  rc =
	    sanei_umax_pp_read (length, dev->tw, dev->dpi, last,
				dev->buf + UMAX_PP_RESERVE);
	}
      else
	rc = sanei_umax_pp_read (length, dev->tw, dev->dpi, last, dev->buf);
      if (rc != UMAX1220P_OK)
	return SANE_STATUS_IO_ERROR;
      dev->buflen = length;
      DBG (64, "sane_read: got %ld bytes of data from scanner\n", length);

      /* we transform data for software lineart */
      if (dev->color == UMAX_PP_MODE_LINEART)
	{
	  DBG (64, "sane_read: software lineart\n");

	  for (y = 0; y < length; y++)
	    {
	      if (dev->buf[y] > max)
		max = dev->buf[y];
	      if (dev->buf[y] < min)
		min = dev->buf[y];
	    }
	  max = (min + max) / 2;
	  for (y = 0; y < length; y++)
	    {
	      if (dev->buf[y] > max)
		dev->buf[y] = 255;
	      else
		dev->buf[y] = 0;
	    }
	}
      else if (dev->color == UMAX_PP_MODE_COLOR)
	{
	  /* number of lines */
	  nl = dev->buflen / ll;
	  DBG (64, "sane_read: reordering %ld bytes of data (lines=%d)\n",
	       length, nl);
	  lbuf = (SANE_Byte *) malloc (dev->bufsize + UMAX_PP_RESERVE);
	  if (lbuf == NULL)
	    {
	      DBG (1, "sane_read: couldn't allocate %ld bytes\n",
		   dev->bufsize + UMAX_PP_RESERVE);
	      return SANE_STATUS_NO_MEM;
	    }
	  /* reorder data in R,G,B values */
	  for (y = 0; y < nl; y++)
	    {
	      for (x = 0; x < dev->tw; x++)
		{
		  switch (sanei_umax_pp_getastra ())
		    {
		    case 610:
		      /* green value: sync'ed */
		      lbuf[x * dev->bpp + y * ll + 1 + UMAX_PP_RESERVE] =
			dev->buf[x + y * ll + 2 * dev->tw + UMAX_PP_RESERVE];

		      /* blue value, +delta line ahead of sync */
		      lbuf[x * dev->bpp + y * ll + 2 + UMAX_PP_RESERVE] =
			dev->buf[x + (y - delta) * ll + dev->tw +
				 UMAX_PP_RESERVE];

		      /* red value, +2*delta line ahead of sync */
		      lbuf[x * dev->bpp + y * ll + UMAX_PP_RESERVE] =
			dev->buf[x + (y - 2 * delta) * ll + UMAX_PP_RESERVE];

		      break;
		    default:
		      /* red value: sync'ed */
		      lbuf[x * dev->bpp + y * ll + UMAX_PP_RESERVE] =
			dev->buf[x + y * ll + 2 * dev->tw + UMAX_PP_RESERVE];

		      /* green value, +delta line ahead of sync */
		      lbuf[x * dev->bpp + y * ll + 1 + UMAX_PP_RESERVE] =
			dev->buf[x + (y - delta) * ll + dev->tw +
				 UMAX_PP_RESERVE];

		      /* blue value, +2*delta line ahead of sync */
		      lbuf[x * dev->bpp + y * ll + 2 + UMAX_PP_RESERVE] =
			dev->buf[x + (y - 2 * delta) * ll + UMAX_PP_RESERVE];
		    }
		}
	    }
	  /* store last data lines for next reordering */
	  if (!last)
	    memcpy (lbuf + UMAX_PP_RESERVE - 2 * delta * ll,
		    dev->buf + UMAX_PP_RESERVE + dev->buflen - 2 * delta * ll,
		    2 * delta * ll);
	  free (dev->buf);
	  dev->buf = lbuf;
	}
      dev->bufread = 0;
    }

  /* how much get data we can get from memory buffer */
  length = dev->buflen - dev->bufread;
  DBG (64, "sane_read: %ld bytes of data available\n", length);
  if (length > max_len)
    length = max_len;



  if (dev->color == UMAX_PP_MODE_COLOR)
    memcpy (buf, dev->buf + dev->bufread + UMAX_PP_RESERVE, length);
  else
    memcpy (buf, dev->buf + dev->bufread, length);
  *len = length;
  dev->bufread += length;
  dev->read += length;
  DBG (64, "sane_read: %ld bytes read\n", length);

  return SANE_STATUS_GOOD;

}

void
sane_cancel (SANE_Handle handle)
{
  Umax_PP_Device *dev = handle;
  int rc;

  DBG (64, "sane_cancel\n");
  if (dev->state == UMAX_PP_STATE_IDLE)
    {
      DBG (3, "cancel: cancelling idle \n");
      return;
    }
  if (dev->state == UMAX_PP_STATE_SCANNING)
    {
      DBG (3, "cancel: stopping current scan\n");

      dev->buflen = 0;

      dev->state = UMAX_PP_STATE_CANCELLED;
      sanei_umax_pp_cancel ();
    }
  else
    {
      /* STATE_CANCELLED */
      DBG (2, "cancel: checking if scanner is still parking head .... \n");

      rc = sanei_umax_pp_status ();

      /* check if scanner busy parking */
      if (rc == UMAX1220P_BUSY)
	{
	  DBG (2, "cancel: scanner busy\n");
	  return;
	}
      dev->state = UMAX_PP_STATE_IDLE;
    }
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  DBG (129, "unused arg: handle = %p, non_blocking = %d\n",
       handle, (int) non_blocking);

  DBG (2, "set_io_mode: not supported\n");

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{

  DBG (129, "unused arg: handle = %p, fd = %p\n", handle, (void *) fd);

  DBG (2, "get_select_fd: not supported\n");

  return SANE_STATUS_UNSUPPORTED;
}
