kopia lustrzana https://gitlab.com/sane-project/backends
2181 wiersze
56 KiB
C
2181 wiersze
56 KiB
C
![]() |
/* sane - Scanner Access Now Easy.
|
||
|
|
||
|
Copyright (C) 2004 - 2006 Gerard Klaver <gerard at gkall dot hobby dot nl>
|
||
|
|
||
|
The teco2 and gl646 backend (Frank Zago) are used as a template for
|
||
|
this backend.
|
||
|
|
||
|
For the usb commands and bayer decoding parts of the following
|
||
|
program are used:
|
||
|
The pencam2 program (GNU GPL license 2)
|
||
|
|
||
|
For the usb commands parts of the following programs are used:
|
||
|
The libgphoto2 (camlib stv0680) (GNU GPL license 2)
|
||
|
The stv680.c/.h kernel module (GNU GPL license 2)
|
||
|
|
||
|
For the stv680_add_text routine the add_text routine and font_6x11.h file
|
||
|
are taken from the webcam.c file, part of xawtv program,
|
||
|
(c) 1998-2002 Gerd Knorr (GNU GPL license 2).
|
||
|
|
||
|
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.
|
||
|
------------------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
/* $Id$
|
||
|
|
||
|
stv680 vidcam 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_read called multiple times;
|
||
|
. . after sane_read returns EOF)
|
||
|
. . go back to sane_start() if more frames desired
|
||
|
. . - sane_cancel() : cancel operation
|
||
|
. - sane_close() : close opened vidcam device
|
||
|
- sane_exit() : terminate use of backend
|
||
|
*/
|
||
|
/*--------------------------------------------------------------------------*/
|
||
|
|
||
|
#define BUILD 1 /* 2004/09/09 update 20-04-2006 */
|
||
|
#define BACKEND_NAME stv680
|
||
|
#define STV680_CONFIG_FILE "stv680.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_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 "../include/font_6x11.h"
|
||
|
/*-----------------------*/
|
||
|
|
||
|
#include "stv680.h"
|
||
|
|
||
|
#define TIMEOUT 1000
|
||
|
|
||
|
/*--------------------------------------------------------------------------*/
|
||
|
/* Lists of possible scan modes. */
|
||
|
static SANE_String_Const scan_mode_list[] = {
|
||
|
COLOR_RGB_STR,
|
||
|
COLOR_RGB_TEXT_STR,
|
||
|
SANE_VALUE_SCAN_MODE_COLOR,
|
||
|
COLOR_RAW_STR,
|
||
|
|
||
|
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 struct dpi_color_adjust stv680_dpi_color_adjust[] = {
|
||
|
|
||
|
/*dpi, y, x, color sequence R G or B */
|
||
|
{176, 144, 0, 1, 2}, /* QCIF selected by dev->CIF */
|
||
|
{352, 288, 0, 1, 2}, /* CIF ,, */
|
||
|
{160, 120, 0, 1, 2}, /* QSIF ,, dev->VGA */
|
||
|
{320, 240, 0, 1, 2}, /* QVGA (SIF) ,, */
|
||
|
{640, 480, 0, 1, 2}, /* VGA ,, */
|
||
|
/* must be the last entry */
|
||
|
{0, 0, 0, 0, 0}
|
||
|
};
|
||
|
|
||
|
static const struct vidcam_hardware vidcams[] = {
|
||
|
|
||
|
{0x0553, 0x0202, USB_CLASS_VENDOR_SPEC,
|
||
|
"AIPTEK", "PENCAM STV0680",
|
||
|
stv680_dpi_color_adjust},
|
||
|
|
||
|
{0x04c8, 0x0722, USB_CLASS_VENDOR_SPEC,
|
||
|
"Konica", "e-mini",
|
||
|
stv680_dpi_color_adjust},
|
||
|
|
||
|
{0x1183, 0x0001, USB_CLASS_VENDOR_SPEC,
|
||
|
"DigitalDream", "l'espion XS",
|
||
|
stv680_dpi_color_adjust},
|
||
|
|
||
|
{0x041e, 0x4007, USB_CLASS_VENDOR_SPEC,
|
||
|
"Creative", "WebCam Go mini",
|
||
|
stv680_dpi_color_adjust}
|
||
|
};
|
||
|
|
||
|
/* List of vidcams attached. */
|
||
|
static Stv680_Vidcam *first_dev = NULL;
|
||
|
static int num_devices = 0;
|
||
|
/* used by sane_get_devices */
|
||
|
static const SANE_Device **devlist = NULL;
|
||
|
|
||
|
/*----------------------------------------------------------- */
|
||
|
|
||
|
/* 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;
|
||
|
}
|
||
|
|
||
|
/* Initialize a vidcam entry. Return an allocated vidcam with some
|
||
|
* */
|
||
|
static Stv680_Vidcam *
|
||
|
stv680_init (void)
|
||
|
{
|
||
|
Stv680_Vidcam *dev;
|
||
|
|
||
|
DBG (DBG_proc, "stv680_init: enter\n");
|
||
|
|
||
|
/* Allocate a new vidcam entry. */
|
||
|
dev = malloc (sizeof (Stv680_Vidcam));
|
||
|
if (dev == NULL)
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
memset (dev, 0, sizeof (Stv680_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 = 0x20;
|
||
|
dev->windowr = malloc (dev->windowr_size);
|
||
|
if (dev->windowr == NULL)
|
||
|
{
|
||
|
free (dev);
|
||
|
free (dev->windoww);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
dev->fd = -1;
|
||
|
|
||
|
DBG (DBG_proc, "stv680_init: exit\n");
|
||
|
|
||
|
return (dev);
|
||
|
}
|
||
|
|
||
|
static SANE_Status
|
||
|
stv680_init_2 (Stv680_Vidcam * dev)
|
||
|
{
|
||
|
SANE_Status status;
|
||
|
|
||
|
DBG (DBG_proc, "stv680_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, format from camera is bayer 422 */
|
||
|
if (dev->CIF)
|
||
|
dev->buffer_size = 356 * 292;
|
||
|
if (dev->VGA)
|
||
|
dev->buffer_size = 644 * 484;
|
||
|
DBG (DBG_proc, "stv680_init_2: dev->bufffer = 0x%lx\n", (unsigned long) (size_t) dev->buffer_size);
|
||
|
|
||
|
dev->buffer = malloc (dev->buffer_size);
|
||
|
|
||
|
if (dev->buffer == NULL)
|
||
|
{
|
||
|
free (dev);
|
||
|
free (dev->windowr);
|
||
|
free (dev->windoww);
|
||
|
return SANE_STATUS_NO_MEM;
|
||
|
}
|
||
|
|
||
|
/* Allocate the output buffer used for bayer conversion */
|
||
|
dev->output_size = dev->buffer_size * 3;
|
||
|
|
||
|
dev->output = malloc (dev->output_size);
|
||
|
if (dev->output == NULL)
|
||
|
{
|
||
|
free (dev);
|
||
|
free (dev->windowr);
|
||
|
free (dev->windoww);
|
||
|
free (dev->buffer);
|
||
|
|
||
|
return SANE_STATUS_NO_MEM;
|
||
|
}
|
||
|
dev->image_size = dev->buffer_size;
|
||
|
|
||
|
dev->image = malloc (dev->image_size);
|
||
|
if (dev->image == NULL)
|
||
|
{
|
||
|
free (dev);
|
||
|
free (dev->windowr);
|
||
|
free (dev->windoww);
|
||
|
free (dev->buffer);
|
||
|
free (dev->output);
|
||
|
|
||
|
return SANE_STATUS_NO_MEM;
|
||
|
}
|
||
|
|
||
|
DBG (DBG_proc, "stv680_init_2: exit\n");
|
||
|
status = SANE_STATUS_GOOD;
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/* Closes an open vidcams. */
|
||
|
static void
|
||
|
stv680_close (Stv680_Vidcam * dev)
|
||
|
{
|
||
|
DBG (DBG_proc, "stv680_close: enter \n");
|
||
|
|
||
|
if (dev->fd != -1)
|
||
|
{
|
||
|
|
||
|
DBG (DBG_proc, "stv680_close: fd !=-1 \n");
|
||
|
sanei_usb_close (dev->fd);
|
||
|
dev->fd = -1;
|
||
|
}
|
||
|
|
||
|
DBG (DBG_proc, "stv680_close: exit\n");
|
||
|
}
|
||
|
|
||
|
/* Frees the memory used by a vidcam. */
|
||
|
static void
|
||
|
stv680_free (Stv680_Vidcam * dev)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
DBG (DBG_proc, "stv680_free: enter\n");
|
||
|
|
||
|
if (dev == NULL)
|
||
|
return;
|
||
|
|
||
|
stv680_close (dev);
|
||
|
if (dev->devicename)
|
||
|
{
|
||
|
free (dev->devicename);
|
||
|
}
|
||
|
if (dev->buffer)
|
||
|
{
|
||
|
free (dev->buffer);
|
||
|
}
|
||
|
if (dev->output)
|
||
|
{
|
||
|
free (dev->output);
|
||
|
}
|
||
|
if (dev->image)
|
||
|
{
|
||
|
free (dev->image);
|
||
|
}
|
||
|
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, "stv680_free: exit\n");
|
||
|
}
|
||
|
|
||
|
static SANE_Status
|
||
|
stv680_set_config (Stv680_Vidcam * dev, int configuration, int interface,
|
||
|
int alternate)
|
||
|
{
|
||
|
SANE_Status status;
|
||
|
DBG (DBG_proc, "stv680_set_config: open\n");
|
||
|
/* seems a problem on some systems (Debian amd64 unstable 19042006)
|
||
|
* not calling usb_set_configuration seems to help reason ?
|
||
|
status = sanei_usb_set_configuration (dev->fd, configuration);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_error,
|
||
|
"stv680_set_config: STV680 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,
|
||
|
"stv680_set_config: STV0680 FAILED to claim interface\n");
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
status = sanei_usb_set_altinterface (dev->fd, alternate);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_error,
|
||
|
"stv680_set_config: STV0680 FAILED to set alternate interface %d\n",
|
||
|
alternate);
|
||
|
return status;
|
||
|
}
|
||
|
DBG (DBG_proc,
|
||
|
"stv680_set_config: configuration=%d, interface=%d, alternate=%d\n",
|
||
|
configuration, interface, alternate);
|
||
|
|
||
|
DBG (DBG_proc, "stv680_set_config: exit\n");
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/* Reset vidcam */
|
||
|
static SANE_Status
|
||
|
stv680_reset_vidcam (Stv680_Vidcam * dev)
|
||
|
{
|
||
|
SANE_Status status;
|
||
|
size_t sizew; /* significant size of window */
|
||
|
size_t sizer;
|
||
|
|
||
|
DBG (DBG_proc, "stv680_reset_vidcam: enter\n");
|
||
|
|
||
|
sizew = dev->windoww_size;
|
||
|
sizer = dev->windowr_size;
|
||
|
|
||
|
memset (dev->windoww, 0, sizew);
|
||
|
memset (dev->windowr, 0, sizer);
|
||
|
|
||
|
sizew = 0x00; /* was 0 ? */
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0x41, 0x0a, 0x0000, 0, sizew,
|
||
|
dev->windoww);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
DBG (DBG_proc, "stv680_reset_vidcam: CMDID_STOP_VIDEO end\n");
|
||
|
|
||
|
/* this is a high priority command; it stops all lower order commands */
|
||
|
|
||
|
sizew = 0x00; /* was 0 */
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0x41, 0x04, 0x0000, 0, sizew,
|
||
|
dev->windoww);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
DBG (DBG_proc, "stv680_reset_vidcam: CMDID_CANCEL_TRANSACTION end\n");
|
||
|
sizer = 0x02;
|
||
|
DBG (DBG_proc, "stv680_reset_vidcam: CMDID_GET_LAST_ERROR begin\n");
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0xc1, 0x80, 0x0000, 0, sizer,
|
||
|
dev->windowr);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
/* Get Last Error; 2 = busy */
|
||
|
DBG (DBG_proc,
|
||
|
"stv680_reset_vidcam: last error: %i, command = 0x%x\n",
|
||
|
dev->windowr[0], dev->windowr[1]);
|
||
|
return status;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DBG (DBG_proc, "stv680_reset_vidcam: Camera reset to idle mode.\n");
|
||
|
}
|
||
|
hexdump (DBG_info2, "stv680_reset_vidcam: CMDID_GET_LAST_ERROR",
|
||
|
dev->windowr, sizer);
|
||
|
|
||
|
/* configuration = 1, interface = 0, alternate = 0 */
|
||
|
/*
|
||
|
status = stv680_set_config (dev, 1, 0, 0);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_error,
|
||
|
"stv680_reset_vidcam: STV680 FAILED to set configure\n");
|
||
|
return status;
|
||
|
}
|
||
|
*/
|
||
|
status = SANE_STATUS_GOOD;
|
||
|
DBG (DBG_proc, "stv680_reset_vidcam: exit\n");
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/* Inquiry a device and returns TRUE if is supported. */
|
||
|
static int
|
||
|
stv680_identify_vidcam (Stv680_Vidcam * dev)
|
||
|
{
|
||
|
SANE_Status status;
|
||
|
SANE_Word vendor;
|
||
|
SANE_Word product;
|
||
|
int i;
|
||
|
size_t sizer;
|
||
|
|
||
|
DBG (DBG_info, "stv680_identify_vidcam: open\n");
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
|
||
|
DBG (DBG_info, "stv680_identify_vidcam: vidcam %x:%x is in list\n",
|
||
|
vendor, product);
|
||
|
|
||
|
dev->hw = &(vidcams[i]);
|
||
|
|
||
|
sizer = dev->windowr_size;
|
||
|
memset (dev->windowr, 0, sizer);
|
||
|
|
||
|
/* configuration = 1, interface = 0, alternate = 0 */
|
||
|
/*
|
||
|
status = stv680_set_config (dev, 1, 0, 0);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_error,
|
||
|
"stv680_vidcam_init: STV680 FAILED to set configure\n");
|
||
|
return status;
|
||
|
}
|
||
|
*/
|
||
|
sizer = 0x02;
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0xc1, 0x88, 0x5678, 0, sizer,
|
||
|
dev->windowr);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_error,
|
||
|
"stv680_identify_vidcam: this is not a STV680 (idVendor = %d, bProduct = %d) writing register failed with %s\n",
|
||
|
vendor, product, sane_strstatus (status));
|
||
|
return SANE_FALSE;
|
||
|
}
|
||
|
if ((dev->windowr[0] != 0x56) || (dev->windowr[1] != 0x78))
|
||
|
{
|
||
|
DBG (DBG_proc,
|
||
|
"STV(e): camera ping failed!!, checkvalue !=0x5678\n");
|
||
|
sizer = 0x02;
|
||
|
hexdump (DBG_info2, "urb12 window", dev->windowr, sizer);
|
||
|
return SANE_FALSE;
|
||
|
}
|
||
|
sizer = 0x02;
|
||
|
hexdump (DBG_info2, "urb12 ping data", dev->windowr, sizer);
|
||
|
|
||
|
sizer = 0x10;
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0xc1, 0x85, 0x0000, 0, sizer,
|
||
|
dev->windowr);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
return SANE_FALSE;
|
||
|
|
||
|
hexdump (DBG_info2, "urbxx CMDID_GET_CAMERA_INFO", dev->windowr,
|
||
|
sizer);
|
||
|
|
||
|
dev->SupportedModes = dev->windowr[7];
|
||
|
i = dev->SupportedModes;
|
||
|
dev->QSIF = 0;
|
||
|
dev->CIF = 0;
|
||
|
dev->QCIF = 0;
|
||
|
dev->VGA = 0;
|
||
|
dev->QVGA = 0;
|
||
|
if (i & 1)
|
||
|
dev->CIF = 1;
|
||
|
if (i & 2)
|
||
|
dev->VGA = 1;
|
||
|
if (i & 8)
|
||
|
dev->QVGA = 1;
|
||
|
if (i & 4)
|
||
|
dev->QCIF = 1;
|
||
|
dev->QSIF = dev->QVGA; /* for software subsample */
|
||
|
if (dev->SupportedModes == 0)
|
||
|
{
|
||
|
DBG (DBG_proc,
|
||
|
"STV(e): There are NO supported STV680 modes!!\n");
|
||
|
i = -1;
|
||
|
return SANE_FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (dev->VGA)
|
||
|
DBG (DBG_proc, "STV(i): VGA is supported\n");
|
||
|
if (dev->CIF)
|
||
|
DBG (DBG_proc, "STV(i): CIF is supported\n");
|
||
|
if (dev->QVGA)
|
||
|
DBG (DBG_proc, "STV(i): QVGA is supported\n");
|
||
|
if (dev->QCIF)
|
||
|
DBG (DBG_proc, "STV(i): QCIF is supported\n");
|
||
|
}
|
||
|
|
||
|
/* FW rev, ASIC rev, sensor ID */
|
||
|
DBG (DBG_proc, "STV(i): Firmware rev is %i.%i\n", dev->windowr[0],
|
||
|
dev->windowr[1]);
|
||
|
DBG (DBG_proc, "STV(i): ASIC rev is %i.%i\n", dev->windowr[2],
|
||
|
dev->windowr[3]);
|
||
|
DBG (DBG_proc, "STV(i): Sensor ID is %i.%i\n", (dev->windowr[4]),
|
||
|
(dev->windowr[5]));
|
||
|
/* Hardware config */
|
||
|
dev->HardwareConfig = dev->windowr[6];
|
||
|
i = dev->HardwareConfig;
|
||
|
/* Comms link, Flicker freq, Mem size */
|
||
|
if (i & 1)
|
||
|
DBG (DBG_proc, "STV(i): Comms link is serial\n");
|
||
|
else
|
||
|
DBG (DBG_proc, "STV(i): Comms link is USB\n");
|
||
|
if (i & 2)
|
||
|
DBG (DBG_proc, "STV(i): Flicker freq = 60 Hz\n");
|
||
|
else
|
||
|
DBG (DBG_proc, "STV(i): Flicker freq = 50 Hz\n");
|
||
|
if (i & 4)
|
||
|
DBG (DBG_proc, "STV(i): Mem size = 16Mbit\n");
|
||
|
else
|
||
|
DBG (DBG_proc, "STV(i): Mem size = 64Mbit\n");
|
||
|
if (i & 8)
|
||
|
DBG (DBG_proc, "STV(i): Thumbnails supported\n");
|
||
|
else
|
||
|
DBG (DBG_proc, "STV(i): Thumbnails N/A\n");
|
||
|
if (i & 16)
|
||
|
DBG (DBG_proc, "STV(i): Video supported\n");
|
||
|
else
|
||
|
DBG (DBG_proc, "STV(i): Video N/A\n");
|
||
|
if (i & 32)
|
||
|
DBG (DBG_proc, "STV(i): Startup Complete\n");
|
||
|
else
|
||
|
DBG (DBG_proc, "STV(i): Startup Not Complete\n");
|
||
|
if (i & 64)
|
||
|
DBG (DBG_proc, "STV(i): Monochrome\n");
|
||
|
else
|
||
|
DBG (DBG_proc, "STV(i): Color\n");
|
||
|
if (i & 128)
|
||
|
DBG (DBG_proc, "STV(i): Mem fitted\n");
|
||
|
else
|
||
|
DBG (DBG_proc, "STV(i): Mem not fitted\n");
|
||
|
|
||
|
DBG (DBG_proc, "urb 25 CMDID_GET_IMAGE_INFO\n");
|
||
|
sizer = 0x10;
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0xc1, 0x86, 0x0000, 0, sizer,
|
||
|
dev->windowr);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
return SANE_FALSE;
|
||
|
}
|
||
|
hexdump (DBG_info2, "urb25 CMDID_GET_IMAGE_INFO", dev->windowr,
|
||
|
sizer);
|
||
|
DBG (DBG_proc, "STV(i): Current image index %d\n",
|
||
|
((dev->windowr[0] << 8) + (dev->windowr[1])));
|
||
|
DBG (DBG_proc,
|
||
|
"If images are stored in camera, they will be lost when captering images is started!!!!!\n");
|
||
|
DBG (DBG_proc, "STV(i): Max images %d\n",
|
||
|
((dev->windowr[2] << 8) + (dev->windowr[3])));
|
||
|
DBG (DBG_proc, "STV(i): Image width (pix) %d\n",
|
||
|
((dev->windowr[4] << 8) + (dev->windowr[5])));
|
||
|
DBG (DBG_proc, "STV(i): Image height (pix) %d\n",
|
||
|
((dev->windowr[6] << 8) + (dev->windowr[7])));
|
||
|
DBG (DBG_proc, "STV(i): Image size camera %d bytes\n",
|
||
|
((dev->windowr[8] << 24) + (dev->windowr[9] << 16) +
|
||
|
(dev->windowr[10] << 8) + (dev->windowr[11])));
|
||
|
|
||
|
/* configuration = 1, interface = 0, alternate = 1 */
|
||
|
status = stv680_set_config (dev, 1, 0, 1);
|
||
|
/*
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_error,
|
||
|
"stv680_vidcam_init: STV680 FAILED to set configure\n");
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
DBG (DBG_info, "stv680_identify_vidcam: exit vidcam supported\n");
|
||
|
*/
|
||
|
return SANE_TRUE;
|
||
|
}
|
||
|
}
|
||
|
DBG (DBG_error, "stv680_identify_vidcam: exit this is not a STV680 exit\n");
|
||
|
return SANE_FALSE;
|
||
|
}
|
||
|
|
||
|
static SANE_Status
|
||
|
stv680_vidcam_init (Stv680_Vidcam * dev)
|
||
|
{
|
||
|
SANE_Status status;
|
||
|
SANE_Byte i = 0;
|
||
|
SANE_Byte val = 0;
|
||
|
size_t sizer;
|
||
|
size_t sizew;
|
||
|
DBG (DBG_proc, "stv680_vidcam_init: open\n");
|
||
|
|
||
|
sizew = dev->windoww_size;
|
||
|
sizer = dev->windowr_size;
|
||
|
|
||
|
memset (dev->windoww, 0, sizew);
|
||
|
memset (dev->windowr, 0, sizer);
|
||
|
|
||
|
DBG (DBG_proc, "stv680_vidcam_init: urb 13 CMDID_GET_USER_INFO\n");
|
||
|
dev->video_status = 0x04; /* dummy value busy */
|
||
|
|
||
|
while (dev->video_status == 0x04)
|
||
|
{
|
||
|
sizer = 0x08;
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0xc1, 0x8d, 0x0000, 0, sizer,
|
||
|
dev->windowr);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
return status;
|
||
|
|
||
|
hexdump (DBG_info2, "stv680_vidcam_init: urb13 CMDID_GET_USER_INFO",
|
||
|
dev->windowr, sizer);
|
||
|
|
||
|
dev->video_status = dev->windowr[1];
|
||
|
if (dev->video_status == 0x02)
|
||
|
{
|
||
|
DBG (DBG_proc, "stv680_vidcam_init: status = video\n");
|
||
|
}
|
||
|
else if ((dev->video_status == 0x01) || (dev->video_status == 0x08))
|
||
|
{
|
||
|
DBG (DBG_proc, "stv680_vidcam_init: status=%d\n",
|
||
|
dev->video_status);
|
||
|
}
|
||
|
else if (dev->video_status != 0x04)
|
||
|
{
|
||
|
DBG (DBG_proc, "stv680_vidcam_init: status = busy\n");
|
||
|
/* CMDID_CANCEL_TRANSACTION */
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0x41, 0x04, 0x0000, 0, 0, 0);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_info,
|
||
|
"stv680_vidcam_init: urb13 CMDID_CANCEL_TRANSACTION NOK\n");
|
||
|
return status;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (dev->video_status == 0x01 || dev->video_status == 0x08)
|
||
|
{
|
||
|
DBG (DBG_proc, "stv680_vidcam_init: urb 21 CMDID_GET_COLDATA_SIZE\n");
|
||
|
sizer = 0x02;
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0xc1, 0x8a, 0x0000, 0, sizer,
|
||
|
dev->windowr);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
return status;
|
||
|
|
||
|
val = dev->windowr[0];
|
||
|
hexdump (DBG_info2, "stv680_vidcam_init: urb21 CMDID_GET_COLDATA_SIZE",
|
||
|
dev->windowr, sizer);
|
||
|
if (dev->windowr[0] &= 0x00)
|
||
|
DBG (DBG_info,
|
||
|
"stv680_vidcam_init: no camera defaults, must be downloaded?\n");
|
||
|
|
||
|
sizer = 0x10;
|
||
|
for (i = 0; i < val; i += 0x10)
|
||
|
{
|
||
|
DBG (DBG_proc,
|
||
|
"stv680_vidcam_init: urb 22, 23, 24 CMDID_GET_COLDATA i=0x%x, val=0x%x\n",
|
||
|
i, val);
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0xc1, 0x8b, (i << 8), 0, sizer,
|
||
|
dev->windowr);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
return status;
|
||
|
hexdump (DBG_info2,
|
||
|
"stv680_vidcam_init: urb22, 23, 24 CMDID_GET_COLDATA",
|
||
|
dev->windowr, sizer);
|
||
|
}
|
||
|
|
||
|
sizer = 0x12;
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0x80, 0x06, 0x0100, 0, sizer,
|
||
|
dev->windowr);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
return status;
|
||
|
/* if (!(i > 0) && (dev->windowr[8] == 0x53) && (dev->windowr[9] == 0x05))
|
||
|
{
|
||
|
DBG (DBG_proc, "STV(e): Could not get descriptor 0100.");
|
||
|
*//* return status; *//*
|
||
|
} */
|
||
|
sizer = 0x12;
|
||
|
hexdump (DBG_info2, "stv680_vidcam_init: CMDID_SET_IMAGE_INDEX",
|
||
|
dev->windowr, sizer);
|
||
|
|
||
|
/* configuration = 1, interface = 0, alternate = 1 */
|
||
|
status = stv680_set_config (dev, 1, 0, 1);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_error,
|
||
|
"stv680_vidcam_init: STV680 FAILED to set configure\n");
|
||
|
return status;
|
||
|
}
|
||
|
}
|
||
|
/* Switch to Video mode: 0x0000 = CIF (352x288), 0x0200 = QCIF (176x144) */
|
||
|
/* Switch to Video mode: 0x0100 = VGA (640x480), 0x0300 = QVGA (320x240) */
|
||
|
sizew = 0x0;
|
||
|
status =
|
||
|
sanei_usb_control_msg (dev->fd, 0x41, 0x09, dev->video_mode, 0, sizew,
|
||
|
dev->windoww);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_proc, "stv680_vidcam_init: video_mode = 0x%x\n",
|
||
|
dev->video_mode);
|
||
|
return status;
|
||
|
}
|
||
|
DBG (DBG_proc,
|
||
|
"stv680_vidcam_init: CMDID_START_VIDEO: video_mode=0x%x\n",
|
||
|
dev->video_mode);
|
||
|
|
||
|
if (dev->x_resolution == 176)
|
||
|
{
|
||
|
usleep (1000); /* delay time needed */
|
||
|
}
|
||
|
status = SANE_STATUS_GOOD;
|
||
|
|
||
|
if (status)
|
||
|
{
|
||
|
DBG (DBG_error, "stv680_vidcam_init failed : %s\n",
|
||
|
sane_strstatus (status));
|
||
|
return status;
|
||
|
}
|
||
|
DBG (DBG_proc, "stv680_vidcam_init: exit\n");
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/* Attach a vidcam to this backend. */
|
||
|
static SANE_Status
|
||
|
attach_vidcam (SANE_String_Const devicename, Stv680_Vidcam ** devp)
|
||
|
{
|
||
|
Stv680_Vidcam *dev;
|
||
|
int fd;
|
||
|
SANE_Status status;
|
||
|
|
||
|
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 = stv680_init ();
|
||
|
if (dev == NULL)
|
||
|
{
|
||
|
DBG (DBG_error, "stv680_init 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");
|
||
|
stv680_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 (stv680_identify_vidcam (dev) == SANE_FALSE)
|
||
|
{
|
||
|
DBG (DBG_error, "ERROR: attach_vidcam: vidcam-identification failed\n");
|
||
|
stv680_free (dev);
|
||
|
return SANE_STATUS_INVAL;
|
||
|
}
|
||
|
|
||
|
/* Allocate a buffer memory. */
|
||
|
status = stv680_init_2 (dev);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_error, "stv680_initi_2, ERROR: not enough memory\n");
|
||
|
return SANE_STATUS_NO_MEM;
|
||
|
}
|
||
|
|
||
|
stv680_close (dev);
|
||
|
|
||
|
DBG (DBG_info, "attach_vidcam: opening USB device %s\n", devicename);
|
||
|
|
||
|
/* 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");
|
||
|
stv680_free (dev);
|
||
|
return SANE_STATUS_NO_MEM;
|
||
|
}
|
||
|
/* for CIF or VGA sensor different resolutions */
|
||
|
if (dev->CIF)
|
||
|
num_entries = 2;
|
||
|
if (dev->VGA)
|
||
|
num_entries = 3;
|
||
|
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->VGA + dev->QVGA] =
|
||
|
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 ("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
|
||
|
stv680_init_options (Stv680_Vidcam * dev)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
DBG (DBG_proc, "stv680_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;
|
||
|
}
|
||
|
DBG (DBG_proc,
|
||
|
"stv680_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;
|
||
|
|
||
|
/* Vidcam 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 = dev->resolutions_list[dev->CIF + dev->QCIF + dev->VGA + dev->QVGA + dev->QSIF]; /* value will be 2 or 3 */
|
||
|
|
||
|
/* 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 */
|
||
|
|
||
|
/* 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;
|
||
|
|
||
|
/* 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 */
|
||
|
|
||
|
DBG (DBG_proc, "stv680_init_options: after blue level\n");
|
||
|
|
||
|
/* 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, "stv680_init_options: exit\n");
|
||
|
}
|
||
|
|
||
|
/* Read the image from the vidcam and fill the temporary buffer with it. */
|
||
|
static SANE_Status
|
||
|
stv680_fill_image (Stv680_Vidcam * dev)
|
||
|
{
|
||
|
SANE_Status status;
|
||
|
size_t size;
|
||
|
size_t bulk_size_read;
|
||
|
|
||
|
assert (dev->image_begin == dev->image_end);
|
||
|
assert (dev->real_bytes_left > 0);
|
||
|
|
||
|
DBG (DBG_proc, "stv680_fill_image: enter\n");
|
||
|
|
||
|
DBG (DBG_proc, "stv680_fill_image: real dev bytes left=0x%lx \n",
|
||
|
(unsigned long) (size_t) dev->real_bytes_left);
|
||
|
bulk_size_read = dev->real_bytes_left;
|
||
|
|
||
|
while (dev->real_bytes_left)
|
||
|
{
|
||
|
/* Try to read the maximum number of bytes. */
|
||
|
DBG (DBG_proc,
|
||
|
"stv680_fill_image: real dev bytes left, while loop=0x%lx \n",
|
||
|
(unsigned long) (size_t) dev->real_bytes_left);
|
||
|
|
||
|
size = dev->real_bytes_left;
|
||
|
if (size < bulk_size_read)
|
||
|
{
|
||
|
size = bulk_size_read; /* it seems size can not be smaller then read by bulk */
|
||
|
}
|
||
|
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,
|
||
|
"stv680_fill_image: dev->real_bytes_left: 0x%lx size: 0x%lx\n",
|
||
|
(unsigned long) (size_t) dev->real_bytes_left, (unsigned long) (size_t) size);
|
||
|
usleep (3000);
|
||
|
/* urb 44 first read bulk */
|
||
|
|
||
|
status = sanei_usb_read_bulk (dev->fd, dev->buffer, &size);
|
||
|
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
DBG (DBG_info,
|
||
|
"stv680_fill_image: size (read) = 0x%lx bytes (bpl=0x%lx)\n",
|
||
|
(unsigned long) (size_t) size, (unsigned long) (size_t) dev->params.bytes_per_line);
|
||
|
|
||
|
memcpy (dev->image + dev->image_end, dev->buffer, size);
|
||
|
|
||
|
dev->image_end += size;
|
||
|
bulk_size_read = size;
|
||
|
if (dev->real_bytes_left > size)
|
||
|
dev->real_bytes_left -= size;
|
||
|
else if (dev->real_bytes_left <= size) /* last loop */
|
||
|
dev->real_bytes_left = 0;
|
||
|
DBG (DBG_info, "stv680_fill_image: real bytes left = 0x%lx\n",
|
||
|
(unsigned long) (size_t) dev->real_bytes_left);
|
||
|
}
|
||
|
/* i = stv_sndctrl (0, dev, 0x80, 0, &window, 0x02); *//* Get Last Error */
|
||
|
/* DBG (DBG_proc, "STV(i): last error: %i, command = 0x%x", window[0], window[1]);
|
||
|
return -1; */
|
||
|
/*
|
||
|
}
|
||
|
return 0; */
|
||
|
|
||
|
DBG (DBG_proc, "stv680_fill_image: exit\n");
|
||
|
return (SANE_STATUS_GOOD); /* unreachable */
|
||
|
}
|
||
|
|
||
|
#define MSG_MAXLEN 45
|
||
|
#define CHAR_HEIGHT 11
|
||
|
#define CHAR_WIDTH 6
|
||
|
#define CHAR_START 4
|
||
|
|
||
|
static SANE_Status
|
||
|
stv680_add_text (SANE_Byte * image, int width, int height, char *txt)
|
||
|
{
|
||
|
SANE_Status status;
|
||
|
time_t t;
|
||
|
struct tm *tm;
|
||
|
char line[MSG_MAXLEN + 1];
|
||
|
SANE_Byte *ptr;
|
||
|
int i, x, y, f, len;
|
||
|
char fmtstring[25] = " %Y-%m-%d %H:%M:%S";
|
||
|
char fmttxt[46];
|
||
|
|
||
|
DBG (DBG_proc, "stv680_add_text: enter\n");
|
||
|
time (&t);
|
||
|
tm = localtime (&t);
|
||
|
if (strlen (txt) > (MSG_MAXLEN - 23))
|
||
|
strncpy (fmttxt, txt, (MSG_MAXLEN - 23));
|
||
|
else
|
||
|
strcpy (fmttxt, txt);
|
||
|
strcat (fmttxt, fmtstring);
|
||
|
|
||
|
len = strftime (line, MSG_MAXLEN, fmttxt, tm);
|
||
|
|
||
|
for (y = 0; y < CHAR_HEIGHT; y++)
|
||
|
{
|
||
|
ptr = image + 3 * width * (height - CHAR_HEIGHT - 2 + y) + 12;
|
||
|
|
||
|
for (x = 0; x < len; x++)
|
||
|
{
|
||
|
f = fontdata[line[x] * CHAR_HEIGHT + y];
|
||
|
for (i = CHAR_WIDTH - 1; i >= 0; i--)
|
||
|
{
|
||
|
if (f & (CHAR_START << i))
|
||
|
{
|
||
|
ptr[0] = 255;
|
||
|
ptr[1] = 255;
|
||
|
ptr[2] = 255;
|
||
|
}
|
||
|
ptr += 3;
|
||
|
} /* for i */
|
||
|
} /* for x */
|
||
|
} /* for y */
|
||
|
|
||
|
DBG (DBG_proc, "stv680_add_text: exit vw=%d, vh=%d\n", width, height);
|
||
|
status = (SANE_STATUS_GOOD);
|
||
|
return status;
|
||
|
|
||
|
}
|
||
|
|
||
|
/* ************************** Video Decoding ********************* */
|
||
|
|
||
|
static SANE_Status
|
||
|
stv680_bayer_unshuffle (Stv680_Vidcam * dev, SANE_Byte * buf, size_t * size)
|
||
|
{
|
||
|
SANE_Status status;
|
||
|
int x, y;
|
||
|
int i = 0;
|
||
|
int RED, GREEN, BLUE;
|
||
|
int w = dev->cwidth;
|
||
|
int vw = dev->x_resolution;
|
||
|
int vh = dev->y_resolution;
|
||
|
SANE_Byte p = 0;
|
||
|
int colour = 0, bayer = 0;
|
||
|
int bright_red;
|
||
|
int bright_green;
|
||
|
int bright_blue;
|
||
|
int count;
|
||
|
|
||
|
RED = dev->red_s;
|
||
|
GREEN = dev->green_s;
|
||
|
BLUE = dev->blue_s;
|
||
|
|
||
|
DBG (DBG_proc, "stv680_bayer_unshuffle: enter\n");
|
||
|
|
||
|
#define AD(x, y, w) (((y)*(w)+(x))*3)
|
||
|
|
||
|
DBG (DBG_proc,
|
||
|
"stv680_bayer_unshuffle: color read RED=%d, GREEN=%d, BLUE=%d\n",
|
||
|
RED, GREEN, BLUE);
|
||
|
|
||
|
DBG (DBG_proc, "stv680_bayer_unshuffle: w=%d, vw=%d, vh=%d, len=0x%lx\n",
|
||
|
w, vw, vh, (unsigned long) (size_t) size);
|
||
|
|
||
|
for (y = 0; y < vh; y++)
|
||
|
{
|
||
|
for (x = 0; x < vw; x++)
|
||
|
{
|
||
|
if (x & 1)
|
||
|
{
|
||
|
p = dev->image[y * w + (x >> 1)];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
p = dev->image[y * w + (x >> 1) + (w >> 1)];
|
||
|
}
|
||
|
if (y & 1)
|
||
|
bayer = 2;
|
||
|
else
|
||
|
bayer = 0;
|
||
|
if (x & 1)
|
||
|
bayer++;
|
||
|
|
||
|
switch (bayer)
|
||
|
{
|
||
|
case 0:
|
||
|
case 3:
|
||
|
colour = 1;
|
||
|
break;
|
||
|
case 1:
|
||
|
colour = 0;
|
||
|
break;
|
||
|
case 2:
|
||
|
colour = 2;
|
||
|
break;
|
||
|
}
|
||
|
i = (y * vw + x) * 3;
|
||
|
*(dev->output + i + colour) = (SANE_Byte) p;
|
||
|
} /* for x */
|
||
|
|
||
|
} /* for y */
|
||
|
|
||
|
/****** gamma correction plus hardcoded white balance */
|
||
|
/* Correction values red[], green[], blue[], are generated by
|
||
|
(pow(i/256.0, GAMMA)*255.0)*white balanceRGB where GAMMA=0.55, 1<i<255.
|
||
|
White balance (RGB)= 1.0, 1.17, 1.48. Values are calculated as double float and
|
||
|
converted to unsigned char. Values are in stv680.h */
|
||
|
if (dev->scan_mode == STV680_COLOR_RGB
|
||
|
|| dev->scan_mode == STV680_COLOR_RGB_TEXT)
|
||
|
{
|
||
|
for (y = 0; y < vh; y++)
|
||
|
{
|
||
|
for (x = 0; x < vw; x++)
|
||
|
{
|
||
|
i = (y * vw + x) * 3;
|
||
|
*(dev->output + i) = red_g[*(dev->output + i)];
|
||
|
*(dev->output + i + 1) = green_g[*(dev->output + i + 1)];
|
||
|
*(dev->output + i + 2) = blue_g[*(dev->output + i + 2)];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
DBG (DBG_proc, "stv680_bayer_unshuffle: gamma correction done\n");
|
||
|
|
||
|
if (dev->scan_mode != STV680_COLOR_RAW)
|
||
|
{
|
||
|
|
||
|
/****** bayer demosaic ******/
|
||
|
for (y = 1; y < (vh - 1); y++)
|
||
|
{
|
||
|
for (x = 1; x < (vw - 1); x++)
|
||
|
{ /* work out pixel type */
|
||
|
if (y & 1)
|
||
|
bayer = 0;
|
||
|
else
|
||
|
bayer = 2;
|
||
|
if (!(x & 1))
|
||
|
bayer++;
|
||
|
switch (bayer)
|
||
|
{
|
||
|
case 0: /* green. blue lr, red tb */
|
||
|
*(dev->output + AD (x, y, vw) + BLUE) =
|
||
|
((int) *(dev->output + AD (x - 1, y, vw) + BLUE) +
|
||
|
(int) *(dev->output + AD (x + 1, y, vw) + BLUE)) >> 1;
|
||
|
*(dev->output + AD (x, y, vw) + RED) =
|
||
|
((int) *(dev->output + AD (x, y - 1, vw) + RED) +
|
||
|
(int) *(dev->output + AD (x, y + 1, vw) + RED)) >> 1;
|
||
|
break;
|
||
|
|
||
|
case 1: /* blue. green lrtb, red diagonals */
|
||
|
*(dev->output + AD (x, y, vw) + GREEN) =
|
||
|
((int) *(dev->output + AD (x - 1, y, vw) + GREEN) +
|
||
|
(int) *(dev->output + AD (x + 1, y, vw) + GREEN) +
|
||
|
(int) *(dev->output + AD (x, y - 1, vw) + GREEN) +
|
||
|
(int) *(dev->output + AD (x, y + 1, vw) + GREEN)) >> 2;
|
||
|
*(dev->output + AD (x, y, vw) + RED) =
|
||
|
((int) *(dev->output + AD (x - 1, y - 1, vw) + RED) +
|
||
|
(int) *(dev->output + AD (x - 1, y + 1, vw) + RED) +
|
||
|
(int) *(dev->output + AD (x + 1, y - 1, vw) + RED) +
|
||
|
(int) *(dev->output + AD (x + 1, y + 1, vw) + RED)) >> 2;
|
||
|
break;
|
||
|
|
||
|
case 2: /* red. green lrtb, blue diagonals */
|
||
|
*(dev->output + AD (x, y, vw) + GREEN) =
|
||
|
((int) *(dev->output + AD (x - 1, y, vw) + GREEN) +
|
||
|
(int) *(dev->output + AD (x + 1, y, vw) + GREEN) +
|
||
|
(int) *(dev->output + AD (x, y - 1, vw) + GREEN) +
|
||
|
(int) *(dev->output + AD (x, y + 1, vw) + GREEN)) >> 2;
|
||
|
*(dev->output + AD (x, y, vw) + BLUE) =
|
||
|
((int) *(dev->output + AD (x - 1, y - 1, vw) + BLUE) +
|
||
|
(int) *(dev->output + AD (x + 1, y - 1, vw) + BLUE) +
|
||
|
(int) *(dev->output + AD (x - 1, y + 1, vw) + BLUE) +
|
||
|
(int) *(dev->output + AD (x + 1, y + 1, vw) +
|
||
|
BLUE)) >> 2;
|
||
|
break;
|
||
|
|
||
|
case 3: /* green. red lr, blue tb */
|
||
|
*(dev->output + AD (x, y, vw) + RED) =
|
||
|
((int) *(dev->output + AD (x - 1, y, vw) + RED) +
|
||
|
(int) *(dev->output + AD (x + 1, y, vw) + RED)) >> 1;
|
||
|
*(dev->output + AD (x, y, vw) + BLUE) =
|
||
|
((int) *(dev->output + AD (x, y - 1, vw) + BLUE) +
|
||
|
(int) *(dev->output + AD (x, y + 1, vw) + BLUE)) >> 1;
|
||
|
break;
|
||
|
} /* switch */
|
||
|
} /* for x */
|
||
|
} /* for y - end demosaic */
|
||
|
} /* no bayer demosaic */
|
||
|
DBG (DBG_proc, "stv680_bayer_unshuffle: bayer demosaic done\n");
|
||
|
|
||
|
/* fix top and bottom row, left and right side */
|
||
|
i = vw * 3;
|
||
|
memcpy (dev->output, (dev->output + i), i);
|
||
|
|
||
|
memcpy ((dev->output + (vh * i)), (dev->output + ((vh - 1) * i)), i);
|
||
|
|
||
|
|
||
|
for (y = 0; y < vh; y++)
|
||
|
{
|
||
|
i = y * vw * 3;
|
||
|
memcpy ((dev->output + i), (dev->output + i + 3), 3);
|
||
|
memcpy ((dev->output + i + (vw * 3)),
|
||
|
(dev->output + i + (vw - 1) * 3), 3);
|
||
|
}
|
||
|
|
||
|
/* process all raw data, then trim to size if necessary */
|
||
|
if (dev->subsample == 160)
|
||
|
{
|
||
|
i = 0;
|
||
|
for (y = 0; y < vh; y++)
|
||
|
{
|
||
|
if (!(y & 1))
|
||
|
{
|
||
|
for (x = 0; x < vw; x++)
|
||
|
{
|
||
|
p = (y * vw + x) * 3;
|
||
|
if (!(x & 1))
|
||
|
{
|
||
|
*(dev->output + i) = *(dev->output + p);
|
||
|
*(dev->output + i + 1) = *(dev->output + p + 1);
|
||
|
*(dev->output + i + 2) = *(dev->output + p + 2);
|
||
|
i += 3;
|
||
|
}
|
||
|
} /* for x */
|
||
|
}
|
||
|
} /* for y */
|
||
|
|
||
|
DBG (DBG_proc,
|
||
|
"stv680_bayer_unshuffle: if needed, trim to size 160 done\n");
|
||
|
}
|
||
|
/* reset to proper width */
|
||
|
if ((dev->subsample == 160))
|
||
|
{
|
||
|
vw = 160;
|
||
|
vh = 120;
|
||
|
}
|
||
|
|
||
|
/* brightness adjustment */
|
||
|
|
||
|
count = vw * vh * 3;
|
||
|
|
||
|
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;
|
||
|
i = x + 2;
|
||
|
if ((*(dev->output + x) + bright_red) >= 255)
|
||
|
*(buf + x) = 255;
|
||
|
|
||
|
else if ((*(dev->output + x) + bright_red) <= 0)
|
||
|
*(buf + x) = 0;
|
||
|
else
|
||
|
*(buf + x) = (*(dev->output + x) + bright_red);
|
||
|
|
||
|
if ((*(dev->output + y) + bright_green) >= 255)
|
||
|
*(buf + y) = 255;
|
||
|
|
||
|
else if ((*(dev->output + y) + bright_green) <= 0)
|
||
|
*(buf + y) = 0;
|
||
|
else
|
||
|
*(buf + y) = (*(dev->output + y) + bright_green);
|
||
|
|
||
|
if ((*(dev->output + i) + bright_blue) >= 255)
|
||
|
*(buf + i) = 255;
|
||
|
|
||
|
else if ((*(dev->output + i) + bright_blue) <= 0)
|
||
|
*(buf + i) = 0;
|
||
|
else
|
||
|
*(buf + i) = (*(dev->output + i) + bright_blue);
|
||
|
|
||
|
x += 2;
|
||
|
}
|
||
|
|
||
|
if (dev->scan_mode == STV680_COLOR_RGB_TEXT)
|
||
|
{
|
||
|
strcpy (dev->picmsg_ps, "STVcam ");
|
||
|
|
||
|
status = stv680_add_text (buf, vw, vh, dev->picmsg_ps);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_info, "stv680_bayer_unshuffle status NOK\n");
|
||
|
return (status);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DBG (DBG_proc, "stv680_bayer_unshuffle: exit vw=%d, vh=%d\n", vw, vh);
|
||
|
status = (SANE_STATUS_GOOD);
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/* Sane entry points */
|
||
|
|
||
|
SANE_Status
|
||
|
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
|
||
|
{
|
||
|
FILE *fp;
|
||
|
char line[PATH_MAX];
|
||
|
size_t len;
|
||
|
|
||
|
num_devices = 0;
|
||
|
devlist = NULL;
|
||
|
first_dev = NULL;
|
||
|
|
||
|
DBG_INIT ();
|
||
|
|
||
|
DBG (DBG_sane_init, "sane_init\n");
|
||
|
|
||
|
authorize = authorize; /* silence gcc */
|
||
|
|
||
|
DBG (DBG_error, "This is sane-stv680 version %d.%d-%d\n", V_MAJOR,
|
||
|
V_MINOR, BUILD);
|
||
|
DBG (DBG_error, "(C) 2004-2006 by Gerard Klaver\n");
|
||
|
|
||
|
if (version_code)
|
||
|
{
|
||
|
*version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, BUILD);
|
||
|
}
|
||
|
|
||
|
DBG (DBG_proc, "sane_init: authorize %s null\n", authorize ? "!=" : "==");
|
||
|
|
||
|
sanei_usb_init ();
|
||
|
|
||
|
fp = sanei_config_open (STV680_CONFIG_FILE);
|
||
|
if (!fp)
|
||
|
{
|
||
|
/* No default vidcam? */
|
||
|
DBG (DBG_warning, "configuration file not found (%s)\n",
|
||
|
STV680_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)
|
||
|
{
|
||
|
Stv680_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)
|
||
|
{
|
||
|
Stv680_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;
|
||
|
}
|
||
|
|
||
|
stv680_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)
|
||
|
{
|
||
|
Stv680_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)
|
||
|
{
|
||
|
Stv680_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_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_RESOLUTION:
|
||
|
case OPT_BRIGHTNESS:
|
||
|
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_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;
|
||
|
|
||
|
if (strcmp (dev->val[OPT_MODE].s, COLOR_RAW_STR) == 0)
|
||
|
{
|
||
|
dev->scan_mode = STV680_COLOR_RAW;
|
||
|
}
|
||
|
else if (strcmp (dev->val[OPT_MODE].s, COLOR_RGB_STR) == 0)
|
||
|
{
|
||
|
dev->scan_mode = STV680_COLOR_RGB;
|
||
|
}
|
||
|
else if (strcmp (dev->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_COLOR)
|
||
|
== 0)
|
||
|
{
|
||
|
dev->scan_mode = STV680_COLOR;
|
||
|
|
||
|
}
|
||
|
else if (strcmp (dev->val[OPT_MODE].s, COLOR_RGB_TEXT_STR) == 0)
|
||
|
{
|
||
|
dev->scan_mode = STV680_COLOR_RGB_TEXT;
|
||
|
|
||
|
}
|
||
|
|
||
|
/* The STV680 supports only a handful of resolution. */
|
||
|
/* This the default resolution range for the STV680 */
|
||
|
|
||
|
dev->depth = 8;
|
||
|
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 lowest. */
|
||
|
dev->val[OPT_RESOLUTION].w = dev->resolutions_list[1];
|
||
|
}
|
||
|
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)
|
||
|
{
|
||
|
Stv680_Vidcam *dev = handle;
|
||
|
int i;
|
||
|
|
||
|
DBG (DBG_proc, "sane_get_parameters: enter\n");
|
||
|
|
||
|
if (!(dev->scanning))
|
||
|
{
|
||
|
dev->x_resolution = dev->val[OPT_RESOLUTION].w;
|
||
|
/* Prepare the parameters for the caller. */
|
||
|
memset (&dev->params, 0, sizeof (SANE_Parameters));
|
||
|
|
||
|
dev->params.last_frame = SANE_TRUE;
|
||
|
|
||
|
switch (dev->scan_mode)
|
||
|
{
|
||
|
case STV680_COLOR_RAW:
|
||
|
dev->bytes_pixel = 1; /* raw image is 422 code, 1 byte/pixel */
|
||
|
break;
|
||
|
case STV680_COLOR_RGB:
|
||
|
case STV680_COLOR_RGB_TEXT:
|
||
|
case STV680_COLOR:
|
||
|
dev->bytes_pixel = 3;
|
||
|
break;
|
||
|
}
|
||
|
dev->params.format = SANE_FRAME_RGB;
|
||
|
dev->params.pixels_per_line = dev->x_resolution;
|
||
|
dev->params.bytes_per_line =
|
||
|
dev->params.pixels_per_line * dev->bytes_pixel;
|
||
|
dev->params.depth = 8;
|
||
|
if (dev->resolutions_list != NULL)
|
||
|
{
|
||
|
/* This vidcam has a fixed number of supported
|
||
|
* resolutions. Find the color sequence for that
|
||
|
* resolution. */
|
||
|
|
||
|
for (i = 0;
|
||
|
dev->hw->color_adjust[i].resolution_x != dev->x_resolution;
|
||
|
i++);
|
||
|
|
||
|
dev->red_s = dev->hw->color_adjust[i].z1_color_0;
|
||
|
dev->green_s = dev->hw->color_adjust[i].z1_color_1;
|
||
|
dev->blue_s = dev->hw->color_adjust[i].z1_color_2;
|
||
|
dev->y_resolution = dev->hw->color_adjust[i].resolution_y;
|
||
|
}
|
||
|
dev->subsample = 0;
|
||
|
switch (dev->val[OPT_RESOLUTION].w)
|
||
|
{
|
||
|
case 176:
|
||
|
dev->video_mode = 0x0200;
|
||
|
dev->cwidth = dev->x_resolution + 2;
|
||
|
dev->cheight = dev->y_resolution + 2;
|
||
|
break;
|
||
|
case 160: /* 160x120 subsampled */
|
||
|
dev->x_resolution = 320;
|
||
|
dev->y_resolution = 240;
|
||
|
dev->video_mode = 0x0300;
|
||
|
dev->cwidth = dev->x_resolution + 2;
|
||
|
dev->cheight = dev->y_resolution + 2;
|
||
|
dev->subsample = 160;
|
||
|
break;
|
||
|
case 320:
|
||
|
dev->video_mode = 0x0300;
|
||
|
dev->cwidth = dev->x_resolution + 2;
|
||
|
dev->cheight = dev->y_resolution + 2;
|
||
|
break;
|
||
|
case 352:
|
||
|
dev->video_mode = 0x0000;
|
||
|
dev->cwidth = dev->x_resolution + 4;
|
||
|
dev->cheight = dev->y_resolution + 4;
|
||
|
break;
|
||
|
case 640:
|
||
|
dev->video_mode = 0x0100;
|
||
|
dev->cwidth = dev->x_resolution + 4;
|
||
|
dev->cheight = dev->y_resolution + 4;
|
||
|
break;
|
||
|
}
|
||
|
dev->params.pixels_per_line = dev->x_resolution;
|
||
|
dev->params.lines = dev->y_resolution;
|
||
|
DBG (DBG_info, "sane_get_parameters: x=%d, y=%d\n", dev->x_resolution,
|
||
|
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)
|
||
|
{
|
||
|
Stv680_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 = stv680_vidcam_init (dev);
|
||
|
if (status)
|
||
|
{
|
||
|
DBG (DBG_error, "ERROR: failed to init the vidcam\n");
|
||
|
stv680_close (dev);
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
dev->image_end = 0;
|
||
|
dev->image_begin = 0;
|
||
|
/* real_byte_left is bulk read bytes, bytes_left is frontend buffer bytes */
|
||
|
dev->real_bytes_left = dev->cwidth * dev->cheight;
|
||
|
dev->bytes_left = dev->params.bytes_per_line * dev->params.lines;
|
||
|
|
||
|
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;
|
||
|
Stv680_Vidcam *dev = handle;
|
||
|
size_t size;
|
||
|
|
||
|
DBG (DBG_proc, "sane_read: enter\n");
|
||
|
|
||
|
*len = 0;
|
||
|
if (dev->deliver_eof)
|
||
|
{
|
||
|
dev->deliver_eof = 0;
|
||
|
return SANE_STATUS_EOF;
|
||
|
}
|
||
|
|
||
|
if (!(dev->scanning))
|
||
|
{
|
||
|
/* OOPS, not scanning, stop a scan. */
|
||
|
stv680_reset_vidcam (dev);
|
||
|
stv680_close (dev);
|
||
|
dev->scanning = SANE_FALSE;
|
||
|
return SANE_STATUS_CANCELLED;
|
||
|
}
|
||
|
|
||
|
if (dev->bytes_left <= 0)
|
||
|
{
|
||
|
return (SANE_STATUS_EOF);
|
||
|
}
|
||
|
|
||
|
if (dev->image_begin == dev->image_end)
|
||
|
{
|
||
|
/* Fill image */
|
||
|
status = stv680_fill_image (dev);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_info, "sane_read: stv680_fill_image status NOK\n");
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
size = dev->bytes_left;
|
||
|
if (((unsigned int) max_len) < size)
|
||
|
{
|
||
|
DBG (DBG_error, "sane_read: max_len < size\n");
|
||
|
return (SANE_FALSE);
|
||
|
}
|
||
|
if ((dev->image_end - dev->image_begin) > size)
|
||
|
{
|
||
|
size = dev->image_end - dev->image_begin;
|
||
|
DBG (DBG_proc, "sane_read: size < dev->image_end - dev->image_begin\n");
|
||
|
}
|
||
|
/* diff between size an dev->bytes_left because of 356/352 and 292/288 */
|
||
|
DBG (DBG_info, "sane_read: size =0x%lx bytes, max_len=0x%lx bytes\n",
|
||
|
(unsigned long) (size_t) size, (unsigned long) (size_t) max_len);
|
||
|
|
||
|
*len = dev->bytes_left; /* needed */
|
||
|
size = dev->bytes_left;
|
||
|
dev->bytes_left = 0; /* needed for frontend or ? */
|
||
|
|
||
|
if (dev->scan_mode != STV680_COLOR_RAW)
|
||
|
{
|
||
|
/* do bayer unshuffle after complete frame is read */
|
||
|
status = stv680_bayer_unshuffle (dev, buf, &size);
|
||
|
if (status != SANE_STATUS_GOOD)
|
||
|
{
|
||
|
DBG (DBG_info, "sane_read: stv680_bayer_unshuffle status NOK\n");
|
||
|
return (status);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Copy the raw data to the frontend buffer. */
|
||
|
memcpy (buf, dev->image, size);
|
||
|
DBG (DBG_info, "sane_read: raw mode\n");
|
||
|
}
|
||
|
DBG (DBG_info, "sane_read: exit\n");
|
||
|
status = SANE_STATUS_GOOD;
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
Stv680_Vidcam *dev = handle;
|
||
|
|
||
|
DBG (DBG_proc, "sane_cancel: enter\n");
|
||
|
|
||
|
/* Stop a scan. */
|
||
|
if (dev->scanning == SANE_TRUE)
|
||
|
{
|
||
|
/* Reset the vidcam */
|
||
|
stv680_reset_vidcam (dev);
|
||
|
stv680_close (dev);
|
||
|
}
|
||
|
dev->scanning = SANE_FALSE;
|
||
|
dev->deliver_eof = 0;
|
||
|
|
||
|
/* return SANE_STATUS_CANCELLED; */
|
||
|
DBG (DBG_proc, "sane_cancel: exit\n");
|
||
|
}
|
||
|
|
||
|
void
|
||
|
sane_close (SANE_Handle handle)
|
||
|
{
|
||
|
Stv680_Vidcam *dev = handle;
|
||
|
Stv680_Vidcam *dev_tmp;
|
||
|
|
||
|
DBG (DBG_proc, "sane_close: enter\n");
|
||
|
|
||
|
/* Stop a scan. */
|
||
|
|
||
|
if (dev->scanning == SANE_TRUE)
|
||
|
{
|
||
|
stv680_reset_vidcam (dev);
|
||
|
stv680_close (dev);
|
||
|
}
|
||
|
dev->scanning = SANE_FALSE;
|
||
|
|
||
|
/* 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
stv680_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");
|
||
|
}
|