/***************************************************************************
 * SANE - Scanner Access Now Easy.

   microtek2.c

   This file (C) 1998, 1999 Bernd Schroeder
   modifications 2000, 2001 Karsten Festag

   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 Microtek scanners with
   SCSI-2 command set.

   (feedback to:  bernd@aquila.muc.de)
   (              karsten.festag@t-online.de)
 ***************************************************************************/


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

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

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

#include <math.h>

#ifdef HAVE_AUTHORIZATION
#include <sys/stat.h>
#endif

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

#ifndef TESTBACKEND
#define BACKEND_NAME microtek2
#else
#define BACKEND_NAME microtek2_test
#endif

/* for testing*/
/*#define NO_PHANTOMTYPE_SHADING*/

#include "../include/sane/sanei_backend.h"

#include "microtek2.h"

#ifdef HAVE_AUTHORIZATION
static SANE_Auth_Callback auth_callback;
#endif

static int md_num_devices = 0;          /* number of devices from config file */
static Microtek2_Device *md_first_dev = NULL;        /* list of known devices */
static Microtek2_Scanner *ms_first_handle = NULL;    /* list of open scanners */

/* options that can be configured in the config file */
static Config_Options md_options =
                               { 1.0, "off", "off", "off", "off", "off", "off"};
static Config_Temp *md_config_temp = NULL;
static int md_dump = 0;                 /* from config file: */
                                        /* 1: inquiry + scanner attributes */
                                        /* 2: + all scsi commands and data */
                                        /* 3: + all scan data */
static int md_dump_clear = 1;


/*---------- sane_cancel() ---------------------------------------------------*/

void
sane_cancel (SANE_Handle handle)
{
    Microtek2_Scanner *ms = handle;

    DBG(30, "sane_cancel: handle=%p\n", handle);

    if ( ms->scanning == SANE_TRUE )
        cleanup_scanner(ms);
    ms->cancelled = SANE_TRUE;
    ms->fd[0] = ms->fd[1] = -1;
}


/*---------- sane_close() ----------------------------------------------------*/


void
sane_close (SANE_Handle handle)
{
    Microtek2_Scanner *ms = handle;

    DBG(30, "sane_close: ms=%p\n", (void *) ms);

    if ( ! ms )
        return;

    /* free malloc'ed stuff */
    cleanup_scanner(ms);

    /* remove Scanner from linked list */
    if ( ms_first_handle == ms )
        ms_first_handle = ms->next;
    else
      {
        Microtek2_Scanner *ts = ms_first_handle;
        while ( (ts != NULL) && (ts->next != ms) )
            ts = ts->next;
        ts->next = ts->next->next; /* == ms->next */
      }
    DBG(100, "free ms at %p\n", (void *) ms);
    free((void *) ms);
    ms = NULL;
}


/*---------- sane_exit() -----------------------------------------------------*/

void
sane_exit (void)
{
    Microtek2_Device *next;
    int i;

    DBG(30, "sane_exit:\n");

    /* close all leftover Scanners */
    while (ms_first_handle != NULL)
        sane_close(ms_first_handle);
    /* free up device list */
    while (md_first_dev != NULL)
      {
        next = md_first_dev->next;

        for ( i = 0; i < 4; i++ )
          {
            if ( md_first_dev->custom_gamma_table[i] )
              {
                DBG(100, "free md_first_dev->custom_gamma_table[%d] at %p\n",
                          i, (void *) md_first_dev->custom_gamma_table[i]);
                free((void *) md_first_dev->custom_gamma_table[i]);
                md_first_dev->custom_gamma_table[i] = NULL;
              }
          }

        if ( md_first_dev->shading_table_w )
          {
            DBG(100, "free md_first_dev->shading_table_w at %p\n",
                      md_first_dev->shading_table_w);
            free((void *) md_first_dev->shading_table_w);
            md_first_dev->shading_table_w = NULL;
          }

        if ( md_first_dev->shading_table_d )
          {
            DBG(100, "free md_first_dev->shading_table_d at %p\n",
                      md_first_dev->shading_table_d);
            free((void *) md_first_dev->shading_table_d);
            md_first_dev->shading_table_d = NULL;
          }

        DBG(100, "free md_first_dev at %p\n", (void *) md_first_dev);
        free((void *) md_first_dev);
        md_first_dev = next;
      }
    sane_get_devices(NULL, SANE_FALSE);     /* free list of SANE_Devices */

    DBG(30, "sane_exit: MICROTEK2 says goodbye.\n");
}


/*---------- sane_get_devices()-----------------------------------------------*/

SANE_Status
sane_get_devices(const SANE_Device ***device_list, SANE_Bool local_only)
{
    /* return a list of available devices; available here means that we get */
    /* a positive response to an 'INQUIRY' and possibly to a */
    /* 'READ SCANNER ATTRIBUTE' call */

    static const SANE_Device **sd_list = NULL;
    Microtek2_Device *md;
    SANE_Status status;
    int index;

    DBG(30, "sane_get_devices: local_only=%d\n", local_only);

    /* this is hack to get the list freed with a call from sane_exit() */
    if ( device_list == NULL )
      {
        if ( sd_list )
          {
            DBG(100, "free sd_list at %p\n", (void *) sd_list);
            free(sd_list);
            sd_list=NULL;
          }
        DBG(30, "sane_get_devices: sd_list_freed\n");
        return SANE_STATUS_GOOD;
      }

    /* first free old list, if there is one; frontend wants a new list */
    if ( sd_list )
      {
        DBG(100, "free sd_list at %p\n", (void *) sd_list);
        free(sd_list);                            /* free array of pointers */
      }

    sd_list = (const SANE_Device **)
               malloc( (md_num_devices + 1) * sizeof(SANE_Device **));
    DBG(100, "sane_get_devices: sd_list=%p, malloc'd %lu bytes\n",
	(void *) sd_list, (u_long)  ((md_num_devices + 1) * sizeof(SANE_Device **)));

    if ( ! sd_list )
      {
        DBG(1, "sane_get_devices: malloc() for sd_list failed\n");
        return SANE_STATUS_NO_MEM;
      }

    *device_list = sd_list;
    index = 0;
    md = md_first_dev;
    while ( md )
      {
        status = attach(md);
        if ( status != SANE_STATUS_GOOD )
          {
            DBG(10, "sane_get_devices: attach status '%s'\n",
                     sane_strstatus(status));
            md = md->next;
            continue;
          }

        /* check whether unit is ready, if so add it to the list */
        status = scsi_test_unit_ready(md);
        if ( status != SANE_STATUS_GOOD )
          {
            DBG(10, "sane_get_devices: test_unit_ready status '%s'\n",
                     sane_strstatus(status));
            md = md->next;
            continue;
          }

        sd_list[index] = &md->sane;

        ++index;
        md = md->next;
      }

    sd_list[index] = NULL;
    return SANE_STATUS_GOOD;
}


/*---------- sane_get_parameters() -------------------------------------------*/

SANE_Status
sane_get_parameters(SANE_Handle handle, SANE_Parameters *params)
{
    Microtek2_Scanner *ms = handle;
    Microtek2_Device *md;
    Option_Value *val;
    Microtek2_Info *mi;
    int mode;
    int depth;
    int bits_pp_in;             /* bits per pixel from scanner */
    int bits_pp_out;            /* bits_per_pixel transferred to frontend */
    int bytes_per_line;
    double x_pixel_per_mm;
    double y_pixel_per_mm;
    double x1_pixel;
    double y1_pixel;
    double width_pixel;
    double height_pixel;


    DBG(40, "sane_get_parameters: handle=%p, params=%p\n", handle,
	(void *) params);


    md = ms->dev;
    mi = &md->info[md->scan_source];
    val= ms->val;

    if ( ! ms->scanning )            /* get an estimate for the params */
      {

        get_scan_mode_and_depth(ms, &mode, &depth, &bits_pp_in, &bits_pp_out);

        switch ( mode )
          {
            case MS_MODE_COLOR:
              if ( mi->onepass )
                {
                  ms->params.format = SANE_FRAME_RGB;
                  ms->params.last_frame = SANE_TRUE;
                }
              else
                {
                  ms->params.format = SANE_FRAME_RED;
                  ms->params.last_frame = SANE_FALSE;
                }
              break;
            case MS_MODE_GRAY:
            case MS_MODE_HALFTONE:
            case MS_MODE_LINEART:
            case MS_MODE_LINEARTFAKE:
              ms->params.format = SANE_FRAME_GRAY;
              ms->params.last_frame = SANE_TRUE;
              break;
            default:
              DBG(1, "sane_get_parameters: Unknown scan mode %d\n", mode);
              break;
          }

      ms->params.depth = (SANE_Int) bits_pp_out;

      /* calculate lines, pixels per line and bytes per line */
      if ( val[OPT_RESOLUTION_BIND].w == SANE_TRUE )
        {
          x_pixel_per_mm = y_pixel_per_mm =
                SANE_UNFIX(val[OPT_RESOLUTION].w) / MM_PER_INCH;
          DBG(30, "sane_get_parameters: x_res=y_res=%f\n",
                  SANE_UNFIX(val[OPT_RESOLUTION].w));
        }
      else
        {
          x_pixel_per_mm = SANE_UNFIX(val[OPT_X_RESOLUTION].w) / MM_PER_INCH;
          y_pixel_per_mm = SANE_UNFIX(val[OPT_Y_RESOLUTION].w) / MM_PER_INCH;
          DBG(30, "sane_get_parameters: x_res=%f, y_res=%f\n",
                  SANE_UNFIX(val[OPT_X_RESOLUTION].w),
                  SANE_UNFIX(val[OPT_Y_RESOLUTION].w));
        }

      DBG(30, "sane_get_parameters: x_ppm=%f, y_ppm=%f\n",
               x_pixel_per_mm, y_pixel_per_mm);

      y1_pixel = SANE_UNFIX(ms->val[OPT_TL_Y].w) * y_pixel_per_mm;
      height_pixel = fabs(SANE_UNFIX(ms->val[OPT_BR_Y].w) * y_pixel_per_mm
                          - y1_pixel) + 0.5;
      ms->params.lines = (SANE_Int) height_pixel;

      x1_pixel =  SANE_UNFIX(ms->val[OPT_TL_X].w) * x_pixel_per_mm;
      width_pixel = fabs(SANE_UNFIX(ms->val[OPT_BR_X].w) * x_pixel_per_mm
                         - x1_pixel) + 0.5;
      ms->params.pixels_per_line = (SANE_Int) width_pixel;


      if ( bits_pp_out == 1 )
          bytes_per_line =  (width_pixel + 7 ) / 8;
      else
        {
          bytes_per_line = ( width_pixel * bits_pp_out ) / 8 ;
          if ( mode == MS_MODE_COLOR && mi->onepass )
              bytes_per_line *= 3;
        }
      ms->params.bytes_per_line = (SANE_Int) bytes_per_line;
    }  /* if ms->scanning */

  if ( params )
     *params = ms->params;

  DBG(30,"sane_get_parameters: format=%d, last_frame=%d, lines=%d\n",
        ms->params.format,ms->params.last_frame, ms->params.lines);
  DBG(30,"sane_get_parameters: depth=%d, ppl=%d, bpl=%d\n",
        ms->params.depth,ms->params.pixels_per_line, ms->params.bytes_per_line);

  return SANE_STATUS_GOOD;
}


/*---------- sane_get_select_fd() --------------------------------------------*/

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int *fd)
{
    Microtek2_Scanner *ms = handle;


    DBG(30, "sane_get_select_fd: ms=%p\n", (void *) ms);

    if ( ! ms->scanning )
      {
        DBG(1, "sane_get_select_fd: Scanner not scanning\n");
        return SANE_STATUS_INVAL;
      }

    *fd = (SANE_Int) ms->fd[0];
    return SANE_STATUS_GOOD;
}


/*---------- sane_init() -----------------------------------------------------*/

SANE_Status
sane_init(SANE_Int *version_code, SANE_Auth_Callback authorize)
{
    Microtek2_Device *md;
    FILE *fp;
    int match;
    SANE_Auth_Callback trash;


    DBG_INIT();
    DBG(1, "sane_init: Microtek2 (v%d.%d build %s) says hello...\n",
           MICROTEK2_MAJOR, MICROTEK2_MINOR, MICROTEK2_BUILD);

    if ( version_code )
        *version_code = SANE_VERSION_CODE(V_MAJOR, V_MINOR, 0);

#ifdef HAVE_AUTHORIZATION
    auth_callback = authorize;
#else
    trash = authorize;     /* prevents compiler warning "unused variable" */
#endif

    sanei_thread_init();

    match = 0;
    fp = sanei_config_open(MICROTEK2_CONFIG_FILE);
    if ( fp == NULL )
        DBG(10, "sane_init: file not opened: '%s'\n", MICROTEK2_CONFIG_FILE);
    else
      {
        /* check config file for devices and associated options */
        parse_config_file(fp, &md_config_temp);

        while ( md_config_temp )
          {
            sanei_config_attach_matching_devices(md_config_temp->device,
                                                 attach_one);
            if ( md_config_temp->next )  /* go to next device, if existent */
                md_config_temp = md_config_temp->next;
            else
                break;
          }

        fclose(fp);
      }

    if ( md_first_dev == NULL )
      {
        /* config file not found or no valid entry; default to /dev/scanner */
        /* instead of insisting on config file */
	add_device_list("/dev/scanner", &md);
        if ( md )
	    attach(md);
      }
    return SANE_STATUS_GOOD;
}


/*---------- sane_open() -----------------------------------------------------*/

SANE_Status
sane_open(SANE_String_Const name, SANE_Handle *handle)
{
    SANE_Status status;
    Microtek2_Scanner *ms;
    Microtek2_Device *md;
#ifdef HAVE_AUTHORIZATION
    struct stat st;
    int rc;
#endif


    DBG(30, "sane_open: device='%s'\n", name);

    *handle = NULL;
    md = md_first_dev;

    if ( name )
      {
        /* add_device_list() returns a pointer to the device struct if */
        /* the device is known or newly added, else it returns NULL */

        status = add_device_list(name, &md);
        if ( status != SANE_STATUS_GOOD )
            return status;
      }

    if ( ! md )
      {
        DBG(10, "sane_open: invalid device name '%s'\n", name);
        return SANE_STATUS_INVAL;
      }

    /* attach calls INQUIRY and READ SCANNER ATTRIBUTES */
    status = attach(md);
    if ( status != SANE_STATUS_GOOD )
        return status;

    ms = malloc(sizeof(Microtek2_Scanner));
    DBG(100, "sane_open: ms=%p, malloc'd %lu bytes\n",
	(void *) ms, (u_long) sizeof(Microtek2_Scanner));
    if ( ms == NULL )
      {
        DBG(1, "sane_open: malloc() for ms failed\n");
        return SANE_STATUS_NO_MEM;
      }

    memset(ms, 0, sizeof(Microtek2_Scanner));
    ms->dev = md;
    ms->scanning = SANE_FALSE;
    ms->cancelled = SANE_FALSE;
    ms->current_pass = 0;
    ms->sfd = -1;
    ms->pid = -1;
    ms->fp = NULL;
    ms->gamma_table = NULL;
    ms->buf.src_buf = ms->buf.src_buffer[0] = ms->buf.src_buffer[1] = NULL;
    ms->control_bytes = NULL;
    ms->shading_image = NULL;
    ms->condensed_shading_w = NULL;
    ms->condensed_shading_d = NULL;
    ms->current_color = MS_COLOR_ALL;
    ms->current_read_color = MS_COLOR_RED;

    init_options(ms, MD_SOURCE_FLATBED);

    /* insert scanner into linked list */
    ms->next = ms_first_handle;
    ms_first_handle = ms;

    *handle = ms;

#ifdef HAVE_AUTHORIZATION
    /* check whether the file with the passwords exists. If it doesnt */
    /* exist, we dont use any authorization */

    rc = stat(PASSWD_FILE, &st);
    if ( rc == -1 && errno == ENOENT )
        return SANE_STATUS_GOOD;
    else
      {
        status = do_authorization(md->name);
        return status;
      }
#else
    return SANE_STATUS_GOOD;
#endif
}


/*---------- sane_read() -----------------------------------------------------*/

SANE_Status
sane_read(SANE_Handle handle, SANE_Byte *buf, SANE_Int maxlen, SANE_Int *len )
{
    Microtek2_Scanner *ms = handle;
    SANE_Status status;
    ssize_t nread;


    DBG(30, "sane_read: handle=%p, buf=%p, maxlen=%d\n", handle, buf, maxlen);

    *len = 0;

    if ( ! ms->scanning || ms->cancelled )
      {
        if ( ms->cancelled )
          {
            status = SANE_STATUS_CANCELLED;
          }
        else
          {
            DBG(15, "sane_read: Scanner %p not scanning\n", (void *) ms);
            status = SANE_STATUS_IO_ERROR;
          }
        DBG(15, "sane_read: scan cancelled or scanner not scanning->cleanup\n");
        cleanup_scanner(ms);
        return status;
      }


    nread = read(ms->fd[0], (void *) buf, (int) maxlen);
    if ( nread == -1 )
      {
        if ( errno == EAGAIN )
          {
            DBG(30, "sane_read: currently no data available\n");
            return SANE_STATUS_GOOD;
          }
        else
          {
            DBG(1, "sane_read: read() failed, errno=%d\n", errno);
            cleanup_scanner(ms);
            return SANE_STATUS_IO_ERROR;
          }
      }

    if ( nread == 0 )
      {
         DBG(15, "sane_read: read 0 bytes -> EOF\n");
         ms->scanning = SANE_FALSE;
         cleanup_scanner(ms);
         return SANE_STATUS_EOF;
      }


    *len = (SANE_Int) nread;
    DBG(30, "sane_read: *len=%d\n", *len);
    return SANE_STATUS_GOOD;
}


/*---------- sane_set_io_mode() ---------------------------------------------*/

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
    Microtek2_Scanner *ms = handle;
    int rc;


    DBG(30, "sane_set_io_mode: handle=%p, nonblocking=%d\n",
             handle, non_blocking);

    if ( ! ms->scanning )
      {
        DBG(1, "sane_set_io_mode: Scanner not scanning\n");
        return SANE_STATUS_INVAL;
      }

    rc = fcntl(ms->fd[0], F_SETFL, non_blocking ? O_NONBLOCK : 0);
    if ( rc == -1 )
      {
        DBG(1, "sane_set_io_mode: fcntl() failed\n");
        return SANE_STATUS_INVAL;
      }

  return SANE_STATUS_GOOD;
}

/*---------- add_device_list() -----------------------------------------------*/

static SANE_Status
add_device_list(SANE_String_Const dev_name, Microtek2_Device **mdev)
{
    Microtek2_Device *md;
    SANE_String hdev;
    size_t len;


    if ( (hdev = strdup(dev_name)) == NULL)
      { 
	DBG(5, "add_device_list: malloc() for hdev failed\n");
        return SANE_STATUS_NO_MEM;
      }

    len = strlen(hdev);
    if ( hdev[len - 1] == '\n' )
        hdev[--len] = '\0';

    DBG(30, "add_device_list: device='%s'\n", hdev);

    /* check, if device is already known */
    md = md_first_dev;
    while ( md )
      {
        if ( strcmp(hdev, md->name) == 0 )
          {
	    DBG(30, "add_device_list: device '%s' already in list\n", hdev);

            *mdev = md;
            return SANE_STATUS_GOOD;
          }
        md = md->next;
    }

    md = (Microtek2_Device *) malloc(sizeof(Microtek2_Device));
    DBG(100, "add_device_list: md=%p, malloc'd %lu bytes\n",
                         (void *) md, (u_long) sizeof(Microtek2_Device));
    if ( md == NULL )
      {
	DBG(1, "add_device_list: malloc() for md failed\n");
        return SANE_STATUS_NO_MEM;
      }

    /* initialize Device and add it at the beginning of the list */
    memset(md, 0, sizeof(Microtek2_Device));
    md->next = md_first_dev;
    md_first_dev = md;
    md->sane.name = NULL;
    md->sane.vendor = NULL;
    md->sane.model = NULL;
    md->sane.type = NULL;
    md->scan_source = MD_SOURCE_FLATBED;
    md->shading_table_w = NULL;
    md->shading_table_d = NULL;
    strncpy(md->name, hdev, PATH_MAX - 1);
    if ( md_config_temp )
        md->opts = md_config_temp->opts;
    else
        md->opts = md_options;
    ++md_num_devices;
    *mdev = md;
    DBG(100, "free hdev at %p\n", hdev);
    free(hdev);

    return SANE_STATUS_GOOD;
}

/*---------- attach() --------------------------------------------------------*/

static SANE_Status
attach(Microtek2_Device *md)
{
    /* This function is called from sane_init() to do the inquiry and to read */
    /* the scanner attributes. If one of these calls fails, or if a new */
    /* device is passed in sane_open() this function may also be called */
    /* from sane_open() or sane_get_devices(). */

    SANE_String model_string; 
    SANE_Status status;
    SANE_Byte source_info;


    DBG(30, "attach: device='%s'\n", md->name);

    status = scsi_inquiry( &md->info[MD_SOURCE_FLATBED], md->name );
    if ( status != SANE_STATUS_GOOD )
      {
	DBG(1, "attach: '%s'\n", sane_strstatus(status));
        return status;
      }

    /* We copy the inquiry info into the info structures for each scansource */
    /* like ADF, TMA, STRIPE and SLIDE */

    for ( source_info = 1; source_info < 5; ++source_info )
        memcpy( &md->info[source_info],
                &md->info[MD_SOURCE_FLATBED],
                sizeof( Microtek2_Info ) );

    /* Here we should insert a function, that stores all the relevant */
    /* information in the info structure in a more conveniant format */
    /* in the device structure, e.g. the model name with a trailing '\0'. */

    status = check_inquiry(md, &model_string);
    if ( status != SANE_STATUS_GOOD )
        return status;

    md->sane.name = md->name;
    md->sane.vendor = "Microtek";
    md->sane.model = strdup(model_string);
    if ( md->sane.model == NULL )
        DBG(1, "attach: strdup for model string failed\n");
    md->sane.type = "flatbed scanner";
    md->revision = strtod(md->info[MD_SOURCE_FLATBED].revision, NULL);

    status = scsi_read_attributes(&md->info[0],
                                  md->name, MD_SOURCE_FLATBED);
    if ( status != SANE_STATUS_GOOD )
      {
	DBG(1, "attach: '%s'\n", sane_strstatus(status));
        return status;
      }

    if ( MI_LUTCAP_NONE( md->info[MD_SOURCE_FLATBED].lut_cap) )
        /* no gamma tables */
        md->model_flags |= MD_NO_GAMMA;

    /* check whether the device supports transparency media adapters */
    if ( md->info[MD_SOURCE_FLATBED].option_device & MI_OPTDEV_TMA )
      {
        status = scsi_read_attributes(&md->info[0],
                                      md->name, MD_SOURCE_TMA);
        if ( status != SANE_STATUS_GOOD )
            return status;
      }

    /* check whether the device supports an ADF */
    if ( md->info[MD_SOURCE_FLATBED].option_device & MI_OPTDEV_ADF )
      {
        status = scsi_read_attributes(&md->info[0],
                                      md->name, MD_SOURCE_ADF);
        if ( status != SANE_STATUS_GOOD )
            return status;
      }

    /* check whether the device supports STRIPES */
    if ( md->info[MD_SOURCE_FLATBED].option_device & MI_OPTDEV_STRIPE )
      {
        status = scsi_read_attributes(&md->info[0],
                                      md->name, MD_SOURCE_STRIPE);
        if ( status != SANE_STATUS_GOOD )
            return status;
      }

    /* check whether the device supports SLIDES */
    if ( md->info[MD_SOURCE_FLATBED].option_device & MI_OPTDEV_SLIDE )
      {
        /* The Phantom 636cx indicates in its attributes that it supports */
        /* slides, but it doesn't. Thus this command would fail. */

        if ( ! (md->model_flags & MD_NO_SLIDE_MODE) )
          {
            status = scsi_read_attributes(&md->info[0],
                                          md->name, MD_SOURCE_SLIDE);
            if ( status != SANE_STATUS_GOOD )
                return status;
          }
      }

    status = scsi_read_system_status(md, -1);
    if ( status != SANE_STATUS_GOOD )
        return status;

    return SANE_STATUS_GOOD;
}


/*---------- attach_one() ----------------------------------------------------*/

static SANE_Status
attach_one (const char *name)
{
    Microtek2_Device *md;
    Microtek2_Device *md_tmp;


    DBG(30, "attach_one: name='%s'\n", name);

    md_tmp = md_first_dev;
    /* if add_device_list() adds an entry it does this at the beginning */
    /* of the list and thus changes md_first_dev */
    add_device_list(name, &md);
    if ( md_tmp != md_first_dev )
        attach(md);

    return SANE_STATUS_GOOD;
}

/*---------- cancel_scan() ---------------------------------------------------*/

static SANE_Status
cancel_scan(Microtek2_Scanner *ms)
{
    SANE_Status status;


    DBG(30, "cancel_scan: ms=%p\n", (void *) ms);

    /* READ IMAGE with a transferlength of 0 aborts a scan */
    ms->transfer_length = 0;
    status = scsi_read_image(ms, (u_int8_t *) NULL, 1);
    if ( status != SANE_STATUS_GOOD )
      {
        DBG(1, "cancel_scan: cancel failed: '%s'\n", sane_strstatus(status));
        status = SANE_STATUS_IO_ERROR;
      }
    else
        status = SANE_STATUS_CANCELLED;

    close(ms->fd[1]);

    /* if we are aborting a scan because, for example, we run out
       of material on a feeder, then pid may be already -1 and
       kill(-1, SIGTERM), i.e. killing all our processes, is not
       likely what we really want - --mj, 2001/Nov/19 */
    if (ms->pid > 1)
      {
       sanei_thread_kill(ms->pid);
       sanei_thread_waitpid(ms->pid, NULL);
      }

    return status;
}


/*---------- check_option() --------------------------------------------------*/

static void
check_option(const char *cp, Config_Options *co)
{
    /* This function analyses options in the config file */

    char *endptr;

    /* When this function is called, it is already made sure that this */
    /* is an option line, i.e. a line that starts with option */

    cp = sanei_config_skip_whitespace(cp);     /* skip blanks */
    cp = sanei_config_skip_whitespace(cp + 6); /* skip "option" */
    if ( strncmp(cp, "dump", 4) == 0 && isspace(cp[4]) )
      {
        cp = sanei_config_skip_whitespace(cp + 4);
        if ( *cp )
          {
            md_dump = (int) strtol(cp, &endptr, 10);
            if ( md_dump > 4 || md_dump < 0 )
              {
                md_dump = 1;
                DBG(30, "check_option: setting dump to %d\n", md_dump);
              }
            cp = sanei_config_skip_whitespace(endptr);
            if ( *cp )
              {
                /* something behind the option value or value wrong */
                md_dump = 1;
                DBG(30, "check_option: option value wrong\n");
              }
          }
        else
          {
            DBG(30, "check_option: missing option value\n");
            /* reasonable fallback */
            md_dump = 1;
          }
      }
    else if ( strncmp(cp, "strip-height", 12) == 0 && isspace(cp[12]) )
      {
        cp = sanei_config_skip_whitespace(cp + 12);
        if ( *cp )
          {
            co->strip_height = strtod(cp, &endptr);
            DBG(30, "check_option: setting strip_height to %f\n",
                     co->strip_height);
            if ( co->strip_height <= 0.0 )
                co->strip_height = 14.0;
            cp = sanei_config_skip_whitespace(endptr);
            if ( *cp )
              {
                /* something behind the option value or value wrong */
                co->strip_height = 14.0;
                DBG(30, "check_option: option value wrong: %f\n",
                         co->strip_height);
              }
          }
      }
    else if ( strncmp(cp, "no-backtrack-option", 19) == 0
              && isspace(cp[19]) )
      {
        cp = sanei_config_skip_whitespace(cp + 19);
        if ( strncmp(cp, "on", 2) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 2);
            co->no_backtracking = "on";
          }
        else if ( strncmp(cp, "off", 3) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 3);
            co->no_backtracking = "off";
          }
        else
            co->no_backtracking = "off";

        if ( *cp )
          {
            /* something behind the option value or value wrong */
            co->no_backtracking = "off";
            DBG(30, "check_option: option value wrong: %s\n", cp);
          }
      }
    else if ( strncmp(cp, "lightlid-35", 11) == 0
              && isspace(cp[11]) )
      {
        cp = sanei_config_skip_whitespace(cp + 11);
        if ( strncmp(cp, "on", 2) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 2);
            co->lightlid35 = "on";
          }
        else if ( strncmp(cp, "off", 3) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 3);
            co->lightlid35 = "off";
          }
        else
            co->lightlid35 = "off";

        if ( *cp )
          {
            /* something behind the option value or value wrong */
            co->lightlid35 = "off";
            DBG(30, "check_option: option value wrong: %s\n", cp);
          }
      }
    else if ( strncmp(cp, "toggle-lamp", 11) == 0
              && isspace(cp[11]) )
      {
        cp = sanei_config_skip_whitespace(cp + 11);
        if ( strncmp(cp, "on", 2) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 2);
            co->toggle_lamp = "on";
          }
        else if ( strncmp(cp, "off", 3) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 3);
            co->toggle_lamp = "off";
          }
        else
            co->toggle_lamp = "off";

        if ( *cp )
          {
            /* something behind the option value or value wrong */
            co->toggle_lamp = "off";
            DBG(30, "check_option: option value wrong: %s\n", cp);
          }
      }
    else if ( strncmp(cp, "lineart-autoadjust", 18) == 0
              && isspace(cp[18]) )
      {
        cp = sanei_config_skip_whitespace(cp + 18);
        if ( strncmp(cp, "on", 2) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 2);
            co->auto_adjust = "on";
          }
        else if ( strncmp(cp, "off", 3) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 3);
            co->auto_adjust = "off";
          }
        else
            co->auto_adjust = "off";

        if ( *cp )
          {
            /* something behind the option value or value wrong */
            co->auto_adjust = "off";
            DBG(30, "check_option: option value wrong: %s\n", cp);
          }
      }
    else if ( strncmp(cp, "backend-calibration", 19) == 0
              && isspace(cp[19]) )
      {
        cp = sanei_config_skip_whitespace(cp + 19);
        if ( strncmp(cp, "on", 2) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 2);
            co->backend_calibration = "on";
          }
        else if ( strncmp(cp, "off", 3) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 3);
            co->backend_calibration = "off";
          }
        else
            co->backend_calibration = "off";

        if ( *cp )
          {
            /* something behind the option value or value wrong */
            co->backend_calibration = "off";
            DBG(30, "check_option: option value wrong: %s\n", cp);
          }
      }
    else if ( strncmp(cp, "colorbalance-adjust", 19) == 0
              && isspace(cp[19]) )
      {
        cp = sanei_config_skip_whitespace(cp + 19);
        if ( strncmp(cp, "on", 2) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 2);
            co->colorbalance_adjust = "on";
          }
        else if ( strncmp(cp, "off", 3) == 0 )
          {
            cp = sanei_config_skip_whitespace(cp + 3);
            co->colorbalance_adjust = "off";
          }
        else
            co->colorbalance_adjust = "off";

        if ( *cp )
          {
            /* something behind the option value or value wrong */
            co->colorbalance_adjust = "off";
            DBG(30, "check_option: option value wrong: %s\n", cp);
          }
      }
    else
        DBG(30, "check_option: invalid option in '%s'\n", cp);
}


/*---------- check_inquiry() -------------------------------------------------*/

static SANE_Status
check_inquiry(Microtek2_Device *md, SANE_String *model_string)
{
    Microtek2_Info *mi;

    DBG(30, "check_inquiry: md=%p\n", (void *) md);

    md->n_control_bytes = 0;
    md->shading_length = 0;
    md->shading_table_contents = 0;

    mi = &md->info[MD_SOURCE_FLATBED];
    if ( mi->scsi_version != MI_SCSI_II_VERSION )
      {
        DBG(1, "check_inquiry: Device is not a SCSI-II device, but 0x%02x\n",
                mi->scsi_version);
        return SANE_STATUS_IO_ERROR;
      }

    if ( mi->device_type != MI_DEVTYPE_SCANNER )
      {
        DBG(1, "check_inquiry: Device is not a scanner, but 0x%02x\n",
                mi->device_type);
        return SANE_STATUS_IO_ERROR;
      }

    if ( strncasecmp("MICROTEK", mi->vendor, INQ_VENDOR_L) != 0
         && strncmp("        ", mi->vendor, INQ_VENDOR_L) != 0
         && strncmp("AGFA    ", mi->vendor, INQ_VENDOR_L) != 0 )
      {
        DBG(1, "check_inquiry: Device is not a Microtek, but '%.*s'\n",
                INQ_VENDOR_L, mi->vendor);
        return SANE_STATUS_IO_ERROR;
      }

    if ( mi->depth & MI_HASDEPTH_16 )
        md->shading_depth = 16;
    else if ( mi->depth & MI_HASDEPTH_14 )
        md->shading_depth = 14;
    else if ( mi->depth & MI_HASDEPTH_12 )
        md->shading_depth = 12;
    else if ( mi->depth & MI_HASDEPTH_10 )
        md->shading_depth = 10;
    else 
        md->shading_depth = 8;

    switch (mi->model_code)
      {
        case 0x81:
        case 0xab:
          *model_string = "ScanMaker 4";
          break;
        case 0x85:
          *model_string = "ScanMaker V300 / ColorPage-EP";
          /* The ScanMaker V300 (FW < 2.70) returns some values for the */
          /* "read image info" command in only two bytes */
          /* and doesn't understand read_image_status */
          md->model_flags |= MD_NO_RIS_COMMAND;
          if ( md->revision < 2.70 )
              md->model_flags |= MD_RII_TWO_BYTES;
          break;
        case 0x87:
          *model_string = "ScanMaker 5";
          md->model_flags |= MD_NO_GAMMA;
          break;
        case 0x89:
          *model_string = "ScanMaker 6400XL";
          break;
        case 0x8a:
          *model_string = "ScanMaker 9600XL";
          break;
        case 0x8c:
          *model_string = "ScanMaker 630 / ScanMaker V600";
          break;
        case 0x8d:
          *model_string = "ScanMaker 336 / ScanMaker V310";
          break;
        case 0x90:
        case 0x92:
          *model_string = "E3+ / Vobis HighScan";
          break;
        case 0x91:
          *model_string = "ScanMaker X6 / Phantom 636";
          /* The X6 indicates a data format of segregated data in TMA mode */
          /* but actually transfers as chunky data */
          md->model_flags |= MD_DATA_FORMAT_WRONG;
          if ( md->revision == 1.00 )
              md->model_flags |= MD_OFFSET_2;
          break;
        case 0x93:
          *model_string = "ScanMaker 336 / ScanMaker V310";
          break;
        case 0x70:
        case 0x71:
        case 0x94:
        case 0xa0:
          *model_string = "Phantom 330cx / Phantom 336cx / SlimScan C3";
          /* These models do not accept gamma tables. Apparently they */
          /* read the control bits and do not accept shading tables */
          /* They also don't support enhancements (contrast, brightness...)*/
          md->model_flags |= MD_NO_SLIDE_MODE
                          | MD_NO_GAMMA
#ifndef NO_PHANTOMTYPE_SHADING
			  | MD_PHANTOM336CX_TYPE_SHADING
#endif
                          | MD_READ_CONTROL_BIT
                          | MD_NO_ENHANCEMENTS;
          md->opt_backend_calib_default = SANE_TRUE;
          md->opt_no_backtrack_default = SANE_TRUE;
          md->n_control_bytes = 320;
          md->shading_length = 18;
          md->shading_depth = 10;
          md->controlbit_offset = 7;
          break;
        case 0x95:
          *model_string = "ArtixScan 1010";
          break;
        case 0x97:
          *model_string = "ScanMaker 636";
          break;
        case 0x98:
          *model_string = "ScanMaker X6EL";
          if ( md->revision == 1.00 )
              md->model_flags |= MD_OFFSET_2;
          break;
        case 0x99:
          *model_string = "ScanMaker X6USB";
          if ( md->revision == 1.00 )
              md->model_flags |= MD_OFFSET_2;
          md->model_flags |= MD_X6_SHORT_TRANSFER;
          break;
        case 0x9a:
          *model_string = "Phantom 636cx / C6";
          /* The Phantom 636cx says it supports the SLIDE mode, but it */
          /* doesn't. Thus inquring the attributes for slide mode would */
          /* fail. Also it does not accept gamma tables. Apparently */
          /* it reads the control bits and does not accept shading tables */
          md->model_flags |= MD_NO_SLIDE_MODE
                          | MD_READ_CONTROL_BIT
                          | MD_NO_GAMMA
                          | MD_PHANTOM_C6;
          md->opt_backend_calib_default = SANE_TRUE;
          md->opt_no_backtrack_default = SANE_TRUE;
          md->n_control_bytes = 647;
          /* md->shading_length = 18; firmware values seem to work better */
          md->shading_depth = 12;
          md->controlbit_offset = 18;
          break;
        case 0x9d:
          *model_string = "AGFA Duoscan T1200";
          break;
        case 0xa3:
          *model_string = "ScanMaker V6USL";
          /* The V6USL does not accept gamma tables */
          md->model_flags |= MD_NO_GAMMA;
          break;
        case 0xa5:
          *model_string = "ArtixScan 4000t";
          break;
        case 0xac:
          *model_string = "ScanMaker V6UL";
          /* The V6USL does not accept gamma tables, perhaps the V6UL also */
          md->model_flags |= MD_NO_GAMMA;
          break;
        case 0xaf:
          *model_string = "SlimScan C3";
          md->model_flags |= MD_NO_SLIDE_MODE
                          | MD_NO_GAMMA
                          | MD_READ_CONTROL_BIT
                          | MD_NO_ENHANCEMENTS;
          md->opt_backend_calib_default = SANE_TRUE;
          md->opt_no_backtrack_default = SANE_TRUE;
          md->n_control_bytes = 320;
          md->controlbit_offset = 7;
          break;
        case 0xb0:
          *model_string = "ScanMaker X12USL";
          md->opt_backend_calib_default = SANE_TRUE;
          md->model_flags |= MD_16BIT_TRANSFER
                          | MD_CALIB_DIVISOR_600;
          break;
        case 0xb3:
           *model_string = "ScanMaker 3600";
           break;
        case 0xb4:
           *model_string = "ScanMaker 4700";
           break;
        case 0xb6:
          *model_string = "ScanMaker V6UPL";
          /* is like V6USL but with USB and Parport interface ?? */
          md->model_flags |= MD_NO_GAMMA;
          break;
        case 0xb8:
           *model_string = "ScanMaker 3700";
           break;
        case 0xde:
           *model_string = "ScanMaker 9800XL";
           md->model_flags |= MD_NO_GAMMA
	                   | MD_16BIT_TRANSFER;
           md->opt_backend_calib_default = SANE_TRUE;
           md->opt_no_backtrack_default = SANE_TRUE;
           break;
        default:
          DBG(1, "check_inquiry: Model 0x%02x not supported\n", mi->model_code);
          return SANE_STATUS_IO_ERROR;
      }

    return SANE_STATUS_GOOD;
}


/*---------- cleanup_scanner() -----------------------------------------------*/

static void
cleanup_scanner(Microtek2_Scanner *ms)
{
    SANE_Status status;
    Microtek2_Device *md;
    md = ms->dev;

    DBG(30, "cleanup_scanner: ms=%p, ms->sfd=%d\n", (void *) ms, ms->sfd);

    if ( ms->scanning == SANE_TRUE )
      status=cancel_scan(ms);

    if ( ms->sfd != -1 )
      sanei_scsi_close(ms->sfd);
    ms->sfd = -1;
    ms->pid = -1;
    ms->fp = NULL;
    ms->current_pass = 0;
    ms->scanning = SANE_FALSE;
    ms->cancelled = SANE_FALSE;

    /* free buffers */
    if ( ms->buf.src_buffer[0] )
      {
        DBG(100, "free ms->buf.src_buffer[0] at %p\n", ms->buf.src_buffer[0]);
        free((void *) ms->buf.src_buffer[0]);
        ms->buf.src_buffer[0] = NULL;
        ms->buf.src_buf = NULL;
      }
    if ( ms->buf.src_buffer[1] )
      {
        DBG(100, "free ms->buf.src_buffer[1] at %p\n", ms->buf.src_buffer[1]);
        free((void *) ms->buf.src_buffer[1]);
        ms->buf.src_buffer[1] = NULL;
        ms->buf.src_buf = NULL;
      }
    if ( ms->buf.src_buf )
      {
        DBG(100, "free ms->buf.src_buf at %p\n", ms->buf.src_buf);
        free((void *) ms->buf.src_buf);
        ms->buf.src_buf = NULL;
      }
    if ( ms->temporary_buffer )
      {
        DBG(100, "free ms->temporary_buffer at %p\n", ms->temporary_buffer);
        free((void *) ms->temporary_buffer);
        ms->temporary_buffer = NULL;
      }
    if ( ms->gamma_table )
      {
        DBG(100, "free ms->gamma_table at %p\n", ms->gamma_table);
        free((void *) ms->gamma_table);
        ms->gamma_table = NULL;
      }
    if ( ms->control_bytes )
      {
        DBG(100, "free ms->control_bytes at %p\n", ms->control_bytes);
        free((void *) ms->control_bytes);
        ms->control_bytes = NULL;
      }
    if ( ms->condensed_shading_w )
      {
        DBG(100, "free ms->condensed_shading_w at %p\n",
                  ms->condensed_shading_w);
        free((void *) ms->condensed_shading_w);
        ms->condensed_shading_w = NULL;
      }
    if ( ms->condensed_shading_d )
      {
        DBG(100, "free ms->condensed_shading_d at %p\n",
                  ms->condensed_shading_d);
        free((void *) ms->condensed_shading_d);
        ms->condensed_shading_d = NULL;
      }

    return;
}

#ifdef HAVE_AUTHORIZATION
/*---------- do_authorization() ----------------------------------------------*/

static SANE_Status
do_authorization(char *ressource)
{
    /* This function implements a simple authorization function. It looks */
    /* up an entry in the file SANE_PATH_CONFIG_DIR/auth. Such an entry */
    /* must be of the form device:user:password where password is a crypt() */
    /* encrypted password. If several users are allowed to access a device */
    /* an entry must be created for each user. If no entry exists for device */
    /* or the file does not exist no authentication is neccessary. If the */
    /* file exists, but cant be opened the authentication fails */

    SANE_Status status;
    FILE *fp;
    int device_found;
    char username[SANE_MAX_USERNAME_LEN];
    char password[SANE_MAX_PASSWORD_LEN];
    char line[MAX_LINE_LEN];
    char *linep;
    char *device;
    char *user;
    char *passwd;
    char *p;


    DBG(30, "do_authorization: ressource=%s\n", ressource);

    if ( auth_callback == NULL )  /* frontend does not require authorization */
        return SANE_STATUS_GOOD;

    /* first check if an entry exists in for this device. If not, we dont */
    /* use authorization */

    fp = fopen(PASSWD_FILE, "r");
    if ( fp == NULL )
      {
        if ( errno == ENOENT )
          {
            DBG(1, "do_authorization: file not found: %s\n", PASSWD_FILE);
            return SANE_STATUS_GOOD;
          }
        else
          {
            DBG(1, "do_authorization: fopen() failed, errno=%d\n", errno);
            return SANE_STATUS_ACCESS_DENIED;
          }
      }
    
    linep = &line[0];
    device_found = 0;
    while ( fgets(line, MAX_LINE_LEN, fp) )
      {
        p = index(linep, SEPARATOR);
        if ( p )
          {
            *p = '\0';
            device = linep;
            if ( strcmp(device, ressource) == 0 )
              {
                DBG(2, "equal\n");
                device_found = 1;
                break; 
              }
          }
      }

    if ( ! device_found )
      {
        fclose(fp);
        return SANE_STATUS_GOOD;
      }

    fseek(fp, 0L, SEEK_SET);
    
    (*auth_callback) (ressource, username, password);
  
    status = SANE_STATUS_ACCESS_DENIED;
    do
      {
        fgets(line, MAX_LINE_LEN, fp);
        if ( ! ferror(fp) && ! feof(fp) )
          {
            /* neither strsep(3) nor strtok(3) seem to work on my system */
            p = index(linep, SEPARATOR);
            if ( p == NULL )
                continue;
            *p = '\0';
            device = linep;
            if ( strcmp( device, ressource) != 0 ) /* not a matching entry */
                continue;

            linep = ++p; 
            p = index(linep, SEPARATOR);
            if ( p == NULL )
                continue;

            *p = '\0';
            user = linep;
            if ( strncmp(user, username, SANE_MAX_USERNAME_LEN) != 0 )
                continue;                  /* username doesnt match */

            linep = ++p;
            /* rest of the line is considered to be the password */
            passwd = linep;
            /* remove newline */
            *(passwd + strlen(passwd) - 1) = '\0';
            p = crypt(password, SALT);
            if ( strcmp(p, passwd) == 0 )
              {
                /* authentication ok */
                status = SANE_STATUS_GOOD;
                break;
              }
            else
                continue;
          }
      } while ( ! ferror(fp) && ! feof(fp) );
    fclose(fp);

    return status;
}
#endif

/*---------- dump_area() -----------------------------------------------------*/

static SANE_Status
dump_area(u_int8_t *area, int len, char *info)
{
    /* this function dumps control or information blocks */

#define BPL    16               /* bytes per line to print */

    int i;
    int o;
    int o_limit;
    char outputline[100];
    char *outbuf;

    if ( ! info[0] )
        info = "No additional info available";

    DBG(30, "dump_area: %s\n", info);

    outbuf = outputline;
    o_limit = (len + BPL - 1) / BPL;
    for ( o = 0; o < o_limit; o++)
      {
        sprintf(outbuf, "  %4d: ", o * BPL);
        outbuf += 8;
        for ( i=0; i < BPL && (o * BPL + i ) < len; i++)
          {
            if ( i == BPL / 2 )
              {
                sprintf(outbuf, " ");
                outbuf +=1;
              }
            sprintf(outbuf, "%02x", area[o * BPL + i]);
            outbuf += 2;
          }

        sprintf(outbuf, "%*s",  2 * ( 2 + BPL - i), " " );
        outbuf += (2 * ( 2 + BPL - i));
        sprintf(outbuf, "%s",  (i == BPL / 2) ? " " : "");
        outbuf += ((i == BPL / 2) ? 1 : 0);

        for ( i = 0; i < BPL && (o * BPL + i ) < len; i++)
          {
            if ( i == BPL / 2 )
              {
                sprintf(outbuf, " ");
                outbuf += 1;
              }
            sprintf(outbuf, "%c", isprint(area[o * BPL + i])
                                  ? area[o * BPL + i]
                                  : '.');
            outbuf += 1;
          }
        outbuf = outputline;
        DBG(1, "%s\n", outbuf);
      }

    return SANE_STATUS_GOOD;
}


/*---------- dump_area2() ----------------------------------------------------*/

static SANE_Status
dump_area2(u_int8_t *area, int len, char *info)
{

#define BPL    16               /* bytes per line to print */

    int i, linelength;
    char outputline[100];
    char *outbuf;
    linelength = BPL * 3;

    if ( ! info[0] )
        info = "No additional info available";    

    DBG(1, "[%s]\n", info);

    outbuf = outputline;
    for ( i = 0; i < len; i++)
      {
        sprintf(outbuf, "%02x,", *(area + i));
        outbuf += 3;
        if ( ((i+1)%BPL == 0) || (i == len-1) )
           {
             outbuf = outputline;
             DBG(1, "%s\n", outbuf);
           }
      }

    return SANE_STATUS_GOOD;
}

/*---------- dump_to_file() --------------------------------------------------*/
/*---  only for debugging, currently not used -----*/
#if 0
static SANE_Status
dump_to_file(u_int8_t *area, int len, char *filename, char *mode)
{
FILE *out;
int i;

    out = fopen(filename, mode);

    for ( i = 0; i < len; i++)
         fputc( *(area + i ), out);

    fclose(out);

    return SANE_STATUS_GOOD;
}
#endif

/*---------- dump_attributes() -----------------------------------------------*/

static SANE_Status
dump_attributes(Microtek2_Info *mi)
{
  /* dump all we know about the scanner */

  int i;

  DBG(30, "dump_attributes: mi=%p\n", (void *) mi);
  DBG(1, "\n");
  DBG(1, "Scanner attributes from device structure\n");
  DBG(1, "========================================\n");
  DBG(1, "Scanner ID...\n");
  DBG(1, "~~~~~~~~~~~~~\n");
  DBG(1, "  Vendor Name%15s: '%s'\n", " ", mi->vendor);
  DBG(1, "  Model Name%16s: '%s'\n", " ", mi->model);
  DBG(1, "  Revision%18s: '%s'\n", " ", mi->revision);
  DBG(1, "  Model Code%16s: 0x%02x\n"," ", mi->model_code);
  switch(mi->model_code)
    {
      case 0x80: DBG(1,  "Redondo 2000XL / ArtixScan 2020\n"); break;
      case 0x81: DBG(1,  "ScanMaker 4 / Aruba\n"); break;
      case 0x82: DBG(1,  "Bali\n"); break;
      case 0x83: DBG(1,  "Washington\n"); break;
      case 0x84: DBG(1,  "Manhattan\n"); break;
      case 0x85: DBG(1,  "ScanMaker V300 / Phantom parallel / TR3\n"); break;
      case 0x86: DBG(1,  "CCP\n"); break;
      case 0x87: DBG(1,  "Scanmaker V\n"); break;
      case 0x88: DBG(1,  "Scanmaker VI\n"); break;
      case 0x89: DBG(1,  "ScanMaker 6400XL / A3-400\n"); break;
      case 0x8a: DBG(1,  "ScanMaker 9600XL / A3-600\n"); break;
      case 0x8b: DBG(1,  "Watt\n"); break;
      case 0x8c: DBG(1,  "ScanMaker V600 / TR6\n"); break;
      case 0x8d: DBG(1,  "ScanMaker V310 / Tr3 10-bit\n"); break;
      case 0x8e: DBG(1,  "CCB\n"); break;
      case 0x8f: DBG(1,  "Sun Rise\n"); break;
      case 0x90: DBG(1,  "ScanMaker E3+ 10-bit\n"); break;
      case 0x91: DBG(1,  "ScanMaker X6 / Phantom 636\n"); break;
      case 0x92: DBG(1,  "ScanMaker E3+ / Vobis Highscan\n"); break;
      case 0x93: DBG(1,  "ScanMaker V310\n"); break;
      case 0x94: DBG(1,  "SlimScan C3 / Phantom 330cx / 336cx\n"); break;
      case 0x95: DBG(1,  "ArtixScan 1010\n"); break;
      case 0x97: DBG(1,  "ScanMaker V636\n"); break;
      case 0x98: DBG(1,  "ScanMaker X6EL\n"); break;
      case 0x99: DBG(1,  "ScanMaker X6 / X6USB\n"); break;
      case 0x9a: DBG(1,  "SlimScan C6 / Phantom 636cx\n"); break;
      case 0x9d: DBG(1,  "AGFA DuoScan T1200\n"); break;
      case 0xa0: DBG(1,  "SlimScan C3 / Phantom 336cx\n"); break;
      case 0xac: DBG(1,  "ScanMaker V6UL\n"); break;
      case 0xa3: DBG(1,  "ScanMaker V6USL\n"); break;
      case 0xaf: DBG(1,  "SlimScan C3 / Phantom 336cx\n"); break;
      case 0xb0: DBG(1,  "ScanMaker X12USL\n"); break;
      case 0xb3: DBG(1,  "ScanMaker 3600\n"); break;
      case 0xb4: DBG(1,  "ScanMaker 4700\n"); break;
      case 0xb6: DBG(1,  "ScanMaker V6UPL\n"); break;
      case 0xb8: DBG(1,  "ScanMaker 3700\n"); break;
      case 0xde: DBG(1,  "ScanMaker 9800XL\n"); break;
      default:   DBG(1,  "Unknown\n"); break;
    }
  DBG(1, "  Device Type Code%10s: 0x%02x (%s),\n", " ",
                  mi->device_type,
	          mi->device_type & MI_DEVTYPE_SCANNER ?
                  "Scanner" : "Unknown type");

  switch (mi->scanner_type)
    {
      case MI_TYPE_FLATBED:
          DBG(1, "  Scanner type%14s:%s", " ", " Flatbed scanner\n");
          break;
      case MI_TYPE_TRANSPARENCY:
          DBG(1, "  Scanner type%14s:%s", " ", " Transparency scanner\n");
          break;
      case MI_TYPE_SHEEDFEED:
          DBG(1, "  Scanner type%14s:%s", " ", " Sheet feed scanner\n");
          break;
      default:
          DBG(1, "  Scanner type%14s:%s", " ", " Unknown\n");
          break;
    }

  DBG(1, "  Supported options%9s: Automatic document feeder: %s\n",
	          " ", mi->option_device & MI_OPTDEV_ADF ? "Yes" : "No");
  DBG(1, "%30sTransparency media adapter: %s\n",
	          " ", mi->option_device & MI_OPTDEV_TMA ? "Yes" : "No");
  DBG(1, "%30sAuto paper detecting: %s\n",
	          " ", mi->option_device & MI_OPTDEV_ADP ? "Yes" : "No");
  DBG(1, "%30sAdvanced picture system: %s\n",
	          " ", mi->option_device & MI_OPTDEV_APS ? "Yes" : "No");
  DBG(1, "%30sStripes: %s\n",
	          " ", mi->option_device & MI_OPTDEV_STRIPE ? "Yes" : "No");
  DBG(1, "%30sSlides: %s\n",
	          " ", mi->option_device & MI_OPTDEV_SLIDE ? "Yes" : "No");
  DBG(1, "  Scan button%15s: %s\n", " ", mi->scnbuttn ? "Yes" : "No");

  DBG(1, "\n");
  DBG(1, "  Imaging Capabilities...\n");
  DBG(1, "  ~~~~~~~~~~~~~~~~~~~~~~~\n");
  DBG(1, "  Color scanner%6s: %s\n", " ", (mi->color) ? "Yes" : "No");
  DBG(1, "  Number passes%6s: %d pass%s\n", " ",
                  (mi->onepass) ? 1 : 3,
                  (mi->onepass) ? "" : "es");
  DBG(1, "  Resolution%9s: X-max: %5d dpi\n%35sY-max: %5d dpi\n",
                  " ", mi->max_xresolution, " ",mi->max_yresolution);
  DBG(1, "  Geometry%11s: Geometric width: %5d pts (%2.2f'')\n", " ",
          mi->geo_width, (float) mi->geo_width / (float) mi->opt_resolution);
  DBG(1, "%23sGeometric height:%5d pts (%2.2f'')\n", " ",
          mi->geo_height, (float) mi->geo_height / (float) mi->opt_resolution);
  DBG(1, "  Optical resolution%1s: %d\n", " ", mi->opt_resolution);

  DBG(1, "  Modes%14s: Lineart:     %s\n%35sHalftone:     %s\n", " ",
	          (mi->scanmode & MI_HASMODE_LINEART) ? " Yes" : " No", " ",
	          (mi->scanmode & MI_HASMODE_HALFTONE) ? "Yes" : "No");

  DBG(1, "%23sGray:     %s\n%35sColor:     %s\n", " ",
	          (mi->scanmode & MI_HASMODE_GRAY) ? "    Yes" : "    No", " ",
	          (mi->scanmode & MI_HASMODE_COLOR) ? "   Yes" : "   No");

  DBG(1, "  Depths%14s: Nibble Gray:  %s\n",
	          " ", (mi->depth & MI_HASDEPTH_NIBBLE) ? "Yes" : "No");
  DBG(1, "%23s10-bit-color: %s\n",
                  " ", (mi->depth & MI_HASDEPTH_10) ? "Yes" : "No");
  DBG(1, "%23s12-bit-color: %s\n", " ",
	          (mi->depth & MI_HASDEPTH_12) ? "Yes" : "No");
  DBG(1, "%23s14-bit-color: %s\n", " ",
	          (mi->depth & MI_HASDEPTH_14) ? "Yes" : "No");
  DBG(1, "%23s16-bit-color: %s\n", " ",
	          (mi->depth & MI_HASDEPTH_16) ? "Yes" : "No");
  DBG(1, "  d/l of HT pattern%2s: %s\n",
                  " ", (mi->has_dnldptrn) ? "Yes" : "No");
  DBG(1, "  Builtin HT pattern%1s: %d\n", " ", mi->grain_slct);

  if ( MI_LUTCAP_NONE(mi->lut_cap) )
      DBG(1, "  LUT capabilities   : None\n");
  if ( mi->lut_cap & MI_LUTCAP_256B )
      DBG(1, "  LUT capabilities   :  256 bytes\n");
  if ( mi->lut_cap & MI_LUTCAP_1024B )
      DBG(1, "  LUT capabilities   : 1024 bytes\n");
  if ( mi->lut_cap & MI_LUTCAP_1024W )
      DBG(1, "  LUT capabilities   : 1024 words\n");
  if ( mi->lut_cap & MI_LUTCAP_4096B )
      DBG(1, "  LUT capabilities   : 4096 bytes\n");
  if ( mi->lut_cap & MI_LUTCAP_4096W )
      DBG(1, "  LUT capabilities   : 4096 words\n");
  if ( mi->lut_cap & MI_LUTCAP_64k_W )
      DBG(1, "  LUT capabilities   :  64k words\n");
  if ( mi->lut_cap & MI_LUTCAP_16k_W )
      DBG(1, "  LUT capabilities   :  16k words\n");
  DBG(1, "\n");
  DBG(1, "  Miscellaneous capabilities...\n");
  DBG(1, "  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
  if ( mi->onepass)
    {
      switch(mi->data_format)
        {
	  case MI_DATAFMT_CHUNKY:
              DBG(1, "  Data format        :%s",
                     " Chunky data, R, G & B in one pixel\n");
              break;
	  case MI_DATAFMT_LPLCONCAT:
              DBG(1, "  Data format        :%s",
                     " Line by line in concatenated sequence,\n");
              DBG(1, "%23swithout color indicator\n", " ");
              break;
	  case MI_DATAFMT_LPLSEGREG:
              DBG(1, "  Data format        :%s",
                     " Line by line in segregated sequence,\n");
              DBG(1, "%23swith color indicator\n", " ");
              break;
	  case MI_DATAFMT_WORDCHUNKY:
              DBG(1, "  Data format        : Word chunky data\n");
              break;
          default:
              DBG(1, "  Data format        : Unknown\n");
          break;
        }
    }
  else
      DBG(1, "No information with 3-pass scanners\n");

  DBG(1, "  Color Sequence%17s: \n", " ");
  for ( i = 0; i < RSA_COLORSEQUENCE_L; i++)
    {
      switch(mi->color_sequence[i])
        {
	  case MI_COLSEQ_RED:   DBG(1,"%34s%s\n", " ","R"); break;
	  case MI_COLSEQ_GREEN: DBG(1,"%34s%s\n", " ","G"); break;
	  case MI_COLSEQ_BLUE:  DBG(1,"%34s%s\n", " ","B"); break;
        }
    }
  if ( mi->new_image_status == SANE_TRUE )
      DBG(1, "  Using new ReadImageStatus format\n");
  else
      DBG(1, "  Using old ReadImageStatus format\n");
  if ( mi->direction & MI_DATSEQ_RTOL )
      DBG(1, "  Scanning direction             : right to left\n");
  else
      DBG(1, "  Scanning direction             : left to right\n");
  DBG(1, "  CCD gap%24s: %d lines\n", " ", mi->ccd_gap);
  DBG(1, "  CCD pixels%21s: %d\n", " ", mi->ccd_pixels);
  DBG(1, "  Calib white stripe location%4s: %d\n",
                  " ",  mi->calib_white);
  DBG(1, "  Max calib space%16s: %d\n", " ", mi->calib_space);
  DBG(1, "  Number of lens%17s: %d\n", " ", mi->nlens);
  DBG(1, "  Max number of windows%10s: %d\n", " ", mi->nwindows);
  DBG(1, "  Shading transfer function%6s: 0x%02x\n", " ",mi->shtrnsferequ);
  DBG(1, "  Red balance%20s: %d\n", " ", mi->balance[0]);
  DBG(1, "  Green balance%18s: %d\n", " ", mi->balance[1]);
  DBG(1, "  Blue balance%19s: %d\n", " " , mi->balance[2]);
  DBG(1, "  Buffer type%20s: %s\n",
                  " ",  mi->buftype ? "Ping-Pong" : "Ring");
  DBG(1, "  FEPROM%25s: %s\n", " ", mi->feprom ? "Yes" : "No");

  md_dump_clear = 0;
  return SANE_STATUS_GOOD;
}

/*---------- max_string_size() -----------------------------------------------*/

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

  for (i = 0; strings[i]; ++i) {
    size = strlen(strings[i]) + 1; /* +1 because NUL counts as part of string */
    if (size > max_size) max_size = size;
  }
  return max_size;
}

/*---------- parse_config_file() ---------------------------------------------*/

static void
parse_config_file(FILE *fp, Config_Temp **ct)
{
    /* builds a list of device names with associated options from the */
    /* config file for later use, when building the list of devices. */
    /* ct->device = NULL indicates global options (valid for all devices */

    char s[PATH_MAX];
    Config_Options global_opts;
    Config_Temp *hct1;
    Config_Temp *hct2;


    DBG(30, "parse_config_file: fp=%p\n", (void *) fp);

    *ct = hct1 = NULL;

    /* first read global options and store them in global_opts */
    /* initialize global_opts with default values */

    global_opts = md_options;

    while ( sanei_config_read(s, sizeof(s), fp) )
      {
        DBG(100, "parse_config_file: read line: %s\n", s);
        if ( *s == '#' || *s == '\0' )  /* ignore empty lines and comments */
            continue;

        if ( strncmp( sanei_config_skip_whitespace(s), "option ", 7) == 0
          || strncmp( sanei_config_skip_whitespace(s), "option\t", 7) == 0 )
          {
            DBG(100, "parse_config_file: found global option %s\n", s);
            check_option(s, &global_opts);
          }
        else                /* it is considered a new device */
            break;
      }

    if ( ferror(fp) || feof(fp) )
      {
        if ( ferror(fp) )
            DBG(1, "parse_config_file: fread failed: errno=%d\n", errno);

        return;
      }

    while ( ! feof(fp) && ! ferror(fp) )
      {
        if ( *s == '#' || *s == '\0' )  /* ignore empty lines and comments */
          {
            sanei_config_read(s, sizeof(s), fp);
            continue;
          }

        if ( strncmp( sanei_config_skip_whitespace(s), "option ", 7) == 0
          || strncmp( sanei_config_skip_whitespace(s), "option\t", 7) == 0 )
          {
            /* when we enter this loop for the first time we allocate */
            /* memory, because the line surely contains a device name, */
            /* so hct1 is always != NULL at this point */
            DBG(100, "parse_config_file: found device option %s\n", s);
            check_option(s, &hct1->opts);
          }


        else                /* it is considered a new device */
          {
            DBG(100, "parse_config_file: found device %s\n", s);
            hct2 = (Config_Temp *) malloc(sizeof(Config_Temp));
            if ( hct2 == NULL )
              {
                DBG(1, "parse_config_file: malloc() failed\n");
                return;
              }

            if ( *ct == NULL )   /* first element */
                *ct = hct1 = hct2;

            hct1->next = hct2;
            hct1 = hct2;

            hct1->device = strdup(s);
            hct1->opts = global_opts;
            hct1->next = NULL;
          }
        sanei_config_read(s, sizeof(s), fp);
      }
    /* set filepointer to the beginning of the file */
    fseek(fp, 0L, SEEK_SET);
    return;
}


/*---------- signal_handler() ------------------------------------------------*/

static RETSIGTYPE
signal_handler (int signal)
{
  if ( signal == SIGTERM )
    {
      sanei_scsi_req_flush_all ();
      _exit (SANE_STATUS_GOOD);
    }
}

/*---------- init_options() --------------------------------------------------*/

static SANE_Status
init_options(Microtek2_Scanner *ms, u_int8_t current_scan_source)
{
    /* This function is called every time, when the scan source changes. */
    /* The option values, that possibly change, are then reinitialized,  */
    /* whereas the option descriptors and option values that never */
    /* change are not */

    SANE_Option_Descriptor *sod;
    SANE_Status status;
    Option_Value *val;
    Microtek2_Device *md;
    Microtek2_Info *mi;
    int tablesize;
    int option_size;
    int max_gamma_value;
    int color;
    int i;
    static int first_call = 1;     /* indicates, whether option */
                                   /* descriptors must be initialized */
       /* cannot be used as after a sane_close the sod's must be initialized */

    DBG(30, "init_options: handle=%p, source=%d\n", (void *) ms,
	current_scan_source);

    sod = ms->sod;
    val = ms->val;
    md = ms->dev;
    mi = &md->info[current_scan_source];

    /* needed for gamma calculation */
    get_lut_size(mi, &md->max_lut_size, &md->lut_entry_size);

    /* calculate new values, where possibly needed */

    /* Scan source */
    if ( val[OPT_SOURCE].s )
        free((void *) val[OPT_SOURCE].s);
    i = 0;
    md->scansource_list[i] = (SANE_String) MD_SOURCESTRING_FLATBED;
    if ( current_scan_source == MD_SOURCE_FLATBED )
        val[OPT_SOURCE].s = (SANE_String) strdup(md->scansource_list[i]);
    if ( md->status.adfcnt )
      {
        md->scansource_list[++i] = (SANE_String) MD_SOURCESTRING_ADF;
        if ( current_scan_source == MD_SOURCE_ADF )
            val[OPT_SOURCE].s = (SANE_String) strdup(md->scansource_list[i]);
      }
    if ( md->status.tmacnt )
      {
        md->scansource_list[++i] = (SANE_String) MD_SOURCESTRING_TMA;
        if ( current_scan_source == MD_SOURCE_TMA )
            val[OPT_SOURCE].s = (SANE_String) strdup(md->scansource_list[i]);
      }
    if ( mi->option_device & MI_OPTDEV_STRIPE )
      {
        md->scansource_list[++i] = (SANE_String) MD_SOURCESTRING_STRIPE;
        if ( current_scan_source == MD_SOURCE_STRIPE )
            val[OPT_SOURCE].s = (SANE_String) strdup(md->scansource_list[i]);
      }

    /* Comment this out as long as I do not know in which bit */
    /* it is indicated, whether a slide adapter is connected */
#if 0
    if ( mi->option_device & MI_OPTDEV_SLIDE )
      {
        md->scansource_list[++i] = (SANE_String) MD_SOURCESTRING_SLIDE;
        if ( current_scan_source == MD_SOURCE_SLIDE )
            val[OPT_SOURCE].s = (SANE_String) strdup(md->scansource_list[i]);
      }
#endif

    md->scansource_list[++i] = NULL;

    /* Scan mode */
    if ( val[OPT_MODE].s )
        free((void *) val[OPT_MODE].s);

    i = 0;
    if ( (mi->scanmode & MI_HASMODE_COLOR) )
      {
	md->scanmode_list[i] = (SANE_String) MD_MODESTRING_COLOR;
        val[OPT_MODE].s = strdup(md->scanmode_list[i]);
        ++i;
      }

    if ( mi->scanmode & MI_HASMODE_GRAY )
      {
	md->scanmode_list[i] = (SANE_String) MD_MODESTRING_GRAY;
        if ( ! (mi->scanmode & MI_HASMODE_COLOR ) )
            val[OPT_MODE].s = strdup(md->scanmode_list[i]);
        ++i;
      }

    if ( mi->scanmode & MI_HASMODE_HALFTONE )
      {
	md->scanmode_list[i] = (SANE_String) MD_MODESTRING_HALFTONE;
        if ( ! (mi->scanmode & MI_HASMODE_COLOR )
            && ! (mi->scanmode & MI_HASMODE_GRAY ) )
            val[OPT_MODE].s = strdup(md->scanmode_list[i]);
        ++i;
      }

    /* Always enable a lineart mode. Some models (X6, FW 1.40) say */
    /* that they have no lineart mode. In this case we will do a grayscale */
    /* scan and convert it to onebit data */
    md->scanmode_list[i] = (SANE_String) MD_MODESTRING_LINEART;
    if ( ! (mi->scanmode & MI_HASMODE_COLOR )
        && ! (mi->scanmode & MI_HASMODE_GRAY )
        && ! (mi->scanmode & MI_HASMODE_HALFTONE ) )
        val[OPT_MODE].s = strdup(md->scanmode_list[i]);
    ++i;
    md->scanmode_list[i] = NULL;

    /* bitdepth */
    i = 0;

#if 0
    if ( mi->depth & MI_HASDEPTH_NIBBLE )
        md->bitdepth_list[++i] = (SANE_Int) MD_DEPTHVAL_4;
#endif

    md->bitdepth_list[++i] = (SANE_Int) MD_DEPTHVAL_8;
    if ( mi->depth & MI_HASDEPTH_10 )
        md->bitdepth_list[++i] = (SANE_Int) MD_DEPTHVAL_10;
    if ( mi->depth & MI_HASDEPTH_12 )
        md->bitdepth_list[++i] = (SANE_Int) MD_DEPTHVAL_12;
    if ( mi->depth & MI_HASDEPTH_14 )
        md->bitdepth_list[++i] = (SANE_Int) MD_DEPTHVAL_14;
    if ( mi->depth & MI_HASDEPTH_16 )
        md->bitdepth_list[++i] = (SANE_Int) MD_DEPTHVAL_16;

    md->bitdepth_list[0] = i;
    if (  md->bitdepth_list[1] == (SANE_Int) MD_DEPTHVAL_8 )
        val[OPT_BITDEPTH].w = md->bitdepth_list[1];
    else
        val[OPT_BITDEPTH].w = md->bitdepth_list[2];

    /* Halftone */
    md->halftone_mode_list[0] = (SANE_String) MD_HALFTONE0;
    md->halftone_mode_list[1] = (SANE_String) MD_HALFTONE1;
    md->halftone_mode_list[2] = (SANE_String) MD_HALFTONE2;
    md->halftone_mode_list[3] = (SANE_String) MD_HALFTONE3;
    md->halftone_mode_list[4] = (SANE_String) MD_HALFTONE4;
    md->halftone_mode_list[5] = (SANE_String) MD_HALFTONE5;
    md->halftone_mode_list[6] = (SANE_String) MD_HALFTONE6;
    md->halftone_mode_list[7] = (SANE_String) MD_HALFTONE7;
    md->halftone_mode_list[8] = (SANE_String) MD_HALFTONE8;
    md->halftone_mode_list[9] = (SANE_String) MD_HALFTONE9;
    md->halftone_mode_list[10] = (SANE_String) MD_HALFTONE10;
    md->halftone_mode_list[11] = (SANE_String) MD_HALFTONE11;
    md->halftone_mode_list[12] = NULL;
    if ( val[OPT_HALFTONE].s )
        free((void *) val[OPT_HALFTONE].s);
    val[OPT_HALFTONE].s = strdup(md->halftone_mode_list[0]);

    /* Resolution */
    md->x_res_range_dpi.min = SANE_FIX(10.0);
    md->x_res_range_dpi.max = SANE_FIX(mi->max_xresolution);
    md->x_res_range_dpi.quant = SANE_FIX(1.0);
    val[OPT_RESOLUTION].w = MIN(MD_RESOLUTION_DEFAULT, md->x_res_range_dpi.max);
    val[OPT_X_RESOLUTION].w = val[OPT_RESOLUTION].w;

    md->y_res_range_dpi.min = SANE_FIX(10.0);
    md->y_res_range_dpi.max = SANE_FIX(mi->max_yresolution);
    md->y_res_range_dpi.quant = SANE_FIX(1.0);
    val[OPT_Y_RESOLUTION].w = val[OPT_RESOLUTION].w; /* bind is default */

    /* Preview mode */
    val[OPT_PREVIEW].w = SANE_FALSE;

    /* Geometry */
    md->x_range_mm.min = SANE_FIX(0.0);
    md->x_range_mm.max = SANE_FIX((double) mi->geo_width
                                  / (double) mi->opt_resolution
                                  * MM_PER_INCH);
    md->x_range_mm.quant = SANE_FIX(0.0);
    md->y_range_mm.min = SANE_FIX(0.0);
    md->y_range_mm.max = SANE_FIX((double) mi->geo_height
                                  / (double) mi->opt_resolution
                                  * MM_PER_INCH);
    md->y_range_mm.quant = SANE_FIX(0.0);
    val[OPT_TL_X].w = SANE_FIX(0.0);
    val[OPT_TL_Y].w = SANE_FIX(0.0);
    val[OPT_BR_X].w = md->x_range_mm.max;
    val[OPT_BR_Y].w = md->y_range_mm.max;

    /* Enhancement group */
    val[OPT_BRIGHTNESS].w = MD_BRIGHTNESS_DEFAULT;
    val[OPT_CONTRAST].w = MD_CONTRAST_DEFAULT;
    val[OPT_THRESHOLD].w = MD_THRESHOLD_DEFAULT;

    /* Gamma */
    /* linear gamma must come first */
    i = 0;
    md->gammamode_list[i++] = (SANE_String) MD_GAMMAMODE_LINEAR;
    md->gammamode_list[i++] = (SANE_String) MD_GAMMAMODE_SCALAR;
    md->gammamode_list[i++] = (SANE_String) MD_GAMMAMODE_CUSTOM;
    if ( val[OPT_GAMMA_MODE].s )
        free((void *) val[OPT_GAMMA_MODE].s);
    val[OPT_GAMMA_MODE].s = strdup(md->gammamode_list[0]);

    md->gammamode_list[i] = NULL;

    /* bind gamma */
    val[OPT_GAMMA_BIND].w = SANE_TRUE;
    val[OPT_GAMMA_SCALAR].w = MD_GAMMA_DEFAULT;
    val[OPT_GAMMA_SCALAR_R].w = MD_GAMMA_DEFAULT;
    val[OPT_GAMMA_SCALAR_G].w = MD_GAMMA_DEFAULT;
    val[OPT_GAMMA_SCALAR_B].w = MD_GAMMA_DEFAULT;

    /* If the device supports gamma tables, we allocate memory according */
    /* to lookup table capabilities, otherwise we allocate 4096 elements */
    /* which is sufficient for a color depth of 12. If the device */
    /* does not support gamma tables, we fill the table according to */
    /* the actual bit depth, i.e. 256 entries with a range of 0..255 */
    /* if the actual bit depth is 8, for example. This will hopefully*/
    /* make no trouble if the bit depth is 1. */
    if ( md->model_flags & MD_NO_GAMMA )
      {
        tablesize = 4096;
        option_size = (int) pow(2.0, (double) val[OPT_BITDEPTH].w );
        max_gamma_value = option_size - 1;
      }
    else
      {
        tablesize = md->max_lut_size;
        option_size = tablesize;
        max_gamma_value = md->max_lut_size - 1;
      }

    for ( color = 0; color < 4; color++ )
      {
        /* index 0 is used if bind gamma == true, index 1 to 3 */
        /* if bind gamma == false */
        if ( md->custom_gamma_table[color] )
            free((void *) md->custom_gamma_table[color]);
        md->custom_gamma_table[color] =
                              (SANE_Int *) malloc(tablesize * sizeof(SANE_Int));
        DBG(100, "init_options: md->custom_gamma_table[%d]=%p, malloc'd %lu bytes\n",
            color, (void *) md->custom_gamma_table[color], (u_long) (tablesize * sizeof(SANE_Int)));
        if ( md->custom_gamma_table[color] == NULL )
          {
            DBG(1, "init_options: malloc for custom gamma table failed\n");
            return SANE_STATUS_NO_MEM;
          }

        for ( i = 0; i < max_gamma_value; i++ )
            md->custom_gamma_table[color][i] = i;
      }

    md->custom_gamma_range.min = 0;
    md->custom_gamma_range.max =  max_gamma_value;
    md->custom_gamma_range.quant = 1;

    sod[OPT_GAMMA_CUSTOM].size = option_size * sizeof (SANE_Int);
    sod[OPT_GAMMA_CUSTOM_R].size = option_size * sizeof (SANE_Int);
    sod[OPT_GAMMA_CUSTOM_G].size = option_size * sizeof (SANE_Int);
    sod[OPT_GAMMA_CUSTOM_B].size = option_size * sizeof (SANE_Int);

    val[OPT_GAMMA_CUSTOM].wa = &md->custom_gamma_table[0][0];
    val[OPT_GAMMA_CUSTOM_R].wa = &md->custom_gamma_table[1][0];
    val[OPT_GAMMA_CUSTOM_G].wa = &md->custom_gamma_table[2][0];
    val[OPT_GAMMA_CUSTOM_B].wa = &md->custom_gamma_table[3][0];

    /* Shadow, midtone, highlight, exposure time */
    md->channel_list[0] = (SANE_String) MD_CHANNEL_MASTER;
    md->channel_list[1] = (SANE_String) MD_CHANNEL_RED;
    md->channel_list[2] = (SANE_String) MD_CHANNEL_GREEN;
    md->channel_list[3] = (SANE_String) MD_CHANNEL_BLUE;
    md->channel_list[4] = NULL;
    if ( val[OPT_CHANNEL].s )
        free((void *) val[OPT_CHANNEL].s);
    val[OPT_CHANNEL].s = strdup(md->channel_list[0]);
    val[OPT_SHADOW].w = MD_SHADOW_DEFAULT;
    val[OPT_SHADOW_R].w = MD_SHADOW_DEFAULT;
    val[OPT_SHADOW_G].w = MD_SHADOW_DEFAULT;
    val[OPT_SHADOW_B].w = MD_SHADOW_DEFAULT;
    val[OPT_MIDTONE].w = MD_MIDTONE_DEFAULT;
    val[OPT_MIDTONE_R].w = MD_MIDTONE_DEFAULT;
    val[OPT_MIDTONE_G].w = MD_MIDTONE_DEFAULT;
    val[OPT_MIDTONE_B].w = MD_MIDTONE_DEFAULT;
    val[OPT_HIGHLIGHT].w = MD_HIGHLIGHT_DEFAULT;
    val[OPT_HIGHLIGHT_R].w = MD_HIGHLIGHT_DEFAULT;
    val[OPT_HIGHLIGHT_G].w = MD_HIGHLIGHT_DEFAULT;
    val[OPT_HIGHLIGHT_B].w = MD_HIGHLIGHT_DEFAULT;
    val[OPT_EXPOSURE].w = MD_EXPOSURE_DEFAULT;
    val[OPT_EXPOSURE_R].w = MD_EXPOSURE_DEFAULT;
    val[OPT_EXPOSURE_G].w = MD_EXPOSURE_DEFAULT;
    val[OPT_EXPOSURE_B].w = MD_EXPOSURE_DEFAULT;

    /* special options */
    val[OPT_RESOLUTION_BIND].w = SANE_TRUE;

    /* enable/disable option for backtracking */
    val[OPT_DISABLE_BACKTRACK].w = md->opt_no_backtrack_default;

    /* enable/disable calibration by backend */
    val[OPT_CALIB_BACKEND].w = md->opt_backend_calib_default;

    /* turn off the lamp during a scan */
    val[OPT_LIGHTLID35].w = SANE_FALSE;

    /* auto adjustment of threshold during a lineart scan */
    val[OPT_AUTOADJUST].w = SANE_FALSE;

    /* color balance (100% means no correction) */
    val[OPT_BALANCE_R].w = SANE_FIX(100);
    val[OPT_BALANCE_G].w = SANE_FIX(100);
    val[OPT_BALANCE_B].w = SANE_FIX(100);

    if ( first_call )
      {
        /* initialize option descriptors and ranges */

        /* Percentage range for brightness, contrast */
        md->percentage_range.min = 0 << SANE_FIXED_SCALE_SHIFT;
        md->percentage_range.max = 200 << SANE_FIXED_SCALE_SHIFT;
        md->percentage_range.quant = 1 << SANE_FIXED_SCALE_SHIFT;

        md->threshold_range.min = 1;
        md->threshold_range.max = 255;
        md->threshold_range.quant = 1;

        md->scalar_gamma_range.min = SANE_FIX(0.1);
        md->scalar_gamma_range.max = SANE_FIX(4.0);
        md->scalar_gamma_range.quant = SANE_FIX(0.1);

        md->shadow_range.min = 0;
        md->shadow_range.max = 253;
        md->shadow_range.quant = 1;

        md->midtone_range.min = 1;
        md->midtone_range.max = 254;
        md->midtone_range.quant = 1;

        md->highlight_range.min = 2;
        md->highlight_range.max = 255;
        md->highlight_range.quant = 1;

        md->exposure_range.min = 0;
        md->exposure_range.max = 510;
        md->exposure_range.quant = 2;

        md->balance_range.min = 0;
        md->balance_range.max = 200 << SANE_FIXED_SCALE_SHIFT;
        md->balance_range.quant = 1 << SANE_FIXED_SCALE_SHIFT;

        /* default for most options */
        for ( i = 0; i < NUM_OPTIONS; i++ )
          {
            sod[i].type = SANE_TYPE_FIXED;
            sod[i].unit = SANE_UNIT_NONE;
            sod[i].size = sizeof(SANE_Fixed);
            sod[i].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
            sod[i].constraint_type = SANE_CONSTRAINT_RANGE;
          }

        sod[OPT_NUM_OPTS].name = SANE_NAME_NUM_OPTIONS;
        sod[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
        sod[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
        sod[OPT_NUM_OPTS].type = SANE_TYPE_INT;
        sod[OPT_NUM_OPTS].size = sizeof (SANE_Int);
        sod[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
        sod[OPT_NUM_OPTS].constraint_type = SANE_CONSTRAINT_NONE;
        val[OPT_NUM_OPTS].w = NUM_OPTIONS;      /* NUM_OPTIONS is no option */
        DBG(255, "sod=%p\n", (void *) sod);
        DBG(255, "OPT_NUM_OPTS=%d\n", OPT_NUM_OPTS);
        DBG(255, "SANE_CAP_SOFT_DETECT=%d\n", SANE_CAP_SOFT_DETECT);
        DBG(255, "OPT_NUM_OPTS.cap=%d\n", sod[0].cap);

        /* The Scan Mode Group */
        sod[OPT_MODE_GROUP].title = M_TITLE_SCANMODEGRP;
        sod[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
        sod[OPT_MODE_GROUP].size = 0;
        sod[OPT_MODE_GROUP].desc = "";
        sod[OPT_MODE_GROUP].cap = 0;
        sod[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

        /* Scan source */
        sod[OPT_SOURCE].name = SANE_NAME_SCAN_SOURCE;
        sod[OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE;
        sod[OPT_SOURCE].desc = SANE_DESC_SCAN_SOURCE;
        sod[OPT_SOURCE].type = SANE_TYPE_STRING;
        sod[OPT_SOURCE].size = max_string_size(md->scansource_list);
        /* if there is only one scan source, deactivate option */
        if ( md->scansource_list[1] == NULL )
            sod[OPT_SOURCE].cap |= SANE_CAP_INACTIVE;
        sod[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
        sod[OPT_SOURCE].constraint.string_list = md->scansource_list;

        /* Scan mode */
        sod[OPT_MODE].name = SANE_NAME_SCAN_MODE;
        sod[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
        sod[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
        sod[OPT_MODE].type = SANE_TYPE_STRING;
        sod[OPT_MODE].size = max_string_size(md->scanmode_list);
        sod[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
        sod[OPT_MODE].constraint.string_list = md->scanmode_list;

        /* Bit depth */
        sod[OPT_BITDEPTH].name = SANE_NAME_BIT_DEPTH;
        sod[OPT_BITDEPTH].title = SANE_TITLE_BIT_DEPTH;
        sod[OPT_BITDEPTH].desc = SANE_DESC_BIT_DEPTH;
        sod[OPT_BITDEPTH].type = SANE_TYPE_INT;
        sod[OPT_BITDEPTH].unit = SANE_UNIT_BIT;
        sod[OPT_BITDEPTH].size = sizeof(SANE_Int);
        /* if we have only 8 bit color deactivate this option */
        if ( md->bitdepth_list[0] == 1 )
            sod[OPT_BITDEPTH].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BITDEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST;
        sod[OPT_BITDEPTH].constraint.word_list = md->bitdepth_list;

        /* Halftone */
        sod[OPT_HALFTONE].name = SANE_NAME_HALFTONE;
        sod[OPT_HALFTONE].title = SANE_TITLE_HALFTONE;
        sod[OPT_HALFTONE].desc = SANE_DESC_HALFTONE;
        sod[OPT_HALFTONE].type = SANE_TYPE_STRING;
        sod[OPT_HALFTONE].size = max_string_size(md->halftone_mode_list);
        sod[OPT_HALFTONE].cap  |= SANE_CAP_INACTIVE;
        sod[OPT_HALFTONE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
        sod[OPT_HALFTONE].constraint.string_list = md->halftone_mode_list;

        /* Resolution */
        sod[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
        sod[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
        sod[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
        sod[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
        sod[OPT_RESOLUTION].constraint.range = &md->x_res_range_dpi;

        sod[OPT_X_RESOLUTION].name = SANE_NAME_SCAN_X_RESOLUTION;
        sod[OPT_X_RESOLUTION].title = SANE_TITLE_SCAN_X_RESOLUTION;
        sod[OPT_X_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
        sod[OPT_X_RESOLUTION].unit = SANE_UNIT_DPI;
        sod[OPT_X_RESOLUTION].cap |= SANE_CAP_INACTIVE;
        sod[OPT_X_RESOLUTION].constraint.range = &md->x_res_range_dpi;

        sod[OPT_Y_RESOLUTION].name = SANE_NAME_SCAN_Y_RESOLUTION;
        sod[OPT_Y_RESOLUTION].title = SANE_TITLE_SCAN_Y_RESOLUTION;
        sod[OPT_Y_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
        sod[OPT_Y_RESOLUTION].unit = SANE_UNIT_DPI;
        sod[OPT_Y_RESOLUTION].cap |= SANE_CAP_INACTIVE;
        sod[OPT_Y_RESOLUTION].constraint.range = &md->y_res_range_dpi;

        /* Preview */
        sod[OPT_PREVIEW].name = SANE_NAME_PREVIEW;
        sod[OPT_PREVIEW].title = SANE_TITLE_PREVIEW;
        sod[OPT_PREVIEW].desc = SANE_DESC_PREVIEW;
        sod[OPT_PREVIEW].type = SANE_TYPE_BOOL;
        sod[OPT_PREVIEW].size = sizeof(SANE_Bool);
        sod[OPT_PREVIEW].constraint_type = SANE_CONSTRAINT_NONE;

        /* Geometry group, for scan area selection */
        sod[OPT_GEOMETRY_GROUP].title = M_TITLE_GEOMGRP;
        sod[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP;
        sod[OPT_GEOMETRY_GROUP].size = 0;
        sod[OPT_GEOMETRY_GROUP].desc = "";
        sod[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED;
        sod[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

        sod[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
        sod[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
        sod[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
        sod[OPT_TL_X].unit = SANE_UNIT_MM;
        sod[OPT_TL_X].constraint.range = &md->x_range_mm;

        sod[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
        sod[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
        sod[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
        sod[OPT_TL_Y].unit = SANE_UNIT_MM;
        sod[OPT_TL_Y].constraint.range = &md->y_range_mm;

        sod[OPT_BR_X].name = SANE_NAME_SCAN_BR_X;
        sod[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
        sod[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X;
        sod[OPT_BR_X].unit = SANE_UNIT_MM;
        sod[OPT_BR_X].constraint.range = &md->x_range_mm;

        sod[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
        sod[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
        sod[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
        sod[OPT_BR_Y].unit = SANE_UNIT_MM;
        sod[OPT_BR_Y].constraint.range = &md->y_range_mm;

        /* Enhancement group */
        sod[OPT_ENHANCEMENT_GROUP].title = M_TITLE_ENHANCEGRP;
        sod[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
        sod[OPT_ENHANCEMENT_GROUP].desc = "";
        sod[OPT_ENHANCEMENT_GROUP].size = 0;
        sod[OPT_ENHANCEMENT_GROUP].cap = 0;
        sod[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

        sod[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
        sod[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
        sod[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
        sod[OPT_BRIGHTNESS].unit = SANE_UNIT_PERCENT;
        sod[OPT_BRIGHTNESS].constraint.range = &md->percentage_range;

        sod[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
        sod[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
        sod[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
        sod[OPT_CONTRAST].unit = SANE_UNIT_PERCENT;
        sod[OPT_CONTRAST].constraint.range = &md->percentage_range;

        sod[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD;
        sod[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD;
        sod[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD;
        sod[OPT_THRESHOLD].type = SANE_TYPE_INT;
        sod[OPT_THRESHOLD].size = sizeof(SANE_Int);
        sod[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
        sod[OPT_THRESHOLD].constraint.range = &md->threshold_range;

        /* automatically adjust threshold for a lineart scan */
        sod[OPT_AUTOADJUST].name = M_NAME_AUTOADJUST;
        sod[OPT_AUTOADJUST].title = M_TITLE_AUTOADJUST;
        sod[OPT_AUTOADJUST].desc = M_DESC_AUTOADJUST;
        sod[OPT_AUTOADJUST].type = SANE_TYPE_BOOL;
        sod[OPT_AUTOADJUST].size = sizeof(SANE_Bool);
        sod[OPT_AUTOADJUST].constraint_type = SANE_CONSTRAINT_NONE;
        if ( strncmp(md->opts.auto_adjust, "off", 3) == 0 )
            sod[OPT_AUTOADJUST].cap |= SANE_CAP_INACTIVE;

        /* Gamma */
        sod[OPT_GAMMA_GROUP].title = "Gamma";
        sod[OPT_GAMMA_GROUP].desc = "";
        sod[OPT_GAMMA_GROUP].type = SANE_TYPE_GROUP;
        sod[OPT_GAMMA_GROUP].size = 0;
        sod[OPT_GAMMA_GROUP].cap = 0;
        sod[OPT_GAMMA_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

        sod[OPT_GAMMA_MODE].name = M_NAME_GAMMA_MODE;
        sod[OPT_GAMMA_MODE].title = M_TITLE_GAMMA_MODE;
        sod[OPT_GAMMA_MODE].desc = M_DESC_GAMMA_MODE;
        sod[OPT_GAMMA_MODE].type = SANE_TYPE_STRING;
        sod[OPT_GAMMA_MODE].size = max_string_size(md->gammamode_list);
        sod[OPT_GAMMA_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
        sod[OPT_GAMMA_MODE].constraint.string_list = md->gammamode_list;

        sod[OPT_GAMMA_BIND].name = M_NAME_GAMMA_BIND;
        sod[OPT_GAMMA_BIND].title = M_TITLE_GAMMA_BIND;
        sod[OPT_GAMMA_BIND].desc = M_DESC_GAMMA_BIND;
        sod[OPT_GAMMA_BIND].type = SANE_TYPE_BOOL;
        sod[OPT_GAMMA_BIND].size = sizeof(SANE_Bool);
        sod[OPT_GAMMA_BIND].constraint_type = SANE_CONSTRAINT_NONE;

        /* this is active if gamma_bind == true and gammamode == scalar */
        sod[OPT_GAMMA_SCALAR].name = M_NAME_GAMMA_SCALAR;
        sod[OPT_GAMMA_SCALAR].title = M_TITLE_GAMMA_SCALAR;
        sod[OPT_GAMMA_SCALAR].desc = M_DESC_GAMMA_SCALAR;
        sod[OPT_GAMMA_SCALAR].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_SCALAR].constraint.range = &md->scalar_gamma_range;

        sod[OPT_GAMMA_SCALAR_R].name = M_NAME_GAMMA_SCALAR_R;
        sod[OPT_GAMMA_SCALAR_R].title = M_TITLE_GAMMA_SCALAR_R;
        sod[OPT_GAMMA_SCALAR_R].desc = M_DESC_GAMMA_SCALAR_R;
        sod[OPT_GAMMA_SCALAR_R].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_SCALAR_R].constraint.range = &md->scalar_gamma_range;

        sod[OPT_GAMMA_SCALAR_G].name = M_NAME_GAMMA_SCALAR_G;
        sod[OPT_GAMMA_SCALAR_G].title = M_TITLE_GAMMA_SCALAR_G;
        sod[OPT_GAMMA_SCALAR_G].desc = M_DESC_GAMMA_SCALAR_G;
        sod[OPT_GAMMA_SCALAR_G].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_SCALAR_G].constraint.range = &md->scalar_gamma_range;

        sod[OPT_GAMMA_SCALAR_B].name = M_NAME_GAMMA_SCALAR_B;
        sod[OPT_GAMMA_SCALAR_B].title = M_TITLE_GAMMA_SCALAR_B;
        sod[OPT_GAMMA_SCALAR_B].desc = M_DESC_GAMMA_SCALAR_B;
        sod[OPT_GAMMA_SCALAR_B].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_SCALAR_B].constraint.range = &md->scalar_gamma_range;

        sod[OPT_GAMMA_CUSTOM].name = SANE_NAME_GAMMA_VECTOR;
        sod[OPT_GAMMA_CUSTOM].title = SANE_TITLE_GAMMA_VECTOR;
        sod[OPT_GAMMA_CUSTOM].desc = SANE_DESC_GAMMA_VECTOR;
        sod[OPT_GAMMA_CUSTOM].type = SANE_TYPE_INT;
        sod[OPT_GAMMA_CUSTOM].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_CUSTOM].size = option_size * sizeof (SANE_Int);
        sod[OPT_GAMMA_CUSTOM].constraint.range = &md->custom_gamma_range;

        sod[OPT_GAMMA_CUSTOM_R].name = SANE_NAME_GAMMA_VECTOR_R;
        sod[OPT_GAMMA_CUSTOM_R].title = SANE_TITLE_GAMMA_VECTOR_R;
        sod[OPT_GAMMA_CUSTOM_R].desc = SANE_DESC_GAMMA_VECTOR_R;
        sod[OPT_GAMMA_CUSTOM_R].type = SANE_TYPE_INT;
        sod[OPT_GAMMA_CUSTOM_R].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_CUSTOM_R].size = option_size * sizeof (SANE_Int);
        sod[OPT_GAMMA_CUSTOM_R].constraint.range = &md->custom_gamma_range;

        sod[OPT_GAMMA_CUSTOM_G].name = SANE_NAME_GAMMA_VECTOR_G;
        sod[OPT_GAMMA_CUSTOM_G].title = SANE_TITLE_GAMMA_VECTOR_G;
        sod[OPT_GAMMA_CUSTOM_G].desc = SANE_DESC_GAMMA_VECTOR_G;
        sod[OPT_GAMMA_CUSTOM_G].type = SANE_TYPE_INT;
        sod[OPT_GAMMA_CUSTOM_G].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_CUSTOM_G].size = option_size * sizeof (SANE_Int);
        sod[OPT_GAMMA_CUSTOM_G].constraint.range = &md->custom_gamma_range;

        sod[OPT_GAMMA_CUSTOM_B].name = SANE_NAME_GAMMA_VECTOR_B;
        sod[OPT_GAMMA_CUSTOM_B].title = SANE_TITLE_GAMMA_VECTOR_B;
        sod[OPT_GAMMA_CUSTOM_B].desc = SANE_DESC_GAMMA_VECTOR_B;
        sod[OPT_GAMMA_CUSTOM_B].type = SANE_TYPE_INT;
        sod[OPT_GAMMA_CUSTOM_B].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_CUSTOM_B].size = option_size * sizeof (SANE_Int);
        sod[OPT_GAMMA_CUSTOM_B].constraint.range = &md->custom_gamma_range;

        /* Shadow, midtone, highlight */
        sod[OPT_SMH_GROUP].title = M_TITLE_SMHGRP;
        sod[OPT_SMH_GROUP].desc = "";
        sod[OPT_SMH_GROUP].type = SANE_TYPE_GROUP;
        sod[OPT_SMH_GROUP].size = 0;
        sod[OPT_SMH_GROUP].cap = 0;
        sod[OPT_SMH_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

        sod[OPT_CHANNEL].name = M_NAME_CHANNEL;
        sod[OPT_CHANNEL].title = M_TITLE_CHANNEL;
        sod[OPT_CHANNEL].desc = M_DESC_CHANNEL;
        sod[OPT_CHANNEL].type = SANE_TYPE_STRING;
        sod[OPT_CHANNEL].size = max_string_size(md->channel_list);
        sod[OPT_CHANNEL].constraint_type = SANE_CONSTRAINT_STRING_LIST;
        sod[OPT_CHANNEL].constraint.string_list = md->channel_list;

        sod[OPT_SHADOW].name = SANE_NAME_SHADOW;
        sod[OPT_SHADOW].title = SANE_TITLE_SHADOW;
        sod[OPT_SHADOW].desc = SANE_DESC_SHADOW;
        sod[OPT_SHADOW].type = SANE_TYPE_INT;
        sod[OPT_SHADOW].size = sizeof(SANE_Int);
        sod[OPT_SHADOW].constraint.range = &md->shadow_range;

        sod[OPT_SHADOW_R].name = SANE_NAME_SHADOW_R;
        sod[OPT_SHADOW_R].title = SANE_TITLE_SHADOW_R;
        sod[OPT_SHADOW_R].desc = SANE_DESC_SHADOW_R;
        sod[OPT_SHADOW_R].type = SANE_TYPE_INT;
        sod[OPT_SHADOW_R].size = sizeof(SANE_Int);
        sod[OPT_SHADOW_R].constraint.range = &md->shadow_range;

        sod[OPT_SHADOW_G].name = SANE_NAME_SHADOW_G;
        sod[OPT_SHADOW_G].title = SANE_TITLE_SHADOW_G;
        sod[OPT_SHADOW_G].desc = SANE_DESC_SHADOW_G;
        sod[OPT_SHADOW_G].type = SANE_TYPE_INT;
        sod[OPT_SHADOW_G].size = sizeof(SANE_Int);
        sod[OPT_SHADOW_G].constraint.range = &md->shadow_range;

        sod[OPT_SHADOW_B].name = SANE_NAME_SHADOW_B;
        sod[OPT_SHADOW_B].title = SANE_TITLE_SHADOW_B;
        sod[OPT_SHADOW_B].desc = SANE_DESC_SHADOW_B;
        sod[OPT_SHADOW_B].type = SANE_TYPE_INT;
        sod[OPT_SHADOW_B].size = sizeof(SANE_Int);
        sod[OPT_SHADOW_B].constraint.range = &md->shadow_range;

        sod[OPT_MIDTONE].name = M_NAME_MIDTONE;
        sod[OPT_MIDTONE].title = M_TITLE_MIDTONE;
        sod[OPT_MIDTONE].desc = M_DESC_MIDTONE;
        sod[OPT_MIDTONE].type = SANE_TYPE_INT;
        sod[OPT_MIDTONE].size = sizeof(SANE_Int);
        sod[OPT_MIDTONE].constraint.range = &md->midtone_range;

        sod[OPT_MIDTONE_R].name = M_NAME_MIDTONE_R;
        sod[OPT_MIDTONE_R].title = M_TITLE_MIDTONE_R;
        sod[OPT_MIDTONE_R].desc = M_DESC_MIDTONE_R;
        sod[OPT_MIDTONE_R].type = SANE_TYPE_INT;
        sod[OPT_MIDTONE_R].size = sizeof(SANE_Int);
        sod[OPT_MIDTONE_R].constraint.range = &md->midtone_range;

        sod[OPT_MIDTONE_G].name = M_NAME_MIDTONE_G;
        sod[OPT_MIDTONE_G].title = M_TITLE_MIDTONE_G;
        sod[OPT_MIDTONE_G].desc = M_DESC_MIDTONE_G;
        sod[OPT_MIDTONE_G].type = SANE_TYPE_INT;
        sod[OPT_MIDTONE_G].size = sizeof(SANE_Int);
        sod[OPT_MIDTONE_G].constraint.range = &md->midtone_range;

        sod[OPT_MIDTONE_B].name = M_NAME_MIDTONE_B;
        sod[OPT_MIDTONE_B].title = M_TITLE_MIDTONE_B;
        sod[OPT_MIDTONE_B].desc = M_DESC_MIDTONE_B;
        sod[OPT_MIDTONE_B].type = SANE_TYPE_INT;
        sod[OPT_MIDTONE_B].size = sizeof(SANE_Int);
        sod[OPT_MIDTONE_B].constraint.range = &md->midtone_range;

        sod[OPT_HIGHLIGHT].name = SANE_NAME_HIGHLIGHT;
        sod[OPT_HIGHLIGHT].title = SANE_TITLE_HIGHLIGHT;
        sod[OPT_HIGHLIGHT].desc = SANE_DESC_HIGHLIGHT;
        sod[OPT_HIGHLIGHT].type = SANE_TYPE_INT;
        sod[OPT_HIGHLIGHT].size = sizeof(SANE_Int);
        sod[OPT_HIGHLIGHT].constraint.range = &md->highlight_range;

        sod[OPT_HIGHLIGHT_R].name = SANE_NAME_HIGHLIGHT_R;
        sod[OPT_HIGHLIGHT_R].title = SANE_TITLE_HIGHLIGHT_R;
        sod[OPT_HIGHLIGHT_R].desc = SANE_DESC_HIGHLIGHT_R;
        sod[OPT_HIGHLIGHT_R].type = SANE_TYPE_INT;
        sod[OPT_HIGHLIGHT_R].size = sizeof(SANE_Int);
        sod[OPT_HIGHLIGHT_R].constraint.range = &md->highlight_range;

        sod[OPT_HIGHLIGHT_G].name = SANE_NAME_HIGHLIGHT_G;
        sod[OPT_HIGHLIGHT_G].title = SANE_TITLE_HIGHLIGHT_G;
        sod[OPT_HIGHLIGHT_G].desc = SANE_DESC_HIGHLIGHT_G;
        sod[OPT_HIGHLIGHT_G].type = SANE_TYPE_INT;
        sod[OPT_HIGHLIGHT_G].size = sizeof(SANE_Int);
        sod[OPT_HIGHLIGHT_G].constraint.range = &md->highlight_range;

        sod[OPT_HIGHLIGHT_B].name = SANE_NAME_HIGHLIGHT_B;
        sod[OPT_HIGHLIGHT_B].title = SANE_TITLE_HIGHLIGHT_B;
        sod[OPT_HIGHLIGHT_B].desc = SANE_DESC_HIGHLIGHT_B;
        sod[OPT_HIGHLIGHT_B].type = SANE_TYPE_INT;
        sod[OPT_HIGHLIGHT_B].size = sizeof(SANE_Int);
        sod[OPT_HIGHLIGHT_B].constraint.range = &md->highlight_range;

        sod[OPT_EXPOSURE].name = SANE_NAME_SCAN_EXPOS_TIME;
        sod[OPT_EXPOSURE].title = SANE_TITLE_SCAN_EXPOS_TIME;
        sod[OPT_EXPOSURE].desc = SANE_DESC_SCAN_EXPOS_TIME;
        sod[OPT_EXPOSURE].type = SANE_TYPE_INT;
        sod[OPT_EXPOSURE].unit = SANE_UNIT_PERCENT;
        sod[OPT_EXPOSURE].size = sizeof(SANE_Int);
        sod[OPT_EXPOSURE].constraint.range = &md->exposure_range;

        sod[OPT_EXPOSURE_R].name = SANE_NAME_SCAN_EXPOS_TIME_R;
        sod[OPT_EXPOSURE_R].title = SANE_TITLE_SCAN_EXPOS_TIME_R;
        sod[OPT_EXPOSURE_R].desc = SANE_DESC_SCAN_EXPOS_TIME_R;
        sod[OPT_EXPOSURE_R].type = SANE_TYPE_INT;
        sod[OPT_EXPOSURE_R].unit = SANE_UNIT_PERCENT;
        sod[OPT_EXPOSURE_R].size = sizeof(SANE_Int);
        sod[OPT_EXPOSURE_R].constraint.range = &md->exposure_range;

        sod[OPT_EXPOSURE_G].name = SANE_NAME_SCAN_EXPOS_TIME_G;
        sod[OPT_EXPOSURE_G].title = SANE_TITLE_SCAN_EXPOS_TIME_G;
        sod[OPT_EXPOSURE_G].desc = SANE_DESC_SCAN_EXPOS_TIME_G;
        sod[OPT_EXPOSURE_G].type = SANE_TYPE_INT;
        sod[OPT_EXPOSURE_G].unit = SANE_UNIT_PERCENT;
        sod[OPT_EXPOSURE_G].size = sizeof(SANE_Int);
        sod[OPT_EXPOSURE_G].constraint.range = &md->exposure_range;

        sod[OPT_EXPOSURE_B].name = SANE_NAME_SCAN_EXPOS_TIME_B;
        sod[OPT_EXPOSURE_B].title = SANE_TITLE_SCAN_EXPOS_TIME_B;
        sod[OPT_EXPOSURE_B].desc = SANE_DESC_SCAN_EXPOS_TIME_B;
        sod[OPT_EXPOSURE_B].type = SANE_TYPE_INT;
        sod[OPT_EXPOSURE_B].unit = SANE_UNIT_PERCENT;
        sod[OPT_EXPOSURE_B].size = sizeof(SANE_Int);
        sod[OPT_EXPOSURE_B].constraint.range = &md->exposure_range;

        /* The Special Options Group */
        sod[OPT_SPECIAL].title = M_TITLE_SPECIALGRP;
        sod[OPT_SPECIAL].type = SANE_TYPE_GROUP;
        sod[OPT_SPECIAL].size = 0;
        sod[OPT_SPECIAL].desc = "";
        sod[OPT_SPECIAL].cap = SANE_CAP_ADVANCED;
        sod[OPT_SPECIAL].constraint_type = SANE_CONSTRAINT_NONE;

        sod[OPT_RESOLUTION_BIND].name = SANE_NAME_RESOLUTION_BIND;
        sod[OPT_RESOLUTION_BIND].title = SANE_TITLE_RESOLUTION_BIND;
        sod[OPT_RESOLUTION_BIND].desc = SANE_DESC_RESOLUTION_BIND;
        sod[OPT_RESOLUTION_BIND].type = SANE_TYPE_BOOL;
        sod[OPT_RESOLUTION_BIND].size = sizeof(SANE_Bool);
        sod[OPT_RESOLUTION_BIND].cap |= SANE_CAP_ADVANCED;
        sod[OPT_RESOLUTION_BIND].constraint_type = SANE_CONSTRAINT_NONE;

        /* enable/disable option for backtracking */
        sod[OPT_DISABLE_BACKTRACK].name = M_NAME_NOBACKTRACK;
        sod[OPT_DISABLE_BACKTRACK].title = M_TITLE_NOBACKTRACK;
        sod[OPT_DISABLE_BACKTRACK].desc = M_DESC_NOBACKTRACK;
        sod[OPT_DISABLE_BACKTRACK].type = SANE_TYPE_BOOL;
        sod[OPT_DISABLE_BACKTRACK].size = sizeof(SANE_Bool);
        sod[OPT_DISABLE_BACKTRACK].cap |= SANE_CAP_ADVANCED;
        sod[OPT_DISABLE_BACKTRACK].constraint_type = SANE_CONSTRAINT_NONE;
        if ( strncmp(md->opts.no_backtracking, "off", 3) == 0 )
            sod[OPT_DISABLE_BACKTRACK].cap |= SANE_CAP_INACTIVE;

        /* calibration by driver */
        sod[OPT_CALIB_BACKEND].name = M_NAME_CALIBBACKEND;
        sod[OPT_CALIB_BACKEND].title = M_TITLE_CALIBBACKEND;
        sod[OPT_CALIB_BACKEND].desc = M_DESC_CALIBBACKEND;
        sod[OPT_CALIB_BACKEND].type = SANE_TYPE_BOOL;
        sod[OPT_CALIB_BACKEND].size = sizeof(SANE_Bool);
        sod[OPT_CALIB_BACKEND].cap |= SANE_CAP_ADVANCED;
        sod[OPT_CALIB_BACKEND].constraint_type = SANE_CONSTRAINT_NONE;
        if ( strncmp(md->opts.backend_calibration, "off", 3) == 0 )
            sod[OPT_CALIB_BACKEND].cap |= SANE_CAP_INACTIVE;

        /* turn off the lamp of the flatbed during a scan */
        sod[OPT_LIGHTLID35].name = M_NAME_LIGHTLID35;
        sod[OPT_LIGHTLID35].title = M_TITLE_LIGHTLID35;
        sod[OPT_LIGHTLID35].desc = M_DESC_LIGHTLID35;
        sod[OPT_LIGHTLID35].type = SANE_TYPE_BOOL;
        sod[OPT_LIGHTLID35].size = sizeof(SANE_Bool);
        sod[OPT_LIGHTLID35].cap |= SANE_CAP_ADVANCED;
        sod[OPT_LIGHTLID35].constraint_type = SANE_CONSTRAINT_NONE;
        if ( strncmp(md->opts.lightlid35, "off", 3) == 0 )
            sod[OPT_LIGHTLID35].cap |= SANE_CAP_INACTIVE;

        /* toggle the lamp of the flatbed */
        sod[OPT_TOGGLELAMP].name = M_NAME_TOGGLELAMP;
        sod[OPT_TOGGLELAMP].title = M_TITLE_TOGGLELAMP;
        sod[OPT_TOGGLELAMP].desc = M_DESC_TOGGLELAMP;
        sod[OPT_TOGGLELAMP].type = SANE_TYPE_BUTTON;
        sod[OPT_TOGGLELAMP].size = 0;
        sod[OPT_TOGGLELAMP].cap |= SANE_CAP_ADVANCED;
        sod[OPT_TOGGLELAMP].constraint_type = SANE_CONSTRAINT_NONE;
        if ( strncmp(md->opts.toggle_lamp, "off", 3) == 0 )
            sod[OPT_TOGGLELAMP].cap |= SANE_CAP_INACTIVE;

        /* color balance */
        sod[OPT_COLORBALANCE].title = M_TITLE_COLBALANCEGRP;
        sod[OPT_COLORBALANCE].type = SANE_TYPE_GROUP;
        sod[OPT_COLORBALANCE].size = 0;
        sod[OPT_COLORBALANCE].desc = "";
        sod[OPT_COLORBALANCE].cap = SANE_CAP_ADVANCED;
        sod[OPT_COLORBALANCE].constraint_type = SANE_CONSTRAINT_NONE;

        sod[OPT_BALANCE_R].name = M_NAME_BALANCE_R;
        sod[OPT_BALANCE_R].title = M_TITLE_BALANCE_R;
        sod[OPT_BALANCE_R].desc = M_DESC_BALANCE_R;
        sod[OPT_BALANCE_R].unit = SANE_UNIT_PERCENT;
        sod[OPT_BALANCE_R].cap |= SANE_CAP_ADVANCED;
        sod[OPT_BALANCE_R].constraint.range = &md->balance_range;
        if ( strncmp(md->opts.colorbalance_adjust, "off", 3) == 0 )
             sod[OPT_BALANCE_R].cap |= SANE_CAP_INACTIVE;

        sod[OPT_BALANCE_G].name = M_NAME_BALANCE_G;
        sod[OPT_BALANCE_G].title = M_TITLE_BALANCE_G;
        sod[OPT_BALANCE_G].desc = M_DESC_BALANCE_G;
        sod[OPT_BALANCE_G].unit = SANE_UNIT_PERCENT;
        sod[OPT_BALANCE_G].cap |= SANE_CAP_ADVANCED;
        sod[OPT_BALANCE_G].constraint.range = &md->balance_range;
        if ( strncmp(md->opts.colorbalance_adjust, "off", 3) == 0 )
             sod[OPT_BALANCE_G].cap |= SANE_CAP_INACTIVE;

        sod[OPT_BALANCE_B].name = M_NAME_BALANCE_B;
        sod[OPT_BALANCE_B].title = M_TITLE_BALANCE_B;
        sod[OPT_BALANCE_B].desc = M_DESC_BALANCE_B;
        sod[OPT_BALANCE_B].unit = SANE_UNIT_PERCENT;
        sod[OPT_BALANCE_B].cap |= SANE_CAP_ADVANCED;
        sod[OPT_BALANCE_B].constraint.range = &md->balance_range;
        if ( strncmp(md->opts.colorbalance_adjust, "off", 3) == 0 )
             sod[OPT_BALANCE_B].cap |= SANE_CAP_INACTIVE;

        sod[OPT_BALANCE_FW].name = M_NAME_BALANCE_FW;
        sod[OPT_BALANCE_FW].title = M_TITLE_BALANCE_FW;
        sod[OPT_BALANCE_FW].desc = M_DESC_BALANCE_FW;
        sod[OPT_BALANCE_FW].type = SANE_TYPE_BUTTON;
        sod[OPT_BALANCE_FW].size = 0;
        sod[OPT_BALANCE_FW].cap |= SANE_CAP_ADVANCED;
        sod[OPT_BALANCE_FW].constraint_type = SANE_CONSTRAINT_NONE;
        if ( strncmp(md->opts.colorbalance_adjust, "off", 3) == 0 )
             sod[OPT_BALANCE_FW].cap |= SANE_CAP_INACTIVE;
      }

    status = set_option_dependencies(ms, sod, val);
    if ( status != SANE_STATUS_GOOD )
        return status;

    return SANE_STATUS_GOOD;
}

/*---------- set_option_dependencies() ---------------------------------------*/

static SANE_Status
set_option_dependencies(Microtek2_Scanner *ms, SANE_Option_Descriptor *sod,
                        Option_Value *val)
{

    Microtek2_Device *md;
    md = ms->dev;

    DBG(40, "set_option_dependencies: val=%p, sod=%p, mode=%s\n",
             (void *) val, (void *) sod, val[OPT_MODE].s);

    if ( strcmp(val[OPT_MODE].s, MD_MODESTRING_COLOR) == 0 )
      {
        /* activate brightness,..., deactivate halftone pattern */
        /* and threshold */
        sod[OPT_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_CONTRAST].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_CHANNEL].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_SHADOW].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_MIDTONE].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_HIGHLIGHT].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_EXPOSURE].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_HALFTONE].cap |= SANE_CAP_INACTIVE;
        sod[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
        if ( md->bitdepth_list[0] != 1 )
            sod[OPT_BITDEPTH].cap &= ~SANE_CAP_INACTIVE;
        else
            sod[OPT_BITDEPTH].cap |= SANE_CAP_INACTIVE;
        sod[OPT_AUTOADJUST].cap |= SANE_CAP_INACTIVE;
        if ( ! ( strncmp(md->opts.colorbalance_adjust, "off", 3) == 0 ) )
          {
            sod[OPT_BALANCE_R].cap &= ~SANE_CAP_INACTIVE;
            sod[OPT_BALANCE_G].cap &= ~SANE_CAP_INACTIVE;
            sod[OPT_BALANCE_B].cap &= ~SANE_CAP_INACTIVE;
            sod[OPT_BALANCE_FW].cap &= ~SANE_CAP_INACTIVE;
          }
        /* reset options values that are inactive to their default */
        val[OPT_THRESHOLD].w = MD_THRESHOLD_DEFAULT;
      }

    else if ( strcmp(val[OPT_MODE].s, MD_MODESTRING_GRAY) == 0 )
      {
        sod[OPT_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_CONTRAST].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_CHANNEL].cap |= SANE_CAP_INACTIVE;
        sod[OPT_SHADOW].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_MIDTONE].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_HIGHLIGHT].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_EXPOSURE].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_HALFTONE].cap |= SANE_CAP_INACTIVE;
        sod[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
        if ( md->bitdepth_list[0] != 1 )
            sod[OPT_BITDEPTH].cap &= ~SANE_CAP_INACTIVE;
        else
            sod[OPT_BITDEPTH].cap |= SANE_CAP_INACTIVE;
        sod[OPT_AUTOADJUST].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_R].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_G].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_B].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_FW].cap |= SANE_CAP_INACTIVE;

        /* reset options values that are inactive to their default */
        if ( val[OPT_CHANNEL].s )
            free((void *) val[OPT_CHANNEL].s);
        val[OPT_CHANNEL].s = strdup((SANE_String) MD_CHANNEL_MASTER);
      }

    else if ( strcmp(val[OPT_MODE].s, MD_MODESTRING_HALFTONE) == 0 )
      {
        sod[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;
        sod[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE;
        sod[OPT_CHANNEL].cap |= SANE_CAP_INACTIVE;
        sod[OPT_SHADOW].cap |= SANE_CAP_INACTIVE;
        sod[OPT_MIDTONE].cap |= SANE_CAP_INACTIVE;
        sod[OPT_HIGHLIGHT].cap |= SANE_CAP_INACTIVE;
        sod[OPT_EXPOSURE].cap |= SANE_CAP_INACTIVE;
        sod[OPT_HALFTONE].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BITDEPTH].cap |= SANE_CAP_INACTIVE;
        sod[OPT_AUTOADJUST].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_R].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_G].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_B].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_FW].cap |= SANE_CAP_INACTIVE;

        /* reset options values that are inactive to their default */
        val[OPT_BRIGHTNESS].w = MD_BRIGHTNESS_DEFAULT;
        val[OPT_CONTRAST].w = MD_CONTRAST_DEFAULT;
        if ( val[OPT_CHANNEL].s )
            free((void *) val[OPT_CHANNEL].s);
        val[OPT_CHANNEL].s = strdup((SANE_String) MD_CHANNEL_MASTER);
        val[OPT_SHADOW].w = MD_SHADOW_DEFAULT;
        val[OPT_MIDTONE].w = MD_MIDTONE_DEFAULT;
        val[OPT_HIGHLIGHT].w = MD_HIGHLIGHT_DEFAULT;
        val[OPT_EXPOSURE].w = MD_EXPOSURE_DEFAULT;
        val[OPT_THRESHOLD].w = MD_THRESHOLD_DEFAULT;
      }

    else if ( strcmp(val[OPT_MODE].s, MD_MODESTRING_LINEART) == 0 )
      {
        sod[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;
        sod[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE;
        sod[OPT_CHANNEL].cap |= SANE_CAP_INACTIVE;
        sod[OPT_SHADOW].cap |= SANE_CAP_INACTIVE;
        sod[OPT_MIDTONE].cap |= SANE_CAP_INACTIVE;
        sod[OPT_HIGHLIGHT].cap |= SANE_CAP_INACTIVE;
        sod[OPT_EXPOSURE].cap |= SANE_CAP_INACTIVE;
        sod[OPT_HALFTONE].cap |= SANE_CAP_INACTIVE;
        if ( val[OPT_AUTOADJUST].w == SANE_FALSE )
            sod[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
        else
            sod[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BITDEPTH].cap |= SANE_CAP_INACTIVE;
        sod[OPT_AUTOADJUST].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_R].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_G].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_B].cap |= SANE_CAP_INACTIVE;
        sod[OPT_BALANCE_FW].cap |= SANE_CAP_INACTIVE;

        /* reset options values that are inactive to their default */
        val[OPT_BRIGHTNESS].w = MD_BRIGHTNESS_DEFAULT;
        val[OPT_CONTRAST].w = MD_CONTRAST_DEFAULT;
        if ( val[OPT_CHANNEL].s )
            free((void *) val[OPT_CHANNEL].s);
        val[OPT_CHANNEL].s = strdup((SANE_String) MD_CHANNEL_MASTER);
        val[OPT_SHADOW].w = MD_SHADOW_DEFAULT;
        val[OPT_MIDTONE].w = MD_MIDTONE_DEFAULT;
        val[OPT_HIGHLIGHT].w = MD_HIGHLIGHT_DEFAULT;
        val[OPT_EXPOSURE].w = MD_EXPOSURE_DEFAULT;
      }

    else
      {
        DBG(1, "set_option_dependencies: unknown mode '%s'\n",
                val[OPT_MODE].s );
        return SANE_STATUS_INVAL;
      }

    /* these ones are always inactive if the mode changes */
    sod[OPT_SHADOW_R].cap |= SANE_CAP_INACTIVE;
    sod[OPT_SHADOW_G].cap |= SANE_CAP_INACTIVE;
    sod[OPT_SHADOW_B].cap |= SANE_CAP_INACTIVE;
    sod[OPT_MIDTONE_R].cap |= SANE_CAP_INACTIVE;
    sod[OPT_MIDTONE_G].cap |= SANE_CAP_INACTIVE;
    sod[OPT_MIDTONE_B].cap |= SANE_CAP_INACTIVE;
    sod[OPT_HIGHLIGHT_R].cap |= SANE_CAP_INACTIVE;
    sod[OPT_HIGHLIGHT_G].cap |= SANE_CAP_INACTIVE;
    sod[OPT_HIGHLIGHT_B].cap |= SANE_CAP_INACTIVE;
    sod[OPT_EXPOSURE_R].cap |= SANE_CAP_INACTIVE;
    sod[OPT_EXPOSURE_G].cap |= SANE_CAP_INACTIVE;
    sod[OPT_EXPOSURE_B].cap |= SANE_CAP_INACTIVE;

    /* reset options values that are inactive to their default */
    val[OPT_SHADOW_R].w = val[OPT_SHADOW_G].w = val[OPT_SHADOW_B].w
            = MD_SHADOW_DEFAULT;
    val[OPT_MIDTONE_R].w = val[OPT_MIDTONE_G].w = val[OPT_MIDTONE_B].w
            = MD_MIDTONE_DEFAULT;
    val[OPT_HIGHLIGHT_R].w = val[OPT_HIGHLIGHT_G].w = val[OPT_HIGHLIGHT_B].w
            = MD_HIGHLIGHT_DEFAULT;
    val[OPT_EXPOSURE_R].w = val[OPT_EXPOSURE_G].w = val[OPT_EXPOSURE_B].w
            = MD_EXPOSURE_DEFAULT;

    if ( SANE_OPTION_IS_SETTABLE(sod[OPT_GAMMA_MODE].cap) )
      {
        restore_gamma_options(sod, val);
      }

    return SANE_STATUS_GOOD;
}

/*---------- sane_control_option() -------------------------------------------*/

SANE_Status
sane_control_option(SANE_Handle handle, SANE_Int option,
                    SANE_Action action, void *value, SANE_Int *info)
{
    Microtek2_Scanner *ms = handle;
    Microtek2_Device *md;
    Microtek2_Info *mi;
    Option_Value *val;
    SANE_Option_Descriptor *sod;
    SANE_Status status;

    md = ms->dev;
    val = &ms->val[0];
    sod = &ms->sod[0];
    mi = &md->info[md->scan_source];

    if ( ms->scanning )
        return SANE_STATUS_DEVICE_BUSY;

    if ( option < 0 || option >= NUM_OPTIONS )
      {
        DBG(100, "sane_control_option: option %d; action %d \n", option, action);
        DBG(10, "sane_control_option: option %d invalid\n", option);
        return SANE_STATUS_INVAL;
      }

    if ( ! SANE_OPTION_IS_ACTIVE(ms->sod[option].cap) )
      {
        DBG(100, "sane_control_option: option %d; action %d \n", option, action);
        DBG(10, "sane_control_option: option %d not active\n", option);
        return SANE_STATUS_INVAL;
      }

    if ( info )
        *info = 0;

    switch ( action )
      {
        case SANE_ACTION_GET_VALUE:   /* read out option values */
          switch ( option )
            {
              /* word options */
              case OPT_BITDEPTH:
              case OPT_RESOLUTION:
              case OPT_X_RESOLUTION:
              case OPT_Y_RESOLUTION:
              case OPT_THRESHOLD:
              case OPT_TL_X:
              case OPT_TL_Y:
              case OPT_BR_X:
              case OPT_BR_Y:
              case OPT_PREVIEW:
              case OPT_BRIGHTNESS:
              case OPT_CONTRAST:
              case OPT_SHADOW:
              case OPT_SHADOW_R:
              case OPT_SHADOW_G:
              case OPT_SHADOW_B:
              case OPT_MIDTONE:
              case OPT_MIDTONE_R:
              case OPT_MIDTONE_G:
              case OPT_MIDTONE_B:
              case OPT_HIGHLIGHT:
              case OPT_HIGHLIGHT_R:
              case OPT_HIGHLIGHT_G:
              case OPT_HIGHLIGHT_B:
              case OPT_EXPOSURE:
              case OPT_EXPOSURE_R:
              case OPT_EXPOSURE_G:
              case OPT_EXPOSURE_B:
              case OPT_GAMMA_SCALAR:
              case OPT_GAMMA_SCALAR_R:
              case OPT_GAMMA_SCALAR_G:
              case OPT_GAMMA_SCALAR_B:
              case OPT_BALANCE_R:
              case OPT_BALANCE_G:
              case OPT_BALANCE_B:

                *(SANE_Word *) value = val[option].w;

                if (sod[option].type == SANE_TYPE_FIXED )
                    DBG(50, "sane_control_option: opt=%d, act=%d, val=%f\n",
                             option, action, SANE_UNFIX(val[option].w));
                else
                    DBG(50, "sane_control_option: opt=%d, act=%d, val=%d\n",
                             option, action, val[option].w);

                return SANE_STATUS_GOOD;

              /* boolean options */
              case OPT_RESOLUTION_BIND:
              case OPT_DISABLE_BACKTRACK:
              case OPT_CALIB_BACKEND:
              case OPT_LIGHTLID35:
              case OPT_GAMMA_BIND:
              case OPT_AUTOADJUST:
                *(SANE_Bool *) value = val[option].w;
                DBG(50, "sane_control_option: opt=%d, act=%d, val=%d\n",
                         option, action, val[option].w);
                return SANE_STATUS_GOOD;

              /* string options */
              case OPT_SOURCE:
              case OPT_MODE:
              case OPT_HALFTONE:
              case OPT_CHANNEL:
              case OPT_GAMMA_MODE:
                strcpy(value, val[option].s);
                DBG(50, "sane_control_option: opt=%d, act=%d, val=%s\n",
                         option, action, val[option].s);
                return SANE_STATUS_GOOD;

              /* word array options */
              case OPT_GAMMA_CUSTOM:
              case OPT_GAMMA_CUSTOM_R:
              case OPT_GAMMA_CUSTOM_G:
              case OPT_GAMMA_CUSTOM_B:
                memcpy(value, val[option].wa, sod[option].size);
                return SANE_STATUS_GOOD;

              /* button options */
              case OPT_TOGGLELAMP:
              case OPT_BALANCE_FW:
                return SANE_STATUS_GOOD;

              /* others */
              case OPT_NUM_OPTS:
                *(SANE_Word *) value = NUM_OPTIONS;
                return SANE_STATUS_GOOD;

              default:
                return SANE_STATUS_UNSUPPORTED;
            }
          /* NOTREACHED */
          /* break; */

        case SANE_ACTION_SET_VALUE:     /* set option values */
          if ( ! SANE_OPTION_IS_SETTABLE(sod[option].cap) )
            {
              DBG(100, "sane_control_option: option %d; action %d \n",
                        option, action);
              DBG(10, "sane_control_option: trying to set unsettable option\n");
              return SANE_STATUS_INVAL;
            }

          /* do not check OPT_BR_Y, xscanimage sometimes tries to set */
          /* it to a too large value; bug in xscanimage ? */
          /* if ( option != OPT_BR_Y )
            { */
              status = sanei_constrain_value(ms->sod + option, value, info);
              if (status != SANE_STATUS_GOOD)
                {
                  DBG(10, "sane_control_option: invalid option value\n");
                  return status;
                }
          /*  } */

          switch ( sod[option].type )
            {
              case SANE_TYPE_BOOL:
                DBG(50, "sane_control_option: option=%d, action=%d, value=%d\n",
                         option, action, *(SANE_Int *) value);
                if ( ! ( ( *(SANE_Bool *) value == SANE_TRUE )
                         || ( *(SANE_Bool *) value == SANE_FALSE ) ) )
                    {
                      DBG(10, "sane_control_option: invalid BOOL option value\n");
                      return SANE_STATUS_INVAL;
                    }
                if ( val[option].w == *(SANE_Bool *) value ) /* no change */
                    return SANE_STATUS_GOOD;
                val[option].w = *(SANE_Bool *) value;
                break;

              case SANE_TYPE_INT:
                if ( sod[option].size == sizeof(SANE_Int) )
                  {
                    /* word option */
                    DBG(50, "sane_control_option: option=%d, action=%d, "
                            "value=%d\n", option, action, *(SANE_Int *) value);
                    if ( val[option].w == *(SANE_Int *) value ) /* no change */
                        return SANE_STATUS_GOOD;
                    val[option].w = *(SANE_Int *) value;
                  }
                else
                  {
                    /* word array option */
                    memcpy(val[option].wa, value, sod[option].size);
                  }
                break;

              case SANE_TYPE_FIXED:
                DBG(50, "sane_control_option: option=%d, action=%d, value=%f\n",
                         option, action, SANE_UNFIX( *(SANE_Fixed *) value));
                if ( val[option].w == *(SANE_Fixed *) value ) /* no change */
                    return SANE_STATUS_GOOD;
                val[option].w = *(SANE_Fixed *) value;
                break;

              case SANE_TYPE_STRING:
                DBG(50, "sane_control_option: option=%d, action=%d, value=%s\n",
                         option, action, (SANE_String) value);
                if ( strcmp(val[option].s, (SANE_String) value) == 0 )
                    return SANE_STATUS_GOOD;         /* no change */
                if ( val[option].s )
                    free((void *) val[option].s);
                val[option].s = strdup(value);
                if ( val[option].s == NULL )
                  {
                    DBG(1, "sane_control_option: strdup failed\n");
                    return SANE_STATUS_NO_MEM;
                  }
                break;

              case SANE_TYPE_BUTTON:
                break;

              default:
                DBG(1, "sane_control_option: unknown type %d\n",
                        sod[option].type);
                break;
            }

          switch ( option )
            {
              case OPT_RESOLUTION:
              case OPT_X_RESOLUTION:
              case OPT_Y_RESOLUTION:
              case OPT_TL_X:
              case OPT_TL_Y:
              case OPT_BR_X:
              case OPT_BR_Y:
                if ( info )
                    *info |= SANE_INFO_RELOAD_PARAMS;
                    return SANE_STATUS_GOOD;
              case OPT_DISABLE_BACKTRACK:
              case OPT_CALIB_BACKEND:
              case OPT_LIGHTLID35:
              case OPT_PREVIEW:
              case OPT_BRIGHTNESS:
              case OPT_THRESHOLD:
              case OPT_CONTRAST:
              case OPT_EXPOSURE:
              case OPT_EXPOSURE_R:
              case OPT_EXPOSURE_G:
              case OPT_EXPOSURE_B:
              case OPT_GAMMA_SCALAR:
              case OPT_GAMMA_SCALAR_R:
              case OPT_GAMMA_SCALAR_G:
              case OPT_GAMMA_SCALAR_B:
              case OPT_GAMMA_CUSTOM:
              case OPT_GAMMA_CUSTOM_R:
              case OPT_GAMMA_CUSTOM_G:
              case OPT_GAMMA_CUSTOM_B:
              case OPT_HALFTONE:
              case OPT_BALANCE_R:
              case OPT_BALANCE_G:
              case OPT_BALANCE_B:
               return SANE_STATUS_GOOD;

              case OPT_BITDEPTH:
                /* If the bitdepth has changed we must change the size of */
                /* the gamma table if the device does not support gamma */
                /* tables. This will hopefully cause no trouble if the */
                /* mode is one bit */

                if ( md->model_flags & MD_NO_GAMMA )
                  {
                    int max_gamma_value;
                    int size;
                    int color;
                    int i;

                    size = (int) pow(2.0, (double) val[OPT_BITDEPTH].w) - 1;
                    max_gamma_value = size - 1;
                    for ( color = 0; color < 4; color++ )
                      {
                        for ( i = 0; i < max_gamma_value; i++ )
                            md->custom_gamma_table[color][i] = (SANE_Int) i;
                      }
                    md->custom_gamma_range.max = (SANE_Int) max_gamma_value;
                    sod[OPT_GAMMA_CUSTOM].size = size * sizeof (SANE_Int);
                    sod[OPT_GAMMA_CUSTOM_R].size = size * sizeof (SANE_Int);
                    sod[OPT_GAMMA_CUSTOM_G].size = size * sizeof (SANE_Int);
                    sod[OPT_GAMMA_CUSTOM_B].size = size * sizeof (SANE_Int);

                    if ( info )
                        *info |= SANE_INFO_RELOAD_OPTIONS;

                  }

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

              case OPT_SOURCE:
                if ( info )
                    *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
                if ( strcmp(val[option].s, MD_SOURCESTRING_FLATBED) == 0 )
                    md->scan_source = MD_SOURCE_FLATBED;
                else if ( strcmp(val[option].s, MD_SOURCESTRING_TMA) == 0 )
                    md->scan_source = MD_SOURCE_TMA;
                else if ( strcmp(val[option].s, MD_SOURCESTRING_ADF) == 0 )
                    md->scan_source = MD_SOURCE_ADF;
                else if ( strcmp(val[option].s, MD_SOURCESTRING_STRIPE) == 0 )
                    md->scan_source = MD_SOURCE_STRIPE;
                else if ( strcmp(val[option].s, MD_SOURCESTRING_SLIDE) == 0 )
                    md->scan_source = MD_SOURCE_SLIDE;
                else
                  {
                    DBG(1, "sane_control_option: unsupported option %s\n",
                            val[option].s);
                    return SANE_STATUS_UNSUPPORTED;
                  }

                init_options(ms, md->scan_source);
                return SANE_STATUS_GOOD;

              case OPT_MODE:
                if ( info )
                    *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;

                status = set_option_dependencies(ms, sod, val);

                /* Options with side effects need special treatment. They are */
                /* reset, even if they were set by set_option_dependencies(): */
                /* if we have more than one color depth activate this option */

                if ( md->bitdepth_list[0] == 1 )
                    sod[OPT_BITDEPTH].cap |= SANE_CAP_INACTIVE;
                if ( strncmp(md->opts.auto_adjust, "off", 3) == 0 )
                    sod[OPT_AUTOADJUST].cap |= SANE_CAP_INACTIVE;

                if ( status != SANE_STATUS_GOOD )
                    return status;
                return SANE_STATUS_GOOD;

              case OPT_CHANNEL:
                if ( info )
                    *info |= SANE_INFO_RELOAD_OPTIONS;
                if ( strcmp(val[option].s, MD_CHANNEL_MASTER) == 0 )
                  {
                    sod[OPT_SHADOW].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_B].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_B].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_B].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_B].cap |= SANE_CAP_INACTIVE;
                  }
                else if ( strcmp(val[option].s, MD_CHANNEL_RED) == 0 )
                  {
                    sod[OPT_SHADOW].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_R].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_R].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_R].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_R].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_B].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_B].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_B].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_B].cap |= SANE_CAP_INACTIVE;
                  }
                else if ( strcmp(val[option].s, MD_CHANNEL_GREEN) == 0 )
                  {
                    sod[OPT_SHADOW].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_G].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_G].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_G].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_G].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_B].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_B].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_B].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_B].cap |= SANE_CAP_INACTIVE;
                  }
                else if ( strcmp(val[option].s, MD_CHANNEL_BLUE) == 0 )
                  {
                    sod[OPT_SHADOW].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_R].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_G].cap |= SANE_CAP_INACTIVE;
                    sod[OPT_SHADOW_B].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_MIDTONE_B].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_HIGHLIGHT_B].cap &= ~SANE_CAP_INACTIVE;
                    sod[OPT_EXPOSURE_B].cap &= ~SANE_CAP_INACTIVE;
                  }
                return SANE_STATUS_GOOD;

              case OPT_GAMMA_MODE:
                restore_gamma_options(sod, val);
                if ( info )
                    *info |= SANE_INFO_RELOAD_OPTIONS;
                return SANE_STATUS_GOOD;

              case OPT_GAMMA_BIND:
                restore_gamma_options(sod, val);
                if ( info )
                    *info |= SANE_INFO_RELOAD_OPTIONS;

                return SANE_STATUS_GOOD;

              case OPT_SHADOW:
              case OPT_SHADOW_R:
              case OPT_SHADOW_G:
              case OPT_SHADOW_B:
                if ( val[option].w >= val[option + 1].w )
                  {
                    val[option + 1].w = val[option].w + 1;
                    if ( info )
                        *info |= SANE_INFO_RELOAD_OPTIONS;
                  }
                if ( val[option + 1].w >= val[option + 2].w )
                    val[option + 2].w = val[option + 1].w + 1;

                return SANE_STATUS_GOOD;

              case OPT_MIDTONE:
              case OPT_MIDTONE_R:
              case OPT_MIDTONE_G:
              case OPT_MIDTONE_B:
                if ( val[option].w <= val[option - 1].w )
                  {
                    val[option - 1].w = val[option].w - 1;
                    if ( info )
                        *info |= SANE_INFO_RELOAD_OPTIONS;
                  }
                if ( val[option].w >= val[option + 1].w )
                  {
                    val[option + 1].w = val[option].w + 1;
                    if ( info )
                        *info |= SANE_INFO_RELOAD_OPTIONS;
                  }

                return SANE_STATUS_GOOD;

              case OPT_HIGHLIGHT:
              case OPT_HIGHLIGHT_R:
              case OPT_HIGHLIGHT_G:
              case OPT_HIGHLIGHT_B:
                if ( val[option].w <= val[option - 1].w )
                  {
                    val[option - 1].w = val[option].w - 1;
                    if ( info )
                        *info |= SANE_INFO_RELOAD_OPTIONS;
                  }
                if ( val[option - 1].w <= val[option - 2].w )
                    val[option - 2].w = val[option - 1].w - 1;

                return SANE_STATUS_GOOD;

              case OPT_RESOLUTION_BIND:
                if ( ms->val[option].w == SANE_FALSE )
                  {
                    ms->sod[OPT_RESOLUTION].cap |= SANE_CAP_INACTIVE;
                    ms->sod[OPT_X_RESOLUTION].cap &= ~SANE_CAP_INACTIVE;
                    ms->sod[OPT_Y_RESOLUTION].cap &= ~SANE_CAP_INACTIVE;
                  }
                else
                  {
                    ms->sod[OPT_RESOLUTION].cap &= ~SANE_CAP_INACTIVE;
                    ms->sod[OPT_X_RESOLUTION].cap |= SANE_CAP_INACTIVE;
                    ms->sod[OPT_Y_RESOLUTION].cap |= SANE_CAP_INACTIVE;
                  }
                if ( info )
                    *info |= SANE_INFO_RELOAD_OPTIONS;
                return SANE_STATUS_GOOD;

              case OPT_TOGGLELAMP:
                status = scsi_read_system_status(md, -1);
                if ( status != SANE_STATUS_GOOD )
                    return SANE_STATUS_IO_ERROR;

                md->status.flamp ^= 1;
                status = scsi_send_system_status(md, -1);
                if ( status != SANE_STATUS_GOOD )
                    return SANE_STATUS_IO_ERROR;
                return SANE_STATUS_GOOD;

              case OPT_AUTOADJUST:
                if ( info )
                    *info |= SANE_INFO_RELOAD_OPTIONS;

                if ( ms->val[option].w == SANE_FALSE )
                    ms->sod[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
                else
                    ms->sod[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;

                return SANE_STATUS_GOOD;

              case OPT_BALANCE_FW:
                   val[OPT_BALANCE_R].w =
                        SANE_FIX((u_int8_t)( (float)mi->balance[0] / 2.55 ) );
                   val[OPT_BALANCE_G].w =
                        SANE_FIX((u_int8_t)( (float)mi->balance[1] / 2.55 ) );
                   val[OPT_BALANCE_B].w =
                        SANE_FIX((u_int8_t)( (float)mi->balance[2] / 2.55 ) );
                   if ( info )
                       *info |= SANE_INFO_RELOAD_OPTIONS;

                return SANE_STATUS_GOOD;


              default:
                return SANE_STATUS_UNSUPPORTED;
            }
#if 0
          break;
#endif
        default:
          DBG(1, "sane_control_option: Unsupported action %d\n", action);
          return SANE_STATUS_UNSUPPORTED;
      }
}

/*---------- sane_get_option_descriptor() ------------------------------------*/

const SANE_Option_Descriptor *
sane_get_option_descriptor(SANE_Handle handle, SANE_Int n)
{
    Microtek2_Scanner *ms = handle;

    DBG(255, "sane_get_option_descriptor: handle=%p, sod=%p, opt=%d\n",
              (void *) handle, (void *) ms->sod, n);

    if ( n < 0 || n > NUM_OPTIONS )
      {
        DBG(30, "sane_get_option_descriptor: invalid option %d\n", n);
        return NULL;
      }

    return &ms->sod[n];
}

/*---------- restore_gamma_options() -----------------------------------------*/

static SANE_Status
restore_gamma_options(SANE_Option_Descriptor *sod, Option_Value *val)
{

    DBG(40, "restore_gamma_options: val=%p, sod=%p\n", (void *) val, (void *) sod);
    /* if we dont have a gamma table return immediately */
    if ( ! val[OPT_GAMMA_MODE].s )
       return SANE_STATUS_GOOD;

    if ( strcmp(val[OPT_MODE].s, MD_MODESTRING_COLOR) == 0 )
      {
        sod[OPT_GAMMA_MODE].cap &= ~SANE_CAP_INACTIVE;
        if ( strcmp(val[OPT_GAMMA_MODE].s, MD_GAMMAMODE_LINEAR) == 0 )
          {
            sod[OPT_GAMMA_BIND].cap |= SANE_CAP_INACTIVE;
            sod[OPT_GAMMA_SCALAR].cap |= SANE_CAP_INACTIVE;
            sod[OPT_GAMMA_SCALAR_R].cap |= SANE_CAP_INACTIVE;
            sod[OPT_GAMMA_SCALAR_G].cap |= SANE_CAP_INACTIVE;
            sod[OPT_GAMMA_SCALAR_B].cap |= SANE_CAP_INACTIVE;
            sod[OPT_GAMMA_CUSTOM].cap |= SANE_CAP_INACTIVE;
            sod[OPT_GAMMA_CUSTOM_R].cap |= SANE_CAP_INACTIVE;
            sod[OPT_GAMMA_CUSTOM_G].cap |= SANE_CAP_INACTIVE;
            sod[OPT_GAMMA_CUSTOM_B].cap |= SANE_CAP_INACTIVE;
          }
        else if ( strcmp(val[OPT_GAMMA_MODE].s, MD_GAMMAMODE_SCALAR) == 0 )
          {
            sod[OPT_GAMMA_BIND].cap &= ~SANE_CAP_INACTIVE;
            if ( val[OPT_GAMMA_BIND].w == SANE_TRUE )
              {
                sod[OPT_GAMMA_SCALAR].cap &= ~SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_R].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_G].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_B].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_R].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_G].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_B].cap |= SANE_CAP_INACTIVE;
              }
            else
              {
                sod[OPT_GAMMA_SCALAR].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_R].cap &= ~SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_G].cap &= ~SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_B].cap &= ~SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_R].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_G].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_B].cap |= SANE_CAP_INACTIVE;
              }
          }
        else if ( strcmp(val[OPT_GAMMA_MODE].s, MD_GAMMAMODE_CUSTOM) == 0 )
          {
            sod[OPT_GAMMA_BIND].cap &= ~SANE_CAP_INACTIVE;
            if ( val[OPT_GAMMA_BIND].w == SANE_TRUE )
              {
                sod[OPT_GAMMA_CUSTOM].cap &= ~SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_R].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_G].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_B].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_R].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_G].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_B].cap |= SANE_CAP_INACTIVE;
              }
            else
              {
                sod[OPT_GAMMA_CUSTOM].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_R].cap &= ~SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_G].cap &= ~SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_CUSTOM_B].cap &= ~SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_R].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_G].cap |= SANE_CAP_INACTIVE;
                sod[OPT_GAMMA_SCALAR_B].cap |= SANE_CAP_INACTIVE;
              }
          }
      }
    else if ( strcmp(val[OPT_MODE].s, MD_MODESTRING_GRAY) == 0 )
      {
        sod[OPT_GAMMA_MODE].cap &= ~SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_BIND].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_SCALAR_R].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_SCALAR_G].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_SCALAR_B].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_CUSTOM_R].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_CUSTOM_G].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_CUSTOM_B].cap |= SANE_CAP_INACTIVE;
        if ( strcmp(val[OPT_GAMMA_MODE].s, MD_GAMMAMODE_LINEAR) == 0 )
          {
            sod[OPT_GAMMA_SCALAR].cap |= SANE_CAP_INACTIVE;
            sod[OPT_GAMMA_CUSTOM].cap |= SANE_CAP_INACTIVE;
          }
        else if ( strcmp(val[OPT_GAMMA_MODE].s, MD_GAMMAMODE_SCALAR) == 0 )
          {
            sod[OPT_GAMMA_SCALAR].cap &= ~SANE_CAP_INACTIVE;
            sod[OPT_GAMMA_CUSTOM].cap |= SANE_CAP_INACTIVE;
          }
        else if ( strcmp(val[OPT_GAMMA_MODE].s, MD_GAMMAMODE_CUSTOM) == 0 )
          {
            sod[OPT_GAMMA_CUSTOM].cap &= ~SANE_CAP_INACTIVE;
            sod[OPT_GAMMA_SCALAR].cap |= SANE_CAP_INACTIVE;
          }
      }
    else if ( strcmp(val[OPT_MODE].s, MD_MODESTRING_HALFTONE) == 0
              || strcmp(val[OPT_MODE].s, MD_MODESTRING_LINEART) == 0 )
      {
        /* reset gamma to default */
        if ( val[OPT_GAMMA_MODE].s )
            free((void *) val[OPT_GAMMA_MODE].s);
        val[OPT_GAMMA_MODE].s = strdup(MD_GAMMAMODE_LINEAR);
        sod[OPT_GAMMA_MODE].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_BIND].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_SCALAR].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_SCALAR_R].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_SCALAR_G].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_SCALAR_B].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_CUSTOM].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_CUSTOM_R].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_CUSTOM_G].cap |= SANE_CAP_INACTIVE;
        sod[OPT_GAMMA_CUSTOM_B].cap |= SANE_CAP_INACTIVE;
      }
    else
        DBG(1, "restore_gamma_options: unknown mode %s\n", val[OPT_MODE].s);

    return SANE_STATUS_GOOD;
}


/*---------- calculate_sane_params() -----------------------------------------*/

static SANE_Status
calculate_sane_params(Microtek2_Scanner *ms)
{
    Microtek2_Device *md;
    Microtek2_Info *mi;


    DBG(30, "calculate_sane_params: ms=%p\n", (void *) ms);

    md = ms->dev;
    mi = &md->info[md->scan_source];

    if ( ! mi->onepass && ms->mode == MS_MODE_COLOR )
      {
        if ( ms->current_pass == 1 )
            ms->params.format = SANE_FRAME_RED;
        else if ( ms->current_pass == 2 )
            ms->params.format = SANE_FRAME_GREEN;
        else if ( ms->current_pass == 3 )
            ms->params.format = SANE_FRAME_BLUE;
        else
          {
            DBG(1, "calculate_sane_params: invalid pass number %d\n",
                    ms->current_pass);
            return SANE_STATUS_IO_ERROR;
          }
      }
    else if ( mi->onepass && ms->mode == MS_MODE_COLOR )
        ms->params.format = SANE_FRAME_RGB;
    else
        ms->params.format = SANE_FRAME_GRAY;

    if ( ! mi->onepass && ms->mode == MS_MODE_COLOR && ms->current_pass < 3 )
        ms->params.last_frame = SANE_FALSE;
    else
        ms->params.last_frame = SANE_TRUE;
    ms->params.lines = ms->src_remaining_lines;
    ms->params.pixels_per_line = ms->ppl;
    ms->params.bytes_per_line = ms->real_bpl;
    ms->params.depth = ms->bits_per_pixel_out;

    return SANE_STATUS_GOOD;

}

/*---------- get_calib_params() ----------------------------------------------*/

static void
get_calib_params(Microtek2_Scanner *ms)
{
    Microtek2_Device *md;
    Microtek2_Info *mi;

    
    DBG(30, "get_calib_params: handle=%p\n", (void *) ms);

    md = ms->dev;
    mi = &md->info[md->scan_source];
    
    if ( md->model_flags & MD_CALIB_DIVISOR_600 )
      {
        if ( ms->x_resolution_dpi <= 600 )
            mi->calib_divisor = 2;
        else
            mi->calib_divisor = 1;
      }
    DBG(30, "Calib Divisor: %d\n", mi->calib_divisor);


    ms->x_resolution_dpi = mi->opt_resolution / mi->calib_divisor;
    ms->y_resolution_dpi = mi->opt_resolution / 5; /* ignore dust particles */
    ms->x1_dots = 0;
    ms->y1_dots = mi->calib_white;
    ms->width_dots = mi->geo_width;
    if ( md->shading_length != 0 )
       ms->height_dots = md->shading_length;
    else
       ms->height_dots = mi->calib_space;

    ms->mode = MS_MODE_COLOR;

    if ( mi->depth & MI_HASDEPTH_16 )
        ms->depth = 16;
    else if ( mi->depth & MI_HASDEPTH_14 )
        ms->depth = 14;
    else if ( mi->depth & MI_HASDEPTH_12 )
        ms->depth = 12;
    else if ( mi->depth & MI_HASDEPTH_10 )
        ms->depth = 10;
    else 
        ms->depth = 8;

    ms->stay = 0;
    if ( mi->calib_space < 10 )
        ms->stay = 1;
    ms->rawdat = 1;
    ms->quality = 1;
    ms->fastscan = 0;
/*    ms->scan_source = md->scan_source; */
    ms->scan_source = 0;
    ms->brightness_m = ms->brightness_r = ms->brightness_g =
                       ms->brightness_b = 128;
    ms->exposure_m = ms->exposure_r = ms->exposure_g = ms->exposure_b = 0;
    ms->contrast_m = ms->contrast_r = ms->contrast_g = ms->contrast_b = 128;
    ms->shadow_m = ms->shadow_r = ms->shadow_g = ms->shadow_b = 0;
    ms->midtone_m = ms->midtone_r = ms->midtone_g = ms->midtone_b = 128;
    ms->highlight_m = ms->highlight_r = ms->highlight_g = ms->highlight_b = 255;

    return;
}


/*---------- get_scan_parameters () ------------------------------------------*/

static SANE_Status
get_scan_parameters(Microtek2_Scanner *ms)
{
    Microtek2_Device *md;
    Microtek2_Info *mi;
    double dpm;                   /* dots per millimeter */
    int x2_dots;
    int y2_dots;
    int i;


    DBG(30, "get_scan_parameters: handle=%p\n", (void *) ms);

    md = ms->dev;
    mi = &md->info[md->scan_source];

    get_scan_mode_and_depth(ms, &ms->mode, &ms->depth,
                            &ms->bits_per_pixel_in, &ms->bits_per_pixel_out);
    
    /* get the scan_source */
    if ( strcmp(ms->val[OPT_SOURCE].s, MD_SOURCESTRING_FLATBED) == 0 )
        ms->scan_source = MS_SOURCE_FLATBED;
    else if ( strcmp(ms->val[OPT_SOURCE].s, MD_SOURCESTRING_ADF) == 0 )
        ms->scan_source = MS_SOURCE_ADF;
    else if ( strcmp(ms->val[OPT_SOURCE].s, MD_SOURCESTRING_TMA) == 0 )
        ms->scan_source = MS_SOURCE_TMA;
    else if ( strcmp(ms->val[OPT_SOURCE].s, MD_SOURCESTRING_STRIPE) == 0 )
        ms->scan_source = MS_SOURCE_STRIPE;
    else if ( strcmp(ms->val[OPT_SOURCE].s, MD_SOURCESTRING_SLIDE) == 0 )
        ms->scan_source = MS_SOURCE_SLIDE;

    /* enable/disable backtracking */
    if ( ms->val[OPT_DISABLE_BACKTRACK].w == SANE_TRUE )
        ms->no_backtracking = 1;
    else
        ms->no_backtracking = 0;

    /* turn off the lamp during a scan */
    if ( ms->val[OPT_LIGHTLID35].w == SANE_TRUE )
        ms->lightlid35 = 1;
    else
        ms->lightlid35 = 0;

    /* automatic adjustment of threshold */
    if ( ms->val[OPT_AUTOADJUST].w == SANE_TRUE)
        ms->auto_adjust = 1;
    else
        ms->auto_adjust = 0;

    /* color calibration by backend */
    if ( ms->val[OPT_CALIB_BACKEND].w == SANE_TRUE )
        ms->calib_backend = 1;
    else
        ms->calib_backend = 0;

    /* if halftone mode select halftone pattern */
    if ( ms->mode == MS_MODE_HALFTONE )
      {
        i = 0;
        while ( strcmp(md->halftone_mode_list[i], ms->val[OPT_HALFTONE].s) ) 
            ++i;
        ms->internal_ht_index = i;
      }

    /* if lineart get the value for threshold */ 
    if ( ms->mode == MS_MODE_LINEART || ms->mode == MS_MODE_LINEARTFAKE)   
        ms->threshold = (u_int8_t) ms->val[OPT_THRESHOLD].w;
    else
        ms->threshold = (u_int8_t) M_THRESHOLD_DEFAULT;

    DBG(30, "get_scan_parameters: mode=%d, depth=%d, bpp_in=%d, bpp_out=%d\n",
             ms->mode, ms->depth, ms->bits_per_pixel_in, 
             ms->bits_per_pixel_out);

    /* calculate positions, width and height in dots */
    /* check for impossible values */
    /* ensure a minimum scan area of 10 x 10 pixels */
    dpm = (double) mi->opt_resolution / MM_PER_INCH;
    ms->x1_dots = (SANE_Int) ( SANE_UNFIX(ms->val[OPT_TL_X].w) * dpm + 0.5 );
    if ( ms->x1_dots > ( mi->geo_width - 10 ) )
        ms->x1_dots = ( mi->geo_width - 10 );
    ms->y1_dots = (SANE_Int) ( SANE_UNFIX(ms->val[OPT_TL_Y].w) * dpm + 0.5 );
    if ( ms->y1_dots > ( mi->geo_height - 10 ) )
        ms->y1_dots = ( mi->geo_height - 10 );
    x2_dots = (int) ( SANE_UNFIX(ms->val[OPT_BR_X].w) * dpm + 0.5 );
    if ( x2_dots >= mi->geo_width )
        x2_dots = mi->geo_width - 1;
    y2_dots = (int) ( SANE_UNFIX(ms->val[OPT_BR_Y].w) * dpm + 0.5 );
    if ( y2_dots >= mi->geo_height )
        y2_dots = mi->geo_height - 1;
    ms->width_dots = x2_dots - ms->x1_dots;
    if ( md->model_flags && MD_OFFSET_2 ) /* this firmware has problems with */
      if ( ( ms->width_dots % 2 ) == 1 )  /* odd pixel numbers */
        ms->width_dots -= 1;
    if ( ms->width_dots < 10 )
        ms->width_dots = 10;
    ms->height_dots = y2_dots - ms->y1_dots;
    if ( ms->height_dots < 10 )
        ms->height_dots = 10;

/*test!!!*/
/*    ms->y1_dots -= 50;*/

    /* take scanning direction into account */
    if ((mi->direction & MI_DATSEQ_RTOL) == 1)
        ms->x1_dots = mi->geo_width - ms->x1_dots - ms->width_dots;

    if ( ms->val[OPT_RESOLUTION_BIND].w == SANE_TRUE )
      {
        ms->x_resolution_dpi =
                    (SANE_Int) (SANE_UNFIX(ms->val[OPT_RESOLUTION].w) + 0.5);
        ms->y_resolution_dpi =
                    (SANE_Int) (SANE_UNFIX(ms->val[OPT_RESOLUTION].w) + 0.5);
      }
    else
      {
        ms->x_resolution_dpi =
                    (SANE_Int) (SANE_UNFIX(ms->val[OPT_X_RESOLUTION].w) + 0.5);
        ms->y_resolution_dpi =
                    (SANE_Int) (SANE_UNFIX(ms->val[OPT_Y_RESOLUTION].w) + 0.5);
      }

    if ( ms->x_resolution_dpi < 10 )
        ms->x_resolution_dpi = 10;
    if ( ms->y_resolution_dpi < 10 )
        ms->y_resolution_dpi = 10;

    DBG(30, "get_scan_parameters: yres=%d, x1=%d, width=%d, y1=%d, height=%d\n",
             ms->y_resolution_dpi, ms->x1_dots, ms->width_dots,
             ms->y1_dots, ms->height_dots);

    /* Preview mode */
    if ( ms->val[OPT_PREVIEW].w == SANE_TRUE )
      {
        ms->fastscan = SANE_TRUE;
        ms->quality = SANE_FALSE;
      }
    else
      {
        ms->fastscan = SANE_FALSE;
        ms->quality = SANE_TRUE;
      }

    ms->rawdat = 0;

    /* brightness, contrast, values 1,..,255 */
    ms->brightness_m = (u_int8_t) (SANE_UNFIX(ms->val[OPT_BRIGHTNESS].w)
                      / SANE_UNFIX(md->percentage_range.max) * 254.0) + 1;
    ms->brightness_r = ms->brightness_g = ms->brightness_b = ms->brightness_m;

    ms->contrast_m = (u_int8_t) (SANE_UNFIX(ms->val[OPT_CONTRAST].w)
                    / SANE_UNFIX(md->percentage_range.max) * 254.0) + 1;
    ms->contrast_r = ms->contrast_g = ms->contrast_b = ms->contrast_m;

    /* shadow, midtone, highlight, exposure */
    ms->shadow_m = (u_int8_t) ms->val[OPT_SHADOW].w;
    ms->shadow_r = (u_int8_t) ms->val[OPT_SHADOW_R].w;
    ms->shadow_g = (u_int8_t) ms->val[OPT_SHADOW_G].w;
    ms->shadow_b = (u_int8_t) ms->val[OPT_SHADOW_B].w;
    ms->midtone_m = (u_int8_t) ms->val[OPT_MIDTONE].w;
    ms->midtone_r = (u_int8_t) ms->val[OPT_MIDTONE_R].w;
    ms->midtone_g = (u_int8_t) ms->val[OPT_MIDTONE_G].w;
    ms->midtone_b = (u_int8_t) ms->val[OPT_MIDTONE_B].w;
    ms->highlight_m = (u_int8_t) ms->val[OPT_HIGHLIGHT].w;
    ms->highlight_r = (u_int8_t) ms->val[OPT_HIGHLIGHT_R].w;
    ms->highlight_g = (u_int8_t) ms->val[OPT_HIGHLIGHT_G].w;
    ms->highlight_b = (u_int8_t) ms->val[OPT_HIGHLIGHT_B].w;
    ms->exposure_m = (u_int8_t) (ms->val[OPT_EXPOSURE].w / 2);
    ms->exposure_r = (u_int8_t) (ms->val[OPT_EXPOSURE_R].w / 2);
    ms->exposure_g = (u_int8_t) (ms->val[OPT_EXPOSURE_G].w / 2);
    ms->exposure_b = (u_int8_t) (ms->val[OPT_EXPOSURE_B].w / 2);

    ms->gamma_mode = strdup( (char *) ms->val[OPT_GAMMA_MODE].s);

    ms->balance[0] = (u_int8_t) (SANE_UNFIX(ms->val[OPT_BALANCE_R].w));
    ms->balance[1] = (u_int8_t) (SANE_UNFIX(ms->val[OPT_BALANCE_G].w));
    ms->balance[2] = (u_int8_t) (SANE_UNFIX(ms->val[OPT_BALANCE_B].w));
    DBG(255, "get_scan_parameters:ms->balance[0]=%d,[1]=%d,[2]=%d\n",
               ms->balance[0], ms->balance[1], ms->balance[2]);

    return SANE_STATUS_GOOD;
}

/*---------- get_scan_mode_and_depth() ---------------------------------------*/

static SANE_Status
get_scan_mode_and_depth(Microtek2_Scanner *ms,
                        int *mode,
                        int *depth,
                        int *bits_per_pixel_in,
                        int *bits_per_pixel_out)
{
    /* This function translates the strings for the possible modes and */
    /* bitdepth into a more conveniant format as needed for SET WINDOW. */
    /* bits_per_pixel is the number of bits per color one pixel needs */
    /* when transferred from the the scanner, bits_perpixel_out is the */
    /* number of bits per color one pixel uses when transferred to the */
    /* frontend. These may be different. For example, with a depth of 4 */
    /* two pixels per byte are transferred from the scanner, but only one */
    /* pixel per byte is transferred to the frontend. */
    /* If lineart_fake is set to !=0, we need the parameters for a */
    /* grayscale scan, because the scanner has no lineart mode */

    Microtek2_Device *md;
    Microtek2_Info *mi;

    DBG(30, "get_scan_mode_and_depth: handle=%p\n", (void *) ms);

    md = ms->dev;
    mi = &md->info[md->scan_source];

    if ( strcmp(ms->val[OPT_MODE].s, MD_MODESTRING_COLOR) == 0 )
	*mode = MS_MODE_COLOR;
    else if ( strcmp(ms->val[OPT_MODE].s, MD_MODESTRING_GRAY) == 0 )
	*mode = MS_MODE_GRAY;
    else if ( strcmp(ms->val[OPT_MODE].s, MD_MODESTRING_HALFTONE) == 0)
	*mode = MS_MODE_HALFTONE;
    else if ( strcmp(ms->val[OPT_MODE].s, MD_MODESTRING_LINEART) == 0 )
      {
        if ( MI_LINEART_NONE(mi->scanmode)
             || ms->val[OPT_AUTOADJUST].w == SANE_TRUE
             || md->model_flags & MD_READ_CONTROL_BIT)
            *mode = MS_MODE_LINEARTFAKE;
        else
	    *mode = MS_MODE_LINEART;
      }
    else
      {
        DBG(1, "get_scan_mode_and_depth: Unknown mode %s\n",
                ms->val[OPT_MODE].s);
        return SANE_STATUS_INVAL;
      }

    if ( strcmp(ms->val[OPT_MODE].s, MD_MODESTRING_COLOR) == 0
         || strcmp(ms->val[OPT_MODE].s, MD_MODESTRING_GRAY) == 0 )
      {
        if ( ms->val[OPT_BITDEPTH].w == MD_DEPTHVAL_16 )
          {
            *depth = 16;
            *bits_per_pixel_in = *bits_per_pixel_out = 16;
          }
        else if ( ms->val[OPT_BITDEPTH].w == MD_DEPTHVAL_14 )
          {
            *depth = 14;
            *bits_per_pixel_in = *bits_per_pixel_out = 16;
          }
        else if ( ms->val[OPT_BITDEPTH].w == MD_DEPTHVAL_12 )
          {
            *depth = 12;
            *bits_per_pixel_in = *bits_per_pixel_out = 16;
          }
        else if ( ms->val[OPT_BITDEPTH].w == MD_DEPTHVAL_10 )
          {
            *depth = 10;
            *bits_per_pixel_in = *bits_per_pixel_out = 16;
          }
        else if ( ms->val[OPT_BITDEPTH].w ==  MD_DEPTHVAL_8 )
          {
            *depth = 8;
            *bits_per_pixel_in = *bits_per_pixel_out = 8;
          }
        else if ( ms->val[OPT_MODE].w == MD_DEPTHVAL_4 )
          {
            *depth = 4;
            *bits_per_pixel_in = 4;
            *bits_per_pixel_out = 8;
          }
      }
    else if ( strcmp(ms->val[OPT_MODE].s, MD_MODESTRING_HALFTONE) == 0  )
      {
        *depth = 1;
        *bits_per_pixel_in = *bits_per_pixel_out = 1;
      }
    else                   /* lineart */
      {
        *bits_per_pixel_out = 1;
        if ( *mode == MS_MODE_LINEARTFAKE )
          {
            *depth = 8;
            *bits_per_pixel_in = 8;
          }
        else
          {
            *depth = 1;
            *bits_per_pixel_in = 1;
          }
      }

#if 0
    if ( ms->val[OPT_PREVIEW].w == SANE_TRUE )
      {
        if ( *depth > 8 )
          {
            *depth = 8;
            *bits_per_pixel_in = *bits_per_pixel_out = 8;
          }
      }
#endif

    DBG(30, "get_scan_mode_and_depth: mode=%d, depth=%d,"
            " bits_pp_in=%d, bits_pp_out=%d, preview=%d\n",
             *mode, *depth, *bits_per_pixel_in, *bits_per_pixel_out,
             ms->val[OPT_PREVIEW].w);
             
    return SANE_STATUS_GOOD;
}


/*---------- scsi_wait_for_image() -------------------------------------------*/

static SANE_Status
scsi_wait_for_image(Microtek2_Scanner *ms)
{
    int retry = 60;
    SANE_Status status;


    DBG(30, "scsi_wait_for_image: ms=%p\n", (void *) ms);

    while ( retry-- > 0 )
      {
        status = scsi_read_image_status(ms);
        if  (status == SANE_STATUS_DEVICE_BUSY )
          {
            sleep(1);
            continue;
          }
        if ( status == SANE_STATUS_GOOD )
            return status;

        /* status != GOOD && != BUSY */
        DBG(1, "scsi_wait_for_image: '%s'\n", sane_strstatus(status));
        return status;
      }

    /* BUSY after n retries */
    DBG(1, "scsi_wait_for_image: '%s'\n", sane_strstatus(status));
    return status;
}


/*---------- scsi_read_gamma() -----------------------------------------------*/

/* currently not used */
/*
static SANE_Status
scsi_read_gamma(Microtek2_Scanner *ms, int color)
{
    u_int8_t readgamma[RG_CMD_L];
    u_int8_t result[3072];
    size_t size;
    SANE_Bool endiantype;
    SANE_Status status;

    RG_CMD(readgamma);
    ENDIAN_TYPE(endiantype);
    RG_PCORMAC(readgamma, endiantype);
    RG_COLOR(readgamma, color);
    RG_WORD(readgamma, ( ms->dev->lut_entry_size == 1 ) ? 0 : 1);
    RG_TRANSFERLENGTH(readgamma, (color == 3 ) ? 3072 : 1024);

    dump_area(readgamma, 10, "ReadGamma");

    size = sizeof(result);
    status = sanei_scsi_cmd(ms->sfd, readgamma, sizeof(readgamma),
                            result, &size);
    if ( status != SANE_STATUS_GOOD ) {
        DBG(1, "scsi_read_gamma: (L,R) read_gamma failed: status '%s'\n",
                sane_strstatus(status));
        return status;
    }

    dump_area(result, 3072, "Result");

    return SANE_STATUS_GOOD;
}
*/


/*---------- scsi_send_gamma() -----------------------------------------------*/

static SANE_Status
scsi_send_gamma(Microtek2_Scanner *ms)
{
    SANE_Bool endiantype;
    SANE_Status status;
    size_t size;
    u_int8_t *cmd, color;


    DBG(30, "scsi_send_gamma: pos=%p, size=%d, word=%d, color=%d\n",
             ms->gamma_table, ms->lut_size_bytes, ms->word, ms->current_color);

    if ( ( 3 * ms->lut_size_bytes ) <= 0xffff ) /*send Gamma with one command*/
      {
        cmd = (u_int8_t *) alloca(SG_CMD_L + 3 * ms->lut_size_bytes);
        if ( cmd == NULL )
          {
            DBG(1, "scsi_send_gamma: Couldn't get buffer for gamma table\n");
            return SANE_STATUS_IO_ERROR;
          }

        SG_SET_CMD(cmd);
        ENDIAN_TYPE(endiantype)
        SG_SET_PCORMAC(cmd, endiantype);
        SG_SET_COLOR(cmd, ms->current_color);
        SG_SET_WORD(cmd, ms->word);
        SG_SET_TRANSFERLENGTH(cmd, 3 * ms->lut_size_bytes);
        memcpy(cmd + SG_CMD_L, ms->gamma_table, 3 * ms->lut_size_bytes);
        size = 3 * ms->lut_size_bytes;
        if ( md_dump >= 2 )
                dump_area2(cmd, SG_CMD_L, "sendgammacmd");
        if ( md_dump >= 3 )
                dump_area2(cmd + SG_CMD_L, size, "sendgammadata");

        status = sanei_scsi_cmd(ms->sfd, cmd, size + SG_CMD_L, NULL, 0);
        if ( status != SANE_STATUS_GOOD )
                DBG(1, "scsi_send_gamma: '%s'\n", sane_strstatus(status));
      }

    else  /* send gamma with 3 commands, one for each color */
      {
        for ( color = 0; color < 3; color++ )
          {
            cmd = (u_int8_t *) alloca(SG_CMD_L + ms->lut_size_bytes);
            if ( cmd == NULL )
              {
                DBG(1, "scsi_send_gamma: Couldn't get buffer for gamma table\n");
                return SANE_STATUS_IO_ERROR;
              }
            SG_SET_CMD(cmd);
            ENDIAN_TYPE(endiantype)
            SG_SET_PCORMAC(cmd, endiantype);
            SG_SET_COLOR(cmd, color);
            SG_SET_WORD(cmd, ms->word);
            SG_SET_TRANSFERLENGTH(cmd, ms->lut_size_bytes);
            memcpy(cmd + SG_CMD_L,
                   ms->gamma_table + color * ms->lut_size_bytes,
                   ms->lut_size_bytes);
            size = ms->lut_size_bytes;
            if ( md_dump >= 2 )
                    dump_area2(cmd, SG_CMD_L, "sendgammacmd");
            if ( md_dump >= 3 )
                    dump_area2(cmd + SG_CMD_L, size, "sendgammadata");

            status = sanei_scsi_cmd(ms->sfd, cmd, size + SG_CMD_L, NULL, 0);
            if ( status != SANE_STATUS_GOOD )
                    DBG(1, "scsi_send_gamma: '%s'\n", sane_strstatus(status));
          }

      }

    return status;
}


/*---------- scsi_inquiry() --------------------------------------------------*/

static SANE_Status
scsi_inquiry(Microtek2_Info *mi, char *device)
{
    SANE_Status status;
    u_int8_t cmd[INQ_CMD_L];
    u_int8_t *result;
    u_int8_t inqlen;
    size_t size;
    int sfd;


    DBG(30, "scsi_inquiry: mi=%p, device='%s'\n", (void *) mi, device);

    status = sanei_scsi_open(device, &sfd, scsi_sense_handler, 0);
    if ( status != SANE_STATUS_GOOD )
      {
        DBG(1, "scsi_inquiry: '%s'\n", sane_strstatus(status));
        return status;
      }

    INQ_CMD(cmd);
    INQ_SET_ALLOC(cmd, INQ_ALLOC_L);
    result = (u_int8_t *) alloca(INQ_ALLOC_L);
    if ( result == NULL )
      {
        DBG(1, "scsi_inquiry: malloc failed\n");
        sanei_scsi_close(sfd);
        return SANE_STATUS_NO_MEM;
      }

    size = INQ_ALLOC_L;
    status = sanei_scsi_cmd(sfd, cmd, sizeof(cmd), result, &size);
    if ( status != SANE_STATUS_GOOD )
      {
        DBG(1, "scsi_inquiry: '%s'\n", sane_strstatus(status));
        sanei_scsi_close(sfd);
        return status;
      }

    INQ_GET_INQLEN(inqlen, result);
    INQ_SET_ALLOC(cmd, inqlen + INQ_ALLOC_L);
    result = alloca(inqlen + INQ_ALLOC_L);
    if ( result == NULL )
      {
        DBG(1, "scsi_inquiry: malloc failed\n");
        sanei_scsi_close(sfd);
        return SANE_STATUS_NO_MEM;
      }
    size = inqlen + INQ_ALLOC_L;
    if (md_dump >= 2 )
        dump_area2(cmd, sizeof(cmd), "inquiry");

    status = sanei_scsi_cmd(sfd, cmd, sizeof(cmd), result, &size);
    if ( status != SANE_STATUS_GOOD )
      {
        DBG(1, "scsi_inquiry: cmd '%s'\n", sane_strstatus(status));
        sanei_scsi_close(sfd);
        return status;
      }
    sanei_scsi_close(sfd);

    if (md_dump >= 2 )
      {
        dump_area2((u_int8_t *) result, size, "inquiryresult");
        dump_area((u_int8_t *) result, size, "inquiryresult");
      }

    /* copy results */
    INQ_GET_QUAL(mi->device_qualifier, result);
    INQ_GET_DEVT(mi->device_type, result);
    INQ_GET_VERSION(mi->scsi_version, result);
    INQ_GET_VENDOR(mi->vendor, result);
    INQ_GET_MODEL(mi->model, result);
    INQ_GET_REV(mi->revision, result);
    INQ_GET_MODELCODE(mi->model_code, result);


    return SANE_STATUS_GOOD;
}


/*---------- scsi_read_attributes() ------------------------------------------*/

static SANE_Status
scsi_read_attributes(Microtek2_Info *pmi, char *device, u_int8_t scan_source)
{
    SANE_Status status;
    Microtek2_Info *mi;
    u_int8_t readattributes[RSA_CMD_L];
    u_int8_t result[RSA_TRANSFERLENGTH];
    size_t size;
    int sfd;


    mi = &pmi[scan_source];

    DBG(30, "scsi_read_attributes: mi=%p, device='%s', source=%d\n",
             (void *) mi, device, scan_source);

    RSA_CMD(readattributes);
    RSA_SETMEDIA(readattributes, scan_source);
    status = sanei_scsi_open(device, &sfd, scsi_sense_handler, 0);
    if ( status != SANE_STATUS_GOOD )
      {
        DBG(1, "scsi_read_attributes: open '%s'\n", sane_strstatus(status));
        return status;
      }

    if (md_dump >= 2 )
        dump_area2(readattributes, sizeof(readattributes), "scannerattributes");

    size = sizeof(result);
    status = sanei_scsi_cmd(sfd, readattributes,
                            sizeof(readattributes), result, &size);
    if ( status != SANE_STATUS_GOOD )
      {
        DBG(1, "scsi_read_attributes: cmd '%s'\n", sane_strstatus(status));
        sanei_scsi_close(sfd);
        return status;
      }

    sanei_scsi_close(sfd);

    /* The X6 appears to lie about the data format for a TMA */
    if ( (&pmi[0])->model_code == 0x91 )
        result[0] &= 0xfd;
    /* default value for calib_divisor ... bit49?? */
    mi->calib_divisor = 1;
    /* 9600XL */
    if ( (&pmi[0])->model_code == 0xde )
        mi->calib_divisor = 2;
    /* 6400XL has problems in lineart mode*/
    if ( (&pmi[0])->model_code == 0x89 )
        result[13] &= 0xfe; /* simulate no lineart */
#if 0
    result[13] &= 0xfe; /* simulate no lineart */
#endif

    /* copy all the stuff into the info structure */
    RSA_COLOR(mi->color, result);
    RSA_ONEPASS(mi->onepass, result);
    RSA_SCANNERTYPE(mi->scanner_type, result);
    RSA_FEPROM(mi->feprom, result);
    RSA_DATAFORMAT(mi->data_format, result);
    RSA_COLORSEQUENCE(mi->color_sequence, result);
    RSA_NIS(mi->new_image_status, result);
    RSA_DATSEQ(mi->direction, result);
    RSA_CCDGAP(mi->ccd_gap, result);
    RSA_MAX_XRESOLUTION(mi->max_xresolution, result);
    RSA_MAX_YRESOLUTION(mi->max_yresolution, result);
    RSA_GEOWIDTH(mi->geo_width, result);
    RSA_GEOHEIGHT(mi->geo_height, result);
    RSA_OPTRESOLUTION(mi->opt_resolution, result);
    RSA_DEPTH(mi->depth, result);
    /* The X12USL doesn't say that it has 14bit */
    if ( (&pmi[0])->model_code == 0xb0 )
        mi->depth |= MI_HASDEPTH_14;
    RSA_SCANMODE(mi->scanmode, result);
    RSA_CCDPIXELS(mi->ccd_pixels, result);
    RSA_LUTCAP(mi->lut_cap, result);
    RSA_DNLDPTRN(mi->has_dnldptrn, result);
    RSA_GRAINSLCT(mi->grain_slct, result);
    RSA_SUPPOPT(mi->option_device, result);
    RSA_CALIBWHITE(mi->calib_white, result);
    RSA_CALIBSPACE(mi->calib_space, result);
    RSA_NLENS(mi->nlens, result);
    RSA_NWINDOWS(mi->nwindows, result);
    RSA_SHTRNSFEREQU(mi->shtrnsferequ, result);
    RSA_SCNBTTN(mi->scnbuttn, result);
    RSA_BUFTYPE(mi->buftype, result);
    RSA_REDBALANCE(mi->balance[0], result);
    RSA_GREENBALANCE(mi->balance[1], result);
    RSA_BLUEBALANCE(mi->balance[2], result);
    RSA_APSMAXFRAMES(mi->aps_maxframes, result);

    if (md_dump >= 2 )
        dump_area2((u_int8_t *) result, sizeof(result),
                   "scannerattributesresults");
    if ( md_dump >= 1 && md_dump_clear )
        dump_attributes(mi);

    return SANE_STATUS_GOOD;
}


/*---------- scsi_read_control_bits() ----------------------------------------*/

static SANE_Status
scsi_read_control_bits(Microtek2_Scanner *ms)
{
    Microtek2_Device *md;
    SANE_Status status;
    u_int8_t cmd[RCB_CMD_L];
    u_int32_t byte;
    int bit;
    int count_1s;

    md = ms->dev;

    DBG(30, "scsi_read_control_bits: ms=%p, fd=%d\n", (void *) ms, ms->sfd);
    DBG(30, "ms->control_bytes = %p\n", ms->control_bytes);

    RCB_SET_CMD(cmd);
    RCB_SET_LENGTH(cmd, ms->n_control_bytes);

    if ( md_dump >= 2)
        dump_area2(cmd, RCB_CMD_L, "readcontrolbits");

    status = sanei_scsi_cmd(ms->sfd,
                            cmd,
                            sizeof(cmd),
                            ms->control_bytes,
                            &ms->n_control_bytes);

    if ( status != SANE_STATUS_GOOD )
      {
        DBG(1, "scsi_read_control_bits: cmd '%s'\n", sane_strstatus(status));
        return status;
      }

    if ( md_dump >= 2)
        dump_area2(ms->control_bytes,
                   ms->n_control_bytes,
                   "readcontrolbitsresult");

    count_1s = 0;
    for ( byte = 0; byte < ms->n_control_bytes; byte++ )
      {
        for ( bit = 0; bit < 8; bit++ )
          {
            if ( (ms->control_bytes[byte] >> bit) & 0x01 )
                ++count_1s;
          }
      }
    DBG(20, "read_control_bits: number of 1's in controlbytes: %d\n", count_1s);

    return SANE_STATUS_GOOD;
}


/*---------- scsi_set_window() -----------------------------------------------*/

static SANE_Status
scsi_set_window(Microtek2_Scanner *ms, int n) {   /* n windows, not yet */
                                                  /* implemented */
    SANE_Status status;
    u_int8_t *setwindow;
    int size;


    DBG(30, "scsi_set_window: ms=%p, wnd=%d\n", (void *) ms, n);

    size = SW_CMD_L + SW_HEADER_L + n * SW_BODY_L;
    setwindow = (u_int8_t *) malloc(size);
    DBG(100, "scsi_set_window: setwindow= %p, malloc'd %d Bytes\n",
              setwindow, size);
    if ( setwindow == NULL )
      {
        DBG(1, "scsi_set_window: malloc for setwindow failed\n");
        return SANE_STATUS_NO_MEM;
      }
    memset(setwindow, 0, size);

    SW_CMD(setwindow);
    SW_PARAM_LENGTH(setwindow, SW_HEADER_L + n * SW_BODY_L);
    SW_WNDDESCLEN(setwindow + SW_HEADER_P, SW_WNDDESCVAL);

#define POS  (setwindow + SW_BODY_P(n-1))

    SW_WNDID(POS, n-1);
    SW_XRESDPI(POS, ms->x_resolution_dpi);
    SW_YRESDPI(POS, ms->y_resolution_dpi);
    SW_XPOSTL(POS, ms->x1_dots);
    SW_YPOSTL(POS, ms->y1_dots);
    SW_WNDWIDTH(POS, ms->width_dots);
    SW_WNDHEIGHT(POS, ms->height_dots);
    SW_THRESHOLD(POS, ms->threshold);
    SW_IMGCOMP(POS, ms->mode);
    SW_BITSPERPIXEL(POS, ms->depth);
    SW_EXTHT(POS, ms->use_external_ht);
    SW_INTHTINDEX(POS, ms->internal_ht_index);
    SW_RIF(POS, 1);
    SW_LENS(POS, 0);                                  /* ???? */
    SW_INFINITE(POS, 0);
    SW_STAY(POS, ms->stay);
    SW_RAWDAT(POS, ms->rawdat);
    SW_QUALITY(POS, ms->quality);
    SW_FASTSCAN(POS, ms->fastscan);
    SW_MEDIA(POS, ms->scan_source);
    SW_BRIGHTNESS_M(POS, ms->brightness_m);
    SW_CONTRAST_M(POS, ms->contrast_m);
    SW_EXPOSURE_M(POS, ms->exposure_m);
    SW_SHADOW_M(POS, ms->shadow_m);
    SW_MIDTONE_M(POS, ms->midtone_m);
    SW_HIGHLIGHT_M(POS, ms->highlight_m);
    /* the following properties are only referenced if it's a color scan */
    /* but I guess they don't matter at a gray scan */
    SW_BRIGHTNESS_R(POS, ms->brightness_r);
    SW_CONTRAST_R(POS, ms->contrast_r);
    SW_EXPOSURE_R(POS, ms->exposure_r);
    SW_SHADOW_R(POS, ms->shadow_r);
    SW_MIDTONE_R(POS, ms->midtone_r);
    SW_HIGHLIGHT_R(POS, ms->highlight_r);
    SW_BRIGHTNESS_G(POS, ms->brightness_g);
    SW_CONTRAST_G(POS, ms->contrast_g);
    SW_EXPOSURE_G(POS, ms->exposure_g);
    SW_SHADOW_G(POS, ms->shadow_g);
    SW_MIDTONE_G(POS, ms->midtone_g);
    SW_HIGHLIGHT_G(POS, ms->highlight_g);
    SW_BRIGHTNESS_B(POS, ms->brightness_b);
    SW_CONTRAST_B(POS, ms->contrast_b);
    SW_EXPOSURE_B(POS, ms->exposure_b);
    SW_SHADOW_B(POS, ms->shadow_b);
    SW_MIDTONE_B(POS, ms->midtone_b);
    SW_HIGHLIGHT_B(POS, ms->highlight_b);

    if ( md_dump >= 2 )
      {
        dump_area2(setwindow, 10, "setwindowcmd");
        dump_area2(setwindow + 10 ,8 , "setwindowheader");
        dump_area2(setwindow + 18 ,61 , "setwindowbody");
      }

    status = sanei_scsi_cmd(ms->sfd, setwindow, size, NULL, 0);
    if ( status != SANE_STATUS_GOOD )
        DBG(1, "scsi_set_window: '%s'\n", sane_strstatus(status));

    DBG(100, "scsi_set_window: free setwindow at %p\n", setwindow);
    free((void *) setwindow);
    return status;
}


/*---------- scsi_read_image_info() ------------------------------------------*/

static SANE_Status
scsi_read_image_info(Microtek2_Scanner *ms)
{
    u_int8_t cmd[RII_CMD_L];
    u_int8_t result[RII_RESULT_L];
    size_t size;
    SANE_Status status;
    Microtek2_Device *md;

    md = ms->dev;

    DBG(30, "scsi_read_image_info: ms=%p\n", (void *) ms);

    RII_SET_CMD(cmd);

    if ( md_dump >= 2)
        dump_area2(cmd, RII_CMD_L, "readimageinfo");

    size = sizeof(result);
    status = sanei_scsi_cmd(ms->sfd, cmd, sizeof(cmd), result, &size);
    if ( status != SANE_STATUS_GOOD )
      {
        DBG(1, "scsi_read_image_info: '%s'\n", sane_strstatus(status));
        return status;
      }

    if ( md_dump >= 2)
        dump_area2(result, size, "readimageinforesult");

    /* The V300 returns some values in only two bytes */
    if ( !(md->revision==2.70) && (md->model_flags & MD_RII_TWO_BYTES) )
      {
        RII_GET_V300_WIDTHPIXEL(ms->ppl, result);
        RII_GET_V300_WIDTHBYTES(ms->bpl, result);
        RII_GET_V300_HEIGHTLINES(ms->src_remaining_lines, result);
        RII_GET_V300_REMAINBYTES(ms->remaining_bytes, result);
      }
    else
      {
        RII_GET_WIDTHPIXEL(ms->ppl, result);
        RII_GET_WIDTHBYTES(ms->bpl, result);
        RII_GET_HEIGHTLINES(ms->src_remaining_lines, result);
        RII_GET_REMAINBYTES(ms->remaining_bytes, result);
      }

    DBG(30, "scsi_read_image_info: ppl=%d, bpl=%d, lines=%d, remain=%d\n",
             ms->ppl, ms->bpl, ms->src_remaining_lines, ms->remaining_bytes);

    return SANE_STATUS_GOOD;
}


/*---------- scsi_read_image() -----------------------------------------------*/

static SANE_Status
scsi_read_image(Microtek2_Scanner *ms, u_int8_t *buffer, int bytes_per_pixel)
{
    u_int8_t cmd[RI_CMD_L];
    SANE_Bool endiantype;
    SANE_Status status;
    size_t size;
    size_t i;
    u_int8_t tmp;


    DBG(30, "scsi_read_image:  ms=%p, buffer=%p\n", (void *) ms, buffer);

    ENDIAN_TYPE(endiantype)
    RI_SET_CMD(cmd);
    RI_SET_PCORMAC(cmd, endiantype);
    RI_SET_COLOR(cmd, ms->current_read_color);
    RI_SET_TRANSFERLENGTH(cmd, ms->transfer_length);

    DBG(30, "scsi_read_image: transferlength=%d\n", ms->transfer_length);

    if ( md_dump >= 2 )
        dump_area2(cmd, RI_CMD_L, "readimagecmd");

    size = ms->transfer_length;
    status = sanei_scsi_cmd(ms->sfd, cmd, sizeof(cmd), buffer, &size);

    if ( buffer && ( ms->dev->model_flags & MD_PHANTOM_C6 ) && endiantype )
      {
	switch(bytes_per_pixel)
	  {
	    case 1: break;
	    case 2:
		    for ( i = 1; i < size; i += 2 )
		      {
			tmp = buffer[i-1];
			buffer[i-1] = buffer[i];
			buffer[i] = tmp;
		      }
		    break;
	    default:
		    DBG(1, "scsi_read_image: Unexpected bytes_per_pixel=%d\n", bytes_per_pixel);
	  }
      }

    if ( status != SANE_STATUS_GOOD )
        DBG(1, "scsi_read_image: '%s'\n", sane_strstatus(status));

    if ( md_dump > 3 )
        dump_area2(buffer, ms->transfer_length, "readimageresult");

    return status;
}


/*---------- scsi_read_image_status() ----------------------------------------*/

static SANE_Status
scsi_read_image_status(Microtek2_Scanner *ms)
{
    Microtek2_Device *md;
    Microtek2_Info *mi;
    u_int8_t cmd[RIS_CMD_L];
    u_int8_t dummy;
    size_t dummy_length;
    SANE_Status status;
    SANE_Bool endian_type;

    md = ms->dev;
    mi = &md->info[md->scan_source];

    DBG(30, "scsi_read_image_status: ms=%p\n", (void *) ms);

    ENDIAN_TYPE(endian_type)
    RIS_SET_CMD(cmd);
    RIS_SET_PCORMAC(cmd, endian_type);
    RIS_SET_COLOR(cmd, ms->current_read_color);

/*    mi->new_image_status = SANE_TRUE;  */  /* for testing*/

    if ( mi->new_image_status == SANE_TRUE )
      {
        DBG(30, "scsi_read_image_status: use new image status \n");
        dummy_length = 1;
        cmd[8] = 1;
      }
    else
      {
        DBG(30, "scsi_read_image_status: use old image status \n");
        dummy_length = 0;
        cmd[8] = 0;
      }

    if ( md_dump >= 2 )
        dump_area2(cmd, sizeof(cmd), "readimagestatus");

    status = sanei_scsi_cmd(ms->sfd, cmd, sizeof(cmd), &dummy, &dummy_length);

    if ( mi->new_image_status == SANE_TRUE )
      {
        if ( dummy == 0 )
            status = SANE_STATUS_GOOD;
        else
            status = SANE_STATUS_DEVICE_BUSY;
      }

        /* For some (X6USB) scanner
        We say we are going to try to read 1 byte of data (as recommended
        in the Microtek SCSI command documentation under "New Image Status")
        so that dubious SCSI host adapters (like the one in at least some
        Microtek X6 USB scanners) don't get wedged trying to do a zero
        length read. However, we do not actually try to read this byte of
        data, as that wedges the USB scanner as well.
        IOW the SCSI command says we are going to read 1 byte, but in fact
        we don't: */
        /*cmd[8] = 1;
        status = sanei_scsi_cmd(ms->sfd, cmd, sizeof(cmd), &dummy, 0); */


    if ( status != SANE_STATUS_GOOD )
        DBG(1, "scsi_read_image_status: '%s'\n", sane_strstatus(status));

    return status;
}

/*---------- scsi_read_shading () --------------------------------------------*/

static SANE_Status
scsi_read_shading(Microtek2_Scanner *ms, u_int8_t *buffer, u_int32_t length)
{
    Microtek2_Device *md;
    u_int8_t cmd[RSI_CMD_L];
    SANE_Bool endiantype;
    SANE_Status status = SANE_STATUS_GOOD;
    size_t size;

    DBG(30, "scsi_read_shading: pos=%p, size=%d, word=%d, color=%d, dark=%d\n",
             buffer, length, ms->word, ms->current_color, ms->dark);

    md = ms->dev;

    size = length;

    RSI_SET_CMD(cmd);
    ENDIAN_TYPE(endiantype)
    RSI_SET_PCORMAC(cmd, endiantype);
    RSI_SET_COLOR(cmd, ms->current_color);
    RSI_SET_DARK(cmd, ms->dark);
    RSI_SET_WORD(cmd, ms->word);
    RSI_SET_TRANSFERLENGTH(cmd, size);

    if ( md_dump >= 2 )
        dump_area2(cmd, RSI_CMD_L, "readshading");

    DBG(100, "scsi_read_shading: sfd=%d, cmd=%p, sizeofcmd=%lu,"
             "dest=%p, destsize=%lu\n",
              ms->sfd, cmd, (u_long) sizeof(cmd), buffer, (u_long) size);

    status = sanei_scsi_cmd(ms->sfd, cmd, sizeof(cmd), buffer, &size);
    if ( status != SANE_STATUS_GOOD )
        DBG(1, "scsi_read_shading: '%s'\n", sane_strstatus(status));

    if ( md_dump > 3)
        dump_area2(buffer,
                   size,
                   "readshadingresult");

    return status;
}


/*---------- scsi_send_shading () --------------------------------------------*/

static SANE_Status
scsi_send_shading(Microtek2_Scanner *ms,
                  u_int8_t *shading_data,
                  u_int32_t length,
                  u_int8_t dark)
{
    SANE_Bool endiantype;
    SANE_Status status;
    size_t size;
    u_int8_t *cmd;


    DBG(30, "scsi_send_shading: pos=%p, size=%d, word=%d, color=%d, dark=%d\n",
             shading_data, length, ms->word, ms->current_color,
             dark);

    cmd = (u_int8_t *) malloc(SSI_CMD_L + length);
    DBG(100, "scsi_send_shading: cmd=%p, malloc'd %d bytes\n",
              cmd, SSI_CMD_L + length);
    if ( cmd == NULL )
      {
        DBG(1, "scsi_send_shading: Couldn't get buffer for shading table\n");
        return SANE_STATUS_NO_MEM;
      }

    SSI_SET_CMD(cmd);
    ENDIAN_TYPE(endiantype)
    SSI_SET_PCORMAC(cmd, endiantype);
    SSI_SET_COLOR(cmd, ms->current_color);
    SSI_SET_DARK(cmd, dark);
    SSI_SET_WORD(cmd, ms->word);
    SSI_SET_TRANSFERLENGTH(cmd, length);
    memcpy(cmd + SSI_CMD_L, shading_data, length);
    size = length;

    if ( md_dump >= 2 )
        dump_area2(cmd, SSI_CMD_L, "sendshading");
    if ( md_dump >= 3 )
        dump_area2(cmd + SSI_CMD_L, size, "sendshadingdata");

    status = sanei_scsi_cmd(ms->sfd, cmd, size + SSI_CMD_L, NULL, 0);
    if ( status != SANE_STATUS_GOOD )
        DBG(1, "scsi_send_shading: '%s'\n", sane_strstatus(status));

    DBG(100, "free cmd at %p\n", cmd);
    free((void *) cmd);

    return status;

}


/*---------- scsi_read_system_status() ---------------------------------------*/

static SANE_Status
scsi_read_system_status(Microtek2_Device *md, int fd)
{
    u_int8_t cmd[RSS_CMD_L];
    u_int8_t result[RSS_RESULT_L];
    int sfd;
    size_t size;
    SANE_Status status;

    DBG(30, "scsi_read_system_status: md=%p, fd=%d\n", (void *) md, fd);

    if ( fd == -1 )
      {
        status = sanei_scsi_open(md->name, &sfd, scsi_sense_handler, 0);
        if ( status != SANE_STATUS_GOOD )
          {
            DBG(1, "scsi_read_system_status: open '%s'\n",
                    sane_strstatus(status));
            return status;
          }
      }
    else
      sfd = fd;

    RSS_CMD(cmd);

    if ( md_dump >= 2)
        dump_area2(cmd, RSS_CMD_L, "readsystemstatus");

    size = sizeof(result);
    status = sanei_scsi_cmd(sfd, cmd, sizeof(cmd), result, &size);

    if ( status != SANE_STATUS_GOOD )
      {
        DBG(1, "scsi_read_system_status: cmd '%s'\n", sane_strstatus(status));
        sanei_scsi_close(sfd);
        return status;
      }

    if ( fd == -1 )
        sanei_scsi_close(sfd);

    if ( md_dump >= 2)
        dump_area2(result, size, "readsystemstatusresult");

    md->status.sskip = RSS_SSKIP(result);
    md->status.ntrack = RSS_NTRACK(result);
    md->status.ncalib = RSS_NCALIB(result);
    md->status.tlamp = RSS_TLAMP(result);
    md->status.flamp = RSS_FLAMP(result);
    md->status.rdyman= RSS_RDYMAN(result);
    md->status.trdy = RSS_TRDY(result);
    md->status.frdy = RSS_FRDY(result);
    md->status.adp = RSS_RDYMAN(result);
    md->status.detect = RSS_DETECT(result);
    md->status.adptime = RSS_ADPTIME(result);
    md->status.lensstatus = RSS_LENSSTATUS(result);
    md->status.aloff = RSS_ALOFF(result);
    md->status.timeremain = RSS_TIMEREMAIN(result);
    md->status.tmacnt = RSS_TMACNT(result);
    md->status.paper = RSS_PAPER(result);
    md->status.adfcnt = RSS_ADFCNT(result);
    md->status.currentmode = RSS_CURRENTMODE(result);
    md->status.buttoncount = RSS_BUTTONCOUNT(result);

    return SANE_STATUS_GOOD;
}


/*---------- scsi_request_sense() --------------------------------------------*/

/* currently not used */

#if 0

static SANE_Status
scsi_request_sense(Microtek2_Scanner *ms)
{
    u_int8_t requestsense[RQS_CMD_L];
    u_int8_t buffer[100];
    SANE_Status status;
    int size;
    int asl;
    int as_info_length;

    DBG(30, "scsi_request_sense: ms=%p\n", (void *) ms);

    RQS_CMD(requestsense);
    RQS_ALLOCLENGTH(requestsense, 100);

    size = sizeof(buffer);
    status = sanei_scsi_cmd(ms->sfd,  requestsense, sizeof(requestsense),
                            buffer, &size);

    if ( status != SANE_STATUS_GOOD )
      {
        DBG(1, "scsi_request_sense: '%s'\n", sane_strstatus(status));
        return status;
      }

    if ( md_dump >= 2 )
        dump_area2(buffer, size, "requestsenseresult");

    dump_area(buffer, RQS_LENGTH(buffer), "RequestSense");
    asl = RQS_ASL(buffer);
    if ( (as_info_length = RQS_ASINFOLENGTH(buffer)) > 0 )
        DBG(25, "scsi_request_sense: info '%.*s'\n",
                as_info_length, RQS_ASINFO(buffer));

    return SANE_STATUS_GOOD;
}
#endif


/*---------- scsi_send_system_status() ---------------------------------------*/

static SANE_Status
scsi_send_system_status(Microtek2_Device *md, int fd)
{
    u_int8_t cmd[SSS_CMD_L + SSS_DATA_L];
    u_int8_t *pos;
    int sfd;
    SANE_Status status;


    DBG(30, "scsi_send_system_status: md=%p, fd=%d\n", (void *) md, fd);

    memset(cmd, 0, SSS_CMD_L + SSS_DATA_L);
    if ( fd == -1 )
      {
        status = sanei_scsi_open(md->name, &sfd, scsi_sense_handler, 0);
        if ( status != SANE_STATUS_GOOD )
          {
            DBG(1, "scsi_send_system_status: open '%s'\n",
                    sane_strstatus(status));
            return status;
          }
      }
    else
      sfd = fd;

    SSS_CMD(cmd);
    pos = cmd + SSS_CMD_L;
    SSS_STICK(pos, md->status.stick);
    SSS_NTRACK(pos, md->status.ntrack);
    SSS_NCALIB(pos, md->status.ncalib);
    SSS_TLAMP(pos, md->status.tlamp);
    SSS_FLAMP(pos, md->status.flamp);
    SSS_RESERVED17(pos, md->status.reserved17);
    SSS_RDYMAN(pos, md->status.rdyman);
    SSS_TRDY(pos, md->status.trdy);
    SSS_FRDY(pos, md->status.frdy);
    SSS_ADP(pos, md->status.adp);
    SSS_DETECT(pos, md->status.detect);
    SSS_ADPTIME(pos, md->status.adptime);
    SSS_LENSSTATUS(pos, md->status.lensstatus);
    SSS_ALOFF(pos, md->status.aloff);
    SSS_TIMEREMAIN(pos, md->status.timeremain);
    SSS_TMACNT(pos, md->status.tmacnt);
    SSS_PAPER(pos, md->status.paper);
    SSS_ADFCNT(pos, md->status.adfcnt);
    SSS_CURRENTMODE(pos, md->status.currentmode);
    SSS_BUTTONCOUNT(pos, md->status.buttoncount);

    if ( md_dump >= 2)
      {
        dump_area2(cmd, SSS_CMD_L, "sendsystemstatus");
        dump_area2(cmd + SSS_CMD_L, SSS_DATA_L, "sendsystemstatusdata");
      }

    status = sanei_scsi_cmd(sfd, cmd, sizeof(cmd), NULL, 0);
    if ( status != SANE_STATUS_GOOD )
        DBG(1, "scsi_send_system_status: '%s'\n", sane_strstatus(status));

    if ( fd == -1 )
        sanei_scsi_close(sfd);
    return status;
}


/*---------- scsi_sense_handler() --------------------------------------------*/
/* rewritten 19.12.2001 for better SANE_STATUS return codes */

static SANE_Status
scsi_sense_handler (int fd, u_char *sense, void *arg)
{
    int as_info_length;
    u_int8_t sense_key;
    u_int8_t asl;
    u_int8_t asc;
    u_int8_t ascq;


    DBG(30, "scsi_sense_handler: fd=%d, sense=%p arg=%p\n",fd, sense, arg);

    dump_area(sense, RQS_LENGTH(sense), "SenseBuffer");

    sense_key = RQS_SENSEKEY(sense);
    asl = RQS_ASL(sense);
    asc = RQS_ASC(sense);
    ascq = RQS_ASCQ(sense);

    DBG(5, "scsi_sense_handler: SENSE KEY (0x%02x), "
           "ASC (0x%02x), ASCQ (0x%02x)\n", sense_key, asc, ascq);

    if ( (as_info_length = RQS_ASINFOLENGTH(sense)) > 0 )
        DBG(5,"scsi_sense_handler: info: '%*s'\n",
                as_info_length, RQS_ASINFO(sense));

    switch ( sense_key )
      {
        case RQS_SENSEKEY_NOSENSE:
          return SANE_STATUS_GOOD;

        case RQS_SENSEKEY_HWERR:
        case RQS_SENSEKEY_ILLEGAL:
        case RQS_SENSEKEY_VENDOR:
          if ( asc == 0x4a && ascq == 0x00 )
              DBG(5, "scsi_sense_handler: Command phase error\n");
          else if ( asc == 0x2c && ascq == 0x00 )
              DBG(5, "scsi_sense_handler: Command sequence error\n");
          else if ( asc == 0x4b && ascq == 0x00 )
              DBG(5, "scsi_sense_handler: Data phase error\n");
          else if ( asc == 0x40 )
            {
              DBG(5, "scsi_sense_handler: Hardware diagnostic failure:\n");
              switch ( ascq )
                {
                  case RQS_ASCQ_CPUERR:
                    DBG(5, "scsi_sense_handler: CPU error\n");
                    break;
                  case RQS_ASCQ_SRAMERR:
                    DBG(5, "scsi_sense_handler: SRAM error\n");
                    break;
                  case RQS_ASCQ_DRAMERR:
                    DBG(5, "scsi_sense_handler: DRAM error\n");
                    break;
                  case RQS_ASCQ_DCOFF:
                    DBG(5, "scsi_sense_handler: DC Offset error\n");
                    break;
                  case RQS_ASCQ_GAIN:
                    DBG(5, "scsi_sense_handler: Gain error\n");
                    break;
                  case RQS_ASCQ_POS:
                    DBG(5, "scsi_sense_handler: Positoning error\n");
                    break;
                  default:
                    DBG(5, "scsi_sense_handler: Unknown combination of ASC"
                           " (0x%02x) and ASCQ (0x%02x)\n", asc, ascq);
                    break;
                }
            }
          else if ( asc == 0x00  && ascq == 0x05)
            {
              DBG(5, "scsi_sense_handler: End of data detected\n");
              return SANE_STATUS_EOF;
            }
          else if ( asc == 0x3d  && ascq == 0x00)
              DBG(5, "scsi_sense_handler: Invalid bit in IDENTIFY\n");
          else if ( asc == 0x2c && ascq == 0x02 )
/* Ok */      DBG(5, "scsi_sense_handler: Invalid comb. of windows specfied\n");
          else if ( asc == 0x20 && ascq == 0x00 )
/* Ok */      DBG(5, "scsi_sense_handler: Invalid command opcode\n");
          else if ( asc == 0x24 && ascq == 0x00 )
/* Ok */      DBG(5, "scsi_sense_handler: Invalid field in CDB\n");
          else if ( asc == 0x26 && ascq == 0x00 )
              DBG(5, "scsi_sense_handler: Invalid field in the param list\n");
          else if ( asc == 0x49 && ascq == 0x00 )
              DBG(5, "scsi_sense_handler: Invalid message error\n");
          else if ( asc == 0x60 && ascq == 0x00 )
              DBG(5, "scsi_sense_handler: Lamp failure\n");
          else if ( asc == 0x25 && ascq == 0x00 )
              DBG(5, "scsi_sense_handler: Unsupported logic. unit\n");
          else if ( asc == 0x53 && ascq == 0x00 )
            {
              DBG(5, "scsi_sense_handler: ADF paper jam or no paper\n");
              return SANE_STATUS_NO_DOCS;
            }
          else if ( asc == 0x54 && ascq == 0x00 )
            {
              DBG(5, "scsi_sense_handler: Media bumping\n");
              return SANE_STATUS_JAMMED; /* Don't know if this is right! */
            }
          else if ( asc == 0x55 && ascq == 0x00 )
            {
              DBG(5, "scsi_sense_handler: Scan Job stopped or cancelled\n");
              return SANE_STATUS_CANCELLED;
            }
          else if ( asc == 0x3a && ascq == 0x00 )
            {
              DBG(5, "scsi_sense_handler: Media (ADF or TMA) not available\n");
              return SANE_STATUS_NO_DOCS;
            }
          else if ( asc == 0x3a && ascq == 0x01 )
            {
              DBG(5, "scsi_sense_handler: Door is not closed\n");
              return SANE_STATUS_COVER_OPEN;
            }
          else if ( asc == 0x3a && ascq == 0x02 )
              DBG(5, "scsi_sense_handler: Door is not opened\n");
          else if ( asc == 0x00 && ascq == 0x00 )
              DBG(5, "scsi_sense_handler:  No additional sense information\n");
/* Ok */  else if ( asc == 0x1a && ascq == 0x00 )
              DBG(5, "scsi_sense_handler: Parameter list length error\n");
          else if ( asc == 0x26 && ascq == 0x02 )
              DBG(5, "scsi_sense_handler: Parameter value invalid\n");
          else if ( asc == 0x03 && ascq == 0x00 )
              DBG(5, "scsi_sense_handler: Peripheral device write fault - "
                     "Firmware Download Error\n");
          else if ( asc == 0x2c && ascq == 0x01 )
              DBG(5, "scsi_sense_handler: Too many windows specified\n");
          else if ( asc == 0x80 && ascq == 0x00 )
              DBG(5, "scsi_sense_handler: Target abort scan\n");
          else if ( asc == 0x96 && ascq == 0x08 )
            {
              DBG(5, "scsi_sense_handler: Firewire Device busy\n");
              return SANE_STATUS_DEVICE_BUSY;
            }
          else
              DBG(5, "scsi_sense_handler: Unknown combination of SENSE KEY "
                     "(0x%02x), ASC (0x%02x) and ASCQ (0x%02x)\n",
                      sense_key, asc, ascq);

          return SANE_STATUS_IO_ERROR;

        default:
           DBG(5, "scsi_sense_handler: Unknown sense key (0x%02x)\n",
                   sense_key);
           return SANE_STATUS_IO_ERROR;
    }
}


/*---------- scsi_test_unit_ready() ------------------------------------------*/

static SANE_Status
scsi_test_unit_ready(Microtek2_Device *md)
{
    SANE_Status status;
    u_int8_t tur[TUR_CMD_L];
    int sfd;


    DBG(30, "scsi_test_unit_ready: md=%s\n", md->name);

    TUR_CMD(tur);
    status = sanei_scsi_open(md->name, &sfd, scsi_sense_handler, 0);
    if ( status != SANE_STATUS_GOOD )
      {
	DBG(1, "scsi_test_unit_ready: open '%s'\n", sane_strstatus(status));
	return status;
      }

    if ( md_dump >= 2 )
        dump_area2(tur, sizeof(tur), "testunitready");

    status = sanei_scsi_cmd(sfd, tur, sizeof(tur), NULL, 0);
    if ( status != SANE_STATUS_GOOD )
        DBG(1, "scsi_test_unit_ready: cmd '%s'\n", sane_strstatus(status));
      
    sanei_scsi_close(sfd); 
    return status;
}


/*---------- sane_start() ----------------------------------------------------*/

SANE_Status
sane_start(SANE_Handle handle)
{
    SANE_Status status = SANE_STATUS_GOOD;
    Microtek2_Scanner *ms = handle;
    Microtek2_Device *md;
    Microtek2_Info *mi;
    u_int8_t *pos;
    int color, rc, retry;

    DBG(30, "sane_start: handle=0x%p\n", handle);

    md = ms->dev;
    mi = &md->info[md->scan_source];
    ms->n_control_bytes = md->n_control_bytes;

    if ( md->model_flags & MD_READ_CONTROL_BIT )
      {
        if (ms->control_bytes) free((void *)ms->control_bytes);
        ms->control_bytes = (u_int8_t *) malloc(ms->n_control_bytes);
        DBG(100, "sane_start: ms->control_bytes=%p, malloc'd %lu bytes\n",
                             ms->control_bytes, (u_long) ms->n_control_bytes);
        if ( ms->control_bytes == NULL )
          {
            DBG(1, "sane_start: malloc() for control bits failed\n");
            status = SANE_STATUS_NO_MEM;
            goto cleanup;
          }
      }

    if (ms->sfd < 0) /* first or only pass of this scan */
      {
        /* open device */
        for ( retry = 0; retry < 10; retry++ )
	  {
            status = sanei_scsi_open (md->sane.name, &ms->sfd,
                                      scsi_sense_handler, 0);
            if ( status != SANE_STATUS_DEVICE_BUSY )
	      break;
            DBG(30, "sane_start: Scanner busy, trying again\n");
            sleep(1);
	  }
        if ( status != SANE_STATUS_GOOD )
          {
            DBG(1, "sane_start: scsi_open: '%s'\n", sane_strstatus(status));
            goto cleanup;
          }

        status = scsi_read_system_status(md, ms->sfd);
        if ( status != SANE_STATUS_GOOD )
            goto cleanup;

        if ( ms->val[OPT_CALIB_BACKEND].w == SANE_TRUE )
            DBG(30, "sane_start: backend calibration on\n");
        else
            DBG(30, "sane_start: backend calibration off\n");

        if ( ( ms->val[OPT_CALIB_BACKEND].w == SANE_TRUE )
             && !( md->model_flags & MD_PHANTOM336CX_TYPE_SHADING ) )
          {
	    /* Read shading only once - possible with CIS scanners */
	    /* assuming only CIS scanners use Controlbits */
	    if ( ( md->shading_table_w == NULL )
                 || !( md->model_flags & MD_READ_CONTROL_BIT ) )
              {
		status = get_scan_parameters(ms);
                if ( status != SANE_STATUS_GOOD )
                        goto cleanup;

                status = read_shading_image(ms);
                if ( status != SANE_STATUS_GOOD )
                        goto cleanup;
              }
          }
    
	status = get_scan_parameters(ms);
        if ( status != SANE_STATUS_GOOD )
            goto cleanup;

        status = scsi_read_system_status(md, ms->sfd);
        if ( status != SANE_STATUS_GOOD )
            goto cleanup;

        md->status.aloff |= 128;
        md->status.timeremain = 10;

        if ( ms->scan_source == MS_SOURCE_FLATBED
             || ms->scan_source == MS_SOURCE_ADF )
          {
            md->status.flamp |= MD_FLAMP_ON;
            md->status.tlamp &= ~MD_TLAMP_ON;
          }
        else
          {
            md->status.flamp &= ~MD_FLAMP_ON;
            md->status.tlamp |= MD_TLAMP_ON;
          }

        if ( ms->lightlid35 )
          {
            md->status.flamp &= ~MD_FLAMP_ON;
/*            md->status.tlamp |= MD_TLAMP_ON;*/
/* with this line on some scanners (X6, 0x91) the Flamp goes on  */
          }
          
        if ( ms->no_backtracking )
            md->status.ntrack |= MD_NTRACK_ON;
        else
            md->status.ntrack &= ~MD_NTRACK_ON;

        status = scsi_send_system_status(md, ms->sfd);
        if ( status != SANE_STATUS_GOOD )
            goto cleanup;

        /* calculate gamma: we assume, that the gamma values are transferred */
        /* with one send gamma command, even if it is a 3 pass scanner */
        if ( md->model_flags & MD_NO_GAMMA )
          {
            ms->lut_size = (int) pow(2.0, (double) ms->depth);
            ms->lut_entry_size = ms->depth > 8 ? 2 : 1;
          }
        else
          {
            get_lut_size(mi, &ms->lut_size, &ms->lut_entry_size);
          }
        ms->lut_size_bytes = ms->lut_size * ms->lut_entry_size;
        ms->word = (ms->lut_entry_size == 2);

        ms->gamma_table = (u_int8_t *) malloc(3 * ms->lut_size_bytes );
        DBG(100, "sane_start: ms->gamma_table=%p, malloc'd %d bytes\n",
                  ms->gamma_table, 3 * ms->lut_size_bytes);
        if ( ms->gamma_table == NULL )
          {
            DBG(1, "sane_start: malloc for gammatable failed\n");
            status = SANE_STATUS_NO_MEM;
            goto cleanup;
          }
        for ( color = 0; color < 3; color++ )
          {
            pos = ms->gamma_table + color * ms->lut_size_bytes;
            calculate_gamma(ms, pos, color, ms->gamma_mode);
          }

        /* Some models ignore the settings for the exposure time, */
        /* so we must do it ourselves. Apparently this seems to be */
        /* the case for all models that have the chunky data format */

        if ( mi->data_format == MI_DATAFMT_CHUNKY )
            set_exposure(ms);

        if ( ! (md->model_flags & MD_NO_GAMMA) )
          {
            status = scsi_send_gamma(ms);
            if ( status != SANE_STATUS_GOOD )
                goto cleanup;
          }

        status = scsi_set_window(ms, 1);
        if ( status != SANE_STATUS_GOOD )
            goto cleanup;

        ms->scanning = SANE_TRUE;
        ms->cancelled = SANE_FALSE;
      }

    ++ms->current_pass;

    status = scsi_read_image_info(ms);
    if ( status != SANE_STATUS_GOOD )
        goto cleanup;

    status = prepare_buffers(ms);
    if ( status != SANE_STATUS_GOOD )
        goto cleanup;

    status = calculate_sane_params(ms);
    if ( status != SANE_STATUS_GOOD )
        goto cleanup;

    if ( !( md->model_flags & MD_NO_RIS_COMMAND ) )
      {
        /* !!FIXME!! - hack for C6USB because RIS over USB doesn't wait until */
        /* scanner ready */
        if (mi->model_code == 0x9a)
            sleep(2);

        status = scsi_wait_for_image(ms);
        if ( status  != SANE_STATUS_GOOD )
            goto cleanup;
      }

    if ( ms->calib_backend
         && ( md->model_flags & MD_PHANTOM336CX_TYPE_SHADING )
         && ( ( md->shading_table_w == NULL )
              || ( ms->mode != md->shading_table_contents )
            )
       )
        status = read_cx_shading(ms);
        
    if ( ms->lightlid35 )  
    /* hopefully this leads to a switched off flatbed lamp with lightlid */
      {
        status = scsi_read_system_status(md, ms->sfd);
        if ( status != SANE_STATUS_GOOD )
            goto cleanup;

        md->status.flamp &= ~MD_FLAMP_ON;
        md->status.tlamp &= ~MD_TLAMP_ON;
      
        status = scsi_send_system_status(md, ms->sfd);
        if ( status != SANE_STATUS_GOOD )
            goto cleanup;
      }

    if ( md->model_flags & MD_READ_CONTROL_BIT )
      {
        status = scsi_read_control_bits(ms);
        if ( status != SANE_STATUS_GOOD )
            goto cleanup;

        if ( ms->calib_backend )
          {
            status = condense_shading(ms);
            if ( status != SANE_STATUS_GOOD )
               goto cleanup;
          }
      }

    /* open a pipe and fork a child process, that actually reads the data */
    rc = pipe(ms->fd);
    if ( rc == -1 )
      {
        DBG(1, "sane_start: pipe failed\n");
        status = SANE_STATUS_IO_ERROR;
        goto cleanup;
      }

    /* create reader routine as new thread or process */
    ms->pid = sanei_thread_begin( reader_process,(void*) ms);

    if ( ms->pid == -1 )
      {
        DBG(1, "sane_start: fork failed\n");
        status = SANE_STATUS_IO_ERROR;
        goto cleanup;
      }

    if (sanei_thread_is_forked()) close(ms->fd[1]);

    return SANE_STATUS_GOOD;

cleanup:
    cleanup_scanner(ms);
    return status;
}

/*---------- prepare_buffers -------------------------------------------------*/

static SANE_Status
prepare_buffers(Microtek2_Scanner *ms)
{
    SANE_Status status;
    Microtek2_Device *md;
    Microtek2_Info *mi;
    u_int32_t strip_lines;
    int i;

    status = SANE_STATUS_GOOD;
    DBG(30, "prepare_buffers: ms=0x%p\n", (void *) ms);
   
    md = ms->dev;
    mi = &md->info[md->scan_source];

    /* calculate maximum number of lines to read */
    strip_lines = (int) ((double) ms->y_resolution_dpi * md->opts.strip_height);
    if ( strip_lines == 0 )
        strip_lines = 1;

    /* calculate number of lines that fit into the source buffer */
#ifdef TESTBACKEND
    ms->src_max_lines = MIN( 5000000 / ms->bpl, strip_lines);
#else
    ms->src_max_lines = MIN( sanei_scsi_max_request_size / ms->bpl, strip_lines);
#endif
    if ( ms->src_max_lines == 0 )
      {
        DBG(1, "sane_start: Scan buffer too small\n");
        status = SANE_STATUS_IO_ERROR;
        goto cleanup;
      }

    /* allocate buffers */
    ms->src_buffer_size = ms->src_max_lines * ms->bpl;       

    if ( ms->mode == MS_MODE_COLOR && mi->data_format == MI_DATAFMT_LPLSEGREG ) 
      {             
        /* In this case the data is not neccessarily in the order RGB */
        /* and there may be different numbers of read red, green and blue */
        /* segments. We allocate a second buffer to read new lines in */
        /* and hold undelivered pixels in the other buffer */
        int extra_buf_size;

        extra_buf_size = 2 * ms->bpl * mi->ccd_gap 
                         * (int) ceil( (double) mi->max_yresolution
                                      / (double) mi->opt_resolution); 
        for ( i = 0; i < 2; i++ )
          {
            if ( ms->buf.src_buffer[i] )
                free((void *) ms->buf.src_buffer[i]);
            ms->buf.src_buffer[i] = (u_int8_t *) malloc(ms->src_buffer_size
                                    + extra_buf_size);
            DBG(100, "prepare_buffers: ms->buf.src_buffer[%d]=%p,"
                     "malloc'd %d bytes\n", i, ms->buf.src_buffer[i],
                     ms->src_buffer_size + extra_buf_size);
            if ( ms->buf.src_buffer[i] == NULL )
              {
                DBG(1, "sane_start: malloc for scan buffer failed\n");
                status = SANE_STATUS_NO_MEM;
                goto cleanup;
              }
          }
        ms->buf.free_lines = ms->src_max_lines + extra_buf_size / ms->bpl;
        ms->buf.free_max_lines = ms->buf.free_lines;
        ms->buf.src_buf = ms->buf.src_buffer[0];
        ms->buf.current_src = 0;         /* index to current buffer */
      }
    else
      {
        if ( ms->buf.src_buf )
            free((void *) ms->buf.src_buf);
        ms->buf.src_buf = malloc(ms->src_buffer_size);
        DBG(100, "sane_start: ms->buf.src_buf=%p, malloc'd %d bytes\n",
                            ms->buf.src_buf, ms->src_buffer_size);
        if ( ms->buf.src_buf == NULL )
          {
            DBG(1, "sane_start: malloc for scan buffer failed\n");
            status = SANE_STATUS_NO_MEM;
            goto cleanup;
          }
      }

    for ( i = 0; i < 3; i++ )
      {
        ms->buf.current_pos[i] = ms->buf.src_buffer[0];
        ms->buf.planes[0][i] = 0;
        ms->buf.planes[1][i] = 0;
      }

    /* allocate a temporary buffer for the data, if auto_adjust threshold */
    /* is selected. */

    if ( ms->auto_adjust == 1 )
      {
        ms->temporary_buffer = (u_int8_t *) malloc(ms->remaining_bytes);
        DBG(100, "sane_start: ms->temporary_buffer=%p, malloc'd %d bytes\n",
                  ms->temporary_buffer, ms->remaining_bytes);
        if ( ms->temporary_buffer == NULL )
          {
            DBG(1, "sane_start: malloc() for temporary buffer failed\n");
            status = SANE_STATUS_NO_MEM;
            goto cleanup;
          } 
      }
    else 
        ms->temporary_buffer = NULL;

    /* some data formats have additional information in a scan line, which */
    /* is not transferred to the frontend; real_bpl is the number of bytes */
    /* per line, that is copied into the frontend's buffer */
    ms->real_bpl = (u_int32_t) ceil( ((double) ms->ppl * 
                                      (double) ms->bits_per_pixel_out) / 8.0 );
    if ( mi->onepass && ms->mode == MS_MODE_COLOR )
        ms->real_bpl *= 3;

    ms->real_remaining_bytes = ms->real_bpl * ms->src_remaining_lines;

    return SANE_STATUS_GOOD;

cleanup:
    cleanup_scanner(ms);
    return status; 

}
static void
write_shading_buf_pnm(Microtek2_Scanner *ms, u_int32_t lines)
{
  FILE *outfile;
  u_int16_t pixel, color, linenr, factor;
  unsigned char  img_val_out;
  float img_val = 0;
  Microtek2_Device *md;
  Microtek2_Info *mi;

  md = ms->dev;
  mi = &md->info[md->scan_source];

  if ( mi->depth & MI_HASDEPTH_16 )
      factor = 256;
  else if ( mi->depth & MI_HASDEPTH_14 )
      factor = 64;
  else if ( mi->depth & MI_HASDEPTH_12 )
      factor = 16;
  else if ( mi->depth & MI_HASDEPTH_10 )
      factor = 4;
  else
      factor = 1;
  if ( md->model_flags & MD_16BIT_TRANSFER )
      factor = 256;

  outfile = fopen("shading_buf_w.pnm", "w");
  fprintf(outfile, "P6\n#imagedata\n%d %d\n255\n",
          mi->geo_width / mi->calib_divisor, lines);
  for ( linenr=0; linenr < lines; linenr++ )
    {
      if (mi->data_format == MI_DATAFMT_LPLSEGREG)
        {
	  DBG(1, "Output of shading buffer unsupported for"
                           "Segreg Data format\n");
          break;
        }

      for ( pixel=0;
            pixel < (u_int16_t) (mi->geo_width / mi->calib_divisor);
            pixel++)
        {
          for ( color=0; color < 3; color++ )
            {
              switch( mi->data_format )
                {
                  case MI_DATAFMT_LPLCONCAT:
                    if ( md->shading_depth > 8)
                      img_val = *((u_int16_t *) ms->shading_image
                                 + linenr * ( ms->bpl / ms->lut_entry_size )
                                 + mi->color_sequence[color]
                                      * ( ms->bpl / ms->lut_entry_size / 3 )
                                 + pixel);
                    else
                      img_val = *((u_int8_t *) ms->shading_image
                                 + linenr * ( ms->bpl / ms->lut_entry_size )
                                 + mi->color_sequence[color]
                                      * ( ms->bpl / ms->lut_entry_size / 3 )
                                 + pixel);
                      
                    break;
                  case MI_DATAFMT_CHUNKY:
                  case MI_DATAFMT_9800:
                    img_val = *((u_int16_t *)ms->shading_image
                               + linenr * 3 * ( mi->geo_width
                                                / mi->calib_divisor )
                               + 3 * pixel
                               + mi->color_sequence[color]);
                    break;
                }
              img_val /= factor;
              img_val_out = (unsigned char)img_val;
              fputc(img_val_out, outfile);
            }
        }
    }
  fclose(outfile);

  return;
}

static void
write_shading_pnm(Microtek2_Scanner *ms)
{
  FILE *outfile_w = NULL, *outfile_d = NULL;
  int pixel, color, line, offset, num_shading_pixels, output_height;
  u_int16_t img_val, factor;

  Microtek2_Device *md;
  Microtek2_Info *mi;

  output_height = 180;
  md = ms->dev;
  mi = &md->info[md->scan_source];

  DBG(30, "write_shading_pnm: ms=%p\n", (void *) ms);

  if ( mi->depth & MI_HASDEPTH_16 )
      factor = 256;
  else if ( mi->depth & MI_HASDEPTH_14 )
      factor = 64;
  else if ( mi->depth & MI_HASDEPTH_12 )
      factor = 16;
  else if ( mi->depth & MI_HASDEPTH_10 )
      factor = 4;
  else
      factor = 1;
  if ( md->model_flags & MD_16BIT_TRANSFER )
      factor = 256;

  if ( md->model_flags & MD_PHANTOM336CX_TYPE_SHADING )
    num_shading_pixels = ms->n_control_bytes * 8;
  else
    num_shading_pixels = mi->geo_width / mi->calib_divisor;
  if ( md->shading_table_w != NULL )
    {
      outfile_w = fopen("microtek2_shading_w.pnm", "w");
      fprintf(outfile_w, "P6\n#imagedata\n%d %d\n255\n",
                      num_shading_pixels, output_height);
    }
  if ( md->shading_table_d != NULL )
    {
      outfile_d = fopen("microtek2_shading_d.pnm", "w");
      fprintf(outfile_d, "P6\n#imagedata\n%d %d\n255\n",
                      num_shading_pixels, output_height);
    }
  for ( line=0; line < output_height; ++line )
    {
      for ( pixel=0; pixel < num_shading_pixels ; ++pixel)
        {
          for ( color=0; color < 3; ++color )
            {
              offset = mi->color_sequence[color]
                        * num_shading_pixels
                        + pixel;
              if ( md->shading_table_w != NULL )
                {
                  if ( ms->lut_entry_size == 2 )
                    {
                      img_val = *((u_int16_t *) md->shading_table_w + offset );
                      img_val /= factor;
                    }
                  else
                      img_val = *((u_int8_t *) md->shading_table_w + offset );
                  fputc((unsigned char)img_val, outfile_w);
                }

              if ( md->shading_table_d != NULL )
                {
                  if ( ms->lut_entry_size == 2 )
                    {
                      img_val = *((u_int16_t *) md->shading_table_d + offset );
                      img_val /= factor;
                    }
                  else
                      img_val = *((u_int8_t *) md->shading_table_d + offset );
                  fputc((unsigned char)img_val, outfile_d);
                }
            }
        }
    }
  if ( md->shading_table_w != NULL )
    fclose(outfile_w);
  if ( md->shading_table_d != NULL )
    fclose(outfile_d);

  return;
}

static void
write_cshading_pnm(Microtek2_Scanner *ms)
{
  FILE *outfile;
  Microtek2_Device *md;
  Microtek2_Info *mi;
  int pixel, color, line, offset, img_val, img_height=30, factor;

  md = ms->dev;
  mi = &md->info[md->scan_source];

  if ( mi->depth & MI_HASDEPTH_16 )
      factor = 256;
  else if ( mi->depth & MI_HASDEPTH_14 )
      factor = 64;
  else if ( mi->depth & MI_HASDEPTH_12 )
      factor = 16;
  else if ( mi->depth & MI_HASDEPTH_10 )
      factor = 4;
  else
      factor = 1;
  if ( md->model_flags & MD_16BIT_TRANSFER )
      factor = 256;

  outfile = fopen("microtek2_cshading_w.pnm", "w");
  if ( ms->mode == MS_MODE_COLOR )
    fprintf(outfile, "P6\n#imagedata\n%d %d\n255\n", ms->ppl, img_height);
  else
    fprintf(outfile, "P5\n#imagedata\n%d %d\n255\n", ms->ppl, img_height);

  for ( line=0; line < img_height; ++line )
    {
      for ( pixel=0; pixel < (int)ms->ppl; ++pixel)
        {
          for ( color=0; color < 3; ++color )
            {
              offset = color * (int)ms->ppl + pixel;
              if ( ms->lut_entry_size == 1 )
                img_val = (int) *((u_int8_t *)ms->condensed_shading_w + offset);
              else
                {
                  img_val = (int) *((u_int16_t *)ms->condensed_shading_w
                                                 + offset);
                  img_val /= factor;
                }
              fputc((unsigned char)img_val, outfile);
              if ( ms->mode == MS_MODE_GRAY )
                break;
            }
        }
    }
  fclose(outfile);

  return;
}



/*---------- condense_shading() ----------------------------------------------*/

static SANE_Status
condense_shading(Microtek2_Scanner *ms)
{
    /* This function extracts the relevant shading pixels from */
    /* the shading image according to the 1's in the result of */
    /* 'read control bits', and stores them in a memory block. */
    /* We will then have as many shading pixels as there are */
    /* pixels per line. The order of the pixels in the condensed */
    /* shading data block will always be left to right. The color */
    /* sequence remains unchanged. */

    Microtek2_Device *md;
    Microtek2_Info *mi;
    u_int32_t byte;
    u_int32_t cond_length;       /* bytes per condensed shading line */
    int color, count, lfd_bit;
    int shad_bplc, shad_pixels;  /* bytes per line & color in shading image */
    int bit, flag;
    u_int32_t sh_offset, csh_offset;
    int gray_filter_color = 1; /* which color of the shading is taken for gray*/

    md = ms->dev;
    mi = &md->info[md->scan_source];

    DBG(30, "condense_shading: ms=%p, ppl=%d\n", (void *) ms, ms->ppl);
    if ( md->shading_table_w == NULL )
      {
        DBG(1, "condense shading: no shading table found, skip shading\n");
        return SANE_STATUS_GOOD;
      }

    get_lut_size( mi, &ms->lut_size, &ms->lut_entry_size );

    if ( md->model_flags & MD_PHANTOM336CX_TYPE_SHADING )
      {
        shad_pixels = ms->n_control_bytes * 8;
        gray_filter_color = 0;  /* 336CX reads only one shading in gray mode*/
      }
    else
        shad_pixels = mi->geo_width;

    shad_bplc = shad_pixels * ms->lut_entry_size;

    if ( md_dump >= 3 )
      {
        dump_area2(md->shading_table_w, shad_bplc * 3, "shading_table_w");
        if ( md->model_flags & MD_PHANTOM336CX_TYPE_SHADING )
          write_shading_pnm(ms);
      }

    cond_length = ms->bpl * ms->lut_entry_size;

    if ( ms->condensed_shading_w )
      {
        free((void*) ms->condensed_shading_w );
        ms->condensed_shading_w = NULL;
      }
    ms->condensed_shading_w = (u_int8_t *)malloc(cond_length);
    DBG(100, "condense_shading: ms->condensed_shading_w=%p,"
             "malloc'd %d bytes\n", ms->condensed_shading_w, cond_length);
    if ( ms->condensed_shading_w == NULL )
      {
        DBG(1, "condense_shading: malloc for white table failed\n");
        return SANE_STATUS_NO_MEM;
      }

    if ( md->shading_table_d != NULL )
      {
        if ( md_dump >= 3 )
           dump_area2(md->shading_table_d, shad_bplc * 3,
                      "shading_table_d");

        if ( ms->condensed_shading_d )
          {
            free((void*) ms->condensed_shading_d );
            ms->condensed_shading_d = NULL;
          }
        ms->condensed_shading_d = (u_int8_t *)malloc(cond_length);
        DBG(100, "condense_shading: ms->condensed_shading_d=%p,"
                 " malloc'd %d bytes\n", ms->condensed_shading_d, cond_length);
        if ( ms->condensed_shading_d == NULL )
          {
            DBG(1, "condense_shading: malloc for dark table failed\n");
            return SANE_STATUS_NO_MEM;
          }
      }

    DBG(128, "controlbit offset=%d\n", md->controlbit_offset);

    count = 0;

    for (lfd_bit = 0; ( lfd_bit < mi->geo_width ) && ( count < (int)ms->ppl );
         ++lfd_bit)
      {
        byte = ( lfd_bit + md->controlbit_offset ) / 8;
        bit = ( lfd_bit + md->controlbit_offset ) % 8;

        if ( mi->direction & MI_DATSEQ_RTOL )
            flag = ((ms->control_bytes[byte] >> bit) & 0x01);
        else
            flag = ((ms->control_bytes[byte] >> (7 - bit)) & 0x01);

        if ( flag == 1 ) /* flag==1 if byte's bit is set */
          {
            for ( color = 0; color < 3; ++color )
              {
                if ( ( ms->mode == MS_MODE_COLOR )
                     || ( ( ms->mode == MS_MODE_GRAY )
                           && ( color == gray_filter_color ) )
                     || ( ( ms->mode == MS_MODE_LINEARTFAKE )
                           && ( color == gray_filter_color ) )
                    )
                  {
                    sh_offset = color * shad_pixels + lfd_bit;
                    if ( md->model_flags & MD_PHANTOM336CX_TYPE_SHADING )
                      sh_offset += md->controlbit_offset;
                    if ( ms->mode == MS_MODE_COLOR )
                      csh_offset = color * ms->ppl + count;
                    else
                      csh_offset = count;

                    if ( csh_offset > cond_length )
                      {
                        DBG(1, "condense_shading: wrong control bits data, " );
                        DBG(1, "csh_offset (%d) > cond_length(%d)\n",
                             csh_offset, cond_length );
                        csh_offset = cond_length;
                      }

                    if ( ms->lut_entry_size == 2 )
                      {
                        *((u_int16_t *)ms->condensed_shading_w + csh_offset) =
                                         *((u_int16_t *)md->shading_table_w
                                          + sh_offset);
                        if ( ms->condensed_shading_d != NULL )
                          *((u_int16_t *)ms->condensed_shading_d + csh_offset) =
                                         *((u_int16_t *)md->shading_table_d
                                          + sh_offset);
                      }
                    else
                      {
                        *((u_int8_t *)ms->condensed_shading_w + csh_offset) =
                                         *((u_int8_t *)md->shading_table_w
                                          + sh_offset);
                        if ( ms->condensed_shading_d != NULL )
                          *((u_int8_t *)ms->condensed_shading_d + csh_offset) =
                                         *((u_int8_t *)md->shading_table_d
                                          + sh_offset);
                      }
                  }
              }
            ++count;
          }
      }

    if ( md_dump >= 3 )
      {
        dump_area2(ms->condensed_shading_w, cond_length, "condensed_shading_w");
        if ( ms->condensed_shading_d != NULL )
          dump_area2(ms->condensed_shading_d, cond_length,
                     "condensed_shading_d");

        write_cshading_pnm(ms);
      }

    return SANE_STATUS_GOOD;
}


/*---------- read_shading_image() --------------------------------------------*/

static SANE_Status
read_shading_image(Microtek2_Scanner *ms)
{
    SANE_Status status;
    Microtek2_Device *md;
    Microtek2_Info *mi;
    u_int32_t lines;
    u_int8_t *buf;
    int max_lines;
    int lines_to_read;

    DBG(30, "read_shading_image: ms=%p\n", (void *) ms);

    md = ms->dev;
    mi = &md->info[md->scan_source];


    if ( ! MI_WHITE_SHADING_ONLY(mi->shtrnsferequ)
        || ( md->model_flags & MD_PHANTOM_C6 ) )

      /* Dark shading correction */
      /* ~~~~~~~~~~~~~~~~~~~~~~~ */
      {
        DBG(30, "read_shading_image: reading black data\n");
        md->status.ntrack |= MD_NTRACK_ON;
        md->status.ncalib &= ~MD_NCALIB_ON;
        md->status.flamp |= MD_FLAMP_ON;
        if ( md->model_flags & MD_PHANTOM_C6 )
          {
            md->status.stick |= MD_STICK_ON;
            md->status.reserved17 |= MD_RESERVED17_ON;
          }

        get_calib_params(ms);
        if ( md->model_flags & MD_PHANTOM_C6 )
             ms->stay = 1;

        status = scsi_send_system_status(md, ms->sfd);
        if ( status != SANE_STATUS_GOOD )
            return status;

        status = scsi_set_window(ms, 1);
        if ( status != SANE_STATUS_GOOD )
            return status;

#ifdef TESTBACKEND
        status = scsi_read_sh_image_info(ms);
#else
        status = scsi_read_image_info(ms);
#endif
        if ( status != SANE_STATUS_GOOD )
            return status;

        status = scsi_wait_for_image(ms);
        if ( status != SANE_STATUS_GOOD )
            return status;

        status = scsi_read_system_status(md, ms->sfd);
        if ( status != SANE_STATUS_GOOD )
            return status;

        md->status.flamp &= ~MD_FLAMP_ON;

        status = scsi_send_system_status(md, ms->sfd);
        if ( status != SANE_STATUS_GOOD )
            return status;

        ms->shading_image = malloc(ms->bpl * ms->src_remaining_lines);
        DBG(100, "read shading image: ms->shading_image=%p,"
                 " malloc'd %d bytes\n",
               ms->shading_image, ms->bpl * ms->src_remaining_lines);
        if ( ms->shading_image == NULL )
          {
            DBG(1, "read_shading_image: malloc for buffer failed\n");
            return SANE_STATUS_NO_MEM;
          }

        buf = ms->shading_image;

#ifdef TESTBACKEND
        max_lines = 5000000 / ms->bpl;
#else
        max_lines = sanei_scsi_max_request_size / ms->bpl;
#endif
        if ( max_lines == 0 )
          {
            DBG(1, "read_shading_image: buffer too small\n");
            return SANE_STATUS_IO_ERROR;
          }
        lines = ms->src_remaining_lines;
        while ( ms->src_remaining_lines > 0 )
          {
            lines_to_read = MIN(max_lines, ms->src_remaining_lines);
            ms->src_buffer_size = lines_to_read * ms->bpl;
            ms->transfer_length = ms->src_buffer_size;
#ifdef TESTBACKEND
            status = scsi_read_sh_d_image(ms, buf);
#else
            status = scsi_read_image(ms, buf, md->shading_depth>8 ? 2 : 1);
#endif
            if ( status != SANE_STATUS_GOOD )
              {
                DBG(1, "read_shading_image: read image failed: '%s'\n",
                        sane_strstatus(status));
                return status;
              }

            ms->src_remaining_lines -= lines_to_read;
            buf += ms->src_buffer_size;
          }

        status =  prepare_shading_data(ms, lines, &md->shading_table_d);
        if ( status != SANE_STATUS_GOOD )
            return status;

        /* send shading data to the device */
        /* Some models use "read_control bit", and the shading must be */
        /* applied by the backend later */
        if ( ! (md->model_flags & MD_READ_CONTROL_BIT) )
          {
            status =  shading_function(ms, md->shading_table_d);
            if ( status != SANE_STATUS_GOOD )
                return status;

            ms->word = ms->lut_entry_size == 2 ? 1 : 0;
            ms->current_color = MS_COLOR_ALL;
            status = scsi_send_shading(ms,
                                       md->shading_table_d,
                                       3 * ms->lut_entry_size
                                         * mi->geo_width / mi->calib_divisor,
                                       1);
            if ( status != SANE_STATUS_GOOD )
                return status;
          }

        DBG(100, "free memory for ms->shading_image at %p\n",
               ms->shading_image);
        free((void *) ms->shading_image);
        ms->shading_image = NULL;
      }

    /* white shading correction */
    /* ~~~~~~~~~~~~~~~~~~~~~~~~ */
    DBG(30, "read_shading_image: reading white data\n");

    /* According to the doc NCalib must be set for white shading data */
    /* if we have a black and a white shading correction and must be */
    /* cleared if we have only a white shading collection */
    if ( ! MI_WHITE_SHADING_ONLY(mi->shtrnsferequ)
        || ( md->model_flags & MD_PHANTOM_C6 ) )
      md->status.ncalib |= MD_NCALIB_ON;
    else
      md->status.ncalib &= ~MD_NCALIB_ON;

    md->status.flamp |= MD_FLAMP_ON;
/*    md->status.tlamp &= ~MD_TLAMP_ON;  */
    md->status.ntrack |= MD_NTRACK_ON;

    if ( md->model_flags & MD_PHANTOM_C6 )
      {
        md->status.stick &= ~MD_STICK_ON;
        md->status.reserved17 |= MD_RESERVED17_ON;
      }

    get_calib_params(ms);

#ifdef NO_PHANTOMTYPE_SHADING
/*    md->status.stick &= ~MD_STICK_ON; */
/*    md->status.ncalib &= ~MD_NCALIB_ON; */
/*    md->status.reserved17 &= ~MD_RESERVED17_ON; */
    ms->rawdat = 0;
#endif

    status = scsi_send_system_status(md, ms->sfd);
    if ( status != SANE_STATUS_GOOD )
        return status;

    status = scsi_set_window(ms, 1);
    if ( status != SANE_STATUS_GOOD )
        return status;

#ifdef TESTBACKEND
    status = scsi_read_sh_image_info(ms);
#else
    status = scsi_read_image_info(ms);
#endif
    if ( status != SANE_STATUS_GOOD )
        return status;

    status = scsi_wait_for_image(ms);
    if ( status != SANE_STATUS_GOOD )
        return status;

#ifdef NO_PHANTOMTYPE_SHADING
    if ( !( md->model_flags & MD_READ_CONTROL_BIT ) )
      {
#endif
	status = scsi_read_system_status(md, ms->sfd);
        if ( status != SANE_STATUS_GOOD )
            return status;
#ifdef NO_PHANTOMTYPE_SHADING
      }
#endif

#ifdef NO_PHANTOMTYPE_SHADING
    if ( mi->model_code == 0x94 )
        status = scsi_read_control_bits(ms);
#endif

    ms->shading_image = malloc(ms->bpl * ms->src_remaining_lines);
    DBG(100, "read shading image: ms->shading_image=%p, malloc'd %d bytes\n",
              ms->shading_image, ms->bpl * ms->src_remaining_lines);
    if ( ms->shading_image == NULL )
      {
        DBG(1, "read_shading_image: malloc for buffer failed\n");
        return SANE_STATUS_NO_MEM;
      }

    buf = ms->shading_image;
#ifdef TESTBACKEND
    max_lines = 5000000 / ms->bpl;
#else
    max_lines = sanei_scsi_max_request_size / ms->bpl;
#endif
    if ( max_lines == 0 )
      {
        DBG(1, "read_shading_image: buffer too small\n");
        return SANE_STATUS_IO_ERROR;
      }
    lines = ms->src_remaining_lines;
    while ( ms->src_remaining_lines > 0 )
      {
        lines_to_read = MIN(max_lines, ms->src_remaining_lines);
        ms->src_buffer_size = lines_to_read * ms->bpl;
        ms->transfer_length = ms->src_buffer_size;

#ifdef TESTBACKEND
        status = scsi_read_sh_w_image(ms, buf);
#else
	status = scsi_read_image(ms, buf, md->shading_depth>8 ? 2 : 1);
#endif
        if ( status != SANE_STATUS_GOOD )
            return status;

        ms->src_remaining_lines -= lines_to_read;
        buf += ms->src_buffer_size;
      }

    status =  prepare_shading_data(ms, lines, &md->shading_table_w);
    if ( status != SANE_STATUS_GOOD )
        return status;

    if ( md_dump >= 3 )
      {
        write_shading_buf_pnm(ms, lines);
        write_shading_pnm(ms);
      }

    /* send shading data to the device */
    /* Some models use "read_control bit", and the shading must be */
    /* applied by the backend later */
    if ( ! (md->model_flags & MD_READ_CONTROL_BIT) )
      {
        status =  shading_function(ms, md->shading_table_w);
        if ( status != SANE_STATUS_GOOD )
            return status;

        ms->word = ms->lut_entry_size == 2 ? 1 : 0;
        ms->current_color = MS_COLOR_ALL;
        status = scsi_send_shading(ms,
                                   md->shading_table_w,
                                   3 * ms->lut_entry_size
                                   * mi->geo_width / mi->calib_divisor,
                                   0);
        if ( status != SANE_STATUS_GOOD )
            return status;
      }

    ms->rawdat = 0;
    ms->stay = 0;
    md->status.ncalib |= MD_NCALIB_ON;

    if ( md->model_flags & MD_PHANTOM_C6 )
      {
        md->status.stick &= ~MD_STICK_ON;
        md->status.reserved17 &= ~MD_RESERVED17_ON;
      }

#ifdef NO_PHANTOMTYPE_SHADING
    if (mi->model_code == 0x94)
        md->status.ncalib &= ~MD_NCALIB_ON;
#endif

    status = scsi_send_system_status(md, ms->sfd);
    if ( status != SANE_STATUS_GOOD )
        return status;

    DBG(100, "free memory for ms->shading_image at %p\n",
    ms->shading_image);
    free((void *) ms->shading_image);
    ms->shading_image = NULL;

    return SANE_STATUS_GOOD;

}

/*---------- prepare_shading_data() ------------------------------------------*/

static SANE_Status
prepare_shading_data(Microtek2_Scanner *ms, u_int32_t lines, u_int8_t **data)
{
  /* This function calculates one line of black or white shading data */
  /* from the shading image. At the end we have one line. The */
  /* color sequence is unchanged. */

#define MICROTEK2_CALIB_USE_MEDIAN

  Microtek2_Device *md;
  Microtek2_Info *mi;
  u_int32_t length,line;
  int color, i;
  SANE_Status status;

#ifdef  MICROTEK2_CALIB_USE_MEDIAN
  u_int16_t *sortbuf, value;
#else
  u_int32_t value;
#endif

  DBG(30, "prepare_shading_data: ms=%p, lines=%d, *data=%p\n",
          (void *) ms, lines, *data);

  md = ms->dev;
  mi = &md->info[md->scan_source];
  status = SANE_STATUS_GOOD;

  get_lut_size(mi, &ms->lut_size, &ms->lut_entry_size);
  length = 3 * ms->lut_entry_size * mi->geo_width / mi->calib_divisor;

  if ( *data == NULL )
    {
      *data = (u_int8_t *) malloc(length);
      DBG(100, "prepare_shading_data: malloc'd %d bytes at %p\n",
                length, *data);
      if ( *data == NULL )
        {
          DBG(1, "prepare_shading_data: malloc for shading table failed\n");
          return SANE_STATUS_NO_MEM;
        }
    }

#ifdef  MICROTEK2_CALIB_USE_MEDIAN
  sortbuf = malloc( lines * ms->lut_entry_size );
  DBG(100, "prepare_shading_data: sortbuf= %p, malloc'd %d Bytes\n",
            (void *) sortbuf, lines * ms->lut_entry_size);
  if ( sortbuf == NULL )
    {
      DBG(1, "prepare_shading_data: malloc for sort buffer failed\n");
      return SANE_STATUS_NO_MEM;
    }
#endif

  switch( mi->data_format )
    {
      case MI_DATAFMT_LPLCONCAT:
        if ( ms->lut_entry_size == 1 )
          {
            DBG(1, "prepare_shading_data: wordsize == 1 unsupported\n");
            return SANE_STATUS_UNSUPPORTED;
          }
        for ( color = 0; color < 3; color++ )
          {
            for ( i = 0; i < ( mi->geo_width / mi->calib_divisor ); i++ )
              {
                value = 0;
                for ( line = 0; line < lines; line++ )
#ifndef  MICROTEK2_CALIB_USE_MEDIAN
/*  average the shading lines to get the shading data */
                      value += *((u_int16_t *) ms->shading_image
                             + line * ( ms->bpl / ms->lut_entry_size )
                             + color * ( ms->bpl / ms->lut_entry_size / 3 )
                             + i);
                value /= lines;
                *((u_int16_t *) *data
                   + color * ( mi->geo_width / mi->calib_divisor ) + i) =
                                           (u_int16_t) MIN(0xffff, value);
#else
/*  use a median filter to get the shading data -- should be better */
                    *(sortbuf + line ) =
                          *((u_int16_t *) ms->shading_image
                             + line * ( ms->bpl / ms->lut_entry_size )
                             + color * ( ms->bpl / ms->lut_entry_size / 3 )
                             + i);
                qsort(sortbuf, lines, sizeof(u_int16_t),
                       (qsortfunc)compare_func_16);
                value = *(sortbuf + ( lines - 1 ) / 2 );
                *((u_int16_t *) *data
                   + color * ( mi->geo_width / mi->calib_divisor ) + i) = value;
#endif
              }
          }
        break;

      case MI_DATAFMT_CHUNKY:
      case MI_DATAFMT_9800:
        if ( ms->lut_entry_size == 1 )
          {
            DBG(1, "prepare_shading_data: wordsize == 1 unsupported\n");
            return SANE_STATUS_UNSUPPORTED;
          }
        for ( color = 0; color < 3; color++ )
          {
            for ( i = 0; i < ( mi->geo_width / mi->calib_divisor ); i++ )
              {
                value = 0;
                for ( line = 0; line < lines; line++ )
#ifndef  MICROTEK2_CALIB_USE_MEDIAN
/*  average the shading lines to get the shading data */
                    value += *((u_int16_t *) ms->shading_image
                             + line * 3 * mi->geo_width / mi->calib_divisor
                             + 3 * i
                             + color);

                value /= lines;
                *((u_int16_t *) *data
                 + color * ( mi->geo_width / mi->calib_divisor ) + i) =
                                               (u_int16_t) MIN(0xffff, value);
#else
/*  use a median filter to get the shading data -- should be better */
                    *(sortbuf + line ) =
                          *((u_int16_t *) ms->shading_image
                             + line * 3 * mi->geo_width / mi->calib_divisor
                             + 3 * i
                             + color);
                qsort(sortbuf, lines, sizeof(u_int16_t),
                       (qsortfunc)compare_func_16);
                value = *(sortbuf + ( lines - 1 ) / 2 );
                *((u_int16_t *) *data
                 + color * ( mi->geo_width / mi->calib_divisor ) + i) = value;
#endif
              }
          }
        break;

      case MI_DATAFMT_LPLSEGREG:
        for ( color = 0; color < 3; color++ )
          {
            for ( i = 0; i < ( mi->geo_width / mi->calib_divisor ); i++ )
              {
                value = 0;
                if ( ms->lut_entry_size == 1 )
		  {
                    for ( line = 0; line < lines; line++ )
			value += *((u_int8_t *) ms->shading_image
				+ line * 3 * mi->geo_width / mi->calib_divisor
				+ 3 * i
				+ color);

		    value /= lines;
                    *((u_int8_t *) *data
			+ color * ( mi->geo_width / mi->calib_divisor ) + i) =
			(u_int8_t) MIN(0xff, value);

		  }
		else
		  {
		    for ( line = 0; line < lines; line++ )
			value += *((u_int16_t *) ms->shading_image
				+ line * 3 * mi->geo_width / mi->calib_divisor
				+ 3 * i
				+ color);

		    value /= lines;
#ifndef  MICROTEK2_CALIB_USE_MEDIAN
		    *((u_int16_t *) *data
			+ color * ( mi->geo_width / mi->calib_divisor ) + i) =
			(u_int16_t) MIN(0xffff, value);
#else
		    *((u_int16_t *) *data
			+ color * ( mi->geo_width / mi->calib_divisor ) + i) = value;
#endif
		  }

              }
          }
        break;

      default:
        DBG(1, "prepare_shading_data: Unsupported data format 0x%02x\n",
                mi->data_format);
        status = SANE_STATUS_UNSUPPORTED;
    }

#ifdef  MICROTEK2_CALIB_USE_MEDIAN
  DBG(100, "prepare_shading_data: free sortbuf at %p\n", (void *) sortbuf);
  free(sortbuf);
  sortbuf = NULL;
#endif
    return status;
}


/*---------- read_cx_shading() -----------------------------------------------*/

static SANE_Status
read_cx_shading(Microtek2_Scanner *ms)
{
    SANE_Status status;
    Microtek2_Device *md;
    md = ms->dev;

    DBG(30, "read_cx_shading: ms=%p\n",(void *) ms);

    md->shading_table_contents = ms->mode;

    if ( ms->mode == MS_MODE_COLOR )
        ms->current_color = MS_COLOR_ALL;
    else
        ms->current_color = MS_COLOR_GREEN;  /* for grayscale */

    ms->word = 1;
    ms->dark = 0;

    status = read_cx_shading_image(ms);
    if ( status  != SANE_STATUS_GOOD )
        goto cleanup;

    ms->word = 0;  /* the Windows driver reads dark shading with word=0 */
    ms->dark = 1;
    status = read_cx_shading_image(ms);
    if ( status  != SANE_STATUS_GOOD )
        goto cleanup;

    return SANE_STATUS_GOOD;

cleanup:
    cleanup_scanner(ms);
    return status;
}


/*---------- read_cx_shading_image() -----------------------------------------*/

static SANE_Status
read_cx_shading_image(Microtek2_Scanner *ms)
{
    SANE_Status status;
    Microtek2_Device *md;
    u_int32_t shading_bytes, linesize, buffer_size;
    u_int8_t *buf;
    int max_lines, lines_to_read, remaining_lines;

    md = ms->dev;

    shading_bytes = ms->n_control_bytes * 8 * md->shading_length;
    if ( ms->current_color == MS_COLOR_ALL )
        shading_bytes *= 3;
    if ( ms->word == 1 )
        shading_bytes *= 2;

    if ( ms->shading_image )
      {
        free((void *) ms->shading_image);
        ms->shading_image = NULL;
      }
    ms->shading_image = malloc(shading_bytes);
    DBG(100, "read_cx_shading: ms->shading_image=%p, malloc'd %d bytes\n",
           ms->shading_image, shading_bytes);
    if ( ms->shading_image == NULL )
      {
        DBG(1, "read_cx_shading: malloc for cx_shading buffer failed\n");
        return SANE_STATUS_NO_MEM;
      }

    buf = ms->shading_image;

    DBG(30, "read_cx_shading_image: ms=%p, shading_bytes=%d\n",
                                       (void *) ms, shading_bytes);

    linesize = shading_bytes / md->shading_length;
#ifdef TESTBACKEND
    max_lines = 5000000 / linesize;
#else
    max_lines = sanei_scsi_max_request_size / linesize;
#endif
    /* the following part is like in "read_shading_image"  */
    remaining_lines = md->shading_length;
    while ( remaining_lines > 0 )
      {
        lines_to_read = MIN(max_lines, remaining_lines);
        buffer_size = lines_to_read * linesize;

        status = scsi_read_shading(ms, buf, buffer_size);
        if ( status != SANE_STATUS_GOOD )
          {
            DBG(1, "read_cx_shading: '%s'\n", sane_strstatus(status));
            return status;
          }
        remaining_lines -= lines_to_read;
        buf += buffer_size;
      }

    status = calc_cx_shading_line(ms);
    if ( status != SANE_STATUS_GOOD )
      {
        DBG(1, "read_cx_shading: '%s'\n", sane_strstatus(status));
        return status;
      }

    if ( ms->shading_image )
      {
        DBG(100, "free memory for ms->shading_image at %p\n",
        ms->shading_image);
        free((void *) ms->shading_image);
        ms->shading_image = NULL;
      }

    return status;
}

/*---------- calc_cx_shading_line() ------------------------------------------*/
/* calculates the mean value of the shading lines and stores one line of      */
/* 8-bit shading data. Scanning direction + color sequence remain as they are */
/* ToDo: more than 8-bit data */

static SANE_Status
calc_cx_shading_line(Microtek2_Scanner *ms)
{
    Microtek2_Device *md;
    SANE_Status status;
    u_int8_t *current_byte, *buf, *shading_table_pointer;
    u_int8_t color, factor;
    u_int32_t shading_line_pixels, shading_line_bytes,
              shading_data_bytes, line, i, accu, color_offset;
    u_int16_t *sortbuf, value;

    md = ms->dev;
    status = SANE_STATUS_GOOD;

    sortbuf = malloc( md->shading_length * sizeof(float) );
    DBG(100, "calc_cx_shading: sortbuf= %p, malloc'd %lu Bytes\n",
	(void *) sortbuf, (u_long) (md->shading_length * sizeof(float)));
    if ( sortbuf == NULL )
      {
        DBG(1, "calc_cx_shading: malloc for sort buffer failed\n");
        return SANE_STATUS_NO_MEM;
      }

    buf = ms->shading_image;
    shading_line_pixels = ms->n_control_bytes * 8; /* = 2560 for 330CX  */
    shading_line_bytes = shading_line_pixels;      /* grayscale         */
    if ( ms->mode == MS_MODE_COLOR )               /* color             */
        shading_line_bytes *= 3;
    shading_data_bytes = shading_line_bytes;      /*    8-bit color depth */
    if (ms->word == 1)                            /* >  8-bit color depth */
        shading_data_bytes *= 2;
    factor = 4; /* shading bit depth = 10bit; shading line bit depth = 8bit */

    if (ms->dark == 0)  /* white shading data  */
      {
        if ( md->shading_table_w )
            free( (void *)md->shading_table_w );
        md->shading_table_w = (u_int8_t *) malloc(shading_line_bytes);
        DBG(100, "calc_cx_shading: md->shading_table_w=%p, malloc'd %d bytes\n",
               md->shading_table_w, shading_line_bytes);
        if ( md->shading_table_w == NULL )
          {
            DBG(100, "calc_cx_shading: malloc for white shadingtable failed\n");
            status = SANE_STATUS_NO_MEM;
            cleanup_scanner(ms);
          }

        shading_table_pointer = md->shading_table_w;
      }

    else               /*  dark  shading data  */
      {
        if ( md->shading_table_d )
            free( (void *)md->shading_table_d);
        md->shading_table_d = (u_int8_t *) malloc(shading_line_bytes);
        DBG(100, "calc_cx_shading: md->shading_table_d=%p, malloc'd %d bytes\n",
               md->shading_table_d, shading_line_bytes);

        if ( md->shading_table_d == NULL )
          {
            DBG(1, "calc_cx_shading: malloc for dark shading table failed\n");
            status = SANE_STATUS_NO_MEM;
            cleanup_scanner(ms);
          }

        shading_table_pointer = md->shading_table_d;
      }

    DBG(30, "calc_cx_shading_line: ms=%p\n"
            "md->shading_table_w=%p\n"
            "md->shading_table_d=%p\n"
            "shading_line_bytes=%d\n"
            "shading_line_pixels=%d\n"
            "shading_table_pointer=%p\n",
             (void *) ms, md->shading_table_w, md->shading_table_d,
             shading_line_bytes, shading_line_pixels, shading_table_pointer);

    /*  calculating the median pixel values over the shading lines  */
    /*  and write them to the shading table                       */
    for (color = 0; color < 3; color++)
      {
        color_offset = color * shading_line_pixels;
        if ( ms->word == 1 )
          color_offset *=2;

        for (i = 0; i < shading_line_pixels; i++)
          {
            value = 0;
            for (line = 0; line < md->shading_length; line++)
              {
                current_byte = buf + ( line * shading_data_bytes )
                               + color_offset + i;
                accu = *current_byte;

                /* word shading data: the lower bytes per line and color are */
                /* transfered first in one block and then the high bytes */
                /* in one block  */
                /* the dark shading data is also 10 bit, but only the */
                /* low byte is transferred (ms->word = 0) */
                if ( ms->word == 1 )
                  {
                    current_byte = buf + ( line * shading_data_bytes )
                               + color_offset + shading_line_pixels + i;
                    accu += ( *current_byte * 256 );
                  }
                *( sortbuf + line ) = accu;
              }
/* this is the Median filter: sort the values ascending and take the middlest */
            qsort(sortbuf, md->shading_length, sizeof(float),
                     (qsortfunc)compare_func_16);
            value = *( sortbuf + ( md->shading_length - 1 ) / 2 );
            *shading_table_pointer = (u_int8_t) (value / factor);
            shading_table_pointer++;
          }
        if ( ms->mode != MS_MODE_COLOR )
           break;
      }
    return status;
}



/*---------- get_lut_size() --------------------------------------------------*/

static SANE_Status
get_lut_size(Microtek2_Info *mi, int *max_lut_size, int *lut_entry_size)
{
    /* returns the maximum lookup table size. A device might indicate */
    /* several lookup table sizes. */

    DBG(30, "get_lut_size: mi=%p\n", (void *) mi);

    *max_lut_size = 0;
    *lut_entry_size = 0;

    /* Normally this function is used for both gamma and shading tables */
    /* If, however, the device indicates, that it does not support */
    /* lookup tables, we set these values as if the device has a maximum */
    /* bitdepth of 12, and these values are only used to determine the */
    /* size of the shading table */
    if ( MI_LUTCAP_NONE(mi->lut_cap) )
      {
        *max_lut_size = 4096;
        *lut_entry_size = 2;
      }

    if ( mi->lut_cap & MI_LUTCAP_256B )
      {
        *max_lut_size = 256;
        *lut_entry_size = 1;
      }
    if ( mi->lut_cap & MI_LUTCAP_1024B )
      {
        *max_lut_size = 1024;
        *lut_entry_size = 1;
      }
    if ( mi->lut_cap & MI_LUTCAP_1024W )
      {
        *max_lut_size = 1024;
        *lut_entry_size = 2;
      }
    if ( mi->lut_cap & MI_LUTCAP_4096B )
      {
        *max_lut_size = 4096;
        *lut_entry_size = 1;
      }
    if ( mi->lut_cap & MI_LUTCAP_4096W )
      {
          *max_lut_size = 4096;
          *lut_entry_size = 2;
      }
    if ( mi->lut_cap & MI_LUTCAP_64k_W )
      {
          *max_lut_size = 65536;
          *lut_entry_size = 2;
      }
    if ( mi->lut_cap & MI_LUTCAP_16k_W )
      {
          *max_lut_size = 16384;
          *lut_entry_size = 2;
      }
    DBG(30, "get_lut_size:  mi=%p, lut_size=%d, lut_entry_size=%d\n",
             (void *) mi, *max_lut_size, *lut_entry_size);
    return SANE_STATUS_GOOD;
}


/*---------- calculate_gamma() -----------------------------------------------*/

static SANE_Status
calculate_gamma(Microtek2_Scanner *ms, u_int8_t *pos, int color, char *mode)
{
    Microtek2_Device *md;
    Microtek2_Info *mi;
    double exp;
    double mult;
    double steps;
    unsigned int val;
    int i;
    int factor;           /* take into account the differences between the */
                          /* possible values for the color and the number */
                          /* of bits the scanner works with internally. */
                          /* If depth == 1 handle this as if the maximum */
                          /* depth was chosen */


    DBG(30, "calculate_gamma: ms=%p, pos=%p, color=%d, mode=%s\n",
             (void *) ms, pos, color, mode);

    md = ms->dev;
    mi = &md->info[md->scan_source];

    /* does this work everywhere ? */
    if ( md->model_flags & MD_NO_GAMMA )
      {
        factor = 1;
        mult = (double) (ms->lut_size - 1);
      }
    else
      {
        if ( mi->depth & MI_HASDEPTH_16 )
          {
            factor = ms->lut_size / 65536;
            mult = 65535.0;
          }
        else if ( mi->depth & MI_HASDEPTH_14 )
          {
            factor = ms->lut_size / 16384;
            mult = 16383.0;
          }
        else if ( mi->depth & MI_HASDEPTH_12 )
          {
            factor = ms->lut_size / 4096;
            mult = 4095.0;
          }
        else if ( mi->depth & MI_HASDEPTH_10 )
          {
            factor = ms->lut_size / 1024;
            mult = 1023.0;
          }
        else
          {
            factor = ms->lut_size / 256;
            mult = 255.0;
          }
      }

#if 0
    factor = ms->lut_size / (int) pow(2.0, (double) ms->depth);
    mult = pow(2.0, (double) ms->depth) - 1.0;  /* depending on output size */
#endif

    steps = (double) (ms->lut_size - 1);      /* depending on input size */

    DBG(30, "calculate_gamma: factor=%d, mult =%f, steps=%f, mode=%s\n",
             factor, mult, steps, ms->val[OPT_GAMMA_MODE].s);


    if ( strcmp(mode, MD_GAMMAMODE_SCALAR) == 0 )
      {
        int option;

        option = OPT_GAMMA_SCALAR;
        /* OPT_GAMMA_SCALAR_R follows OPT_GAMMA_SCALAR directly */
        if ( ms->val[OPT_GAMMA_BIND].w == SANE_TRUE )
            exp = 1.0 / SANE_UNFIX(ms->val[option].w);
        else
            exp = 1.0 / SANE_UNFIX(ms->val[option + color + 1].w);

        for ( i = 0; i < ms->lut_size; i++ )
          {
            val = (unsigned int) (mult * pow((double) i / steps, exp) + .5);

            if ( ms->lut_entry_size == 2 )
                *((u_int16_t *) pos + i) = (u_int16_t) val;
            else
                *((u_int8_t *) pos + i) = (u_int8_t) val;
          }
      }
    else if ( strcmp(mode, MD_GAMMAMODE_CUSTOM) == 0 )
      {
        int option;
        SANE_Int *src;

        option = OPT_GAMMA_CUSTOM;
        if ( ms->val[OPT_GAMMA_BIND].w == SANE_TRUE )
            src = ms->val[option].wa;
        else
            src = ms->val[option + color + 1].wa;

        for ( i = 0; i < ms->lut_size; i++ )
          {
            if ( ms->lut_entry_size == 2 )
                *((u_int16_t *) pos + i) = (u_int16_t) (src[i] / factor);
            else
                *((u_int8_t *) pos + i) = (u_int8_t) (src[i] / factor);
          }
      }
    else if ( strcmp(mode, MD_GAMMAMODE_LINEAR) == 0 )
      {
        for ( i = 0; i < ms->lut_size; i++ )
          {
            if ( ms->lut_entry_size == 2 )
                *((u_int16_t *) pos + i) = (u_int16_t) (i / factor);
            else
                *((u_int8_t *) pos + i) = (u_int8_t) (i / factor);
          }
      }

    return SANE_STATUS_GOOD;
}


/*---------- shading_function() ----------------------------------------------*/

static SANE_Status
shading_function(Microtek2_Scanner *ms, u_int8_t *data)
{
    Microtek2_Device *md;
    Microtek2_Info *mi;
    u_int32_t value;
    int color;
    int i;


    DBG(40, "shading_function: ms=%p, data=%p\n", (void *) ms, data);

    md = ms->dev;
    mi = &md->info[md->scan_source];
/*    mi = &md->info[MD_SOURCE_FLATBED]; */

    if ( ms->lut_entry_size == 1 )
      {
        DBG(1, "shading_function: wordsize = 1 unsupported\n");
         return SANE_STATUS_IO_ERROR;
      }

    for ( color = 0; color < 3; color++ )
      {
        for ( i = 0; i < ( mi->geo_width / mi->calib_divisor ); i++)
          {
            value = *((u_int16_t *) data
                      + color * ( mi->geo_width / mi->calib_divisor ) + i);
            switch ( mi->shtrnsferequ )
              {
                case 0x00:
                  /* output == input */
                  break;

                case 0x01:
                  value = (ms->lut_size * ms->lut_size) / value;
                  *((u_int16_t *) data
                    + color * ( mi->geo_width / mi->calib_divisor ) + i) =
                                               (u_int16_t) MIN(0xffff, value);
                  break;

                case 0x11:
                  value = (ms->lut_size * ms->lut_size)
                           / (u_int32_t) ( (double) value
                                           * ((double) mi->balance[color]
                                             / 255.0));
                  *((u_int16_t *) data
                    + color * ( mi->geo_width / mi->calib_divisor ) + i) =
                                               (u_int16_t) MIN(0xffff, value);
                  break;
                case 0x15:
                  value = (u_int32_t) ( ( 1073741824 / (double) value )
                                           * ( (double) mi->balance[color]
                                            / 256.0) );
                  value = MIN(value, (u_int32_t)65535);
                 *((u_int16_t *) data
                    + color * ( mi->geo_width / mi->calib_divisor ) + i) =
                                               (u_int16_t) MIN(0xffff, value);
                  break;

                default:
                  DBG(1, "Unsupported shading transfer function 0x%02x\n",
                  mi->shtrnsferequ );
                  break;
              }
          }
      }

    return SANE_STATUS_GOOD;
}


/*---------- set_exposure() --------------------------------------------------*/

static void
set_exposure(Microtek2_Scanner *ms)
{
    /* This function manipulates the colors according to the exposure time */
    /* settings on models where they are ignored. Currently this seems to */
    /* be the case for all models with the data format chunky data. They */
    /* all have tables with two byte gamma output, so for now we ignore */
    /* gamma tables with one byte output */

    Microtek2_Device *md;
    Microtek2_Info *mi;
    int color;
    int size;
    int depth;
    int maxval;
    int byte;
    u_int32_t val32;
    u_int8_t *from;
    u_int8_t exposure;
    u_int8_t exposure_rgb[3];

    
    DBG(30, "set_exposure: ms=%p\n", (void *) ms);

    md = ms->dev;
    mi = &md->info[md->scan_source];

    if ( ms->lut_entry_size == 1 )
      {
        DBG(1, "set_exposure: 1 byte gamma output tables currently ignored\n");
        return;
      }

    if ( mi->depth & MI_HASDEPTH_16 )
        depth = 16;
    else if ( mi->depth & MI_HASDEPTH_14 )
        depth = 14;
    else if ( mi->depth & MI_HASDEPTH_12 )
        depth = 12;
    else if ( mi->depth & MI_HASDEPTH_10 )
        depth = 10;
    else
        depth = 8;

    maxval = ( 1 << depth ) - 1;

    from = ms->gamma_table;
    size = ms->lut_size;

    /* first master channel, apply transformation to all colors */
    exposure = ms->exposure_m;
    for ( byte = 0; byte < ms->lut_size; byte++ )
      {
        for ( color = 0; color < 3; color++)
          {
            val32 = (u_int32_t) *((u_int16_t *) from + color * size + byte);
            val32 = MIN(val32 + val32
                     * (2 * (u_int32_t) exposure / 100), (u_int32_t) maxval);
            *((u_int16_t *) from + color * size + byte) = (u_int16_t) val32;
          }
      }

    /* and now apply transformation to each channel */

    exposure_rgb[0] = ms->exposure_r;
    exposure_rgb[1] = ms->exposure_g;
    exposure_rgb[2] = ms->exposure_b;
    for ( color = 0; color < 3; color++ )
      {
        for ( byte = 0; byte < size; byte++ )
          {
            val32 = (u_int32_t) *((u_int16_t *) from + color * size + byte);
            val32 = MIN(val32 + val32 
                         * (2 * (u_int32_t) exposure_rgb[color] / 100),
                         (u_int32_t) maxval);
            *((u_int16_t *) from + color * size + byte) = (u_int16_t) val32;
          }
      }

    return;
}


/*---------- reader_process() ------------------------------------------------*/

static int
reader_process(void *data)
{
    Microtek2_Scanner *ms = (Microtek2_Scanner *) data;

    SANE_Status status;
    Microtek2_Info *mi;
    Microtek2_Device *md;
    struct SIGACTION act;
    sigset_t sigterm_set;
    static u_int8_t *temp_current = NULL;

    DBG(30, "reader_process: ms=%p\n", (void *) ms);

    md = ms->dev;
    mi = &md->info[md->scan_source];

    if (sanei_thread_is_forked()) close(ms->fd[0]);

    sigemptyset (&sigterm_set);
    sigaddset (&sigterm_set, SIGTERM);
    memset (&act, 0, sizeof (act));
    act.sa_handler = signal_handler;
    sigaction (SIGTERM, &act, 0);

    ms->fp = fdopen(ms->fd[1], "w");
    if ( ms->fp == NULL )
      {
        DBG(1, "reader_process: fdopen() failed, errno=%d\n", errno);
        return SANE_STATUS_IO_ERROR;
      }
   
    if ( ms->auto_adjust == 1 )
      {
        if ( temp_current == NULL )
            temp_current = ms->temporary_buffer;
      }

    while ( ms->src_remaining_lines > 0 )
      {
       
        ms->src_lines_to_read = MIN(ms->src_remaining_lines, ms->src_max_lines);
        ms->transfer_length = ms->src_lines_to_read * ms->bpl;

        DBG(30, "reader_process: transferlength=%d, lines=%d, linelength=%d, "
                "real_bpl=%d, srcbuf=%p\n", ms->transfer_length,
                 ms->src_lines_to_read, ms->bpl, ms->real_bpl, ms->buf.src_buf);

        sigprocmask (SIG_BLOCK, &sigterm_set, 0);
        status = scsi_read_image(ms, ms->buf.src_buf, (ms->depth > 8) ? 2 : 1);
        sigprocmask (SIG_UNBLOCK, &sigterm_set, 0);
        if ( status != SANE_STATUS_GOOD ) 
            return SANE_STATUS_IO_ERROR;

        ms->src_remaining_lines -= ms->src_lines_to_read;

        /* prepare data for frontend */
        switch (ms->mode) 
          {
	    case MS_MODE_COLOR:
              if ( ! mi->onepass )
                /* TODO */
                {
                  DBG(1, "reader_process: 3 pass not yet supported\n");
                  return SANE_STATUS_IO_ERROR;
                }
              else 
                {
                  switch ( mi->data_format ) 
                    {
		      case MI_DATAFMT_CHUNKY:
		      case MI_DATAFMT_9800:
                        status = chunky_proc_data(ms);
                        if ( status != SANE_STATUS_GOOD )
                            return status;
                        break;
		      case MI_DATAFMT_LPLCONCAT:
		        status = lplconcat_proc_data(ms);
                        if ( status != SANE_STATUS_GOOD )
                            return status;
                        break;
		      case MI_DATAFMT_LPLSEGREG:
		        status = segreg_proc_data(ms);
                        if ( status != SANE_STATUS_GOOD )
                            return status;
                        break;
		      case MI_DATAFMT_WORDCHUNKY:
                        status = wordchunky_proc_data(ms);
                        if ( status != SANE_STATUS_GOOD )
                            return status;
                        break;
                      default:
                        DBG(1, "reader_process: format %d\n", mi->data_format);
                        return SANE_STATUS_IO_ERROR;
                    }      
                }
              break;   
	    case MS_MODE_GRAY:
	      status = gray_proc_data(ms);
              if ( status != SANE_STATUS_GOOD )
                  return status;
              break;
	    case MS_MODE_HALFTONE:
	    case MS_MODE_LINEART:
              status = proc_onebit_data(ms);
              if ( status != SANE_STATUS_GOOD )
                  return status;
              break;
	    case MS_MODE_LINEARTFAKE:
              if ( ms->auto_adjust == 1 )
                  status = auto_adjust_proc_data(ms, &temp_current);
              else
                  status = lineartfake_proc_data(ms);

              if ( status != SANE_STATUS_GOOD )
                  return status;
              break;
            default:
              DBG(1, "reader_process: Unknown scan mode %d\n", ms->mode);
              return SANE_STATUS_IO_ERROR;
          }
      }

    fclose(ms->fp);
    return SANE_STATUS_GOOD;
}

/*---------- chunky_proc_data() ----------------------------------------------*/

static SANE_Status                
chunky_proc_data(Microtek2_Scanner *ms)
{
    SANE_Status status;
    Microtek2_Device *md;
    Microtek2_Info *mi;
    u_int32_t line;
    u_int8_t *from;
    int pad;
    int bpp;                    /* bytes per pixel */
    int bits_pp_in;             /* bits per pixel input */
    int bits_pp_out;            /* bits per pixel output */
    int bpl_ppl_diff;


    DBG(30, "chunky_proc_data: ms=%p\n", (void *) ms);
             
    md = ms->dev;
    mi = &md->info[md->scan_source];
    bits_pp_in = ms->bits_per_pixel_in;
    bits_pp_out = ms->bits_per_pixel_out;
    pad = (int) ceil( (double) (ms->ppl * bits_pp_in) / 8.0 ) % 2;
    bpp = bits_pp_out / 8;

    /* Some models have 3 * ppl + 6 bytes per line if the number of pixels */
    /* per line is even and 3 * ppl + 3 bytes per line if the number of */
    /* pixels per line is odd. According to the documentation it should be */
    /* bpl = 3*ppl (even number of pixels) or bpl=3*ppl+1 (odd number of */
    /* pixels. Even worse: On different models it is different at which */
    /* position in a scanline the image data starts. bpl_ppl_diff tries */
    /* to fix this. */

    if ( (md->model_flags & MD_OFFSET_2) && pad == 1 )
        bpl_ppl_diff = 2;
    else
        bpl_ppl_diff = 0;

#if 0
    if ( md->revision == 1.00 && mi->model_code != 0x81 )
        bpl_ppl_diff = ms->bpl - ( 3 * ms->ppl * bpp ) - pad;
    else
        bpl_ppl_diff = ms->bpl - ( 3 * ms->ppl * bpp );

    if ( md->revision > 1.00 )
        bpl_ppl_diff = ms->bpl - ( 3 * ms->ppl * bpp );
    else
        bpl_ppl_diff = ms->bpl - ( 3 * ms->ppl * bpp ) - pad;
#endif

    from = ms->buf.src_buf;
    from += bpl_ppl_diff;

    DBG(30, "chunky_proc_data: lines=%d, bpl=%d, ppl=%d, bpp=%d, depth=%d"
            " junk=%d\n", ms->src_lines_to_read, ms->bpl, ms->ppl,
             bpp, ms->depth, bpl_ppl_diff);

    for ( line = 0; line < (u_int32_t) ms->src_lines_to_read; line++ )
      {
        status = chunky_copy_pixels(ms, from);
        if ( status != SANE_STATUS_GOOD )
            return status;
        from += ms->bpl;
      }

    return SANE_STATUS_GOOD;
}

/*---------- chunky_copy_pixels() --------------------------------------------*/

static SANE_Status
chunky_copy_pixels(Microtek2_Scanner *ms, u_int8_t *from)
{
    Microtek2_Device *md;
    u_int32_t pixel;
    int color;

    DBG(30, "chunky_copy_pixels: from=%p, pixels=%d, fp=%p, depth=%d\n",
             from, ms->ppl, (void *) ms->fp, ms->depth);

    md = ms->dev;
    if ( ms->depth > 8 )
      {
        if ( !( md->model_flags & MD_16BIT_TRANSFER ) )
          {
            int scale1;
            int scale2;
            u_int16_t val16;

            scale1 = 16 - ms->depth;
            scale2 = 2 * ms->depth - 16;
            for ( pixel = 0; pixel < ms->ppl; pixel++ )
              {
                for ( color = 0; color < 3; color++ )
                  {
                    val16 = *( (u_int16_t *) from + 3 * pixel + color );
                    val16 = ( val16 << scale1 ) | ( val16 >> scale2 );
                    fwrite((void *) &val16, 2, 1, ms->fp);
                  }
              }
          }
        else
          {
            fwrite((void *) from, 2, 3 * ms->ppl, ms->fp);
          }
      }
    else if ( ms->depth == 8 )
      {
        fwrite((void *) from, 1, 3 * ms->ppl, ms->fp);
      }
    else
      {
        DBG(1, "chunky_copy_pixels: Unknown depth %d\n", ms->depth);
        return SANE_STATUS_IO_ERROR;
      }

    return SANE_STATUS_GOOD;
}

/*---------- segreg_proc_data() ----------------------------------------------*/

static SANE_Status
segreg_proc_data(Microtek2_Scanner *ms)
{
    SANE_Status status;
    Microtek2_Device *md;
    Microtek2_Info *mi;
    char colormap[] = "RGB";
    u_int8_t *from;
    u_int32_t lines_to_deliver;
    int bpp;                    /* bytes per pixel */
    int bpf;                    /* bytes per frame including color indicator */
    int pad;
    int colseq2;
    int color;
    int save_current_src;
    int frame;
    int right_to_left;

    DBG(30, "segreg_proc_data: ms=%p\n", (void *) ms);

    md = ms->dev;
    mi = &md->info[md->scan_source];
    /* take a trailing junk byte into account */
    pad = (int) ceil( (double) (ms->ppl * ms->bits_per_pixel_in) / 8.0 ) % 2;
    bpp = ms->bits_per_pixel_out / 8; /* bits_per_pixel_out is either 8 or 16 */
    bpf = ms->bpl / 3;
    right_to_left = mi->direction & MI_DATSEQ_RTOL;

    DBG(30, "segreg_proc_data: lines=%d, bpl=%d, ppl=%d, bpf=%d, bpp=%d,\n"
            "depth=%d, pad=%d, freelines=%d, calib_backend=%d\n",
             ms->src_lines_to_read, ms->bpl, ms->ppl, bpf, bpp,
             ms->depth, pad, ms->buf.free_lines, ms->calib_backend);

    /* determine how many planes of each color are in the source buffer */
    from = ms->buf.src_buf;
    for ( frame = 0; frame < 3 * ms->src_lines_to_read; frame++, from += bpf )
      {
        switch ( *from )
          {
            case 'R':
              ++ms->buf.planes[0][MS_COLOR_RED];
              break;
            case 'G':
              ++ms->buf.planes[0][MS_COLOR_GREEN];
              break;
            case 'B':
              ++ms->buf.planes[0][MS_COLOR_BLUE];
              break;
            default:
              DBG(1, "segreg_proc_data: unknown color indicator (1) "
                     "0x%02x\n", *from);
              return SANE_STATUS_IO_ERROR;
          }
      }

    ms->buf.free_lines -= ms->src_lines_to_read;
    save_current_src = ms->buf.current_src;
    if ( ms->buf.free_lines < ms->src_max_lines )
      {
        ms->buf.current_src = ++ms->buf.current_src % 2;
        ms->buf.src_buf = ms->buf.src_buffer[ms->buf.current_src];
        ms->buf.free_lines = ms->buf.free_max_lines;
      }
    else
        ms->buf.src_buf += ms->src_lines_to_read * ms->bpl;

    colseq2 = mi->color_sequence[2];
    lines_to_deliver = ms->buf.planes[0][colseq2] + ms->buf.planes[1][colseq2];
    if ( lines_to_deliver == 0 )
        return SANE_STATUS_GOOD;

    DBG(30, "segreg_proc_data: planes[0][0]=%d, planes[0][1]=%d, "
            "planes[0][2]=%d\n", ms->buf.planes[0][0], ms->buf.planes[0][1],
             ms->buf.planes[0][2] );
    DBG(30, "segreg_proc_data: planes[1][0]=%d, planes[1][1]=%d, "
            "planes[1][2]=%d\n", ms->buf.planes[1][0], ms->buf.planes[1][1],
             ms->buf.planes[1][2] );

    while ( lines_to_deliver > 0 )
      {
        for ( color = 0; color < 3; color++ )
          {
            /* get the position of the next plane for each color */
            do
              {
                if ( *ms->buf.current_pos[color] == colormap[color] )
                    break;
                ms->buf.current_pos[color] += bpf;
              } while ( 1 );

            ms->buf.current_pos[color] += 2;    /* skip color indicator */
          }

        status = segreg_copy_pixels(ms);
        if ( status != SANE_STATUS_GOOD )
          {
            DBG(1, "segreg_copy_pixels:status %d\n", status);
            return status;
          }

        for ( color = 0; color < 3; color++ )
          {
            /* skip a padding byte at the end, if present */
            ms->buf.current_pos[color] += pad;

            if ( ms->buf.planes[1][color] > 0 )
              {
                --ms->buf.planes[1][color];
                if ( ms->buf.planes[1][color] == 0 )
                    /* we have copied from the prehold buffer and are */
                    /* done now, we continue with the source buffer */
                    ms->buf.current_pos[color] =
                                        ms->buf.src_buffer[save_current_src];
              }
            else
              {
                --ms->buf.planes[0][color];
                if ( ms->buf.planes[0][color] == 0
                     && ms->buf.current_src != save_current_src )

                    ms->buf.current_pos[color] =
                                    ms->buf.src_buffer[ms->buf.current_src];
              }
          }
        DBG(100, "planes_to_deliver=%d\n", lines_to_deliver);
        --lines_to_deliver;
      }

    if ( ms->buf.current_src != save_current_src )
      {
        for ( color = 0; color < 3; color++ )
          {
            ms->buf.planes[1][color] += ms->buf.planes[0][color];
            ms->buf.planes[0][color] = 0;
          }
      }

    DBG(30, "segreg_proc_data: src_buf=%p, free_lines=%d\n",
             ms->buf.src_buf, ms->buf.free_lines);

    return SANE_STATUS_GOOD;
}

/*---------- segreg_copy_pixels() --------------------------------------------*/

static SANE_Status
segreg_copy_pixels(Microtek2_Scanner *ms)
{
    Microtek2_Device *md;
    Microtek2_Info *mi;
    u_int32_t pixel;
    int color, i, gamma_by_backend, right_to_left, scale1, scale2, bpp_in;
    float s_w, s_d;          /* shading byte from condensed_shading */
    float val, maxval = 0, shading_factor = 0;
    u_int16_t val16 = 0;
    u_int8_t val8 = 0;
    u_int8_t *from_effective;
    u_int8_t *gamma[3];
    float f[3];                            /* color balance factor */

    md = ms->dev;
    mi = &md->info[md->scan_source];
    gamma_by_backend =  md->model_flags & MD_NO_GAMMA ? 1 : 0;
    right_to_left = mi->direction & MI_DATSEQ_RTOL;
    scale1 = 16 - ms->depth;
    scale2 = 2 * ms->depth - 16;
    bpp_in = ( ms->bits_per_pixel_in + 7 ) / 8; /*Bytes per pixel from scanner*/

    if ((md->model_flags & MD_READ_CONTROL_BIT) && ms->calib_backend)
      {
        maxval = (float) pow(2.0, (float) ms->depth) - 1.0;
        s_w = maxval;
        s_d = 0.0;
        shading_factor = (float) pow(2.0, (double) (md->shading_depth
							 - ms->depth) );
      }

    if ( gamma_by_backend )
      {
        i = (ms->depth > 8) ? 2 : 1;
        for ( color = 0; color < 3; color++)
           gamma[color] = ms->gamma_table
                          + i * (int) pow(2.0, (double)ms->depth);
      }

    DBG(30, "segreg_copy_pixels: pixels=%d\n", ms->ppl);
    DBG(100, "segreg_copy_pixels: buffer 0x%p, right_to_left=%d, depth=%d\n",
	(void *) ms->buf.current_pos, right_to_left, ms->depth);

    for (color = 0; color < 3; color++ )
        f[color] = (float) ms->balance[color] / 100.0;

    DBG(100, "segreg_copy_pixels: color balance:\n"
             " ms->balance[R]=%d, ms->balance[G]=%d, ms->balance[B]=%d\n",
             ms->balance[0], ms->balance[1], ms->balance[2]);

    for ( pixel = 0; pixel < ms->ppl; pixel++ )
      {
        for ( color = 0; color < 3; color++ )
          {
            if ( right_to_left )
               from_effective = ms->buf.current_pos[color]
                                + ( ms->ppl - 1 - pixel ) * bpp_in;
            else
               from_effective = ms->buf.current_pos[color]  +  pixel * bpp_in;

            if ( ms->depth > 8 )
                val = (float) *(u_int16_t *)from_effective;
            else if ( ms->depth == 8 )
                val = (float) *from_effective;
            else
            {
              DBG(1, "segreg_copy_pixels: Unknown depth %d\n", ms->depth);
              return SANE_STATUS_IO_ERROR;
            }

	    if ((md->model_flags & MD_READ_CONTROL_BIT) && ms->calib_backend
                 && ( ms->condensed_shading_w != NULL ))
                 /* apply shading by backend */
              {
                get_cshading_values(ms,
                                    color,
                                    pixel,
                                    shading_factor,
                                    right_to_left,
                                    &s_d,
                                    &s_w);


                if ( s_w == s_d ) s_w = s_d + 1;
                if ( val < s_d ) val = s_d;
                val = maxval *( val - s_d ) / ( s_w - s_d );

                val *= f[color];

                /* if scanner doesn't support brightness, contrast */
                if ( md->model_flags & MD_NO_ENHANCEMENTS )
                  {
                     val += ( ( ms->brightness_m - 128 ) * 2 );
                     val = ( val - 128 ) * ( ms->contrast_m / 128 ) + 128;
                  }

                val = MAX( 0.0, val);
                val = MIN( maxval, val );
              }
            
	    val16 = (u_int16_t) val;
            val8  = (u_int8_t)  val;

            /* apply gamma correction if needed */
            if ( gamma_by_backend )
              {
                if ( ms->depth > 8 )
                  val16 = *((u_int16_t *) gamma[color] + val16);
                else
                  val8 = gamma[color][val8];
              }

            if ( ms->depth > 8 )
              {
                val16 = ( val16 << scale1 ) | ( val16 >> scale2 );
                fwrite((void *) &val16, 2, 1, ms->fp);
              }
            else
              {
                fputc((unsigned char) val8, ms->fp);
              }

          }
      }
    for ( color = 0; color < 3; color++ )
      {
        ms->buf.current_pos[color] += ms->ppl;
        if ( ms->depth > 8 )
            ms->buf.current_pos[color] += ms->ppl;
      }

    return SANE_STATUS_GOOD;

}


/*---------- lplconcat_proc_data() -------------------------------------------*/
static SANE_Status
lplconcat_proc_data(Microtek2_Scanner *ms)
{
    SANE_Status status;
    Microtek2_Device *md;
    Microtek2_Info *mi;
    u_int32_t line;
    u_int8_t *from[3];
    u_int8_t *save_from[3];
    int color;
    int bpp;
    int pad;
    int gamma_by_backend;
    int right_to_left;       /* 0=left to right, 1=right to left */


    DBG(30, "lplconcat_proc_data: ms=%p\n", (void *) ms);

    /* This data format seems to honour the color sequence indicator */

    md = ms->dev;
    mi = &md->info[md->scan_source];

    bpp = ms->bits_per_pixel_out / 8; /* ms->bits_per_pixel_out is 8 or 16 */
    pad = (ms->ppl * bpp) % 2;
    right_to_left = mi->direction & MI_DATSEQ_RTOL;
    gamma_by_backend =  md->model_flags & MD_NO_GAMMA ? 1 : 0;

    if ( right_to_left == 1 )
      {
        for ( color = 0; color < 3; color++ )
          {
            from[color] = ms->buf.src_buf
                          + ( mi->color_sequence[color] + 1 ) * ( ms->bpl / 3 )
                          - bpp - (ms->bpl - 3 * ms->ppl * bpp) / 3;
          }
      }
    else
        for ( color = 0; color < 3; color++ )
            from[color] = ms->buf.src_buf
                          + mi->color_sequence[color] * ( ms->bpl / 3 );

    for ( line = 0; line < (u_int32_t) ms->src_lines_to_read; line++ )
      {
        for ( color = 0 ; color < 3; color++ )
            save_from[color] = from[color];

        status = lplconcat_copy_pixels(ms,
                                       from,
                                       right_to_left,
                                       gamma_by_backend);
        if ( status != SANE_STATUS_GOOD )
            return status;

        for ( color = 0; color < 3; color++ )
            from[color] = save_from[color] + ms->bpl;
      }

    return SANE_STATUS_GOOD;
}


/*---------- lplconcat_copy_pixels() -----------------------------------------*/

static SANE_Status
lplconcat_copy_pixels(Microtek2_Scanner *ms,
                      u_int8_t **from,
                      int right_to_left,
                      int gamma_by_backend)
{
  Microtek2_Device *md;
  Microtek2_Info *mi;
  u_int32_t pixel;
  u_int16_t val16 = 0;
  u_int8_t val8 = 0;
  u_int8_t *gamma[3];
  float s_d;                             /* dark shading pixel */
  float s_w;                             /* white shading pixel */
  float shading_factor = 0;
  float f[3];                            /* color balance factor */
  float val, maxval = 0;
  int color;
  int step, scale1, scale2;
  int i;


  DBG(30, "lplconcat_copy_pixels: ms=%p, righttoleft=%d, gamma=%d,\n",
           (void *) ms, right_to_left, gamma_by_backend);

  md = ms->dev;
  mi = &md->info[md->scan_source];

  if ((md->model_flags & MD_READ_CONTROL_BIT) && ms->calib_backend)
    {
      shading_factor = (float) pow(2.0,(double)(md->shading_depth - ms->depth));
      maxval = (float) pow(2.0, (double) ms->depth) - 1.0;
      s_w = maxval;
      s_d = 0.0;
    }

  step = ( right_to_left == 1 ) ? -1 : 1;
  if ( ms->depth > 8 ) step *= 2;
  scale1 = 16 - ms->depth;
  scale2 = 2 * ms->depth - 16;

  if ( gamma_by_backend )
    {
      i =  ( ms->depth > 8 ) ? 2 : 1;
      for ( color = 0; color < 3; color++ )
          gamma[color] = ms->gamma_table + i * (int) pow(2.0,(double)ms->depth);
    }

  for (color = 0; color < 3; color++ )
      f[color] = (float)ms->balance[color] / 100.0;

  DBG(100, "lplconcat_copy_pixels: color balance:\n"
             " ms->balance[R]=%d, ms->balance[G]=%d, ms->balance[B]=%d\n",
             ms->balance[0], ms->balance[1], ms->balance[2]);

  for ( pixel = 0; pixel < ms->ppl; pixel++ )
    {
      for ( color = 0; color < 3; color++ )
        {
          if ( ms->depth > 8 )
              val = (float) *(u_int16_t *) from[color];
          else if ( ms->depth == 8 )
              val = (float) *from[color];
          else
            {
              DBG(1, "lplconcat_copy_pixels: Unknown depth %d\n", ms->depth);
              return SANE_STATUS_IO_ERROR;
            }
	  
	  if ((md->model_flags & MD_READ_CONTROL_BIT) && ms->calib_backend
               && ( ms->condensed_shading_w != NULL ))
               /* apply shading by backend */
            {
              get_cshading_values(ms,
                                  mi->color_sequence[color],
                                  pixel,
                                  shading_factor,
                                  right_to_left,
                                  &s_d,
                                  &s_w);

              if ( val < s_d ) val = s_d;
              if ( s_w == s_d ) s_w = s_d + 1;
              val = ( maxval * ( val - s_d ) ) / (s_w - s_d);

              val *= f[color]; /* apply color balance */

              /* if scanner doesn't support brightness, contrast ... */
              if ( md->model_flags & MD_NO_ENHANCEMENTS )
                {
                   val += ( ( ms->brightness_m - 128 ) * 2 );
                   val = ( val - 128 ) * ( ms->contrast_m / 128 ) + 128;
                }

              if ( val > maxval ) val = maxval;
              if ( val < 0.0 ) val = 0.0;
            }

          val16 = (u_int16_t) val;
          val8  = (u_int8_t)  val;

          /* apply gamma correction if needed */
          if ( gamma_by_backend )
            {
              if ( ms->depth > 8 )
                val16 = *((u_int16_t *) gamma[color] + val16);
              else
                val8 = gamma[color][val8];
            }

          if ( ms->depth > 8 )
            {
              val16 = ( val16 << scale1 ) | ( val16 >> scale2 );
              fwrite((void *) &val16, 2, 1, ms->fp);
            }
          else
            {
              fputc((unsigned char) val8, ms->fp);
            }
          from[color] += step;
        }
    }
  return SANE_STATUS_GOOD;
}




/*---------- wordchunky_proc_data() ------------------------------------------*/

static SANE_Status
wordchunky_proc_data(Microtek2_Scanner *ms)
{
    SANE_Status status;
    u_int8_t *from;
    u_int32_t line;


    DBG(30, "wordchunky_proc_data: ms=%p\n", (void *) ms);

    from = ms->buf.src_buf;
    for ( line = 0; line < (u_int32_t) ms->src_lines_to_read; line++ )
      {
        status = wordchunky_copy_pixels(from, ms->ppl, ms->depth, ms->fp);
        if ( status != SANE_STATUS_GOOD )
            return status;
        from += ms->bpl;
      }

    return SANE_STATUS_GOOD;
}


/*---------- wordchunky_copy_pixels() ----------------------------------------*/

static SANE_Status
wordchunky_copy_pixels(u_int8_t *from, u_int32_t pixels, int depth, FILE *fp)
{
    u_int32_t pixel;
    int color;

    DBG(30, "wordchunky_copy_pixels: from=%p, pixels=%d, depth=%d\n",
             from, pixels, depth);

    if ( depth > 8 )
      {
        int scale1;
        int scale2;
        u_int16_t val16;

        scale1 = 16 - depth;
        scale2 = 2 * depth - 16;
        for ( pixel = 0; pixel < pixels; pixel++ )
          {
            for ( color = 0; color < 3; color++ )
              {
                val16 = *(u_int16_t *) from;
                val16 = ( val16 << scale1 ) | ( val16 >> scale2 );
                fwrite((void *) &val16, 2, 1, fp);
                from += 2;
              }
          }
      }
    else if ( depth == 8 )
      {
        pixel = 0;
        do
          {
            fputc((char ) *from, fp);
            fputc((char) *(from + 2), fp);
            fputc((char) *(from + 4), fp);
            ++pixel;
            if ( pixel < pixels )
              {
                fputc((char) *(from + 1), fp);
                fputc((char) *(from + 3), fp);
                fputc((char) *(from + 5), fp);
                ++pixel;
              }
            from += 6;
          } while ( pixel < pixels );
      }
    else
      {
        DBG(1, "wordchunky_copy_pixels: Unknown depth %d\n", depth);
        return SANE_STATUS_IO_ERROR;
      }

    return SANE_STATUS_GOOD;
}


/*---------- gray_proc_data() ------------------------------------------------*/

static SANE_Status
gray_proc_data(Microtek2_Scanner *ms)
{
    SANE_Status status;
    Microtek2_Device *md;
    Microtek2_Info *mi;
    u_int8_t *from;
    int gamma_by_backend, bpp;
    int right_to_left;   /* for scanning direction */


    DBG(30, "gray_proc_data: lines=%d, bpl=%d, ppl=%d, depth=%d\n",
             ms->src_lines_to_read, ms->bpl, ms->ppl, ms->depth);

    md = ms->dev;
    mi = &md->info[md->scan_source];

    gamma_by_backend =  md->model_flags & MD_NO_GAMMA ? 1 : 0;
    right_to_left = mi->direction & MI_DATSEQ_RTOL;
    bpp = ( ms->bits_per_pixel_in + 7 ) / 8;

    if ( right_to_left == 1 )
      from = ms->buf.src_buf + ms->ppl * bpp - bpp;
    else
      from = ms->buf.src_buf;

    do
      {
        status = gray_copy_pixels(ms,
                                  from,
                                  right_to_left,
                                  gamma_by_backend);
        if ( status != SANE_STATUS_GOOD )
            return status;

        from += ms->bpl;
        --ms->src_lines_to_read;
      } while ( ms->src_lines_to_read > 0 );

    return SANE_STATUS_GOOD;
}


/*---------- gray_copy_pixels() ----------------------------------------------*/

static SANE_Status
gray_copy_pixels(Microtek2_Scanner *ms,
                 u_int8_t *from,
                 int right_to_left,
                 int gamma_by_backend)
{
    Microtek2_Device *md;
    u_int32_t pixel;
    u_int16_t val16;
    u_int8_t val8;
    int step, scale1, scale2;
    float val, maxval = 0;
    float s_w, s_d, shading_factor = 0;

    DBG(30, "gray_copy_pixels: pixels=%d, from=%p, fp=%p, depth=%d\n",
             ms->ppl, from, (void *) ms->fp, ms->depth);

    md = ms->dev;
    step = right_to_left == 1 ? -1 : 1;
    if ( ms->depth > 8 ) step *= 2;
    val = 0;
    scale1 = 16 - ms->depth;
    scale2 = 2 * ms->depth - 16;

    if ((md->model_flags & MD_READ_CONTROL_BIT) && ms->calib_backend)
      {
        maxval = (float) pow(2.0, (float) ms->depth) - 1.0;
        s_w = maxval;
        s_d = 0.0;
        shading_factor = (float) pow(2.0, (double) (md->shading_depth - ms->depth) );
      }

    if ( ms->depth >= 8 )
      {
        for ( pixel = 0; pixel < ms->ppl; pixel++ )
          {
            if ( ms->depth > 8 )
                val = (float) *(u_int16_t *) from;
            if ( ms->depth == 8 )
                val = (float) *from;

            if ((md->model_flags & MD_READ_CONTROL_BIT) && ms->calib_backend
                 && ( ms->condensed_shading_w != NULL ))
                 /* apply shading by backend */
              {
                get_cshading_values(ms,
                                    0,
                                    pixel,
                                    shading_factor,
                                    right_to_left,
                                    &s_d,
                                    &s_w);

                if ( val < s_d ) val = s_d;
                val = ( val - s_d ) * maxval / (s_w - s_d );
                val = MAX( 0.0, val );
                val = MIN( maxval, val );
              }

            if ( ms->depth > 8 )
              {
                val16 = (u_int16_t) val;
                if ( gamma_by_backend )
                    val16 = *((u_int16_t *) ms->gamma_table + val16);
                if ( !( md->model_flags & MD_16BIT_TRANSFER ) )
                    val16 = ( val16 << scale1 ) | ( val16 >> scale2 );
                fwrite((void *) &val16, 2, 1, ms->fp);
              }

            if ( ms->depth == 8 )
              {
                val8  = (u_int8_t)  val;
                if ( gamma_by_backend )
                    val8 =  ms->gamma_table[(int)val8];
                fputc((char)val8, ms->fp);
              }
            from += step;
          }
      }
    else if ( ms->depth == 4 )
      {
        pixel = 0;
        while ( pixel < ms->ppl )
          {
            fputc((char) ( ((*from >> 4) & 0x0f) | (*from & 0xf0) ), ms->fp);
            ++pixel;
            if ( pixel < ms->ppl )
                fputc((char) ((*from & 0x0f) | ((*from << 4) & 0xf0)), ms->fp);
            from += step;
            ++pixel;
          }
      }
    else
      {
        DBG(1, "gray_copy_pixels: Unknown depth %d\n", ms->depth);
        return SANE_STATUS_IO_ERROR;
      }

    return SANE_STATUS_GOOD;
}

/*---------- proc_onebit_data() ----------------------------------------------*/

static SANE_Status
proc_onebit_data(Microtek2_Scanner *ms)
{
    Microtek2_Device *md;
    Microtek2_Info *mi;
    u_int32_t bytes_to_copy;     /* bytes per line to copy */
    u_int32_t line;
    u_int32_t byte;
    u_int32_t ppl;
    u_int8_t *from;
    u_int8_t to;
    int right_to_left;
    int bit;
    int toindex;


    DBG(30, "proc_onebit_data: ms=%p\n", (void *) ms);

    md = ms->dev;
    mi = &md->info[md->scan_source];
    from = ms->buf.src_buf;
    bytes_to_copy = ( ms->ppl + 7 ) / 8 ;
    right_to_left = mi->direction & MI_DATSEQ_RTOL;

    DBG(30, "proc_onebit_data: bytes_to_copy=%d, lines=%d\n",
             bytes_to_copy, ms->src_lines_to_read);

    line = 0;
    to = 0;
    do
      {
        /* in onebit mode black and white colors are inverted */
        if ( right_to_left )
          {
            /* If the direction is right_to_left, we must skip some */
            /* trailing bits at the end of the scan line and invert the */
            /* bit sequence. We copy 8 bits into a byte, but these bits */
            /* are normally not byte aligned. */

            /* Determine the position of the first bit to copy */
            ppl = ms->ppl;
            byte = ( ppl + 7 ) / 8 - 1;
            bit = ppl % 8 - 1;
            to = 0;
            toindex = 8;

            while ( ppl > 0 )
              {
                to |= ( ( from[byte] >> (7 - bit) ) & 0x01);
                --toindex;
                if ( toindex == 0 )
                  {
                    fputc( (char) ~to, ms->fp);
                    toindex = 8;
                    to = 0;
                  }
                else
                    to <<= 1;

                --bit;
                if ( bit < 0 )
                  {
                    bit = 7;
                    --byte;
                  }
                --ppl;
              }
            /* print the last byte of the line, if it was not */
            /*  completely filled */
            bit = ms->ppl % 8;
            if ( bit != 0 )
                fputc( (char) ~(to << (7 - bit)), ms->fp);
          }
        else
            for ( byte = 0; byte < bytes_to_copy; byte++ )
                fputc( (char) ~from[byte], ms->fp);

        from += ms->bpl;

      } while ( ++line < (u_int32_t) ms->src_lines_to_read );

    return SANE_STATUS_GOOD;
}


/*---------- lineartfake_proc_data() -----------------------------------------*/

static SANE_Status
lineartfake_proc_data(Microtek2_Scanner *ms)
{
    Microtek2_Device *md;
    Microtek2_Info *mi;
    SANE_Status status;
    u_int8_t *from;
    int right_to_left;


    DBG(30, "lineartfake_proc_data: lines=%d, bpl=%d, ppl=%d, depth=%d\n",
             ms->src_lines_to_read, ms->bpl, ms->ppl, ms->depth);

    md = ms->dev;
    mi = &md->info[md->scan_source];
    right_to_left = mi->direction & MI_DATSEQ_RTOL;

    if ( right_to_left == 1 )
        from = ms->buf.src_buf + ms->ppl - 1;
    else
        from = ms->buf.src_buf;

    do
      {
        status = lineartfake_copy_pixels(ms,
                                         from,
                                         ms->ppl,
                                         ms->threshold,
                                         right_to_left,
                                         ms->fp);
        if ( status != SANE_STATUS_GOOD )
            return status;

        from += ms->bpl;
        --ms->src_lines_to_read;
      } while ( ms->src_lines_to_read > 0 );

    return SANE_STATUS_GOOD;
}

/*---------- lineartfake_copy_pixels() ---------------------------------------*/

static SANE_Status
lineartfake_copy_pixels(Microtek2_Scanner *ms,
                        u_int8_t *from,
                        u_int32_t pixels,
                        u_int8_t threshold,
                        int right_to_left,
                        FILE *fp)
{
    Microtek2_Device *md;
    u_int32_t pixel;
    u_int32_t bit;
    u_int8_t dest;
    u_int8_t val;
    float s_d, s_w, maxval, shading_factor, grayval;
    int step;


    DBG(30, "lineartfake_copy_pixels: from=%p,pixels=%d,threshold=%d,file=%p\n",
             from, pixels, threshold, (void *) fp);
    md = ms->dev;
    bit = 0;
    dest = 0;
    step = right_to_left == 1 ? -1 : 1;
    maxval = 255.0;
    s_w = maxval;
    s_d = 0.0;
    shading_factor = (float) pow(2.0, (double) (md->shading_depth - 8) );

    for ( pixel = 0; pixel < pixels; pixel++ )
      {
        if ((md->model_flags & MD_READ_CONTROL_BIT) && ms->calib_backend
             && ( ms->condensed_shading_w != NULL ))
             /* apply shading by backend */
          {
            get_cshading_values(ms,
                                0,
                                pixel,
                                shading_factor,
                                right_to_left,
                                &s_d,
                                &s_w);
          }
        else    /* no shading */
          {
            s_w = maxval;
            s_d = 0.0;
          }

        grayval = (float) *from;

        if ( grayval < s_d ) grayval = s_d;
        grayval = ( grayval - s_d ) * maxval / (s_w - s_d );
        grayval = MAX( 0.0, grayval );
        grayval = MIN( maxval, grayval );

        if ( (u_int8_t)grayval < threshold ) val = 1; else val = 0;
        dest = ( dest << 1 ) | val;
        bit = ( bit + 1 ) % 8;
        if ( bit == 0 )                   /* 8 input bytes processed */
          {
            fputc((char) dest, fp);
            dest = 0;
          }
        from += step;
      }

    if ( bit != 0 )
      {
        dest <<= 7 - bit;
        fputc((char) dest, fp);
      }

    return SANE_STATUS_GOOD;
}

/*---------- auto_adjust_proc_data() -----------------------------------------*/

static SANE_Status
auto_adjust_proc_data(Microtek2_Scanner *ms, u_int8_t **temp_current)
{
    Microtek2_Device *md;
    Microtek2_Info *mi;
    SANE_Status status;
    u_int8_t *from;
    u_int32_t line;
    u_int32_t lines;
    u_int32_t pixel;
    u_int32_t threshold;
    int right_to_left;


    DBG(30, "auto_adjust_proc_data: ms=%p, temp_current=%p\n",
            (void *) ms, *temp_current);

    md = ms->dev;
    mi = &md->info[md->scan_source];
    right_to_left = mi->direction & MI_DATSEQ_RTOL;

    memcpy(*temp_current, ms->buf.src_buf, ms->transfer_length);
    *temp_current += ms->transfer_length;
    threshold = 0;
    status = SANE_STATUS_GOOD;

    if ( ms->src_remaining_lines == 0 ) /* we have read all the image data, */
      {                                 /* calculate threshold value */
        for ( pixel = 0; pixel < ms->remaining_bytes; pixel++ )
            threshold += *(ms->temporary_buffer + pixel);

        threshold /= ms->remaining_bytes;
        lines = ms->remaining_bytes / ms->bpl;
        for ( line = 0; line < lines; line++ )
          {
            from = ms->temporary_buffer + line * ms->bpl;
            if ( right_to_left == 1 )
                from += ms->ppl - 1;
            status = lineartfake_copy_pixels(ms,
                                             from,
                                             ms->ppl,
                                             (u_int8_t) threshold,
                                             right_to_left,
                                             ms->fp);
          }
        *temp_current = NULL;
      }

    return status;
}

/*-------------- get_cshading_values -----------------------------------------*/

static SANE_Status
get_cshading_values(Microtek2_Scanner *ms,
                    u_int8_t color,
                    u_int32_t pixel,
                    float shading_factor,
                    int right_to_left,
                    float *s_d,
                    float *s_w)
{
  Microtek2_Device *md;
  u_int32_t csh_offset;

  md = ms->dev;

  if ( right_to_left == 1 )
    csh_offset = (color + 1) * ms->ppl - 1 - pixel;
  else
    csh_offset = color * ms->ppl + pixel;

  if ( ( md->shading_depth > 8 ) && ( ms->lut_entry_size == 2) )
    /* condensed shading is 2 byte color data */
    {
      if ( ms->condensed_shading_d != NULL )
          *s_d = (float) *( (u_int16_t *)ms->condensed_shading_d
                                         + csh_offset );
      else
          *s_d = 0.0;

      *s_w = (float) *( (u_int16_t *)ms->condensed_shading_w
                                     + csh_offset );
      *s_w /= shading_factor;
      *s_d /= shading_factor;
    }

  else
    /* condensed shading is 8 bit data */
    {
      *s_w = (float) *( ms->condensed_shading_w + csh_offset );
      if ( ms->condensed_shading_d != NULL )
        *s_d = (float) *( ms->condensed_shading_d + csh_offset );
      else
        *s_d = 0.0;
    }
  return SANE_STATUS_GOOD;
}
