/* sane - Scanner Access Now Easy.

   Copyright (C) 2004-2005 Gerard Klaver  <gerard at gkall dot hobby dot nl>
   The teco2 and gl646 backend (Frank Zago) are used as a template for 
   this backend. Some info is taken from the libghoto2 camlib soundvision 
   program
   
   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. 
*/

/*  $Id: soundvision.c,v 1.01 2004/09/30 17:37:22 gekl-guest Exp $

   Soundvison  camera driver Gerard Klaver
*/

/*SANE FLOW DIAGRAM

   - sane_init() : initialize backend, attach vidcams
   . - sane_get_devices() : query list of vidcam devices
   . - sane_open() : open a particular vidcam device
   . . - sane_set_io_mode : set blocking mode
   . . - sane_get_select_fd : get vidcam fd
   . . - sane_get_option_descriptor() : get option information
   . . - sane_control_option() : change option values
   . .
   . . - sane_start() : start image acquisition
   . .   - sane_get_parameters() : returns actual scan parameters
   . .   - sane_read() : read image data (from pipe)
   . .
   . . - sane_cancel() : cancel operation
   . - sane_close() : close opened vidcam device
   - sane_exit() : terminate use of backend
*/
/*--------------------------------------------------------------------------*/

#define BUILD 1			/* 2004/09/20  update 29-03-2005 */
#define BACKEND_NAME soundvision
#define SOUNDVISION1_CONFIG_FILE "soundvision.conf"

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

/* must be first include */
#include "../include/sane/config.h"

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


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

/* for add-text routine  */
#include <time.h>
#include "font_6x11.h"
/*-----------------------*/

#include "soundvision.h"

#define TIMEOUT 1000

/*--------------------------------------------------------------------------*/
/* Lists of possible scan modes. */
static SANE_String_Const scan_mode_list[] = {
  COLOR_RAW_STR,
  COLOR_RGB_STR,
  COLOR_RGB_TEXT_STR,
  SANE_VALUE_SCAN_MODE_COLOR,

  NULL
};

/*-----------------------------------------minium, maximum, quantization----*/
static const SANE_Range brightness_range = { -128, 128, 1 };

static const SANE_Range red_level_range = { -32, 32, 1 };

static const SANE_Range green_level_range = { -32, 32, 1 };

static const SANE_Range blue_level_range = { -32, 32, 1 };

static const SANE_Range contrast_range = { 0, 255, 1 };

static const SANE_Range hue_range = { 0, 255, 1 };

static const SANE_Range saturation_range = { 0, 255, 1 };

static const SANE_Range threshold_range = { 0, 255, 0 };

static const SANE_Range black_level_range = { 0, 255, 1 };

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

static const struct dpi_color_adjust soundvision_dpi_color_adjust[] = {

  /*dpi, y, x, color sequence R G or B */
/*  {640, 480, 1, 0, 2, 1, 2},  */
  {320, 240, 0, 1, 2},
  {160, 120, 0, 1, 2},
  /* must be the last entry */
  {0, 0, 0, 0, 0}
};

static const struct dpi_color_adjust clarify_dpi_color_adjust[] = {

  /*dpi, y, x, color sequence R G or B */
  {640, 480, 0, 1, 2},
  {320, 240, 0, 1, 2},
  {160, 120, 0, 1, 2},
  /* must be the last entry */
  {0, 0, 0, 0, 0}
};

/* For all vidcams. apear in the soundvision and soundvision_dpi_color_adjust list. */
#define DEF_RESOLUTION 320

static const struct vidcam_hardware vidcams[] = {
/* vid, pid, class, name, name, max x and y resolution, res. table name    */
  {0x06bd, 0x0403, USB_CLASS_VENDOR_SPEC,
   "Agfa", "ePhoto CL18", soundvision_dpi_color_adjust},

  {0x055f, 0xa350, USB_CLASS_VENDOR_SPEC,
   "Mustek", "gSmart 350", clarify_dpi_color_adjust},

  {0x0784, 0x0100, USB_CLASS_VENDOR_SPEC,
   "RCA", "CDS1005", clarify_dpi_color_adjust},

/*"Ixla:DualCam 640",0x0784,0x0100,0}, */

  {0x0748, 0x5300, USB_CLASS_VENDOR_SPEC,
   "Pretec", "dc530", clarify_dpi_color_adjust},

  {0x0919, 0x0100, USB_CLASS_VENDOR_SPEC,
   "Generic SoundVision", "Clarity2", clarify_dpi_color_adjust}
  /*      {"Argus:DC-2200",0x0919,0x100,0},
     {"CoolCam:CP086",0x0919,0x100,0},
     {"DigitalDream:l'elite",0x0919,0x0100,0},
     {"FujiFilm:@xia ix-100",0x0919,0x0100,0},
     {"Media-Tech:mt-406",0x0919,0x0100,0},
     {"Oregon Scientific:DShot II",0x0919,0x0100,0},
     {"Oregon Scientific:DShot III",0x0919,0x0100,0},
     {"Scott:APX 30",0x0919,0x0100,0},
     {"StarCam:CP086",0x0919,0x100,0},
     {"Tiger:Fast Flicks",0x0919,0x0100,0},   */
};

/* List of vidcams attached. */
static Soundvision_Vidcam *first_dev = NULL;
static int num_devices = 0;
/* used by sane_get_devices */
static const SANE_Device **devlist = NULL;

/* Used for jpeg decompression */
static struct jpeg_decompress_struct cinfo;
static djpeg_dest_ptr dest_mgr = NULL;

/*----------------------------------------------------------- */
typedef struct
{
  struct jpeg_source_mgr pub;
  JOCTET *buffer;
}
my_source_mgr;
typedef my_source_mgr *my_src_ptr;

METHODDEF (boolean) jpeg_fill_input_buffer (j_decompress_ptr cinfo)
{
  int n;
  int data_file_current_index;
  int data_file_total_size;

  my_src_ptr src = (my_src_ptr) cinfo->src;

  if (data_file_current_index + 512 > data_file_total_size)
    {
      n = data_file_total_size - data_file_current_index;
    }
  else
    {
      n = 512;
    }

  /* memcpy (src->buffer, data_ptr + data_file_current_index, n); */
  data_file_current_index += n;

  src->pub.next_input_byte = src->buffer;
  src->pub.bytes_in_buffer = n;

  return TRUE;
}

METHODDEF (void) jpeg_skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{

  my_src_ptr src = (my_src_ptr) cinfo->src;

  if (num_bytes > 0)
    {
      while (num_bytes > (long) src->pub.bytes_in_buffer)
	{
	  num_bytes -= (long) src->pub.bytes_in_buffer;
	  (void) jpeg_fill_input_buffer (cinfo);
	}
    }
  src->pub.next_input_byte += (size_t) num_bytes;
  src->pub.bytes_in_buffer -= (size_t) num_bytes;
}

METHODDEF (void)
jpeg_term_source (j_decompress_ptr /*UNUSEDARG*/ cinfo)
{
  /* no work necessary here */
}

/* Local functions. */

/* Display a buffer in the log. Display by lines of 16 bytes. */
static void
hexdump (int level, const char *comment, unsigned char *buf, const int length)
{
  int i;
  char line[128];
  char *ptr;
  char asc_buf[17];
  char *asc_ptr;

  DBG (level, "  %s\n", comment);

  i = 0;
  goto start;

  do
    {
      if (i < length)
	{
	  ptr += sprintf (ptr, " %2.2x", *buf);

	  if (*buf >= 32 && *buf <= 127)
	    {
	      asc_ptr += sprintf (asc_ptr, "%c", *buf);
	    }
	  else
	    {
	      asc_ptr += sprintf (asc_ptr, ".");
	    }
	}
      else
	{
	  /* After the length; do nothing. */
	  ptr += sprintf (ptr, "   ");
	}

      i++;
      buf++;

      if ((i % 16) == 0)
	{
	  /* It's a new line */
	  DBG (level, "  %s    %s\n", line, asc_buf);

	start:
	  ptr = line;
	  *ptr = '\0';
	  asc_ptr = asc_buf;
	  *asc_ptr = '\0';

	  ptr += sprintf (ptr, "  %3.3d:", i);
	}

    }
  while (i < ((length + 15) & ~15));
}

/* Returns the length of the longest string, including the terminating
 * character. */
static size_t
max_string_size (SANE_String_Const strings[])
{
  size_t size, max_size = 0;
  int i;

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

  return max_size;
}

/* Lookup a string list from one array and return its index. */
static int
get_string_list_index (SANE_String_Const list[], SANE_String_Const name)
{
  int index;

  index = 0;
  while (list[index] != NULL)
    {
      if (strcmp (list[index], name) == 0)
	{
	  return (index);
	}
      index++;
    }

  DBG (DBG_error, "name %s not found in list\n", name);

  assert (0);			/* bug in backend, core dump */

  return (-1);
}

/* Initialize a vidcam entry. Return an allocated vidcam with some
 *  */
static Soundvision_Vidcam *
soundvision_init (void)
{
  Soundvision_Vidcam *dev;

  DBG (DBG_proc, "soundvision_init: enter\n");

  /* Allocate a new vidcam entry. */
  dev = calloc (1, sizeof (Soundvision_Vidcam));
  if (dev == NULL)
    {
      return NULL;
    }
  memset (dev, 0, sizeof (Soundvision_Vidcam));
  
/* Allocate the windoww buffer*/
  dev->windoww_size = 0x20;
  dev->windoww = malloc (dev->windoww_size);
  if (dev->windoww == NULL)
    {
      free (dev);
      return NULL;
    }

/* Allocate the windowr buffer*/
  dev->windowr_size = 0x60;
  dev->windowr = malloc (dev->windowr_size);
  if (dev->windowr == NULL)
    {
      free (dev);
      return NULL;
    }

  dev->fd = -1;

  DBG (DBG_proc, "soundvision_init: exit\n");

  return (dev);
}

static Soundvision_Vidcam *
soundvision_init_2 (void)
{
  Soundvision_Vidcam *dev;

  DBG (DBG_proc, "soundvision_init_2: enter\n");

  /* Allocate the buffer used to transfer the USB data */
  /* Check for max. format image size so buffer size can
   * be adjusted                                        */
  /* if (dev->CIF)
    dev->buffer_size = 352 * 288 * 3;
  if (dev->VGA)  */
    dev->buffer_size = 914 * 1024;	/*644 * 484 * 3 VGA + 4 format, 914 * 1024 */

  DBG (DBG_proc, "soundvision_init_2: dev->bufffer = 0x%x\n", dev->buffer_size);

  dev->buffer = malloc (dev->buffer_size);

  if (dev->buffer == NULL)
    {
      free (dev);
      return NULL;
    }

  /* Allocate the output buffer used for bayer conversion */
  dev->output_size = dev->buffer_size;

  dev->output = malloc (dev->output_size);
  if (dev->output == NULL)
    {
      free (dev);
      return NULL;
    }

  DBG (DBG_proc, "soundvision_init_2: exit\n");

  return (dev);
}


/* Closes an open vidcams. */
static void
soundvision_close (Soundvision_Vidcam * dev)
{
  DBG (DBG_proc, "soundvision_close: enter \n");

  if (dev->fd != -1)
    {
      sanei_usb_close (dev->fd);
      dev->fd = -1;
    }

  DBG (DBG_proc, "soundvision_close: exit\n");
}

/* Frees the memory used by a vidcam. */
static void
soundvision_free (Soundvision_Vidcam * dev)
{
  int i;

  DBG (DBG_proc, "soundvision_free: enter\n");

  if (dev == NULL)
    return;

  soundvision_close (dev);
  if (dev->devicename)
    {
      free (dev->devicename);
    }
  if (dev->buffer)
    {
      free (dev->buffer);
    }
 if (dev->output)
    {
      free (dev->output);
    }
  if (dev->windoww)
    {
      free (dev->windoww);
    }
  if (dev->windowr)
    {
      free (dev->windowr);
    }
  for (i = 1; i < OPT_NUM_OPTIONS; i++)
    {
      if (dev->opt[i].type == SANE_TYPE_STRING && dev->val[i].s)
	{
	  free (dev->val[i].s);
	}
    }
  if (dev->resolutions_list)
    free (dev->resolutions_list);

  free (dev);

  DBG (DBG_proc, "soundvision_free: exit\n");
}


static SANE_Status
soundvision_set_config (Soundvision_Vidcam * dev, int configuration,
			int interface, int alternate)
{
  SANE_Status status;
  DBG (DBG_proc, "soundvision_set_config: open\n");

  status = sanei_usb_set_configuration (dev->fd, configuration);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "soudvision_vidcam_init: SOUNDVISION FAILED to set configuration %d\n",
	   configuration);
      return status;
    }

  status = sanei_usb_claim_interface (dev->fd, interface);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "soundvision_vidcam_init: SOUNDVISION FAILED to claim interface\n");
      return status;
    }

  status = sanei_usb_set_altinterface (dev->fd, alternate);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "soundvision_vidcam_init: SOUNDVISION FAILED to set alternate interface %d\n",
	   alternate);
      return status;
    }
  DBG (DBG_proc,
       "soundvision_vidcam_init: configuration=%d, interface=%d, alternate=%d\n",
       configuration, interface, alternate);

  DBG (DBG_proc, "soundvsion_set_config: exit\n");
  return status;
}

/* Reset vidcam */
static SANE_Status
soundvision_reset_vidcam (Soundvision_Vidcam * dev)
{
  SANE_Status status;
  size_t sizew;			/* significant size of window */
  size_t sizer;

  DBG (DBG_proc, "soundvision_reset_vidcam: enter\n");

      sizew = dev->windoww_size;
      sizer = dev->windowr_size;

      memset (dev->windoww, 0, sizew);
      memset (dev->windowr, 0, sizer);


            /* urb 195, write endpoint 02  stop program
       * 12 bytes 08 00 00 00 94 00 00 00 00 00 00 00*/
     /* sizew = 0x0c;
      dev->windoww[0] = 0x08;
      dev->windoww[4] = 0x94;
      dev->windoww[5] = 0x00;

      status = sanei_usb_write_bulk (dev->fd, dev->windoww, &sizew);
      if (status != SANE_STATUS_GOOD)
	return status;
      hexdump (DBG_info2, "urb 195 windoww", dev->windoww, sizew);
*/
            /* urb 196, write endpoint 02  stop program
       * 12 bytes 08 00 00 00 ff 01 00 00 00 00 00 00*/
  /*    dev->windoww[4] = 0xff;
      dev->windoww[5] = 0x01;
      status = sanei_usb_write_bulk (dev->fd, dev->windoww, &sizew);
      if (status != SANE_STATUS_GOOD)
	return status;
      hexdump (DBG_info2, "urb 196 windoww", dev->windoww, sizew);
*/
  status = SANE_STATUS_GOOD;
  DBG (DBG_proc, "soundvision_reset_vidcam: exit, status=%d\n", status);

  return status;
}


/* Inquiry a device and returns TRUE if is supported. */
static int
soundvision_identify_scanner (Soundvision_Vidcam * dev)
{
  SANE_Status status;
  SANE_Word vendor;
  SANE_Word product;
  int i;
  size_t sizew;			/* significant size of window */
  size_t sizer;

  DBG (DBG_info, "soundvision_identify_vidcam: open\n");
  
  sizew = dev->windoww_size;
  sizer = dev->windowr_size;

  memset (dev->windoww, 0, sizew);
  memset (dev->windowr, 0, sizer);

  status = sanei_usb_get_vendor_product (dev->fd, &vendor, &product);

  /* Loop through our list to make sure this scanner is supported. */
  for (i = 0; i < NELEMS (vidcams); i++)
    {
      if (vidcams[i].vendor == vendor && vidcams[i].product == product)
	{
	  unsigned char val;

	  DBG (DBG_info,
	       "soundvision_identify_scanner: scanner %x:%x is in list\n",
	       vendor, product);
	  dev->hw = &(vidcams[i]);

	   dev->init_loop_count=0; 
	  return SANE_TRUE;
	}
    }
  DBG (DBG_error,
       "soundvision_identify_vidcam: this is not a SOUNDVISION exit\n");
  return SANE_FALSE;
}

static SANE_Status
soundvision_vidcam_init (Soundvision_Vidcam * dev)
{
  SANE_Status status;
  size_t sizew;			/* significant size of window */
  size_t sizer;

  DBG (DBG_proc, "soundvision_vidcam_init: open\n");
  
  sizew = dev->windoww_size;
  sizer = dev->windowr_size;

  memset (dev->windoww, 0, sizew);
  memset (dev->windowr, 0, sizer);

   /* urb 3: configuration = 0,  interface = 0, alternate = 0 */
	  status = soundvision_set_config (dev, 0, 0, 0);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_error,
		   "soundvision_vidcam_init: SOUNDVISION FAILED to set configure\n");
	      return status;
	    }
	   usleep (4000);

  if (dev->init_loop_count == 0) {
         
	 	  /* checkend 83 is bulk endpoint read, 0x02 is bulk write, seems there is 
	   * no control and no isochronous mode used in agfa twain software */
	  
	  /* urb 4, write bulk endpoint 02   12 bytes 08 00 00 00 06 01 00 00 00 00 00 00 */
	  sizew = 0x0c;
	  dev->windoww[0] = 0x08;
	  dev->windoww[4] = 0x06;
	  dev->windoww[5] = 0x01;
	  status = sanei_usb_write_bulk (dev->fd, dev->windoww, &sizew);
	  if (status != SANE_STATUS_GOOD)
	    return status;
	  hexdump (DBG_info2, "urb 4 Set data firmware version ready", dev->windoww, sizew);
          
	  usleep (4000);
	  /* urb 5, first read  endpoint 83,  32 2e 31 36 61 00 00 00 */
	  sizer = 0x08;
	  status = sanei_usb_read_bulk (dev->fd, dev->windowr, &sizer);
	  if (status != SANE_STATUS_GOOD)
	    return status;

	  hexdump (DBG_info2, "urb 5 Read firmware version 32 2e 31 36 61 => 2.16a",
		   dev->windowr, sizer);

	  dev->windoww[0] = 0x08;	/* other data is 0x00 */
	  dev->windoww[4] = 0x14;
	  dev->windoww[5] = 0x01;
	  /* urb 6, write endpoint 02   12 bytes 08 00 00 00 14 01 00 00 00 00 00 00 */
	  status = sanei_usb_write_bulk (dev->fd, dev->windoww, &sizew);
	  if (status != SANE_STATUS_GOOD)
	    return status;
	  hexdump (DBG_info2, "urb 6 Set data ?table from camera ready", dev->windoww, sizew);

	  usleep (4000);

	  /* urb 7, read endpoint 83  60 bytes */
	  sizer = 0x60;
	  status = sanei_usb_read_bulk (dev->fd, dev->windowr, &sizer);
	  if (status != SANE_STATUS_GOOD)
	    return status;

	  hexdump (DBG_info2, "urb 7 Read ?table from camera", dev->windowr, sizer);

  sizew = 0x0c;
  dev->windoww[0] = 0x08;
  dev->windoww[4] = 0x01;
  dev->windoww[5] = 0x00;
  /* urb 8, write endpoint 02   12 bytes 08 00 00 00 01 00 00 00 00 00 00 00 */
  status = sanei_usb_write_bulk (dev->fd, dev->windoww, &sizew);
  if (status != SANE_STATUS_GOOD)
    return status;

  hexdump (DBG_info2, "urb 8 windoww", dev->windoww, sizew);
  
  usleep (4000);

  dev->windoww[4] = 0x04;
  dev->windoww[8] = 0x05;
  /* urb 9, write endpoint 02   0x0c bytes 08 00 00 00 04 00 00 00 05 00 00 00 */
  status = sanei_usb_write_bulk (dev->fd, dev->windoww, &sizew);
  if (status != SANE_STATUS_GOOD)
    return status;
  hexdump (DBG_info2, "urb 9 windoww", dev->windoww, sizew);

  usleep (4000);

  dev->windoww[4] = 0x92;
  dev->windoww[8] = 0x00;
  /* urb 10, write endpoint 02   12 bytes 08 00 00 00 92 00 00 00 00 00 00 00 */
  status = sanei_usb_write_bulk (dev->fd, dev->windoww, &sizew);
  if (status != SANE_STATUS_GOOD)
    return status;
  hexdump (DBG_info2, "urb 10 windoww", dev->windoww, sizew);

  }
  dev->init_loop_count = 1;
  status = (SANE_STATUS_GOOD);
  
  return status;
}

static SANE_Status
soundvision_scan (Soundvision_Vidcam * dev)
{
  SANE_Status status;

  DBG (DBG_proc, "soundvision_scan: enter\n");


  status = SANE_STATUS_GOOD;

  DBG (DBG_proc, "soundvision_scan: exit, status=%d\n", status);

  return status;
}


/* Attach a vidcam to this backend. */
static SANE_Status
attach_vidcam (SANE_String_Const devicename, Soundvision_Vidcam ** devp)
{
  Soundvision_Vidcam *dev;
  int fd;

  DBG (DBG_proc, "attach_vidcam: %s\n", devicename);

  if (devp)
    *devp = NULL;

  /* Check if we know this device name. */
  for (dev = first_dev; dev; dev = dev->next)
    {
      if (strcmp (dev->sane.name, devicename) == 0)
	{
	  if (devp)
	    {
	      *devp = dev;
	    }
	  DBG (DBG_info, "device is already known\n");
	  return SANE_STATUS_GOOD;
	}
    }

  /* Allocate a new vidcam entry. */
  dev = soundvision_init ();
  if (dev == NULL)
    {
      DBG (DBG_error, "ERROR: not enough memory\n");
      return SANE_STATUS_NO_MEM;
    }

  DBG (DBG_info, "attach_vidcam: opening USB device %s\n", devicename);

  if (sanei_usb_open (devicename, &fd) != 0)
    {
      DBG (DBG_error, "ERROR: attach_vidcam: open failed\n");
      soundvision_free (dev);
      return SANE_STATUS_INVAL;
    }
  /* Fill some scanner specific values. */
  dev->devicename = strdup (devicename);
  dev->fd = fd;

  /* Now, check that it is a vidcam we support. */

  if (soundvision_identify_scanner (dev) == SANE_FALSE)
    {
      DBG (DBG_error, "ERROR: attach_vidcam: vidcam-identification failed\n");
      soundvision_free (dev);
      return SANE_STATUS_INVAL;
    }
  
  /* Allocate a buffer memory. */
  dev = soundvision_init_2 ();
  if (dev == NULL)
    {
      DBG (DBG_error, "soundvision_initi_2, ERROR: not enough memory\n");
      return SANE_STATUS_NO_MEM;
    }

  soundvision_close (dev);

  /* Build list of vidcam supported resolutions. */
  DBG (DBG_proc, "attach_vidcam: build resolution list\n");

  if (dev->hw->color_adjust[0].resolution_x != 0)
    {
      int num_entries;
      int i;
      num_entries = 0;

      while (dev->hw->color_adjust[num_entries].resolution_x != 0)
	num_entries++;

      dev->resolutions_list = malloc (sizeof (SANE_Word) * (num_entries + 1));

      if (dev->resolutions_list == NULL)
	{
	  DBG (DBG_error,
	       "ERROR: attach_vidcam: vidcam resolution list failed\n");
	  soundvision_free (dev);
	  return SANE_STATUS_NO_MEM;
	}

      dev->resolutions_list[0] = num_entries;
      DBG (DBG_proc, "attach_vidcam: make color resolution table \n");
      for (i = 0; i < num_entries; i++)
	{
	  dev->resolutions_list[i + 1] =
	    dev->hw->color_adjust[i].resolution_x;
	}
    }
  else
    {
      dev->resolutions_list = NULL;
    }

  /* Set the default options for that vidcam. */
  dev->sane.name = dev->devicename;
  dev->sane.vendor = dev->hw->vendor_name;
  dev->sane.model = dev->hw->product_name;
  dev->sane.type = SANE_I18N ("vidcam/webcam");

  /* Link the vidcam with the others. */
  dev->next = first_dev;
  first_dev = dev;

  if (devp)
    {
      *devp = dev;
    }

  num_devices++;

  DBG (DBG_proc, "attach_vidcam: exit\n");

  return SANE_STATUS_GOOD;
}

static SANE_Status
attach_one (const char *dev)
{
  DBG (DBG_proc, "attach_one: open \n");
  attach_vidcam (dev, NULL);
  DBG (DBG_proc, "attach_one: exit \n");
  return SANE_STATUS_GOOD;
}

/* Reset the options for that vidcam. */
static void
soundvision_init_options (Soundvision_Vidcam * dev)
{
  int i;

  DBG (DBG_proc, "soundvision_init_options: open\n");

  /* Pre-initialize the options. */
  memset (dev->opt, 0, sizeof (dev->opt));
  memset (dev->val, 0, sizeof (dev->val));

  for (i = 0; i < OPT_NUM_OPTIONS; ++i)
    {
      dev->opt[i].size = sizeof (SANE_Word);
      dev->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
    }
  /*  Default settings */
  /*
   */
  DBG (DBG_proc,
       "soundvision_init_options: done loop opt_num_options=%d, i=%d \n",
       OPT_NUM_OPTIONS, i);
  /* Number of options. */
  dev->opt[OPT_NUM_OPTS].name = "";
  dev->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
  dev->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
  dev->val[OPT_NUM_OPTS].w = OPT_NUM_OPTIONS;

  /* Mode group */
  dev->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode");
  dev->opt[OPT_MODE_GROUP].desc = "";	/* not valid for a group */
  dev->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_MODE_GROUP].cap = 0;
  dev->opt[OPT_MODE_GROUP].size = 0;
  dev->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Scanner supported modes */
  dev->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
  dev->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
  dev->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
  dev->opt[OPT_MODE].type = SANE_TYPE_STRING;
  dev->opt[OPT_MODE].size = max_string_size (scan_mode_list);
  dev->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_MODE].constraint.string_list = scan_mode_list;
  dev->val[OPT_MODE].s = (SANE_Char *) strdup ("");	/* will be set later */

  /* X and Y resolution */
  dev->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].type = SANE_TYPE_INT;
  dev->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  dev->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->val[OPT_RESOLUTION].w = DEF_RESOLUTION;

  /* brightness   */
  dev->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
  dev->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
  dev->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
  dev->val[OPT_BRIGHTNESS].w = 0;	/* to get middle value */

  /* contrast     */
  dev->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
  dev->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
  dev->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
  dev->opt[OPT_CONTRAST].type = SANE_TYPE_INT;
  dev->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE;
  dev->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_CONTRAST].constraint.range = &contrast_range;
  dev->val[OPT_CONTRAST].w = 80;	/* to get middle value */

  /* hue          */
  dev->opt[OPT_HUE].name = SANE_NAME_HUE;
  dev->opt[OPT_HUE].title = SANE_TITLE_HUE;
  dev->opt[OPT_HUE].desc = SANE_DESC_HUE;
  dev->opt[OPT_HUE].type = SANE_TYPE_INT;
  dev->opt[OPT_HUE].unit = SANE_UNIT_NONE;
  dev->opt[OPT_HUE].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_HUE].constraint.range = &hue_range;
  dev->val[OPT_HUE].w = 02;	/* to get middle value */

  /* saturation   */
  dev->opt[OPT_SATURATION].name = SANE_NAME_SATURATION;
  dev->opt[OPT_SATURATION].title = SANE_TITLE_SATURATION;
  dev->opt[OPT_SATURATION].desc = SANE_DESC_SATURATION;
  dev->opt[OPT_SATURATION].type = SANE_TYPE_INT;
  dev->opt[OPT_SATURATION].unit = SANE_UNIT_NONE;
  dev->opt[OPT_SATURATION].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_SATURATION].constraint.range = &saturation_range;
  dev->val[OPT_SATURATION].w = 80;	/* to get middle value */


  /* Enhancement group */
  dev->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N ("Enhancement");
  dev->opt[OPT_ENHANCEMENT_GROUP].desc = "";	/* not valid for a group */
  dev->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_ENHANCEMENT_GROUP].cap = SANE_CAP_ADVANCED;
  dev->opt[OPT_ENHANCEMENT_GROUP].size = 0;
  dev->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Threshold */
  dev->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD;
  dev->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD;
  dev->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD;
  dev->opt[OPT_THRESHOLD].type = SANE_TYPE_INT;
  dev->opt[OPT_THRESHOLD].unit = SANE_UNIT_NONE;
  dev->opt[OPT_THRESHOLD].size = sizeof (SANE_Int);
  dev->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_THRESHOLD].constraint.range = &threshold_range;
  dev->val[OPT_THRESHOLD].w = 128;

  /* black level correction */
  dev->opt[OPT_BLACK_LEVEL].name = SANE_NAME_BLACK_LEVEL;
  dev->opt[OPT_BLACK_LEVEL].title = SANE_TITLE_BLACK_LEVEL;
  dev->opt[OPT_BLACK_LEVEL].desc = SANE_DESC_BLACK_LEVEL;
  dev->opt[OPT_BLACK_LEVEL].type = SANE_TYPE_INT;
  dev->opt[OPT_BLACK_LEVEL].unit = SANE_UNIT_NONE;
  dev->opt[OPT_BLACK_LEVEL].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BLACK_LEVEL].constraint.range = &black_level_range;
  dev->val[OPT_BLACK_LEVEL].w = 02;	/* to get middle value */

  /* red level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_R].name = SANE_NAME_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].title = SANE_TITLE_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].desc = SANE_DESC_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_R].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_R].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_R].constraint.range = &red_level_range;
  dev->val[OPT_WHITE_LEVEL_R].w = 00;	/* to get middle value */

  /* green level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_G].name = SANE_NAME_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].title = SANE_TITLE_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].desc = SANE_DESC_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_G].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_G].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_G].constraint.range = &green_level_range;
  dev->val[OPT_WHITE_LEVEL_G].w = 00;	/* to get middle value */

  /* blue level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_B].name = SANE_NAME_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].title = SANE_TITLE_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].desc = SANE_DESC_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_B].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_B].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_B].constraint.range = &blue_level_range;
  dev->val[OPT_WHITE_LEVEL_B].w = 00;	/* to get middle value */

  /* Lastly, set the default scan mode. This might change some
   * values previously set here. */

  sane_control_option (dev, OPT_MODE, SANE_ACTION_SET_VALUE,
		       (SANE_String_Const *) scan_mode_list[0], NULL);
  DBG (DBG_proc, "soundvision_init_options: exit\n");
}

/* Read the image from the vidcam and fill the temporary buffer with it. */
static SANE_Status
soundvision_fill_image (Soundvision_Vidcam * dev)
{
  SANE_Status status;
  size_t size;
  size_t sizew;			/* significant size of window */
  size_t sizer;
  int i=0;

  DBG (DBG_proc, "soundvision_fill_image: enter\n");

  dev->image_begin = 0;
  dev->image_end = 0;

  sizew = dev->windoww_size;
  sizer = dev->windowr_size;

  /* Check that window is big enough */
  assert (dev->image_begin == dev->image_end);
  /* assert (dev->real_bytes_left > 0);  */
  
  memset (dev->windoww, 0, sizew);
  memset (dev->windowr, 0, sizer);

  /* urb 11  get frame length   f8880784 nr. 000002bb, usb command?????*/

  /*  0xc0000e00    */
  /* repeat loop ????????????????????????????????? after urb 15 */
  usleep (4000);
  sizew =0x0c;
  dev->windoww[0] = 0x08;
  dev->windoww[4] = 0x07;
  dev->windoww[5] = 0x01;
  /* urb 12, 16, write endpoint 02   12 bytes 08 00 00 00 07 01 00 00 00 00 00 00 */
  status = sanei_usb_write_bulk (dev->fd, dev->windoww, &sizew);
  if (status != SANE_STATUS_GOOD)
    return status;

  hexdump (DBG_info2, "urb 12 start command get frame", dev->windoww, sizew);

  /* urb 13, 17 first read  endpoint 83 0x00e40c00 urb 17 28c40c00 
   * some times skipped, direct reading frame, selection choice ?
   *  4 bytes, should be something like framelength of  image
   *  if 00 e4 0c 00 maybe start of frame ready ??
   *  first byte maybe light level, ff dark, 00 light?
   *
   *  bulk read fails to read complete frame because length of frame?
   *  reading of size < length is ok
   *  reading data when size > length is a problem, solution?
   *  length can be between 0xe000 to 19fff for 320x160 */

  usleep (4000);
  sizer = 0x04;

  status = sanei_usb_read_bulk (dev->fd, dev->windowr, &sizer);
  if (status != SANE_STATUS_GOOD)
    return status;

  hexdump (DBG_info2, "urb 13 Read 4 bytes info frame ?first byte ff -00 (dark-light)??", dev->windowr, sizer);
 /* dev->real_bytes_left = 0x19fff; */ /* max size image ?*/
  dev->real_bytes_left = 0xf000;
  dev->bytes_left      = dev->real_bytes_left;
  DBG (DBG_proc, "soundvision_fill_image: size of next frame %x\n",
       dev->real_bytes_left);
  while (dev->real_bytes_left)
    {
      /* * Try to read the maximum number of bytes.  */
      DBG (DBG_proc,
	   "soundvision_fill_image: real dev bytes left, while loop=0x%x \n",
	   dev->real_bytes_left);

      size = dev->real_bytes_left;

      if (size == 0)
	{
	  /* Probably reached the end of the buffer.  Check, just in case. */
	  assert (dev->image_end != 0);
	  return (SANE_STATUS_GOOD);
	}

      /* Do the transfer */
      DBG (DBG_proc, "soundvision_fill_image: size= 0x%x\n", size);
      
      /* urb 14, 18 first read frame image endpoint 83 */
      status = sanei_usb_read_bulk (dev->fd, dev->buffer, &size);

      if (status != SANE_STATUS_GOOD)
	return status;

      DBG (DBG_info,
	   "soundvision_fill_image: size read = 0x%lx bytes (bpl=0x%x)\n",
	   (long) size, dev->params.bytes_per_line);
    sizew = 0x20;
    hexdump (DBG_info2, "first 0x20 bytes from read frame", dev->buffer, sizew);
    
         
     memcpy (dev->image + dev->image_end, dev->buffer, size);
      i++;
      dev->image_end += size;
      if (size == 0xf000) {
        dev->real_bytes_left = 0xf000; 
        dev->bytes_left += size;
   usleep (2000);
  sizew =0x0c;
  dev->windoww[0] = 0x08;
  dev->windoww[4] = 0x07;
  dev->windoww[5] = 0x01;
  /* urb 12-2, 16, write endpoint 02   12 bytes 08 00 00 00 07 01 00 00 00 00 00 00 */
  status = sanei_usb_write_bulk (dev->fd, dev->windoww, &sizew);
  if (status != SANE_STATUS_GOOD)
    return status;

  hexdump (DBG_info2, "urb 12-2 start command get frame", dev->windoww, sizew);

  usleep (2000);
  sizer = 0x04;

  status = sanei_usb_read_bulk (dev->fd, dev->windowr, &sizer);
  if (status != SANE_STATUS_GOOD)
    return status;

  hexdump (DBG_info2, "urb 13-2 Read 4 bytes info frame ?first byte ff -00 (dark-light)??", dev->windowr, sizer);

      }
      else if (dev->real_bytes_left > size) {
	dev->real_bytes_left = 0x00;
      dev->bytes_left += size;
      }
      else if (dev->real_bytes_left < size) {
	      dev->bytes_left += (size - dev->real_bytes_left);
      dev->real_bytes_left =0x00;
      }
     
    }
     usleep (4000); 

      DBG (DBG_info, "soundvision_fill_image: real bytes left = 0x%lx\n",
	   (long) dev->real_bytes_left);

      /* urb 15, write endpoint 02  command for ????
       * 12 bytes 08 00 00 00 1a 80 00 00 00 00 00 00 , release read buffer?????*/
/*      sizew= 0x0c;
      dev->windoww[0] = 0x08;
      dev->windoww[4] = 0x1a;
      dev->windoww[5] = 0x80;
      status = sanei_usb_write_bulk (dev->fd, dev->windoww, &sizew);
      if (status != SANE_STATUS_GOOD)
	return status;
      hexdump (DBG_info2, "urb 15 windoww", dev->windoww, sizew);
*/
  status = (SANE_STATUS_GOOD);
  DBG (DBG_proc, "soundvision_fill_image: exit\n");
  return status;
}

/**********************************************************************
 * Video Decoding
 **********************************************************************/
/*******  routines from the pencam program; soundvision kernel module  ********/
/*
 * STV0680 Vision Camera Chipset Driver
 * Copyright (C) 2000 Adam Harrison <adam@antispin.org> 
*/

static SANE_Status
soundvision_bayer_unshuffle (Soundvision_Vidcam * dev, SANE_Byte * buf,
			     size_t * len)
{
  SANE_Status status;
  size_t size;
  int x, y, i, z;
  int vw = dev->x_resolution;
  int vh = dev->y_resolution;
  int colour = 0, bayer = 0;
  size_t count1;
  SANE_Byte p = 0;
  SANE_Byte output[640 * 480 * 3];
  size_t sizeb;
 int bright_red;
  int bright_green;
  int bright_blue;
  int count;

#define RED 0
#define GREEN 1
#define BLUE 2
#define AD(x, y, w) (((y)*(w)+(x))*3)

  DBG (DBG_proc, "soundvision_bayer_unshuffle: enter, len=0x%x\n", len);

  size = 0x40;
  hexdump (DBG_proc, " soundvsion_bayer_unshuffle: header raw image", buf,
	   size);

  /* copy from buf to output, converted data will be placed in buf */
  sizeb = 3 * vw * vh;
  memcpy (output, buf, sizeb);

/* brightness adjustment                            */

  count = vw * vh * dev->bytes_pixel;

  bright_red = (dev->val[OPT_BRIGHTNESS].w) + (dev->val[OPT_WHITE_LEVEL_R].w);
  bright_green =
    (dev->val[OPT_BRIGHTNESS].w) + (dev->val[OPT_WHITE_LEVEL_G].w);
  bright_blue =
    (dev->val[OPT_BRIGHTNESS].w) + (dev->val[OPT_WHITE_LEVEL_B].w);

  for (x = 0; x < count; x++)
    {
      y = x + 1;
      z = x + 2;
      if ((*(dev->output + x) + bright_red) >= 255)
	*(dev->output + x) = 255;

      else if ((*(dev->output + x) + bright_red) <= 0)
	*(dev->output + x) = 0;
      else
	*(dev->output + x) += bright_red;

      if ((*(dev->output + y) + bright_green) >= 255)
	*(dev->output + y) = 255;

      else if ((*(dev->output + y) + bright_green) <= 0)
	*(dev->output + y) = 0;
      else
	*(dev->output + y) += bright_green;

      if ((*(dev->output + z) + bright_blue) >= 255)
	*(dev->output + z) = 255;

      else if ((*(dev->output + z) + bright_blue) <= 0)
	*(dev->output + z) = 0;
      else
	*(dev->output + z) += bright_blue;

      x += 2;
    }
    DBG (DBG_proc, "sipix_bayer_unshuffle: brightness done\n");

  /* copy from output to buf,  */
  size = vw * vh * 3;

  memcpy (buf, output, size);
  DBG (DBG_proc, "soundvision_bayer_unshuffle: exit vw = %d. vh = %d\n", vw,
       vh);
  status = (SANE_STATUS_GOOD);
  return status;
}				/* bayer_unshuffle */

/*******  end routines from the modifyed pencam program soundvision kernel module *********/
/*
 * converter_fill_buffer - Fill line buffer with next input line from image.  
 * 	Currently assumes jpeg, but this is where we would put the switch 
 * 	to handle other image types.
 */
static SANE_Int
converter_fill_buffer (void)
{

/* 
 * FIXME:  Current implementation reads one scan line at a time.  Part
 * of the reason for this is in the original code is to give the frontend 
 * a chance to update  * the progress marker periodically.  Since the gphoto2
 * driver sucks in the whole image before decoding it, perhaps we could
 * come up with a simpler implementation.
 */

  SANE_Int lines = 1;
  SANE_Byte linebuffer;

  (void) jpeg_read_scanlines (&cinfo, dest_mgr->buffer, lines);
  (*dest_mgr->put_pixel_rows) (&cinfo, dest_mgr, lines, (char *) linebuffer);

  return cinfo.output_width * cinfo.output_components * lines;
}


/*
 * converter_scan_complete  - Check if all the data for the image has been read. 
 *	Currently assumes jpeg, but this is where we would put the 
 *	switch to handle other image types.
 */
static SANE_Bool
converter_scan_complete (void)
{
  if (cinfo.output_scanline >= cinfo.output_height)
    {
      return SANE_TRUE;
    }
  else
    {
      return SANE_FALSE;
    }
}

/*
 * converter_init  - Initialize image conversion data.
 *	Currently assumes jpeg, but this is where we would put the 
 *	switch to handle other image types.
 */
static SANE_Status
converter_init (SANE_Handle handle)
{
  Soundvision_Vidcam *dev = handle;
  SANE_Int row_stride;
  struct jpeg_error_mgr jerr;
  my_src_ptr src;

  int jpeg_init_source = 0;
  int data_file_current_index = 0;
  SANE_Byte data_ptr[2];

  /* Basic check to see if this is really a jpeg file */
  if (data_ptr[0] != 0xff || data_ptr[1] != 0xd8)
    {
      sane_cancel (handle);
      exit (1);
      return SANE_STATUS_INVAL;
    }

  cinfo.err = jpeg_std_error (&jerr);
  jpeg_create_decompress (&cinfo);

  cinfo.src =
    (struct jpeg_source_mgr *) (*cinfo.mem->
				alloc_small) ((j_common_ptr) & cinfo,
					      JPOOL_PERMANENT,
					      sizeof (my_source_mgr));
  src = (my_src_ptr) cinfo.src;

  src->buffer = (JOCTET *) (*cinfo.mem->alloc_small) ((j_common_ptr) &
						      cinfo,
						      JPOOL_PERMANENT,
						      1024 * sizeof (JOCTET));
  src->pub.init_source = jpeg_init_source;
  src->pub.fill_input_buffer = jpeg_fill_input_buffer;
  src->pub.skip_input_data = jpeg_skip_input_data;
  src->pub.resync_to_restart = jpeg_resync_to_restart;	/* default */
  src->pub.term_source = jpeg_term_source;
  src->pub.bytes_in_buffer = 0;
  src->pub.next_input_byte = NULL;

  (void) jpeg_read_header (&cinfo, TRUE);
  dest_mgr = sanei_jpeg_jinit_write_ppm (&cinfo);
  (void) jpeg_start_decompress (&cinfo);

  row_stride = cinfo.output_width * cinfo.output_components;

  dev->params.bytes_per_line = cinfo.output_width * 3;	/* 3 colors */
  dev->params.pixels_per_line = cinfo.output_width;
  dev->params.lines = cinfo.output_height;

  int linebuffer_size = 0;
  int linebuffer_index = 0;

  return (SANE_STATUS_GOOD);
}

/* Stop a scan. */
static SANE_Status
do_cancel (Soundvision_Vidcam * dev)
{
  DBG (DBG_sane_proc, "do_cancel enter\n");

  if (dev->scanning == SANE_TRUE)
    {

      /* Reset the vidcam */
      soundvision_reset_vidcam (dev);
      soundvision_close (dev);
    }

  dev->scanning = SANE_FALSE;

  DBG (DBG_sane_proc, "do_cancel exit\n");

  return SANE_STATUS_CANCELLED;
}

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

/* Sane entry points */

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

  DBG_INIT ();

  DBG (DBG_sane_init, "sane_init\n");

  authorize = authorize;	/* silence gcc */

  DBG (DBG_error, "This is sane-soundvision version %d.%d-%d\n", V_MAJOR,
       V_MINOR, BUILD);
  DBG (DBG_error, "(C) 2004 by Gerard Klaver\n");

  if (version_code)
    {
      *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, BUILD);
    }

  sanei_usb_init ();

  fp = sanei_config_open (SOUNDVISION1_CONFIG_FILE);
  if (!fp)
    {
      /* No default vidcam? */
      DBG (DBG_warning, "configuration file not found (%s)\n",
	   SOUNDVISION1_CONFIG_FILE);

      return SANE_STATUS_GOOD;
    }

  while (sanei_config_read (line, sizeof (line), fp))
    {
      SANE_Word vendor;
      SANE_Word product;

      if (line[0] == '#')	/* ignore line comments */
	continue;
      len = strlen (line);

      if (!len)
	continue;		/* ignore empty lines */
      if (sscanf (line, "usb %i %i", &vendor, &product) == 2)
	{

	  sanei_usb_attach_matching_devices (line, attach_one);
	}
      else
	{
	  /* Garbage. Ignore. */
	  DBG (DBG_warning, "bad configuration line: \"%s\" - ignoring.\n",
	       line);
	}

    }

  fclose (fp);

  DBG (DBG_proc, "sane_init: leave\n");

  return SANE_STATUS_GOOD;
}

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

  DBG (DBG_proc, "sane_get_devices: enter\n");

  local_only = local_only;	/* silence gcc */

  if (devlist)
    free (devlist);

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

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

  *device_list = devlist;

  DBG (DBG_proc, "sane_get_devices: exit\n");

  return SANE_STATUS_GOOD;
}


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

  DBG (DBG_proc, "sane_open: enter\n");

  /* search for devicename */
  if (devicename[0])
    {
      DBG (DBG_info, "sane_open: devicename=%s\n", devicename);

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

      if (!dev)
	{
	  status = attach_vidcam (devicename, &dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      return status;
	    }
	}
    }
  else
    {
      DBG (DBG_sane_info, "sane_open: no devicename, opening first device\n");
      dev = first_dev;		/* empty devicename -> use first device */
    }

  if (!dev)
    {
      DBG (DBG_error, "No vidcam found\n");

      return SANE_STATUS_INVAL;
    }

  soundvision_init_options (dev);

  *handle = dev;

  DBG (DBG_proc, "sane_open: exit\n");

  return SANE_STATUS_GOOD;
}


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

  DBG (DBG_proc, "sane_get_option_descriptor: enter, option %d\n", option);

  if ((unsigned) option >= OPT_NUM_OPTIONS)
    {
      return NULL;
    }

  DBG (DBG_proc, "sane_get_option_descriptor: exit\n");

  return dev->opt + option;
}


SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val, SANE_Int * info)
{
  Soundvision_Vidcam *dev = handle;
  SANE_Status status;
  SANE_Word cap;

  DBG (DBG_proc, "sane_control_option: enter, option %d, action %d\n",
       option, action);

  if (info)
    {
      *info = 0;
    }

  if (dev->scanning)
    {
      return SANE_STATUS_DEVICE_BUSY;
    }

  if (option < 0 || option >= OPT_NUM_OPTIONS)
    {
      return SANE_STATUS_INVAL;
    }

  cap = dev->opt[option].cap;
  if (!SANE_OPTION_IS_ACTIVE (cap))
    {
      return SANE_STATUS_INVAL;
    }

  if (action == SANE_ACTION_GET_VALUE)
    {

      switch (option)
	{
	  /* word options */
	case OPT_NUM_OPTS:
	case OPT_RESOLUTION:
	case OPT_BRIGHTNESS:
	case OPT_CONTRAST:
	case OPT_HUE:
	case OPT_SATURATION:
	case OPT_THRESHOLD:
	case OPT_BLACK_LEVEL:
	case OPT_WHITE_LEVEL_R:
	case OPT_WHITE_LEVEL_G:
	case OPT_WHITE_LEVEL_B:
	  *(SANE_Word *) val = dev->val[option].w;
	  return SANE_STATUS_GOOD;
	case OPT_MODE:
	  strcpy (val, dev->val[option].s);
	  return SANE_STATUS_GOOD;
	default:
	  return SANE_STATUS_INVAL;
	}
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {

      if (!SANE_OPTION_IS_SETTABLE (cap))
	{
	  DBG (DBG_error, "could not set option, not settable\n");
	  return SANE_STATUS_INVAL;
	}

      status = sanei_constrain_value (dev->opt + option, val, info);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "could not set option, invalid value\n");
	  return status;
	}

      switch (option)
	{

	  /* Numeric side-effect options */
	case OPT_THRESHOLD:
	case OPT_RESOLUTION:
	case OPT_BRIGHTNESS:
	case OPT_CONTRAST:
	case OPT_HUE:
	case OPT_SATURATION:
	case OPT_BLACK_LEVEL:
	case OPT_WHITE_LEVEL_R:
	case OPT_WHITE_LEVEL_G:
	case OPT_WHITE_LEVEL_B:
	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_PARAMS;
	    }
	  dev->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	  /* String side-effect options */
	case OPT_MODE:
	  if (strcmp (dev->val[option].s, val) == 0)
	    return SANE_STATUS_GOOD;

	  free (dev->val[OPT_MODE].s);
	  dev->val[OPT_MODE].s = (SANE_Char *) strdup (val);

	  dev->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
	  dev->opt[OPT_BLACK_LEVEL].cap &= ~SANE_CAP_INACTIVE;
	  dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	  dev->opt[OPT_WHITE_LEVEL_G].cap &= ~SANE_CAP_INACTIVE;
	  dev->opt[OPT_WHITE_LEVEL_B].cap &= ~SANE_CAP_INACTIVE;

	  dev->depth = 8;



	  if (strcmp (dev->val[OPT_MODE].s, COLOR_RAW_STR) == 0)
	    {
	      dev->scan_mode = SOUNDVISION_COLOR_RAW;

	    }
	  else if (strcmp (dev->val[OPT_MODE].s, COLOR_RGB_STR) == 0)
	    {
	      dev->scan_mode = SOUNDVISION_COLOR_RGB;

	    }
	  else if (strcmp (dev->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_COLOR)
		   == 0)
	    {
	      dev->scan_mode = SOUNDVISION_COLOR;

	    }
         else if (strcmp (dev->val[OPT_MODE].s, COLOR_RGB_TEXT_STR) == 0)
	    dev->scan_mode = SOUNDVISION_COLOR_RGB_TEXT;

	  /* The soundvision  supports only a handful of resolution. */
	  /* This the default resolution range for the SOUNDVISION_532 */

	  if (dev->resolutions_list != NULL)
	    {
	      int i;

	      dev->opt[OPT_RESOLUTION].constraint_type =
		SANE_CONSTRAINT_WORD_LIST;
	      dev->opt[OPT_RESOLUTION].constraint.word_list =
		dev->resolutions_list;

	      /* If the resolution isn't in the list, set a default. */
	      for (i = 1; i <= dev->resolutions_list[0]; i++)
		{
		  if (dev->resolutions_list[i] >= dev->val[OPT_RESOLUTION].w)
		    break;
		}
	      if (i > dev->resolutions_list[0])
		{
		  /* Too big. Take default. */
		  dev->val[OPT_RESOLUTION].w = DEF_RESOLUTION;
		}
	      else
		{
		  /* Take immediate superioir value. */
		  dev->val[OPT_RESOLUTION].w = dev->resolutions_list[i];
		}
	    }

	  /* String side-effect options */

	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
	    }
	  return SANE_STATUS_GOOD;
	default:
	  return SANE_STATUS_INVAL;
	}
    }

  DBG (DBG_proc, "sane_control_option: exit, bad\n");

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  Soundvision_Vidcam *dev = handle;
  int bytes_pixel;

  DBG (DBG_proc, "sane_get_parameters: enter\n");

  if (!(dev->scanning))
    {

      /*  dev->x_resolution = dev->val[OPT_RESOLUTION].w;
         dev->y_resolution = dev->val[OPT_RESOLUTION].w;  */
      switch (dev->val[OPT_RESOLUTION].w)
	{
	case 160:
	  dev->x_resolution = 160;
	  dev->y_resolution = 120;
	  break;
	case 320:
	  dev->x_resolution = 320;
	  dev->y_resolution = 240;
	  break;
	}

      switch (dev->scan_mode)
	{
	case SOUNDVISION_COLOR_RAW:
	  bytes_pixel = 3;
	  break;
	case SOUNDVISION_COLOR_RGB:
	case SOUNDVISION_COLOR_RGB_TEXT:
	  bytes_pixel = 3;
	  break;
	case SOUNDVISION_COLOR:
	  bytes_pixel = 3;
	  break;
	}
      /* Prepare the parameters for the caller. */
      memset (&dev->params, 0, sizeof (SANE_Parameters));

      dev->params.last_frame = SANE_TRUE;

      dev->params.format = SANE_FRAME_RGB;
      dev->params.pixels_per_line = dev->x_resolution;
      dev->params.bytes_per_line = dev->params.pixels_per_line * bytes_pixel;
      dev->params.depth = 8;

      dev->params.lines = dev->y_resolution;
    }

  /* Return the current values. */
  if (params)
    {
      *params = (dev->params);
    }

  DBG (DBG_proc, "sane_get_parameters: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle handle)
{
  Soundvision_Vidcam *dev = handle;
  SANE_Status status;

  DBG (DBG_proc, "sane_start: enter\n");

  if (!(dev->scanning))
    {
      sane_get_parameters (dev, NULL);

      /* Open again the vidcam  */
      if (sanei_usb_open (dev->devicename, &(dev->fd)) != 0)
	{
	  DBG (DBG_error, "ERROR: sane_start: open failed\n");
	  return SANE_STATUS_INVAL;
	}

      /* Initialize the vidcam. */
      status = soundvision_vidcam_init (dev);
      if (status)
	{
	  DBG (DBG_error, "ERROR: failed to init the vidcam\n");
	  soundvision_close (dev);
	  return status;
	}

      dev->image_size = dev->buffer_size;
      dev->image = malloc (dev->image_size);
      if (dev->image == NULL)
	{
	  return SANE_STATUS_NO_MEM;
	}

      status = soundvision_scan (dev);
      if (status)
	{
	  soundvision_close (dev);
	  return status;
	}

    }

  dev->image_end = 0;
  dev->image_begin = 0;

/*  dev->bytes_left = dev->params.bytes_per_line * dev->params.lines;
  dev->real_bytes_left = dev->params.bytes_per_line * dev->params.lines;
*/
  dev->bytes_left = 0x1ffff;
  dev->real_bytes_left= 0x1ffff;  /* max size for .jpg file at the moment */

  dev->scanning = SANE_TRUE;

  DBG (DBG_proc, "sane_start: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
	   SANE_Int * len)
{
  SANE_Status status;
  Soundvision_Vidcam *dev = handle;
  size_t size;
  size_t sizel;
  int buf_offset;		/* offset into buf */
  DBG (DBG_proc, "sane_read: enter\n");

  *len = 0;

  if (!(dev->scanning))
    {
      DBG (DBG_proc, "sane_read: before do_cancel enter\n");
      /* OOPS, not scanning */
      return do_cancel (dev);
    }

  if (dev->bytes_left <= 0)
    {
      return (SANE_STATUS_EOF);
    }

  buf_offset = 0;

  do
    {
      if (dev->image_begin == dev->image_end)
	{
	  /* Fill image */
	  status = soundvision_fill_image (dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      return (status);
	    }
	}

      /* Something must have been read */
      if (dev->image_begin == dev->image_end)
	{
	  DBG (DBG_info, "sane_read: nothing read\n");
	  return SANE_STATUS_IO_ERROR;
	}

      /* Copy the data to the frontend buffer. */
      size = max_len - buf_offset;
      if (size > dev->bytes_left)
	{
	  size = dev->bytes_left;
	}

      sizel = dev->image_end - dev->image_begin;
      if (sizel > size)
	{
	  sizel = size;
	}
      size = sizel;

      DBG (DBG_info, "sane_read: size =0x%x bytes, max_len=0x%x bytes\n",
	   size, max_len);

      memcpy (buf + buf_offset, dev->image + dev->image_begin, size);

      dev->image_begin += sizel;
      buf_offset += size;

      dev->bytes_left -= size;
      *len += size;
      if ((dev->x_resolution == 160) || (dev->x_resolution == 320))
	{			/* jpeg format < bmp format */
	  dev->bytes_left = dev->real_bytes_left;
	}
    }
  while ((buf_offset != max_len) && dev->bytes_left);

  if (dev->scan_mode != SOUNDVISION_COLOR_RAW)
    {

      /* do bayer unshuffle  after complete frame is read, return data in buf */
      DBG (DBG_info, "sane read test line\n");
      status = soundvision_bayer_unshuffle (dev, buf, &size);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_info,
	       "sane_read: soundvision_bayer_unshuffle status NOK\n");
	  return (status);
	}
    }
  else
    {
      usleep (20000);		/* some time compensation  */
      DBG (DBG_info, "sane_read: raw mode\n");
    }

  DBG (DBG_info, "sane_read: leave, bytes_left=%x\n", dev->bytes_left);

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{

  DBG (DBG_proc, "sane_set_io_mode: enter\n");

  handle = handle;		/* silence gcc */
  non_blocking = non_blocking;	/* silence gcc */


  DBG (DBG_proc, "sane_set_io_mode: exit\n");

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  DBG (DBG_proc, "sane_get_select_fd: enter\n");

  handle = handle;		/* silence gcc */
  fd = fd;			/* silence gcc */

  DBG (DBG_proc, "sane_get_select_fd: exit\n");

  return SANE_STATUS_UNSUPPORTED;
}

void
sane_cancel (SANE_Handle handle)
{
  Soundvision_Vidcam *dev = handle;

  DBG (DBG_proc, "sane_cancel: enter\n");

  do_cancel (dev);

  DBG (DBG_proc, "sane_cancel: exit\n");
}

void
sane_close (SANE_Handle handle)
{
  Soundvision_Vidcam *dev = handle;
  Soundvision_Vidcam *dev_tmp;

  DBG (DBG_proc, "sane_close: enter\n");

  do_cancel (dev);
  soundvision_close (dev);

  /* Unlink dev. */
  if (first_dev == dev)
    {
      first_dev = dev->next;
    }
  else
    {
      dev_tmp = first_dev;
      while (dev_tmp->next && dev_tmp->next != dev)
	{
	  dev_tmp = dev_tmp->next;
	}
      if (dev_tmp->next != NULL)
	{
	  dev_tmp->next = dev_tmp->next->next;
	}
    }

  soundvision_free (dev);
  num_devices--;

  DBG (DBG_proc, "sane_close: exit\n");
}

void
sane_exit (void)
{
  DBG (DBG_proc, "sane_exit: enter\n");

  while (first_dev)
    {
      sane_close (first_dev);
    }

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

  DBG (DBG_proc, "sane_exit: exit\n");
}
