/* HP Scanjet 3900 series - SANE Backend controller
   Copyright (C) 2005-2009 Jonathan Bravo Lopez <jkdsoft@gmail.com>

   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, see <https://www.gnu.org/licenses/>.

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

/* Backend Code for SANE*/
#define HP3900_CONFIG_FILE "hp3900.conf"
#define GAMMA_DEFAULT 1.0

#include "../include/sane/config.h"
#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/sanei_backend.h"
#include "../include/sane/sanei_config.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_usb.h"
#include "../include/sane/sanei_debug.h"

#include "hp3900_rts8822.c"

struct st_convert
{
  SANE_Int colormode;
  SANE_Int depth;
  SANE_Int threshold;
  SANE_Int negative;
  SANE_Int real_depth;
};

/* options enumerator */
typedef enum
{
  opt_begin = 0,

  grp_geometry,
  opt_tlx, opt_tly, opt_brx, opt_bry,
  opt_resolution,

  /* gamma tables */
  opt_gamma_red,
  opt_gamma_green,
  opt_gamma_blue,

  opt_scantype,
  opt_colormode,
  opt_depth,
  opt_threshold,

  /* debugging options */
  grp_debug,
  opt_model,
  opt_negative,
  opt_nogamma,
  opt_nowshading,
  opt_realdepth,
  opt_emulategray,
  opt_nowarmup,
  opt_dbgimages,
  opt_reset,

  /* device information */
  grp_info,
  opt_chipname,
  opt_chipid,
  opt_scancount,
  opt_infoupdate,

  /* supported buttons. RTS8822 supports up to 6 buttons */
  grp_sensors,
  opt_button_0,
  opt_button_1,
  opt_button_2,
  opt_button_3,
  opt_button_4,
  opt_button_5,

  opt_count
} EOptionIndex;

/* linked list of SANE_Device structures */
typedef struct TDevListEntry
{
  struct TDevListEntry *pNext;
  SANE_Device dev;
  char *devname;
} TDevListEntry;

typedef struct
{
  char *pszVendor;
  char *pszName;
} TScannerModel;

typedef union
{
  SANE_Word w;
  SANE_Word *wa;		/* word array */
  SANE_String s;
} TOptionValue;

typedef struct
{
  SANE_Int model;
  SANE_Option_Descriptor aOptions[opt_count];
  TOptionValue aValues[opt_count];
  struct params ScanParams;

  /* lists */
  SANE_String_Const *list_colormodes;
  SANE_Int *list_depths;
  SANE_String_Const *list_models;
  SANE_Int *list_resolutions;
  SANE_String_Const *list_sources;

  SANE_Word *aGammaTable[3];	/* a 16-to-16 bit color lookup table */
  SANE_Range rng_gamma;

  /* reading image */
  SANE_Byte *image;
  SANE_Byte *rest;
  SANE_Int rest_amount;
  SANE_Int mylin;

  /* conversion settings */
  struct st_convert cnv;

  /* ranges */
  SANE_Range rng_threshold;
  SANE_Range rng_horizontal;
  SANE_Range rng_vertical;

  SANE_Int scan_count;
  SANE_Int fScanning;		/* TRUE if actively scanning */
} TScanner;

/* functions to manage backend's options */
static void options_init (TScanner * scanner);
static void options_free (TScanner * scanner);

/* devices listing */
static SANE_Int _ReportDevice (TScannerModel * pModel,
			       const char *pszDeviceName);
static SANE_Status attach_one_device (SANE_String_Const devname);

/* capabilities */
static SANE_Status bknd_colormodes (TScanner * scanner, SANE_Int model);
static void bknd_constrains (TScanner * scanner, SANE_Int source,
			     SANE_Int type);
static SANE_Status bknd_depths (TScanner * scanner, SANE_Int model);
static SANE_Status bknd_info (TScanner * scanner);
static SANE_Status bknd_models (TScanner * scanner);
static SANE_Status bknd_resolutions (TScanner * scanner, SANE_Int model);
static SANE_Status bknd_sources (TScanner * scanner, SANE_Int model);

/* conversions */
static void Color_Negative (SANE_Byte * buffer, SANE_Int size,
			    SANE_Int depth);
static void Color_to_Gray (SANE_Byte * buffer, SANE_Int size, SANE_Int depth);
static void Gray_to_Lineart (SANE_Byte * buffer, SANE_Int size,
			     SANE_Int threshold);
static void Depth_16_to_8 (SANE_Byte * from_buffer, SANE_Int size,
			   SANE_Byte * to_buffer);

/* gamma functions */
static void gamma_apply (TScanner * s, SANE_Byte * buffer, SANE_Int size,
			 SANE_Int depth);
static SANE_Int gamma_create (TScanner * s, double gamma);
static void gamma_free (TScanner * s);

static SANE_Int Get_Colormode (SANE_String colormode);
static SANE_Int Get_Model (SANE_String model);
static SANE_Int Get_Source (SANE_String source);
static SANE_Int GetUSB_device_model (SANE_String_Const name);
static size_t max_string_size (const SANE_String_Const strings[]);

static SANE_Status get_button_status (TScanner * s);

/* reading buffers */
static SANE_Status img_buffers_alloc (TScanner * scanner, SANE_Int size);
static SANE_Status img_buffers_free (TScanner * scanner);

static SANE_Status option_get (TScanner * scanner, SANE_Int optid,
			       void *result);
static SANE_Status option_set (TScanner * scanner, SANE_Int optid,
			       void *value, SANE_Int * pInfo);

static void Set_Coordinates (SANE_Int scantype, SANE_Int resolution,
			     struct st_coords *coords);
static SANE_Int set_ScannerModel (SANE_Int proposed, SANE_Int product,
				  SANE_Int vendor);
static void Silent_Compile (void);
static SANE_Status Translate_coords (struct st_coords *coords);

/* SANE functions */
void sane_cancel (SANE_Handle h);
void sane_close (SANE_Handle h);
SANE_Status sane_control_option (SANE_Handle h, SANE_Int n,
				 SANE_Action Action, void *pVal,
				 SANE_Int * pInfo);
void sane_exit (void);
SANE_Status sane_get_devices (const SANE_Device *** device_list,
			      SANE_Bool local_only);
const SANE_Option_Descriptor *sane_get_option_descriptor (SANE_Handle h,
							  SANE_Int n);
SANE_Status sane_get_parameters (SANE_Handle h, SANE_Parameters * p);
SANE_Status sane_get_select_fd (SANE_Handle handle, SANE_Int * fd);
SANE_Status sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize);
SANE_Status sane_open (SANE_String_Const name, SANE_Handle * h);
SANE_Status sane_read (SANE_Handle h, SANE_Byte * buf, SANE_Int maxlen,
		       SANE_Int * len);
SANE_Status sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking);
SANE_Status sane_start (SANE_Handle h);

/* variables */
static struct st_device *device = NULL;
static TDevListEntry *_pFirstSaneDev = 0;
static SANE_Int iNumSaneDev = 0;
static const SANE_Device **_pSaneDevList = 0;

/* Own functions */

static SANE_Status
bknd_resolutions (TScanner * scanner, SANE_Int model)
{
  SANE_Status rst = SANE_STATUS_INVAL;

  DBG (DBG_FNC, "> bknd_resolutions(*scanner, model=%i)\n", model);

  if (scanner != NULL)
    {
      SANE_Int *res = NULL;

      switch (model)
	{
	case BQ5550:
	case UA4900:
	  {
	    SANE_Int myres[] = { 8, 50, 75, 100, 150, 200, 300, 600, 1200 };

	    res = (SANE_Int *) malloc (sizeof (myres));
	    if (res != NULL)
	      memcpy (res, &myres, sizeof (myres));
	  }
	  break;

	case HPG2710:
	case HP3800:
	  {
	    /* 1200 and 2400 dpi are disabled until problems are solved */
	    SANE_Int myres[] = { 7, 50, 75, 100, 150, 200, 300, 600 };

	    res = (SANE_Int *) malloc (sizeof (myres));
	    if (res != NULL)
	      memcpy (res, &myres, sizeof (myres));
	  }
	  break;

	case HP4370:
	case HPG3010:
	case HPG3110:
	  {
	    SANE_Int myres[] =
	      { 10, 50, 75, 100, 150, 200, 300, 600, 1200, 2400, 4800 };

	    res = (SANE_Int *) malloc (sizeof (myres));
	    if (res != NULL)
	      memcpy (res, &myres, sizeof (myres));
	  }
	  break;

	default:		/* HP3970 & HP4070 & UA4900 */
	  {
	    SANE_Int myres[] =
	      { 9, 50, 75, 100, 150, 200, 300, 600, 1200, 2400 };

	    res = (SANE_Int *) malloc (sizeof (myres));
	    if (res != NULL)
	      memcpy (res, &myres, sizeof (myres));
	  }
	  break;
	}

      if (res != NULL)
	{
	  if (scanner->list_resolutions != NULL)
	    free (scanner->list_resolutions);

	  scanner->list_resolutions = res;
	  rst = SANE_STATUS_GOOD;
	}
    }

  return rst;
}

static SANE_Status
bknd_models (TScanner * scanner)
{
  SANE_Status rst = SANE_STATUS_INVAL;

  DBG (DBG_FNC, "> bknd_models:\n");

  if (scanner != NULL)
    {
      SANE_String_Const *model = NULL;

      /* at this moment all devices use the same list */
      SANE_String_Const mymodel[] =
	{ "HP3800", "HP3970", "HP4070", "HP4370", "UA4900", "HPG3010",
"BQ5550", "HPG2710", "HPG3110", 0 };

      /* allocate space to save list */
      model = (SANE_String_Const *) malloc (sizeof (mymodel));
      if (model != NULL)
	memcpy (model, &mymodel, sizeof (mymodel));

      if (model != NULL)
	{
	  /* free previous list */
	  if (scanner->list_models != NULL)
	    free (scanner->list_models);

	  /* set new list */
	  scanner->list_models = model;
	  rst = SANE_STATUS_GOOD;
	}
    }

  return rst;
}

static SANE_Status
bknd_colormodes (TScanner * scanner, SANE_Int model)
{
  SANE_Status rst = SANE_STATUS_INVAL;

  DBG (DBG_FNC, "> bknd_colormodes(*scanner, model=%i)\n", model);

  if (scanner != NULL)
    {
      SANE_String_Const *colormode = NULL;

      /* at this moment all devices use the same list */
      SANE_String_Const mycolormode[] =
	{ SANE_VALUE_SCAN_MODE_COLOR, SANE_VALUE_SCAN_MODE_GRAY, SANE_VALUE_SCAN_MODE_LINEART, 0 };

      /* silence gcc */
      (void) model;

      colormode = (SANE_String_Const *) malloc (sizeof (mycolormode));
      if (colormode != NULL)
	memcpy (colormode, &mycolormode, sizeof (mycolormode));

      if (colormode != NULL)
	{
	  if (scanner->list_colormodes != NULL)
	    free (scanner->list_colormodes);

	  scanner->list_colormodes = colormode;
	  rst = SANE_STATUS_GOOD;
	}
    }

  return rst;
}

static SANE_Status
bknd_sources (TScanner * scanner, SANE_Int model)
{
  SANE_Status rst = SANE_STATUS_INVAL;

  DBG (DBG_FNC, "> bknd_sources(*scanner, model=%i)\n", model);

  if (scanner != NULL)
    {
      SANE_String_Const *source = NULL;

      switch (model)
	{
	case UA4900:
	  {
	    SANE_String_Const mysource[] = { SANE_I18N ("Flatbed"), 0 };
	    source = (SANE_String_Const *) malloc (sizeof (mysource));
	    if (source != NULL)
	      memcpy (source, &mysource, sizeof (mysource));
	  }
	  break;
	default:		/* hp3970, hp4070, hp4370 and others */
	  {
	    SANE_String_Const mysource[] =
	      { SANE_I18N ("Flatbed"), SANE_I18N ("Slide"),
SANE_I18N ("Negative"), 0 };
	    source = (SANE_String_Const *) malloc (sizeof (mysource));
	    if (source != NULL)
	      memcpy (source, &mysource, sizeof (mysource));
	  }
	  break;
	}

      if (source != NULL)
	{
	  if (scanner->list_sources != NULL)
	    free (scanner->list_sources);

	  scanner->list_sources = source;
	  rst = SANE_STATUS_GOOD;
	}
    }

  return rst;
}

static SANE_Status
bknd_depths (TScanner * scanner, SANE_Int model)
{
  SANE_Status rst = SANE_STATUS_INVAL;

  DBG (DBG_FNC, "> bknd_depths(*scanner, model=%i\n", model);

  if (scanner != NULL)
    {
      SANE_Int *depth = NULL;

      /* at this moment all devices use the same list */
      SANE_Int mydepth[] = { 2, 8, 16 };	/*{3, 8, 12, 16}; */

      /* silence gcc */
      (void) model;

      depth = (SANE_Int *) malloc (sizeof (mydepth));
      if (depth != NULL)
	memcpy (depth, &mydepth, sizeof (mydepth));

      if (depth != NULL)
	{
	  if (scanner->list_depths != NULL)
	    free (scanner->list_depths);

	  scanner->list_depths = depth;
	  rst = SANE_STATUS_GOOD;
	}
    }

  return rst;
}

static SANE_Status
bknd_info (TScanner * scanner)
{
  SANE_Status rst = SANE_STATUS_INVAL;

  DBG (DBG_FNC, "> bknd_info(*scanner)");

  if (scanner != NULL)
    {
      char data[256];

      /* update chipset name */
      Chipset_Name (device, data, 255);
      if (scanner->aValues[opt_chipname].s != NULL)
	{
	  free (scanner->aValues[opt_chipname].s);
	  scanner->aValues[opt_chipname].s = NULL;
	}

      scanner->aValues[opt_chipname].s = strdup (data);
      scanner->aOptions[opt_chipname].size = strlen (data) + 1;

      /* update chipset id */
      scanner->aValues[opt_chipid].w = Chipset_ID (device);

      /* update scans counter */
      scanner->aValues[opt_scancount].w = RTS_ScanCounter_Get (device);

      rst = SANE_STATUS_GOOD;
    }

  return rst;
}

static SANE_Int
GetUSB_device_model (SANE_String_Const name)
{
  SANE_Int usbid, model;

  /* default model is unknown */
  model = -1;

  /* open usb device */
  if (sanei_usb_open (name, &usbid) == SANE_STATUS_GOOD)
    {
      SANE_Int vendor, product;

      if (sanei_usb_get_vendor_product (usbid, &vendor, &product) ==
	  SANE_STATUS_GOOD)
	model = Device_get (product, vendor);

      sanei_usb_close (usbid);
    }

  return model;
}

static void
Silent_Compile (void)
{
  /*
     There are some functions in hp3900_rts8822.c that aren't used yet.
     To avoid compilation warnings we will use them here
   */

  SANE_Byte a = 1;

  if (a == 0)
    {
      Buttons_Status (device);
      Calib_WriteTable (device, NULL, 0, 0);
      Gamma_GetTables (device, NULL);
    }
}

static void
bknd_constrains (TScanner * scanner, SANE_Int source, SANE_Int type)
{
  struct st_coords *coords = Constrains_Get (device, source);

  if ((coords != NULL) && (scanner != NULL))
    {
      switch (type)
	{
	case 1:		/* Y */
	  scanner->rng_vertical.max = coords->height;
	  break;
	default:		/* X */
	  scanner->rng_horizontal.max = coords->width;
	  break;
	}
    }
}

static SANE_Status
img_buffers_free (TScanner * scanner)
{
  if (scanner != NULL)
    {
      if (scanner->image != NULL)
	{
	  free (scanner->image);
	  scanner->image = NULL;
	}

      if (scanner->rest != NULL)
	{
	  free (scanner->rest);
	  scanner->rest = NULL;
	}

      scanner->rest_amount = 0;
    }

  return SANE_STATUS_GOOD;
}

static SANE_Status
img_buffers_alloc (TScanner * scanner, SANE_Int size)
{
  SANE_Status rst;

  /* default result at this point */
  rst = SANE_STATUS_INVAL;

  if (scanner != NULL)
    {
      /* default result at this point */
      rst = SANE_STATUS_NO_MEM;

      /* free previous allocs */
      img_buffers_free (scanner);

      scanner->image = (SANE_Byte *) malloc (size * sizeof (SANE_Byte));
      if (scanner->image != NULL)
	{
	  scanner->rest = (SANE_Byte *) malloc (size * sizeof (SANE_Byte));
	  if (scanner->rest != NULL)
	    rst = SANE_STATUS_GOOD;	/* ok !! */
	}

      if (rst != SANE_STATUS_GOOD)
	img_buffers_free (scanner);
    }

  return rst;
}

static SANE_Int
set_ScannerModel (SANE_Int proposed, SANE_Int product, SANE_Int vendor)
{
  /* This function will set the device behaviour */

  SANE_Int current = Device_get (product, vendor);
  char *sdevname[10] =
    { "Unknown", "HP3970", "HP4070", "HP4370", "UA4900", "HP3800", "HPG3010",
"BQ5550", "HPG2710", "HPG3110" };

  DBG (DBG_FNC,
       "> set_ScannerModel(proposed=%i, product=%04x, vendor=%04x)\n",
       proposed, product, vendor);

  if (proposed < 0)
    {
      if ((current < 0) || (current >= DEVSCOUNT))
	{
	  DBG (DBG_VRB, " -> Unknown device. Defaulting to HP3970...\n");
	  RTS_Debug->dev_model = HP3970;
	}
      else
	{
	  RTS_Debug->dev_model = current;
	  DBG (DBG_VRB, " -> Device model is %s\n", sdevname[current + 1]);
	}
    }
  else
    {
      if (proposed < DEVSCOUNT)
	{
	  RTS_Debug->dev_model = proposed;
	  DBG (DBG_VRB, " -> Device %s ,  treating as %s ...\n",
	       sdevname[current + 1], sdevname[proposed + 1]);
	}
      else
	{
	  if ((current >= 0) && (current < DEVSCOUNT))
	    {
	      RTS_Debug->dev_model = current;
	      DBG (DBG_VRB,
		   " -> Device not supported. Defaulting to %s ...\n",
		   sdevname[current + 1]);
	    }
	  else
	    {
	      RTS_Debug->dev_model = HP3970;
	      DBG (DBG_VRB,
		   "-> Device not supported. Defaulting to HP3970...\n");
	    }
	}
    }

  return OK;
}

static void
Set_Coordinates (SANE_Int scantype, SANE_Int resolution,
		 struct st_coords *coords)
{
  struct st_coords *limits = Constrains_Get (device, scantype);

  DBG (DBG_FNC, "> Set_Coordinates(res=%i, *coords):\n", resolution);

  if (coords->left == -1)
    coords->left = 0;

  if (coords->width == -1)
    coords->width = limits->width;

  if (coords->top == -1)
    coords->top = 0;

  if (coords->height == -1)
    coords->height = limits->height;

  DBG (DBG_FNC, " -> Coords [MM] : xy(%i, %i) wh(%i, %i)\n", coords->left,
       coords->top, coords->width, coords->height);

  coords->left = MM_TO_PIXEL (coords->left, resolution);
  coords->width = MM_TO_PIXEL (coords->width, resolution);
  coords->top = MM_TO_PIXEL (coords->top, resolution);
  coords->height = MM_TO_PIXEL (coords->height, resolution);

  DBG (DBG_FNC, " -> Coords [px] : xy(%i, %i) wh(%i, %i)\n", coords->left,
       coords->top, coords->width, coords->height);

  Constrains_Check (device, resolution, scantype, coords);

  DBG (DBG_FNC, " -> Coords [check]: xy(%i, %i) wh(%i, %i)\n", coords->left,
       coords->top, coords->width, coords->height);
}

static void
Color_Negative (SANE_Byte * buffer, SANE_Int size, SANE_Int depth)
{
  if (buffer != NULL)
    {
      SANE_Int a;
      SANE_Int max_value = (1 << depth) - 1;

      if (depth > 8)
	{
	  USHORT *sColor = (void *) buffer;
	  for (a = 0; a < size / 2; a++)
	    {
	      *sColor = max_value - *sColor;
	      sColor++;
	    }
	}
      else
	{
	  for (a = 0; a < size; a++)
	    *(buffer + a) = max_value - *(buffer + a);
	}
    }
}

static SANE_Status
get_button_status (TScanner * s)
{
  if (s != NULL)
    {
      SANE_Int a, b, status, btn;

      b = 1;
      status = Buttons_Released (device) & 63;
      for (a = 0; a < 6; a++)
	{
	  if ((status & b) != 0)
	    {
	      btn = Buttons_Order (device, b);
	      if (btn != -1)
		s->aValues[opt_button_0 + btn].w = SANE_TRUE;
	    }

	  b <<= 1;
	}
    }

  return SANE_STATUS_GOOD;
}

static void
Depth_16_to_8 (SANE_Byte * from_buffer, SANE_Int size, SANE_Byte * to_buffer)
{
  if ((from_buffer != NULL) && (to_buffer != NULL))
    {
      SANE_Int a, b;

      a = 1;
      b = 0;

      while (a < size)
	{
	  *(to_buffer + b) = *(from_buffer + a);
	  a += 2;
	  b++;
	}
    }
}

static void
Gray_to_Lineart (SANE_Byte * buffer, SANE_Int size, SANE_Int threshold)
{
  /* code provided by tobias leutwein */

  if (buffer != NULL)
    {
      SANE_Byte toBufferByte;
      SANE_Int fromBufferPos_i = 0;
      SANE_Int toBufferPos_i = 0;
      SANE_Int bitPos_i;

      while (fromBufferPos_i < size)
	{
	  toBufferByte = 0;

	  for (bitPos_i = 7; bitPos_i != (-1); bitPos_i--)
	    {
	      if ((fromBufferPos_i < size)
		  && (buffer[fromBufferPos_i] < threshold))
		toBufferByte |= (1u << bitPos_i);

	      fromBufferPos_i++;
	    }

	  buffer[toBufferPos_i] = toBufferByte;
	  toBufferPos_i++;
	}
    }
}

static void
Color_to_Gray (SANE_Byte * buffer, SANE_Int size, SANE_Int depth)
{
  /* converts 3 color channel into 1 gray channel of specified bit depth */

  if (buffer != NULL)
    {
      SANE_Int c, chn, chn_size;
      SANE_Byte *ptr_src = NULL;
      SANE_Byte *ptr_dst = NULL;
      float data, chn_data;
      float coef[3] = { 0.299, 0.587, 0.114 };	/* coefficients per channel */

      chn_size = (depth > 8) ? 2 : 1;
      ptr_src = (void *) buffer;
      ptr_dst = (void *) buffer;

      for (c = 0; c < size / (3 * chn_size); c++)
	{
	  data = 0.;

	  /* get, apply coeffs and sum channels */
	  for (chn = 0; chn < 3; chn++)
	    {
	      chn_data = data_lsb_get (ptr_src + (chn * chn_size), chn_size);
	      data += (chn_data * coef[chn]);
	    }

	  /* save result */
	  data_lsb_set (ptr_dst, (SANE_Int) data, chn_size);

	  ptr_src += 3 * chn_size;	/* next triplet */
	  ptr_dst += chn_size;
	}
    }
}

static void
gamma_free (TScanner * s)
{
  DBG (DBG_FNC, "> gamma_free()\n");

  if (s != NULL)
    {
      /* Destroy gamma tables */
      SANE_Int a;

      for (a = CL_RED; a <= CL_BLUE; a++)
	{
	  if (s->aGammaTable[a] != NULL)
	    {
	      free (s->aGammaTable[a]);
	      s->aGammaTable[a] = NULL;
	    }
	}
    }
}

static SANE_Int
gamma_create (TScanner * s, double gamma)
{
  SANE_Int rst = ERROR;		/* by default */

  DBG (DBG_FNC, "> gamma_create(*s)\n");

  if (s != NULL)
    {
      SANE_Int a;
      double value, c;

      /* default result */
      rst = OK;

      /* destroy previous gamma tables */
      gamma_free (s);

      /* check gamma value */
      if (gamma < 0)
	gamma = GAMMA_DEFAULT;

      /* allocate space for 16 bit gamma tables */
      for (a = CL_RED; a <= CL_BLUE; a++)
	{
	  s->aGammaTable[a] = malloc (65536 * sizeof (SANE_Word));
	  if (s->aGammaTable[a] == NULL)
	    {
	      rst = ERROR;
	      break;
	    }
	}

      if (rst == OK)
	{
	  /* fill tables */
	  for (a = 0; a < 65536; a++)
	    {
	      value = (a / (65536. - 1));
	      value = pow (value, (1. / gamma));
	      value = value * (65536. - 1);

	      c = (SANE_Int) value;
	      if (c > (65536. - 1))
		c = (65536. - 1);
	      else if (c < 0)
		c = 0;

	      s->aGammaTable[CL_RED][a] = c;
	      s->aGammaTable[CL_GREEN][a] = c;
	      s->aGammaTable[CL_BLUE][a] = c;
	    }
	}
      else
	gamma_free (s);
    }

  return rst;
}

static void
gamma_apply (TScanner * s, SANE_Byte * buffer, SANE_Int size, SANE_Int depth)
{
  if ((s != NULL) && (buffer != NULL))
    {
      SANE_Int c;
      SANE_Int dot_size = 3 * ((depth > 8) ? 2 : 1);
      SANE_Byte *pColor = buffer;
      USHORT *sColor = (void *) buffer;

      if ((s->aGammaTable[CL_RED] != NULL)
	  && (s->aGammaTable[CL_GREEN] != NULL)
	  && (s->aGammaTable[CL_BLUE] != NULL))
	{
	  for (c = 0; c < size / dot_size; c++)
	    {
	      if (depth > 8)
		{
		  *sColor = s->aGammaTable[CL_RED][*sColor];
		  *(sColor + 1) = s->aGammaTable[CL_GREEN][*(sColor + 1)];
		  *(sColor + 2) = s->aGammaTable[CL_BLUE][*(sColor + 2)];
		  sColor += 3;
		}
	      else
		{
		  /* 8 bits gamma */
		  *pColor =
		    (s->aGammaTable[CL_RED][*pColor * 256] >> 8) & 0xff;
		  *(pColor + 1) =
		    (s->
		     aGammaTable[CL_GREEN][*(pColor + 1) * 256] >> 8) & 0xff;
		  *(pColor + 2) =
		    (s->
		     aGammaTable[CL_BLUE][*(pColor + 2) * 256] >> 8) & 0xff;
		  pColor += 3;
		}
	    }
	}
    }
}

static SANE_Int
Get_Model (SANE_String model)
{
  SANE_Int rst;

  if (strcmp (model, "HP3800") == 0)
    rst = HP3800;
  else if (strcmp (model, "HPG2710") == 0)
    rst = HPG2710;
  else if (strcmp (model, "HP3970") == 0)
    rst = HP3970;
  else if (strcmp (model, "HP4070") == 0)
    rst = HP4070;
  else if (strcmp (model, "HP4370") == 0)
    rst = HP4370;
  else if (strcmp (model, "HPG3010") == 0)
    rst = HPG3010;
  else if (strcmp (model, "HPG3110") == 0)
    rst = HPG3110;
  else if (strcmp (model, "UA4900") == 0)
    rst = UA4900;
  else if (strcmp (model, "BQ5550") == 0)
    rst = BQ5550;
  else
    rst = HP3970;		/* default */

  return rst;
}

static SANE_Int
Get_Source (SANE_String source)
{
  SANE_Int rst;

  if (strcmp (source, SANE_I18N ("Flatbed")) == 0)
    rst = ST_NORMAL;
  else if (strcmp (source, SANE_I18N ("Slide")) == 0)
    rst = ST_TA;
  else if (strcmp (source, SANE_I18N ("Negative")) == 0)
    rst = ST_NEG;
  else
    rst = ST_NORMAL;		/* default */

  return rst;
}

static SANE_Int
Get_Colormode (SANE_String colormode)
{
  SANE_Int rst;

  if (strcmp (colormode, SANE_VALUE_SCAN_MODE_COLOR) == 0)
    rst = CM_COLOR;
  else if (strcmp (colormode, SANE_VALUE_SCAN_MODE_GRAY) == 0)
    rst = CM_GRAY;
  else if (strcmp (colormode, SANE_VALUE_SCAN_MODE_LINEART) == 0)
    rst = CM_LINEART;
  else
    rst = CM_COLOR;		/* default */

  return rst;
}

static SANE_Status
Translate_coords (struct st_coords *coords)
{
  SANE_Int data;

  DBG (DBG_FNC, "> Translate_coords(*coords)\n");

  if ((coords->left < 0) || (coords->top < 0) ||
      (coords->width < 0) || (coords->height < 0))
    return SANE_STATUS_INVAL;

  if (coords->width < coords->left)
    {
      data = coords->left;
      coords->left = coords->width;
      coords->width = data;
    }

  if (coords->height < coords->top)
    {
      data = coords->top;
      coords->top = coords->height;
      coords->height = data;
    }

  coords->width -= coords->left;
  coords->height -= coords->top;

  if (coords->width == 0)
    coords->width++;

  if (coords->height == 0)
    coords->height++;

  return SANE_STATUS_GOOD;
}

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

  DBG (DBG_FNC, "> max_string_size:\n");

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

  return max_size;
}

static void
options_free (TScanner * scanner)
{
  /* frees all information contained in controls */

  DBG (DBG_FNC, "> options_free\n");

  if (scanner != NULL)
    {
      SANE_Int i;
      SANE_Option_Descriptor *pDesc;
      TOptionValue *pVal;

      /* free gamma tables */
      gamma_free (scanner);

      /* free lists */
      if (scanner->list_resolutions != NULL)
	free (scanner->list_resolutions);

      if (scanner->list_depths != NULL)
	free (scanner->list_depths);

      if (scanner->list_sources != NULL)
	free (scanner->list_sources);

      if (scanner->list_colormodes != NULL)
	free (scanner->list_colormodes);

      if (scanner->list_models != NULL)
	free (scanner->list_models);

      /* free values in certain controls */
      for (i = opt_begin; i < opt_count; i++)
	{
	  pDesc = &scanner->aOptions[i];
	  pVal = &scanner->aValues[i];

	  if (pDesc->type == SANE_TYPE_STRING)
	    {
	      if (pVal->s != NULL)
		free (pVal->s);
	    }
	}
    }
}

static void
options_init (TScanner * scanner)
{
  /* initializes all controls */

  DBG (DBG_FNC, "> options_init\n");

  if (scanner != NULL)
    {
      SANE_Int i;
      SANE_Option_Descriptor *pDesc;
      TOptionValue *pVal;

      /* set gamma */
      gamma_create (scanner, 1.0);

      /* color conversion */
      scanner->cnv.colormode = -1;
      scanner->cnv.negative = FALSE;
      scanner->cnv.threshold = 40;
      scanner->cnv.real_depth = FALSE;
      scanner->cnv.depth = -1;

      /* setting threshold */
      scanner->rng_threshold.min = 0;
      scanner->rng_threshold.max = 255;
      scanner->rng_threshold.quant = 0;

      /* setting gamma range (16 bits depth) */
      scanner->rng_gamma.min = 0;
      scanner->rng_gamma.max = 65535;
      scanner->rng_gamma.quant = 0;

      /* setting default horizontal constraint in millimeters */
      scanner->rng_horizontal.min = 0;
      scanner->rng_horizontal.max = 220;
      scanner->rng_horizontal.quant = 1;

      /* setting default vertical constraint in millimeters */
      scanner->rng_vertical.min = 0;
      scanner->rng_vertical.max = 300;
      scanner->rng_vertical.quant = 1;

      /* allocate option lists */
      bknd_info (scanner);
      bknd_colormodes (scanner, RTS_Debug->dev_model);
      bknd_depths (scanner, RTS_Debug->dev_model);
      bknd_models (scanner);
      bknd_resolutions (scanner, RTS_Debug->dev_model);
      bknd_sources (scanner, RTS_Debug->dev_model);

      /* By default preview scan */
      scanner->ScanParams.scantype = ST_NORMAL;
      scanner->ScanParams.colormode = CM_COLOR;
      scanner->ScanParams.resolution_x = 75;
      scanner->ScanParams.resolution_y = 75;
      scanner->ScanParams.coords.left = 0;
      scanner->ScanParams.coords.top = 0;
      scanner->ScanParams.coords.width = 220;
      scanner->ScanParams.coords.height = 300;
      scanner->ScanParams.depth = 8;
      scanner->ScanParams.channel = 0;

      for (i = opt_begin; i < opt_count; i++)
	{
	  pDesc = &scanner->aOptions[i];
	  pVal = &scanner->aValues[i];

	  /* defaults */
	  pDesc->name = "";
	  pDesc->title = "";
	  pDesc->desc = "";
	  pDesc->type = SANE_TYPE_INT;
	  pDesc->unit = SANE_UNIT_NONE;
	  pDesc->size = sizeof (SANE_Word);
	  pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	  pDesc->cap = 0;

	  switch (i)
	    {
	    case opt_begin:
	      pDesc->title = SANE_TITLE_NUM_OPTIONS;
	      pDesc->desc = SANE_DESC_NUM_OPTIONS;
	      pDesc->cap = SANE_CAP_SOFT_DETECT;
	      pVal->w = (SANE_Word) opt_count;
	      break;

	    case grp_geometry:
	      pDesc->name = SANE_NAME_GEOMETRY;
	      pDesc->title = SANE_TITLE_GEOMETRY;
	      pDesc->desc = SANE_DESC_GEOMETRY;
	      pDesc->type = SANE_TYPE_GROUP;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = 0;
	      pDesc->cap = 0;
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.range = 0;
	      pVal->w = 0;
	      break;

	    case opt_tlx:
	      pDesc->name = SANE_NAME_SCAN_TL_X;
	      pDesc->title = SANE_TITLE_SCAN_TL_X;
	      pDesc->desc = SANE_DESC_SCAN_TL_X;
	      pDesc->unit = SANE_UNIT_MM;
	      pDesc->constraint_type = SANE_CONSTRAINT_RANGE;
	      pDesc->constraint.range = &scanner->rng_horizontal;
	      pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	      pVal->w = 0;
	      break;

	    case opt_tly:
	      pDesc->name = SANE_NAME_SCAN_TL_Y;
	      pDesc->title = SANE_TITLE_SCAN_TL_Y;
	      pDesc->desc = SANE_DESC_SCAN_TL_Y;
	      pDesc->unit = SANE_UNIT_MM;
	      pDesc->constraint_type = SANE_CONSTRAINT_RANGE;
	      pDesc->constraint.range = &scanner->rng_vertical;
	      pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	      pVal->w = 0;
	      break;

	    case opt_brx:
	      pDesc->name = SANE_NAME_SCAN_BR_X;
	      pDesc->title = SANE_TITLE_SCAN_BR_X;
	      pDesc->desc = SANE_DESC_SCAN_BR_X;
	      pDesc->unit = SANE_UNIT_MM;
	      pDesc->constraint_type = SANE_CONSTRAINT_RANGE;
	      pDesc->constraint.range = &scanner->rng_horizontal;
	      pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	      pVal->w = scanner->rng_horizontal.max;
	      break;

	    case opt_bry:
	      pDesc->name = SANE_NAME_SCAN_BR_Y;
	      pDesc->title = SANE_TITLE_SCAN_BR_Y;
	      pDesc->desc = SANE_DESC_SCAN_BR_Y;
	      pDesc->unit = SANE_UNIT_MM;
	      pDesc->constraint_type = SANE_CONSTRAINT_RANGE;
	      pDesc->constraint.range = &scanner->rng_vertical;
	      pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	      pVal->w = scanner->rng_vertical.max;
	      break;

	    case opt_resolution:
	      pDesc->name = SANE_NAME_SCAN_RESOLUTION;
	      pDesc->title = SANE_TITLE_SCAN_RESOLUTION;
	      pDesc->desc = SANE_DESC_SCAN_RESOLUTION;
	      pDesc->unit = SANE_UNIT_DPI;
	      pDesc->constraint_type = SANE_CONSTRAINT_WORD_LIST;
	      pDesc->constraint.word_list = scanner->list_resolutions;
	      pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	      pVal->w = scanner->list_resolutions[1];
	      break;

	    case opt_gamma_red:
	      pDesc->name = SANE_NAME_GAMMA_VECTOR_R;
	      pDesc->title = SANE_TITLE_GAMMA_VECTOR_R;
	      pDesc->desc = SANE_DESC_GAMMA_VECTOR_R;
	      pDesc->size = scanner->rng_gamma.max * sizeof (SANE_Word);
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->constraint_type = SANE_CONSTRAINT_RANGE;
	      pDesc->constraint.range = &scanner->rng_gamma;
	      pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	      pVal->wa = scanner->aGammaTable[CL_RED];
	      break;

	    case opt_gamma_green:
	      pDesc->name = SANE_NAME_GAMMA_VECTOR_G;
	      pDesc->title = SANE_TITLE_GAMMA_VECTOR_G;
	      pDesc->desc = SANE_DESC_GAMMA_VECTOR_G;
	      pDesc->size = scanner->rng_gamma.max * sizeof (SANE_Word);
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->constraint_type = SANE_CONSTRAINT_RANGE;
	      pDesc->constraint.range = &scanner->rng_gamma;
	      pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	      pVal->wa = scanner->aGammaTable[CL_GREEN];
	      break;

	    case opt_gamma_blue:
	      pDesc->name = SANE_NAME_GAMMA_VECTOR_B;
	      pDesc->title = SANE_TITLE_GAMMA_VECTOR_B;
	      pDesc->desc = SANE_DESC_GAMMA_VECTOR_B;
	      pDesc->size = scanner->rng_gamma.max * sizeof (SANE_Word);
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->constraint_type = SANE_CONSTRAINT_RANGE;
	      pDesc->constraint.range = &scanner->rng_gamma;
	      pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	      pVal->wa = scanner->aGammaTable[CL_BLUE];
	      break;

	    case opt_scantype:
	      pDesc->name = SANE_NAME_SCAN_SOURCE;
	      pDesc->title = SANE_TITLE_SCAN_SOURCE;
	      pDesc->desc = SANE_DESC_SCAN_SOURCE;
	      pDesc->type = SANE_TYPE_STRING;
	      pDesc->size = max_string_size (scanner->list_sources);
	      pDesc->constraint_type = SANE_CONSTRAINT_STRING_LIST;
	      pDesc->constraint.string_list = scanner->list_sources;
	      pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	      pVal->s = strdup (scanner->list_sources[0]);
	      break;

	    case opt_colormode:
	      pDesc->name = SANE_NAME_SCAN_MODE;
	      pDesc->title = SANE_TITLE_SCAN_MODE;
	      pDesc->desc = SANE_DESC_SCAN_MODE;
	      pDesc->type = SANE_TYPE_STRING;
	      pDesc->size = max_string_size (scanner->list_colormodes);
	      pDesc->constraint_type = SANE_CONSTRAINT_STRING_LIST;
	      pDesc->constraint.string_list = scanner->list_colormodes;
	      pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	      pVal->s = strdup (scanner->list_colormodes[0]);
	      break;

	    case opt_depth:
	      pDesc->name = SANE_NAME_BIT_DEPTH;
	      pDesc->title = SANE_TITLE_BIT_DEPTH;
	      pDesc->desc = SANE_DESC_BIT_DEPTH;
	      pDesc->type = SANE_TYPE_INT;
	      pDesc->unit = SANE_UNIT_BIT;
	      pDesc->constraint_type = SANE_CONSTRAINT_WORD_LIST;
	      pDesc->constraint.word_list = scanner->list_depths;
	      pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
	      pVal->w = scanner->list_depths[1];
	      break;

	    case opt_threshold:
	      pDesc->name = SANE_NAME_THRESHOLD;
	      pDesc->title = SANE_TITLE_THRESHOLD;
	      pDesc->desc = SANE_DESC_THRESHOLD;
	      pDesc->type = SANE_TYPE_INT;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->constraint_type = SANE_CONSTRAINT_RANGE;
	      pDesc->constraint.range = &scanner->rng_threshold;
	      pDesc->cap |=
		SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT |
		SANE_CAP_INACTIVE;
	      pVal->w = 0x80;
	      break;

	      /* debugging options */
	    case grp_debug:
	      pDesc->name = "grp_debug";
	      pDesc->title = SANE_I18N ("Debugging Options");
	      pDesc->desc = "";
	      pDesc->type = SANE_TYPE_GROUP;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = 0;
	      pDesc->cap = SANE_CAP_ADVANCED;
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.range = 0;
	      pVal->w = 0;
	      break;

	    case opt_model:
	      pDesc->name = "opt_model";
	      pDesc->title = SANE_I18N ("Scanner model");
	      pDesc->desc =
		SANE_I18N
		("Allows one to test device behavior with other supported models");
	      pDesc->type = SANE_TYPE_STRING;
	      pDesc->size = max_string_size (scanner->list_models);
	      pDesc->constraint_type = SANE_CONSTRAINT_STRING_LIST;
	      pDesc->constraint.string_list = scanner->list_models;
	      pDesc->cap =
		SANE_CAP_ADVANCED | SANE_CAP_SOFT_SELECT |
		SANE_CAP_SOFT_DETECT;
	      pVal->s = strdup (scanner->list_models[0]);
	      break;

	    case opt_negative:
	      pDesc->name = "opt_negative";
	      pDesc->title = SANE_I18N ("Negative");
	      pDesc->desc = SANE_I18N ("Image colors will be inverted");
	      pDesc->type = SANE_TYPE_BOOL;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = sizeof (SANE_Word);
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.range = 0;
	      pDesc->cap =
		SANE_CAP_ADVANCED | SANE_CAP_SOFT_DETECT |
		SANE_CAP_SOFT_SELECT;
	      pVal->w = SANE_FALSE;
	      break;

	    case opt_nogamma:
	      pDesc->name = "opt_nogamma";
	      pDesc->title = SANE_I18N ("Disable gamma correction");
	      pDesc->desc = SANE_I18N ("Gamma correction will be disabled");
	      pDesc->type = SANE_TYPE_BOOL;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = sizeof (SANE_Word);
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.range = 0;
	      pDesc->cap =
		SANE_CAP_ADVANCED | SANE_CAP_SOFT_DETECT |
		SANE_CAP_SOFT_SELECT;
	      pVal->w = SANE_FALSE;
	      break;

	    case opt_nowshading:
	      pDesc->name = "opt_nowshading";
	      pDesc->title = SANE_I18N ("Disable white shading correction");
	      pDesc->desc =
		SANE_I18N ("White shading correction will be disabled");
	      pDesc->type = SANE_TYPE_BOOL;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = sizeof (SANE_Word);
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.range = 0;
	      pDesc->cap =
		SANE_CAP_ADVANCED | SANE_CAP_SOFT_DETECT |
		SANE_CAP_SOFT_SELECT;
	      pVal->w = SANE_FALSE;
	      break;

	    case opt_nowarmup:
	      pDesc->name = "opt_nowarmup";
	      pDesc->title = SANE_I18N ("Skip warmup process");
	      pDesc->desc = SANE_I18N ("Warmup process will be disabled");
	      pDesc->type = SANE_TYPE_BOOL;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = sizeof (SANE_Word);
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.range = 0;
	      pDesc->cap =
		SANE_CAP_ADVANCED | SANE_CAP_SOFT_DETECT |
		SANE_CAP_SOFT_SELECT;
	      pVal->w = SANE_FALSE;
	      break;

	    case opt_realdepth:
	      pDesc->name = "opt_realdepth";
	      pDesc->title = SANE_I18N ("Force real depth");
	      pDesc->desc =
		SANE_I18N
		("If gamma is enabled, scans are always made in 16 bits depth to improve image quality and then converted to the selected depth. This option avoids depth emulation.");
	      pDesc->type = SANE_TYPE_BOOL;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = sizeof (SANE_Word);
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.range = 0;
	      pDesc->cap =
		SANE_CAP_ADVANCED | SANE_CAP_SOFT_DETECT |
		SANE_CAP_SOFT_SELECT;
	      pVal->w = SANE_FALSE;
	      break;

	    case opt_emulategray:
	      pDesc->name = "opt_emulategray";
	      pDesc->title = SANE_I18N ("Emulate Grayscale");
	      pDesc->desc =
		SANE_I18N
		("If enabled, image will be scanned in color mode and then converted to grayscale by software. This may improve image quality in some circumstances.");
	      pDesc->type = SANE_TYPE_BOOL;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = sizeof (SANE_Word);
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.range = 0;
	      pDesc->cap =
		SANE_CAP_ADVANCED | SANE_CAP_SOFT_DETECT |
		SANE_CAP_SOFT_SELECT;
	      pVal->w = SANE_FALSE;
	      break;

	    case opt_dbgimages:
	      pDesc->name = "opt_dbgimages";
	      pDesc->title = SANE_I18N ("Save debugging images");
	      pDesc->desc =
		SANE_I18N
		("If enabled, some images involved in scanner processing are saved to analyze them.");
	      pDesc->type = SANE_TYPE_BOOL;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = sizeof (SANE_Word);
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.range = 0;
	      pDesc->cap =
		SANE_CAP_ADVANCED | SANE_CAP_SOFT_DETECT |
		SANE_CAP_SOFT_SELECT;
	      pVal->w = SANE_FALSE;
	      break;

	    case opt_reset:
	      pDesc->name = "opt_reset";
	      pDesc->title = SANE_I18N ("Reset chipset");
	      pDesc->desc = SANE_I18N ("Resets chipset data");
	      pDesc->type = SANE_TYPE_BUTTON;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = 0;
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.string_list = 0;
	      pDesc->cap = SANE_CAP_ADVANCED | SANE_CAP_SOFT_SELECT;
	      pVal->w = 0;
	      break;

	      /* device information */
	    case grp_info:
	      pDesc->name = "grp_info";
	      pDesc->title = SANE_I18N ("Information");
	      pDesc->desc = "";
	      pDesc->type = SANE_TYPE_GROUP;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = 0;
	      pDesc->cap = 0;
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.range = 0;
	      pVal->w = 0;
	      break;

	    case opt_chipname:
	      pDesc->name = "opt_chipname";
	      pDesc->title = SANE_I18N ("Chipset name");
	      pDesc->desc = SANE_I18N ("Shows chipset name used in device.");
	      pDesc->type = SANE_TYPE_STRING;
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->cap = SANE_CAP_ADVANCED | SANE_CAP_SOFT_DETECT;
	      pVal->s = strdup (SANE_I18N ("Unknown"));
	      pDesc->size = strlen(pVal->s) + 1;
	      break;

	    case opt_chipid:
	      pDesc->name = "opt_chipid";
	      pDesc->title = SANE_I18N ("Chipset ID");
	      pDesc->desc = SANE_I18N ("Shows the chipset ID");
	      pDesc->type = SANE_TYPE_INT;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->cap = SANE_CAP_ADVANCED | SANE_CAP_SOFT_DETECT;
	      pVal->w = -1;
	      break;

	    case opt_scancount:
	      pDesc->name = "opt_scancount";
	      pDesc->title = SANE_I18N ("Scan counter");
	      pDesc->desc =
		SANE_I18N ("Shows the number of scans made by scanner");
	      pDesc->type = SANE_TYPE_INT;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->cap = SANE_CAP_ADVANCED | SANE_CAP_SOFT_DETECT;
	      pVal->w = -1;
	      break;

	    case opt_infoupdate:
	      pDesc->name = "opt_infoupdate";
	      pDesc->title = SANE_I18N ("Update information");
	      pDesc->desc = SANE_I18N ("Updates information about device");
	      pDesc->type = SANE_TYPE_BUTTON;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = 0;
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.string_list = 0;
	      pDesc->cap = SANE_CAP_ADVANCED | SANE_CAP_SOFT_SELECT;
	      pVal->w = 0;
	      break;

	      /* buttons support */
	    case grp_sensors:
	      pDesc->name = SANE_NAME_SENSORS;
	      pDesc->title = SANE_TITLE_SENSORS;
	      pDesc->desc = SANE_DESC_SENSORS;
	      pDesc->type = SANE_TYPE_GROUP;
	      pDesc->unit = SANE_UNIT_NONE;
	      pDesc->size = 0;
	      pDesc->cap = 0;
	      pDesc->constraint_type = SANE_CONSTRAINT_NONE;
	      pDesc->constraint.range = 0;
	      pVal->w = 0;
	      break;

	    case opt_button_0:
	    case opt_button_1:
	    case opt_button_2:
	    case opt_button_3:
	    case opt_button_4:
	    case opt_button_5:
	      {
		char name[12];
		char title[128];

		sprintf (name, "button %d", i - opt_button_0);
		sprintf (title, "Scanner button %d", i - opt_button_0);
		pDesc->name = strdup (name);
		pDesc->title = strdup (title);
		pDesc->desc =
		  SANE_I18N
		  ("This option reflects a front panel scanner button");
		pDesc->type = SANE_TYPE_BOOL;
		pDesc->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;

		if (i - opt_button_0 >= Buttons_Count (device))
		  pDesc->cap |= SANE_CAP_INACTIVE;

		pDesc->unit = SANE_UNIT_NONE;
		pDesc->size = sizeof (SANE_Word);
		pDesc->constraint_type = SANE_CONSTRAINT_NONE;
		pVal->w = SANE_FALSE;
	      }
	      break;
	    }
	}
    }
}

static SANE_Int
_ReportDevice (TScannerModel * pModel, const char *pszDeviceName)
{
  SANE_Int rst = ERROR;
  TDevListEntry *pNew, *pDev;

  DBG (DBG_FNC, "> _ReportDevice:\n");

  pNew = malloc (sizeof (TDevListEntry));
  if (pNew != NULL)
    {
      rst = OK;

      /* add new element to the end of the list */
      if (_pFirstSaneDev != NULL)
	{
	  /* Add at the end of existing list */
	  for (pDev = _pFirstSaneDev; pDev->pNext; pDev = pDev->pNext);

	  pDev->pNext = pNew;
	}
      else
	_pFirstSaneDev = pNew;

      /* fill in new element */
      pNew->pNext = NULL;
      pNew->devname = (char *) strdup (pszDeviceName);
      pNew->dev.name = pNew->devname;
      pNew->dev.vendor = pModel->pszVendor;
      pNew->dev.model = pModel->pszName;
      pNew->dev.type = SANE_I18N ("flatbed scanner");

      iNumSaneDev++;
    }

  return rst;
}

static SANE_Status
attach_one_device (SANE_String_Const devname)
{
  static TScannerModel sModel;

  DBG (DBG_FNC, "> attach_one_device(devname=%s)\n", devname);

  switch (GetUSB_device_model (devname))
    {
    case HP3800:
      sModel.pszVendor = (char *) strdup ("Hewlett-Packard");
      sModel.pszName = (char *) strdup ("Scanjet 3800");
      break;
    case HPG2710:
      sModel.pszVendor = (char *) strdup ("Hewlett-Packard");
      sModel.pszName = (char *) strdup ("Scanjet G2710");
      break;
    case HP3970:
      sModel.pszVendor = (char *) strdup ("Hewlett-Packard");
      sModel.pszName = (char *) strdup ("Scanjet 3970");
      break;
    case HP4070:
      sModel.pszVendor = (char *) strdup ("Hewlett-Packard");
      sModel.pszName = (char *) strdup ("Scanjet 4070 Photosmart");
      break;
    case HP4370:
      sModel.pszVendor = (char *) strdup ("Hewlett-Packard");
      sModel.pszName = (char *) strdup ("Scanjet 4370");
      break;
    case HPG3010:
      sModel.pszVendor = (char *) strdup ("Hewlett-Packard");
      sModel.pszName = (char *) strdup ("Scanjet G3010");
      break;
    case HPG3110:
      sModel.pszVendor = (char *) strdup ("Hewlett-Packard");
      sModel.pszName = (char *) strdup ("Scanjet G3110");
      break;
    case UA4900:
      sModel.pszVendor = (char *) strdup ("UMAX");
      sModel.pszName = (char *) strdup ("Astra 4900");
      break;
    case BQ5550:
      sModel.pszVendor = (char *) strdup ("BenQ");
      sModel.pszName = (char *) strdup ("5550");
      break;
    default:
      sModel.pszVendor = (char *) strdup ("Unknown");
      sModel.pszName = (char *) strdup ("RTS8822 chipset based");
      break;
    }

  _ReportDevice (&sModel, devname);

  return SANE_STATUS_GOOD;
}

/* Sane default functions */

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  FILE *conf_fp;		/* Config file stream  */
  SANE_Char line[PATH_MAX];
  SANE_Char *str = NULL;
  SANE_String_Const proper_str;
  SANE_Int nline = 0;

  /* Initialize debug */
  DBG_INIT ();

  DBG (DBG_FNC, "> sane_init\n");

  /* silence gcc */
  (void) authorize;

  /* Initialize usb */
  sanei_usb_init ();

  /* Parse config file */
  conf_fp = sanei_config_open (HP3900_CONFIG_FILE);
  if (conf_fp)
    {
      while (sanei_config_read (line, sizeof (line), conf_fp))
	{
	  nline++;
	  if (str)
	    free (str);

	  proper_str = sanei_config_get_string (line, &str);

	  /* Discards white lines and comments */
	  if ((str != NULL) && (proper_str != line) && (str[0] != '#'))
	    {
	      /* If line's not blank or a comment, then it's the device
	       * filename or a usb directive. */
	      sanei_usb_attach_matching_devices (line, attach_one_device);
	    }
	}
      fclose (conf_fp);
    }
  else
    {
      /* default */
      DBG (DBG_VRB, "- %s not found. Looking for hardcoded usb ids ...\n",
	   HP3900_CONFIG_FILE);

      sanei_usb_attach_matching_devices ("usb 0x03f0 0x2605", attach_one_device);	/* HP3800  */
      sanei_usb_attach_matching_devices ("usb 0x03f0 0x2805", attach_one_device);	/* HPG2710 */
      sanei_usb_attach_matching_devices ("usb 0x03f0 0x2305", attach_one_device);	/* HP3970  */
      sanei_usb_attach_matching_devices ("usb 0x03f0 0x2405", attach_one_device);	/* HP4070  */
      sanei_usb_attach_matching_devices ("usb 0x03f0 0x4105", attach_one_device);	/* HP4370  */
      sanei_usb_attach_matching_devices ("usb 0x03f0 0x4205", attach_one_device);	/* HPG3010 */
      sanei_usb_attach_matching_devices ("usb 0x03f0 0x4305", attach_one_device);	/* HPG3110 */
      sanei_usb_attach_matching_devices ("usb 0x06dc 0x0020", attach_one_device);	/* UA4900  */
      sanei_usb_attach_matching_devices ("usb 0x04a5 0x2211", attach_one_device);	/* BQ5550  */
    }

  /* Return backend version */
  if (version_code != NULL)
    *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, 0);

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  SANE_Status rst = SANE_STATUS_GOOD;

  (void) local_only;

  if (_pSaneDevList)
    free (_pSaneDevList);

  _pSaneDevList = malloc (sizeof (*_pSaneDevList) * (iNumSaneDev + 1));
  if (_pSaneDevList != NULL)
    {
      TDevListEntry *pDev;
      SANE_Int i = 0;

      for (pDev = _pFirstSaneDev; pDev; pDev = pDev->pNext)
	_pSaneDevList[i++] = &pDev->dev;

      _pSaneDevList[i++] = 0;	/* last entry is 0 */
      *device_list = _pSaneDevList;
    }
  else
    rst = SANE_STATUS_NO_MEM;

  DBG (DBG_FNC, "> sane_get_devices: %i\n", rst);

  return rst;
}

SANE_Status
sane_open (SANE_String_Const name, SANE_Handle * h)
{
  TScanner *s;
  SANE_Status rst;

  /* check the name */
  if (strlen (name) == 0)
    /* default to first available device */
    name = _pFirstSaneDev->dev.name;

  /* allocate space for RTS environment */
  device = RTS_Alloc ();
  if (device != NULL)
    {
      /* Open device */
      rst = sanei_usb_open (name, &device->usb_handle);
      if (rst == SANE_STATUS_GOOD)
	{
	  /* Allocating memory for device */
	  s = malloc (sizeof (TScanner));
	  if (s != NULL)
	    {
	      memset (s, 0, sizeof (TScanner));

	      /* Initializing RTS */
	      if (Init_Vars () == OK)
		{
		  SANE_Int vendor, product;

		  /* Setting device model */
		  if (sanei_usb_get_vendor_product
		      (device->usb_handle, &vendor,
		       &product) == SANE_STATUS_GOOD)
		    s->model = Device_get (product, vendor);
		  else
		    s->model = HP3970;

		  set_ScannerModel (s->model, product, vendor);

		  /* Initialize device */
		  if (RTS_Scanner_Init (device) == OK)
		    {
		      /* silencing unused functions */
		      Silent_Compile ();

		      /* initialize backend options */
		      options_init (s);
		      *h = s;

		      /* everything went ok */
		      rst = SANE_STATUS_GOOD;
		    }
		  else
		    {
		      free ((void *) s);
		      rst = SANE_STATUS_INVAL;
		    }
		}
	      else
		rst = SANE_STATUS_NO_MEM;
	    }
	  else
	    rst = SANE_STATUS_NO_MEM;
	}
    }
  else
    rst = SANE_STATUS_NO_MEM;

  DBG (DBG_FNC, "> sane_open(name=%s): %i\n", name, rst);

  return rst;
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle h, SANE_Int n)
{
  SANE_Option_Descriptor *rst = NULL;

  if ((n >= opt_begin) && (n < opt_count))
    {
      TScanner *s = (TScanner *) h;
      rst = &s->aOptions[n];
    }

  DBG (DBG_FNC, "> SANE_Option_Descriptor(handle, n=%i): %i\n", n,
       (rst == NULL) ? -1 : 0);

  return rst;
}

static SANE_Status
option_get (TScanner * scanner, SANE_Int optid, void *result)
{
  /* This function returns value contained in selected option */

  DBG (DBG_FNC, "> option_get(optid=%i)\n", optid);

  if ((scanner != NULL) && (result != NULL))
    {
      switch (optid)
	{
	  /* SANE_Word */
	case opt_begin:	/* null */
	case opt_reset:	/* null */
	case opt_negative:
	case opt_nogamma:
	case opt_nowshading:
	case opt_emulategray:
	case opt_dbgimages:
	case opt_nowarmup:
	case opt_realdepth:
	case opt_depth:
	case opt_resolution:
	case opt_threshold:
	case opt_brx:
	case opt_tlx:
	case opt_bry:
	case opt_tly:
	  *(SANE_Word *) result = scanner->aValues[optid].w;
	  break;

	  /* SANE_Int */
	case opt_chipid:
	case opt_scancount:
	  *(SANE_Int *) result = scanner->aValues[optid].w;
	  break;

	  /* SANE_Word array */
	case opt_gamma_red:
	case opt_gamma_green:
	case opt_gamma_blue:
	  memcpy (result, scanner->aValues[optid].wa,
		  scanner->aOptions[optid].size);
	  break;

	  /* String */
	case opt_colormode:
	case opt_scantype:
	case opt_model:
	case opt_chipname:
	  strncpy (result, scanner->aValues[optid].s, scanner->aOptions[optid].size);
	  ((char*)result)[scanner->aOptions[optid].size-1] = '\0';

	  break;

	  /* scanner buttons */
	case opt_button_0:
	  get_button_status (scanner);
          // fall through
	case opt_button_1:
	case opt_button_2:
	case opt_button_3:
	case opt_button_4:
	case opt_button_5:
	  /* copy the button state */
	  *(SANE_Word *) result = scanner->aValues[optid].w;
	  /* clear the button state */
	  scanner->aValues[optid].w = SANE_FALSE;
	  break;
	}
    }

  return SANE_STATUS_GOOD;
}

static SANE_Status
option_set (TScanner * scanner, SANE_Int optid, void *value, SANE_Int * pInfo)
{
  SANE_Status rst;

  DBG (DBG_FNC, "> option_set(optid=%i)\n", optid);

  rst = SANE_STATUS_INVAL;

  if (scanner != NULL)
    {
      if (scanner->fScanning == FALSE)
	{
	  SANE_Int info = 0;

	  rst = SANE_STATUS_GOOD;

	  switch (optid)
	    {
	    case opt_brx:
	    case opt_tlx:
	    case opt_bry:
	    case opt_tly:
	    case opt_depth:
	    case opt_nogamma:
	    case opt_nowshading:
	    case opt_nowarmup:
	    case opt_negative:
	    case opt_emulategray:
	    case opt_dbgimages:
	    case opt_threshold:
	    case opt_resolution:
	      info |= SANE_INFO_RELOAD_PARAMS;
	      scanner->aValues[optid].w = *(SANE_Word *) value;
	      break;

	    case opt_gamma_red:
	    case opt_gamma_green:
	    case opt_gamma_blue:
	      memcpy (scanner->aValues[optid].wa, value,
		      scanner->aOptions[optid].size);
	      break;

	    case opt_scantype:
	      if (strcmp (scanner->aValues[optid].s, value) != 0)
		{
		  struct st_coords *coords;
		  SANE_Int source;

		  if (scanner->aValues[optid].s)
		    free (scanner->aValues[optid].s);

		  scanner->aValues[optid].s = strdup (value);

		  source = Get_Source (scanner->aValues[opt_scantype].s);
		  coords = Constrains_Get (device, source);
		  if (coords != NULL)
		    {
		      bknd_constrains (scanner, source, 0);
		      bknd_constrains (scanner, source, 1);
		      scanner->aValues[opt_tlx].w = 0;
		      scanner->aValues[opt_tly].w = 0;
		      scanner->aValues[opt_brx].w = coords->width;
		      scanner->aValues[opt_bry].w = coords->height;
		    }

		  info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
		}
	      break;

	    case opt_colormode:
	      if (strcmp (scanner->aValues[optid].s, value) != 0)
		{
		  if (scanner->aValues[optid].s)
		    free (scanner->aValues[optid].s);
		  scanner->aValues[optid].s = strdup (value);
		  if (Get_Colormode (scanner->aValues[optid].s) == CM_LINEART)
		    scanner->aOptions[opt_threshold].cap &=
		      ~SANE_CAP_INACTIVE;
		  else
		    scanner->aOptions[opt_threshold].cap |= SANE_CAP_INACTIVE;
		  info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
		}
	      break;

	    case opt_model:
	      if (strcmp (scanner->aValues[optid].s, value) != 0)
		{
		  SANE_Int model;

		  if (scanner->aValues[optid].s)
		    free (scanner->aValues[optid].s);
		  scanner->aValues[optid].s = strdup (value);

		  model = Get_Model (scanner->aValues[optid].s);
		  if (model != RTS_Debug->dev_model)
		    {
		      SANE_Int source;
		      struct st_coords *coords;

		      /* free configuration of last model */
		      Free_Config (device);

		      /* set new model */
		      RTS_Debug->dev_model = model;

		      /* and load configuration of current model */
		      Load_Config (device);

		      /* update options according to selected device */
		      bknd_info (scanner);
		      bknd_colormodes (scanner, model);
		      bknd_depths (scanner, model);
		      bknd_resolutions (scanner, model);
		      bknd_sources (scanner, model);

		      /* updating lists */
		      scanner->aOptions[opt_colormode].size =
			max_string_size (scanner->list_colormodes);
		      scanner->aOptions[opt_colormode].constraint.
			string_list = scanner->list_colormodes;
		      scanner->aOptions[opt_depth].constraint.word_list =
			scanner->list_depths;
		      scanner->aOptions[opt_resolution].constraint.word_list =
			scanner->list_resolutions;
		      scanner->aOptions[opt_scantype].size =
			max_string_size (scanner->list_sources);
		      scanner->aOptions[opt_scantype].constraint.string_list =
			scanner->list_sources;

		      /* default values */
		      if (scanner->aValues[opt_colormode].s != NULL)
			free (scanner->aValues[opt_colormode].s);

		      if (scanner->aValues[opt_scantype].s != NULL)
			free (scanner->aValues[opt_scantype].s);

		      scanner->aValues[opt_colormode].s =
			strdup (scanner->list_colormodes[0]);
		      scanner->aValues[opt_scantype].s =
			strdup (scanner->list_sources[0]);
		      scanner->aValues[opt_resolution].w =
			scanner->list_resolutions[1];
		      scanner->aValues[opt_depth].w = scanner->list_depths[1];

		      source = Get_Source (scanner->aValues[opt_scantype].s);
		      coords = Constrains_Get (device, source);
		      if (coords != NULL)
			{
			  bknd_constrains (scanner, source, 0);
			  bknd_constrains (scanner, source, 1);
			  scanner->aValues[opt_tlx].w = 0;
			  scanner->aValues[opt_tly].w = 0;
			  scanner->aValues[opt_brx].w = coords->width;
			  scanner->aValues[opt_bry].w = coords->height;
			}
		    }

		  info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
		}
	      break;

	    case opt_reset:
	      Chipset_Reset (device);
	      break;

	    case opt_realdepth:
	      scanner->aValues[optid].w =
		(scanner->cnv.real_depth == TRUE) ? SANE_TRUE : SANE_FALSE;
	      break;

	    case opt_infoupdate:
	      if (bknd_info (scanner) == SANE_STATUS_GOOD)
		info |= SANE_INFO_RELOAD_OPTIONS;
	      break;

	    default:
	      rst = SANE_STATUS_INVAL;
	      break;
	    }

	  if (pInfo != NULL)
	    *pInfo = info;
	}
    }

  return rst;
}

SANE_Status
sane_control_option (SANE_Handle h, SANE_Int n, SANE_Action Action,
		     void *pVal, SANE_Int * pInfo)
{
  TScanner *scanner;
  SANE_Status rst;

  DBG (DBG_FNC, "> sane_control_option\n");

  scanner = (TScanner *) h;

  switch (Action)
    {
    case SANE_ACTION_GET_VALUE:
      rst = option_get (scanner, n, pVal);
      break;

    case SANE_ACTION_SET_VALUE:
      rst = option_set (scanner, n, pVal, pInfo);
      break;

    case SANE_ACTION_SET_AUTO:
      rst = SANE_STATUS_UNSUPPORTED;
      break;

    default:
      rst = SANE_STATUS_INVAL;
      break;
    }

  return rst;
}

SANE_Status
sane_get_parameters (SANE_Handle h, SANE_Parameters * p)
{
  SANE_Status rst = SANE_STATUS_INVAL;
  TScanner *s = (TScanner *) h;

  DBG (DBG_FNC, "+ sane_get_parameters:");

  if (s != NULL)
    {
      struct st_coords coords;
      SANE_Int res, source, depth, colormode, frameformat, bpl;

      /* first do some checks */

      /* colormode */
      colormode = Get_Colormode (s->aValues[opt_colormode].s);

      /* frameformat */
      frameformat =
	(colormode == CM_COLOR) ? SANE_FRAME_RGB : SANE_FRAME_GRAY;

      /* depth */
      depth = (colormode == CM_LINEART) ? 1 : s->aValues[opt_depth].w;

      /* scan type */
      source = Get_Source (s->aValues[opt_scantype].s);

      /* resolution */
      res = s->aValues[opt_resolution].w;

      /* image coordinates in millimeters */
      coords.left = s->aValues[opt_tlx].w;
      coords.top = s->aValues[opt_tly].w;
      coords.width = s->aValues[opt_brx].w;
      coords.height = s->aValues[opt_bry].w;

      /* validate coords */
      if (Translate_coords (&coords) == SANE_STATUS_GOOD)
	{
	  Set_Coordinates (source, res, &coords);

	  if (colormode != CM_LINEART)
	    {
	      bpl = coords.width * ((depth > 8) ? 2 : 1);
	      if (colormode == CM_COLOR)
		bpl *= 3;	/* three channels */
	    }
	  else
	    bpl = (coords.width + 7) / 8;

	  /* return the data */
	  p->format = frameformat;
	  p->last_frame = SANE_TRUE;
	  p->depth = depth;
	  p->lines = coords.height;
	  p->pixels_per_line = coords.width;
	  p->bytes_per_line = bpl;

	  DBG (DBG_FNC, " -> Depth : %i\n", depth);
	  DBG (DBG_FNC, " -> Height: %i\n", coords.height);
	  DBG (DBG_FNC, " -> Width : %i\n", coords.width);
	  DBG (DBG_FNC, " -> BPL   : %i\n", bpl);

	  rst = SANE_STATUS_GOOD;
	}
    }

  DBG (DBG_FNC, "- sane_get_parameters: %i\n", rst);

  return rst;
}

SANE_Status
sane_start (SANE_Handle h)
{
  SANE_Status rst = SANE_STATUS_INVAL;
  TScanner *s;

  DBG (DBG_FNC, "+ sane_start\n");

  s = (TScanner *) h;
  if (s != NULL)
    {
      struct st_coords coords;
      SANE_Int res, source, colormode, depth, channel;

      /* first do some checks */
      /* Get Scan type */
      source = Get_Source (s->aValues[opt_scantype].s);

      /* Check if scanner supports slides and negatives in case selected source is tma */
      if (!((source != ST_NORMAL) && (RTS_isTmaAttached (device) == FALSE)))
	{
	  /* Get depth */
	  depth = s->aValues[opt_depth].w;

	  /* Get color mode */
	  colormode = Get_Colormode (s->aValues[opt_colormode].s);

	  /* Emulating certain color modes */
	  if (colormode == CM_LINEART)
	    {
	      /* emulate lineart */
	      s->cnv.colormode = CM_LINEART;
	      colormode = CM_GRAY;
	      depth = 8;
	    }
	  else if ((colormode == CM_GRAY)
		   && (s->aValues[opt_emulategray].w == SANE_TRUE))
	    {
	      /* emulate grayscale */
	      s->cnv.colormode = CM_GRAY;
	      colormode = CM_COLOR;
	    }
	  else
	    s->cnv.colormode = -1;

	  /* setting channel for colormodes different than CM_COLOR */
	  channel = (colormode != CM_COLOR) ? 1 : 0;

	  /* negative colors */
	  s->cnv.negative =
	    (s->aValues[opt_negative].w == SANE_TRUE) ? TRUE : FALSE;

	  /* Get threshold */
	  s->cnv.threshold = s->aValues[opt_threshold].w;

	  /* Get resolution */
	  res = s->aValues[opt_resolution].w;

	  /* set depth emulation */
	  if (s->cnv.colormode == CM_LINEART)
	    s->cnv.real_depth = TRUE;
	  else
	    s->cnv.real_depth =
	      (s->aValues[opt_realdepth].w == SANE_TRUE) ? TRUE : FALSE;

	  /* use gamma? */
	  RTS_Debug->EnableGamma =
	    (s->aValues[opt_nogamma].w == SANE_TRUE) ? FALSE : TRUE;

	  /* disable white shading correction? */
	  RTS_Debug->wshading =
	    (s->aValues[opt_nowshading].w == SANE_TRUE) ? FALSE : TRUE;

	  /* skip warmup process? */
	  RTS_Debug->warmup =
	    (s->aValues[opt_nowarmup].w == SANE_TRUE) ? FALSE : TRUE;

	  /* save debugging images? */
	  RTS_Debug->SaveCalibFile =
	    (s->aValues[opt_dbgimages].w == SANE_TRUE) ? TRUE : FALSE;

	  /* Get image coordinates in millimeters */
	  coords.left = s->aValues[opt_tlx].w;
	  coords.top = s->aValues[opt_tly].w;
	  coords.width = s->aValues[opt_brx].w;
	  coords.height = s->aValues[opt_bry].w;

	  /* Validate coords */
	  if (Translate_coords (&coords) == SANE_STATUS_GOOD)
	    {

	      /* Stop previusly started scan */
	      RTS_Scanner_StopScan (device, TRUE);

	      s->ScanParams.scantype = source;
	      s->ScanParams.colormode = colormode;
	      s->ScanParams.resolution_x = res;
	      s->ScanParams.resolution_y = res;
	      s->ScanParams.channel = channel;

	      memcpy (&s->ScanParams.coords, &coords,
		      sizeof (struct st_coords));
	      Set_Coordinates (source, res, &s->ScanParams.coords);

	      /* emulating depth? */
	      if ((s->cnv.real_depth == FALSE) && (depth < 16)
		  && (RTS_Debug->EnableGamma == TRUE))
		{
		  /* In order to improve image quality, we will scan at 16bits if
		     we are using gamma correction */
		  s->cnv.depth = depth;
		  s->ScanParams.depth = 16;
		}
	      else
		{
		  s->ScanParams.depth = depth;
		  s->cnv.depth = -1;
		}

	      /* set scanning parameters */
	      if (RTS_Scanner_SetParams (device, &s->ScanParams) == OK)
		{
		  /* Start scanning process */
		  if (RTS_Scanner_StartScan (device) == OK)
		    {
		      /* Allocate buffer to read one line */
		      s->mylin = 0;
		      rst = img_buffers_alloc (s, bytesperline);
		    }
		}
	    }
	}
      else
	rst = SANE_STATUS_COVER_OPEN;
    }

  DBG (DBG_FNC, "- sane_start: %i\n", rst);

  return rst;
}

SANE_Status
sane_read (SANE_Handle h, SANE_Byte * buf, SANE_Int maxlen, SANE_Int * len)
{
  SANE_Status rst = SANE_STATUS_GOOD;
  TScanner *s = (TScanner *) h;

  DBG (DBG_FNC, "+ sane_read\n");

  if ((s != NULL) && (buf != NULL) && (len != NULL))
    {
      /* nothing has been read at the moment */
      *len = 0;

      /* if we read all the lines return EOF */
      if ((s->mylin == s->ScanParams.coords.height)
	  || (device->status->cancel == TRUE))
	{
	  rst =
	    (device->status->cancel ==
	     TRUE) ? SANE_STATUS_CANCELLED : SANE_STATUS_EOF;

	  RTS_Scanner_StopScan (device, FALSE);
	  img_buffers_free (s);
	}
      else
	{
	  SANE_Int emul_len, emul_maxlen;
	  SANE_Int thwidth, transferred, bufflength;
	  SANE_Byte *buffer, *pbuffer;

	  emul_len = 0;
	  if (s->cnv.depth != -1)
	    emul_maxlen = maxlen * (s->ScanParams.depth / s->cnv.depth);
	  else
	    emul_maxlen = maxlen;

	  /* if grayscale emulation is enabled check that retrieved data is multiple of three */
	  if (s->cnv.colormode == CM_GRAY)
	    {
	      SANE_Int chn_size, rest;

	      chn_size = (s->ScanParams.depth > 8) ? 2 : 1;
	      rest = emul_maxlen % (3 * chn_size);

	      if (rest != 0)
		emul_maxlen -= rest;
	    }

	  /* this is important to keep lines alignment in lineart mode */
	  if (s->cnv.colormode == CM_LINEART)
	    emul_maxlen = s->ScanParams.coords.width;

	  /* if we are emulating depth, we scan at 16bit when frontend waits
	     for 8bit data. Next buffer will be used to retrieve data from
	     scanner prior to convert to 8 bits depth */
	  buffer = (SANE_Byte *) malloc (emul_maxlen * sizeof (SANE_Byte));

	  if (buffer != NULL)
	    {
	      pbuffer = buffer;

	      /* get bytes per line */
	      if (s->ScanParams.colormode != CM_LINEART)
		{
		  thwidth =
		    s->ScanParams.coords.width *
		    ((s->ScanParams.depth > 8) ? 2 : 1);

		  if (s->ScanParams.colormode == CM_COLOR)
		    thwidth *= 3;	/* three channels */
		}
	      else
		thwidth = (s->ScanParams.coords.width + 7) / 8;

	      /* read as many lines the buffer may contain and while there are lines to be read */
	      while ((emul_len < emul_maxlen)
		     && (s->mylin < s->ScanParams.coords.height))
		{
		  /* Is there any data waiting for being passed ? */
		  if (s->rest_amount != 0)
		    {
		      /* copy to buffer as many bytes as we can */
		      bufflength =
			min (emul_maxlen - emul_len, s->rest_amount);
		      memcpy (pbuffer, s->rest, bufflength);
		      emul_len += bufflength;
		      pbuffer += bufflength;
		      s->rest_amount -= bufflength;
		      if (s->rest_amount == 0)
			s->mylin++;
		    }
		  else
		    {
		      /* read from scanner up to one line */
		      if (Read_Image
			  (device, bytesperline, s->image,
			   &transferred) != OK)
			{
			  /* error, exit function */
			  rst = SANE_STATUS_EOF;
			  break;
			}

		      /* is there any data? */
		      if (transferred != 0)
			{
			  /* copy to buffer as many bytes as we can */
			  bufflength = min (emul_maxlen - emul_len, thwidth);

			  memcpy (pbuffer, s->image, bufflength);
			  emul_len += bufflength;
			  pbuffer += bufflength;

			  /* the rest will be copied to s->rest buffer */
			  if (bufflength < thwidth)
			    {
			      s->rest_amount = thwidth - bufflength;
			      memcpy (s->rest, s->image + bufflength,
				      s->rest_amount);
			    }
			  else
			    s->mylin++;
			}
		      else
			break;
		    }
		}		/* while */

	      /* process buffer before sending to frontend */
	      if ((emul_len > 0) && (rst != SANE_STATUS_EOF))
		{
		  /* at this point ...
		     buffer  : contains retrieved image
		     emul_len: contains size in bytes of retrieved image

		     after this code ...
		     buf : will contain postprocessed image
		     len : will contain size in bytes of postprocessed image */

		  /* apply gamma if necessary */
		  if (RTS_Debug->EnableGamma == TRUE)
		    gamma_apply (s, buffer, emul_len, s->ScanParams.depth);

		  /* if we are scanning negatives, let's invert colors */
		  if (s->ScanParams.scantype == ST_NEG)
		    {
		      if (s->cnv.negative == FALSE)
			Color_Negative (buffer, emul_len,
					s->ScanParams.depth);
		    }
		  else if (s->cnv.negative != FALSE)
		    Color_Negative (buffer, emul_len, s->ScanParams.depth);

		  /* emulating grayscale ? */
		  if (s->cnv.colormode == CM_GRAY)
		    {
		      Color_to_Gray (buffer, emul_len, s->ScanParams.depth);
		      emul_len /= 3;
		    }

		  /* emulating depth */
		  if (s->cnv.depth != -1)
		    {
		      switch (s->cnv.depth)
			{
			  /* case 1: treated separately as lineart */
			  /*case 12: in the future */
			case 8:
			  Depth_16_to_8 (buffer, emul_len, buffer);
			  emul_len /= 2;
			  break;
			}
		    }

		  /* lineart mode ? */
		  if (s->cnv.colormode == CM_LINEART)
		    {
		      /* I didn't see any scanner supporting lineart mode.
		         Windows drivers scan in grayscale and then convert image to lineart
		         so let's perform conversion */
		      SANE_Int rest = emul_len % 8;

		      Gray_to_Lineart (buffer, emul_len, s->cnv.threshold);
		      emul_len /= 8;
		      if (rest > 0)
			emul_len++;
		    }

		  /* copy postprocessed image */
		  *len = emul_len;
		  memcpy (buf, buffer, *len);
		}

	      free (buffer);
	    }
	}
    }
  else
    rst = SANE_STATUS_EOF;

  DBG (DBG_FNC, "- sane_read: %s\n", sane_strstatus (rst));

  return rst;
}

void
sane_cancel (SANE_Handle h)
{
  DBG (DBG_FNC, "> sane_cancel\n");

  /* silence gcc */
  (void) h;

  device->status->cancel = TRUE;
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  DBG (DBG_FNC, "> sane_set_io_mode\n");

  /* silence gcc */
  (void) handle;
  (void) non_blocking;

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  DBG (DBG_FNC, "> sane_get_select_fd\n");

  /* silence gcc */
  (void) handle;
  (void) fd;

  return SANE_STATUS_UNSUPPORTED;
}

void
sane_close (SANE_Handle h)
{
  TScanner *scanner = (TScanner *) h;

  DBG (DBG_FNC, "- sane_close...\n");

  /* stop previous scans */
  RTS_Scanner_StopScan (device, TRUE);

  /* close usb */
  sanei_usb_close (device->usb_handle);

  /* free scanner internal variables */
  RTS_Scanner_End (device);

  /* free RTS environment */
  RTS_Free (device);

  /* free backend variables */
  if (scanner != NULL)
    {
      options_free (scanner);

      img_buffers_free (scanner);
    }
}

void
sane_exit (void)
{
  /* free device list memory */
  if (_pSaneDevList)
    {
      TDevListEntry *pDev, *pNext;

      for (pDev = _pFirstSaneDev; pDev; pDev = pNext)
	{
	  pNext = pDev->pNext;
	  /* pDev->dev.name is the same pointer that pDev->devname */
	  free (pDev->devname);
	  free (pDev);
	}

      _pFirstSaneDev = NULL;
      free (_pSaneDevList);
      _pSaneDevList = NULL;
    }
}
