summaryrefslogtreecommitdiff
path: root/backend/qcam.c
diff options
context:
space:
mode:
Diffstat (limited to 'backend/qcam.c')
-rw-r--r--backend/qcam.c2264
1 files changed, 2264 insertions, 0 deletions
diff --git a/backend/qcam.c b/backend/qcam.c
new file mode 100644
index 0000000..0148bea
--- /dev/null
+++ b/backend/qcam.c
@@ -0,0 +1,2264 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1997 David Mosberger-Tang
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+
+ This file implements a SANE backend for the Connectix QuickCam. At
+ present, only the color camera is supported though the driver
+ should be able to easily accommodate black and white cameras.
+
+ Portions of this code are derived from Scott Laird's qcam driver.
+ It's copyright notice is reproduced here:
+
+ Copyright (C) 1996 by Scott Laird
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use, copy,
+ modify, merge, publish, distribute, sublicense, and/or sell copies
+ of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL SCOTT LAIRD BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+
+#ifdef _AIX
+# include "lalloca.h" /* MUST come first for AIX! */
+#endif
+
+#include "../include/sane/config.h"
+#include "lalloca.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <math.h>
+#include <setjmp.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"
+
+
+#define BACKEND_NAME qcam
+#include "../include/sane/sanei_backend.h"
+
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+#include "../include/sane/sanei_config.h"
+#define QCAM_CONFIG_FILE "qcam.conf"
+
+#include "qcam.h"
+
+/* status bits */
+#define NeedRamTable (1 << 1)
+#define BlackBalanceInProgress (1 << 6)
+#define CameraNotReady (1 << 7)
+
+/* lpdata bits: */
+#define Cmd0_7 0xff
+#define CamRdy2 ( 1 << 0) /* byte mode */
+#define Data0_6 (0x7f << 1) /* byte mode */
+
+/* lpstatus bits: */
+#define CamRdy1 ( 1 << 3) /* nibble mode */
+#define Nibble0_3 (0x0f << 4) /* nibble mode */
+#define Data7_11 (0x1f << 3) /* byte mode */
+
+/* lpcontrol bits: */
+#define Strobe ( 1 << 0) /* unused */
+#define Autofeed ( 1 << 1)
+#define Reset_N ( 1 << 2)
+#define PCAck ( 1 << 3)
+#define BiDir ( 1 << 5)
+
+static int num_devices;
+static QC_Device *first_dev;
+static QC_Scanner *first_handle;
+
+static const SANE_String_Const resolution_list[] = {
+ "Low", /* million-mode */
+ "High", /* billion-mode */
+ 0
+};
+
+static const SANE_Int mono_depth_list[] = {
+ 2, /* # of elements */
+ 4, 6
+};
+
+static const SANE_Int color_depth_list[] = {
+ /*2 */ 1,
+ /* # of elements */
+ /*16, */ 24
+ /* "thousand" mode not implemented yet */
+};
+
+static const SANE_Int xfer_scale_list[] = {
+ 3, /* # of elements */
+ 1, 2, 4
+};
+
+static const SANE_Range u8_range = {
+ /* min, max, quantization */
+ 0, 255, 0
+};
+
+static const SANE_Range brightness_range = {
+ /* min, max, quantization */
+ 0, 254, 0 /* 255 is bulb mode! */
+};
+
+static const SANE_Range x_range[] = {
+ /* min, max, quantization */
+ {0, 338, 2}, /* million mode */
+ {0, 676, 4}, /* billion mode */
+};
+
+static const SANE_Range odd_x_range[] = {
+ /* min, max, quantization */
+ {1, 339, 2}, /* million mode */
+ {3, 683, 4}, /* billion mode */
+};
+
+static const SANE_Range y_range[] = {
+ /* min, max, quantization */
+ {0, 249, 1}, /* million mode */
+ {0, 498, 2}, /* billion mode */
+};
+
+static const SANE_Range odd_y_range[] = {
+ /* min, max, quantization */
+ {0, 249, 1}, /* million mode */
+ {1, 499, 2}, /* billion mode */
+};
+
+static const SANE_Range bw_x_range = { 0, 334, 2 };
+static const SANE_Range odd_bw_x_range = { 1, 335, 2 };
+static const SANE_Range bw_y_range = { 0, 241, 1 };
+static const SANE_Range odd_bw_y_range = { 1, 242, 1 };
+
+#if defined(HAVE_SYS_IO_H) || defined(HAVE_ASM_IO_H) || defined (HAVE_SYS_HW_H)
+
+#ifdef HAVE_SYS_IO_H
+# include <sys/io.h> /* GNU libc based OS */
+#elif HAVE_ASM_IO_H
+# include <asm/io.h> /* older Linux */
+#elif HAVE_SYS_HW_H
+# include <sys/hw.h> /* OS/2 */
+#endif
+
+#endif /* <sys/io.h> || <asm/io.h> || <sys/hw.h> */
+
+#define read_lpdata(d) inb ((d)->port)
+#define read_lpstatus(d) inb ((d)->port + 1)
+#define read_lpcontrol(d) inb ((d)->port + 2)
+#define write_lpdata(d,v) outb ((v), (d)->port)
+#define write_lpcontrol(d,v) outb ((v), (d)->port + 2)
+
+
+static SANE_Status
+enable_ports (QC_Device * q)
+{
+ /* better safe than sorry */
+ if (q->port < 0x278 || q->port > 0x3bc)
+ return SANE_STATUS_INVAL;
+
+ if (ioperm (q->port, 3, 1) < 0)
+ return SANE_STATUS_INVAL;
+
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+disable_ports (QC_Device * q)
+{
+ if (ioperm (q->port, 3, 0) < 0)
+ return SANE_STATUS_INVAL;
+
+ return SANE_STATUS_GOOD;
+}
+
+/* We need a short delay loop -- somthing well under a millisecond.
+ Unfortunately, adding 2 usleep(1)'s to qc_command slowed it down by
+ a factor of over 1000 over the same loop with 2 usleep(0)'s, and
+ that's too slow -- qc_start was taking over a second to run. This
+ seems to help, but if anyone has a good speed-independent pause
+ routine, please tell me. -- Scott
+
+ If you're worried about hogging the CPU: don't worry, the qcam
+ interface leaves you no choice, so this doesn't make the situation
+ any worse... */
+
+static int
+qc_wait (QC_Device * q)
+{
+ return read_lpstatus (q);
+}
+
+
+/* This function uses POSIX fcntl-style locking on a file created in
+ the /tmp directory. Because it uses the Unix record locking
+ facility, locks are relinquished automatically on process
+ termination, so "dead locks" are not a problem. (FYI, the lock
+ file will remain after process termination, but this is actually
+ desired so that the next process need not re-creat(2)e it... just
+ lock it.) The wait argument indicates whether or not this funciton
+ should "block" waiting for the previous lock to be relinquished.
+ This is ideal so that multiple processes (eg. qcam) taking
+ "snapshots" can peacefully coexist.
+
+ -- Dave Plonka (plonka@carroll1.cc.edu) */
+static SANE_Status
+qc_lock_wait (QC_Device * q, int wait)
+{
+#ifdef F_SETLK
+
+#ifndef HAVE_STRUCT_FLOCK
+ struct flock
+ {
+ off_t l_start;
+ off_t l_len;
+ pid_t l_pid;
+ short l_type;
+ short l_whence;
+ };
+#endif /* !HAVE_STRUCT_FLOCK */
+ struct flock sfl;
+#endif
+
+ DBG (3, "qc_lock_wait: acquiring lock for 0x%x\n", q->port);
+
+#ifdef F_SETLK
+ memset (&sfl, 0, sizeof (sfl));
+#endif
+
+ if (q->lock_fd < 0)
+ {
+ char lockfile[128];
+
+ sprintf (lockfile, "/tmp/LOCK.qcam.0x%x", q->port);
+ q->lock_fd = open (lockfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (q->lock_fd < 0)
+ {
+ DBG (1, "qc_lock_wait: failed to open %s (%s)\n",
+ lockfile, strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+
+ }
+
+#ifdef F_SETLK
+ sfl.l_type = F_WRLCK;
+ if (fcntl (q->lock_fd, wait ? F_SETLKW : F_SETLK, &sfl) != 0)
+ {
+ DBG (1, "qc_lock_wait: failed to acquire lock (%s)\n",
+ strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+#endif
+
+ DBG (3, "qc_lock_wait: got lock for 0x%x\n", q->port);
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+qc_unlock (QC_Device * q)
+{
+ SANE_Status status;
+ char lockfile[128];
+#ifdef F_SETLK
+#ifndef HAVE_STRUCT_FLOCK
+ struct flock
+ {
+ off_t l_start;
+ off_t l_len;
+ pid_t l_pid;
+ short l_type;
+ short l_whence;
+ };
+#endif /* !HAVE_STRUCT_FLOCK */
+ struct flock sfl;
+#endif
+
+ DBG (3, "qc_unlock: releasing lock for 0x%x\n", q->port);
+
+#ifdef F_SETLK
+ memset (&sfl, 0, sizeof (sfl));
+#endif
+ if (q->lock_fd < 0)
+ {
+ DBG (3, "qc_unlock; port was not locked\n");
+ return SANE_STATUS_INVAL;
+ }
+ /* clear the exclusive lock */
+
+#ifdef F_SETLK
+ sfl.l_type = F_UNLCK;
+
+ if (fcntl (q->lock_fd, F_SETLK, &sfl) != 0)
+ {
+ DBG (3, "qc_unlock: failed to release lock (%s)\n", strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+#endif
+ sprintf (lockfile, "/tmp/LOCK.qcam.0x%x", q->port);
+ DBG (1, "qc_unlock: /tmp/LOCK.qcam.0x%x\n", q->port);
+ unlink (lockfile);
+ close (q->lock_fd);
+ q->lock_fd = -1;
+ DBG (1, "qc_unlock: exit\n");
+ status = SANE_STATUS_GOOD;
+ return status;
+}
+
+static SANE_Status
+qc_lock (QC_Device * q)
+{
+ return qc_lock_wait (q, 1);
+}
+
+/* Busy-waits for a handshake signal from the QuickCam. Almost all
+ communication with the camera requires handshaking. This is why
+ qcam is a CPU hog. */
+static int
+qc_waithand (QC_Device * q, int val)
+{
+ int status;
+
+ while (((status = read_lpstatus (q)) & CamRdy1) != val);
+ return status;
+}
+
+/* This is used when the qcam is in bidirectional ("byte") mode, and
+ the handshaking signal is CamRdy2 (bit 0 of data reg) instead of
+ CamRdy1 (bit 3 of status register). It also returns the last value
+ read, since this data is useful. */
+static unsigned int
+qc_waithand2 (QC_Device * q, int val)
+{
+ unsigned int status;
+
+ do
+ {
+ status = read_lpdata (q);
+ }
+ while ((status & CamRdy2) != (unsigned int) val);
+ return status;
+}
+
+static unsigned int
+qc_send (QC_Device * q, unsigned int byte)
+{
+ unsigned int echo;
+ int n1, n2;
+
+ write_lpdata (q, byte);
+ qc_wait (q);
+ write_lpcontrol (q, Autofeed | Reset_N);
+ qc_wait (q);
+
+ n1 = qc_waithand (q, CamRdy1);
+
+ write_lpcontrol (q, Autofeed | Reset_N | PCAck);
+ qc_wait (q);
+ n2 = qc_waithand (q, 0);
+
+ echo = (n1 & 0xf0) | ((n2 & 0xf0) >> 4);
+#ifndef NDEBUG
+ if (echo != byte)
+ {
+ DBG (1, "qc_send: sent 0x%02x, camera echoed 0x%02x\n", byte, echo);
+ n2 = read_lpstatus (q);
+ echo = (n1 & 0xf0) | ((n2 & 0xf0) >> 4);
+ if (echo != byte)
+ DBG (1, "qc_send: (re-read does not help)\n");
+ else
+ DBG (1, "qc_send: (fixed on re-read)\n");
+ }
+#endif
+ return echo;
+}
+
+static int
+qc_readparam (QC_Device * q)
+{
+ int n1, n2;
+ int cmd;
+
+ write_lpcontrol (q, Autofeed | Reset_N); /* clear PCAck */
+ n1 = qc_waithand (q, CamRdy1);
+
+ write_lpcontrol (q, Autofeed | Reset_N | PCAck); /* set PCAck */
+ n2 = qc_waithand (q, 0);
+
+ cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4);
+ return cmd;
+}
+
+static unsigned int
+qc_getstatus (QC_Device * q)
+{
+ unsigned int status;
+
+ qc_send (q, QC_SEND_STATUS);
+ status = qc_readparam (q);
+ DBG (3, "qc_getstatus: status=0x%02x\n", status);
+ return status;
+}
+
+static void
+qc_setscanmode (QC_Scanner * s, u_int * modep)
+{
+ QC_Device *q = s->hw;
+ u_int mode = 0;
+
+ if (q->version != QC_COLOR)
+ {
+ switch (s->val[OPT_XFER_SCALE].w)
+ {
+ case 1:
+ mode = 0;
+ break;
+ case 2:
+ mode = 4;
+ break;
+ case 4:
+ mode = 8;
+ break;
+ }
+ switch (s->val[OPT_DEPTH].w)
+ {
+ case 4:
+ break;
+ case 6:
+ mode += 2;
+ break;
+ }
+ }
+ else
+ {
+ switch (s->val[OPT_XFER_SCALE].w)
+ {
+ case 1:
+ mode = 0;
+ break;
+ case 2:
+ mode = 2;
+ break;
+ case 4:
+ mode = 4;
+ break;
+ }
+ if (s->resolution == QC_RES_LOW)
+ mode |= 0x18; /* millions mode */
+ else
+ mode |= 0x10; /* billions mode */
+ }
+ if (s->val[OPT_TEST].w)
+ mode |= 0x40; /* test mode */
+
+ if (q->port_mode == QC_BIDIR)
+ mode |= 1;
+
+ DBG (2, "scanmode (before increment): 0x%x\n", mode);
+
+ if (q->version == QC_COLOR)
+ ++mode;
+
+ *modep = mode;
+}
+
+/* Read data bytes from the camera. The number of bytes read is
+ returned as the function result. Depending on the mode, it may be
+ either 1, 3 or 6. On failure, 0 is returned. If buffer is 0, the
+ internal state-machine is reset. */
+static size_t
+qc_readbytes (QC_Scanner * s, unsigned char buffer[])
+{
+ QC_Device *q = s->hw;
+ unsigned int hi, lo;
+ unsigned int hi2, lo2;
+ size_t bytes = 0;
+
+ if (!buffer)
+ {
+ s->readbytes_state = 0;
+ return 0;
+ }
+
+ switch (q->port_mode)
+ {
+ case QC_BIDIR:
+ /* bi-directional port */
+
+ /* read off 24 bits: */
+ write_lpcontrol (q, Autofeed | Reset_N | BiDir);
+ lo = qc_waithand2 (q, 1) >> 1;
+ hi = (read_lpstatus (q) >> 3) & 0x1f;
+ write_lpcontrol (q, Autofeed | Reset_N | PCAck | BiDir);
+ lo2 = qc_waithand2 (q, 0) >> 1;
+ hi2 = (read_lpstatus (q) >> 3) & 0x1f;
+ if (q->version == QC_COLOR)
+ {
+ /* is Nibble3 inverted for color quickcams only? */
+ hi ^= 0x10;
+ hi2 ^= 0x10;
+ }
+ switch (s->val[OPT_DEPTH].w)
+ {
+ case 4:
+ buffer[0] = lo & 0xf;
+ buffer[1] = ((lo & 0x70) >> 4) | ((hi & 1) << 3);
+ buffer[2] = (hi & 0x1e) >> 1;
+ buffer[3] = lo2 & 0xf;
+ buffer[4] = ((lo2 & 0x70) >> 4) | ((hi2 & 1) << 3);
+ buffer[5] = (hi2 & 0x1e) >> 1;
+ bytes = 6;
+ break;
+
+ case 6:
+ buffer[0] = lo & 0x3f;
+ buffer[1] = ((lo & 0x40) >> 6) | (hi << 1);
+ buffer[2] = lo2 & 0x3f;
+ buffer[3] = ((lo2 & 0x40) >> 6) | (hi2 << 1);
+ bytes = 4;
+ break;
+
+ case 24:
+ buffer[0] = lo | ((hi & 0x1) << 7);
+ buffer[1] = ((hi2 & 0x1e) >> 1) | ((hi & 0x1e) << 3);
+ buffer[2] = lo2 | ((hi2 & 0x1) << 7);
+ bytes = 3;
+ break;
+ }
+ break;
+
+ case QC_UNIDIR: /* Unidirectional Port */
+ write_lpcontrol (q, Autofeed | Reset_N);
+ lo = (qc_waithand (q, CamRdy1) & 0xf0) >> 4;
+ write_lpcontrol (q, Autofeed | Reset_N | PCAck);
+ hi = (qc_waithand (q, 0) & 0xf0) >> 4;
+
+ if (q->version == QC_COLOR)
+ {
+ /* invert Nibble3 */
+ hi ^= 8;
+ lo ^= 8;
+ }
+
+ switch (s->val[OPT_DEPTH].w)
+ {
+ case 4:
+ buffer[0] = lo;
+ buffer[1] = hi;
+ bytes = 2;
+ break;
+
+ case 6:
+ switch (s->readbytes_state)
+ {
+ case 0:
+ buffer[0] = (lo << 2) | ((hi & 0xc) >> 2);
+ s->saved_bits = (hi & 3) << 4;
+ s->readbytes_state = 1;
+ bytes = 1;
+ break;
+
+ case 1:
+ buffer[0] = lo | s->saved_bits;
+ s->saved_bits = hi << 2;
+ s->readbytes_state = 2;
+ bytes = 1;
+ break;
+
+ case 2:
+ buffer[0] = ((lo & 0xc) >> 2) | s->saved_bits;
+ buffer[1] = ((lo & 3) << 4) | hi;
+ s->readbytes_state = 0;
+ bytes = 2;
+ break;
+
+ default:
+ DBG (1, "qc_readbytes: bad unidir 6-bit stat %d\n",
+ s->readbytes_state);
+ break;
+ }
+ break;
+
+ case 24:
+ buffer[0] = (lo << 4) | hi;
+ bytes = 1;
+ break;
+
+ default:
+ DBG (1, "qc_readbytes: bad unidir bit depth %d\n",
+ s->val[OPT_DEPTH].w);
+ break;
+ }
+ break;
+
+ default:
+ DBG (1, "qc_readbytes: bad port_mode %d\n", q->port_mode);
+ break;
+ }
+ return bytes;
+}
+
+static void
+qc_reset (QC_Device * q)
+{
+ write_lpcontrol (q, Strobe | Autofeed | Reset_N | PCAck);
+ qc_wait (q);
+ write_lpcontrol (q, Strobe | Autofeed | PCAck);
+ qc_wait (q);
+ write_lpcontrol (q, Strobe | Autofeed | Reset_N | PCAck);
+}
+
+/* This function is executed as a child process. The reason this is
+ executed as a subprocess is because the qcam interface directly reads
+ off of a I/O port (rather than a filedescriptor). Thus, to have
+ something to select() on, we transfer the data through a pipe.
+
+ WARNING: Since this is executed as a subprocess, it's NOT possible
+ to update any of the variables in the main process (in particular
+ the scanner state cannot be updated). */
+
+static jmp_buf env;
+
+static void
+sighandler (int signal)
+{
+ DBG (3, "sighandler: got signal %d\n", signal);
+ longjmp (env, 1);
+}
+
+/* Original despeckling code by Patrick Reynolds <patrickr@virginia.edu> */
+
+static void
+despeckle (int width, int height, SANE_Byte * in, SANE_Byte * out)
+{
+ long x, i;
+ /* The light-check threshold. Higher numbers remove more lights but
+ blur the image more. 30 is good for indoor lighting. */
+# define NO_LIGHTS 30
+
+ /* macros to make the code a little more readable, p=previous, n=next */
+# define R in[i*3]
+# define G in[i*3+1]
+# define B in[i*3+2]
+# define pR in[i*3-3]
+# define pG in[i*3-2]
+# define pB in[i*3-1]
+# define nR in[i*3+3]
+# define nG in[i*3+4]
+# define nB in[i*3+5]
+
+ DBG (1, "despeckle: width=%d, height=%d\n", width, height);
+
+ for (x = i = 0; i < width * height; ++i)
+ {
+ if (x == 0 || x == width - 1)
+ memcpy (&out[i * 3], &in[i * 3], 3);
+ else
+ {
+ if (R - (G + B) / 2 >
+ NO_LIGHTS + ((pR - (pG + pB) / 2) + (nR - (nG + nB) / 2)))
+ out[i * 3] = (pR + nR) / 2;
+ else
+ out[i * 3] = R;
+
+ if (G - (R + B) / 2 >
+ NO_LIGHTS + ((pG - (pR + pB) / 2) + (nG - (nR + nB) / 2)))
+ out[i * 3 + 1] = (pG + nG) / 2;
+ else
+ out[i * 3 + 1] = G;
+
+ if (B - (G + R) / 2 >
+ NO_LIGHTS + ((pB - (pG + pR) / 2) + (nB - (nG + nR) / 2)))
+ out[i * 3 + 2] = (pB + nB) / 2;
+ else
+ out[i * 3 + 2] = B;
+ }
+ if (++x >= width)
+ x = 0;
+ }
+# undef R
+# undef G
+# undef B
+# undef pR
+# undef pG
+# undef pB
+# undef nR
+# undef nG
+# undef nB
+}
+
+static void
+despeckle32 (int width, int height, SANE_Byte * in, SANE_Byte * out)
+{
+ long x, i;
+ /* macros to make the code a little more readable, p=previous, n=next */
+# define B in[i*4]
+# define Ga in[i*4 + 1]
+# define Gb in[i*4 + 1] /* ignore Gb and use Ga instead---Gb is weird */
+# define R in[i*4 + 3]
+# define pB in[i*4 - 4]
+# define pGa in[i*4 - 3]
+# define pGb in[i*4 - 1] /* ignore Gb and use Ga instead---Gb is weird */
+# define pR in[i*4 - 1]
+# define nB in[i*4 + 4]
+# define nGa in[i*4 + 5]
+# define nGb in[i*4 + 5] /* ignore Gb and use Ga instead---Gb is weird */
+# define nR in[i*4 + 7]
+
+ DBG (1, "despeckle32: width=%d, height=%d\n", width, height);
+
+ for (x = i = 0; i < width * height; ++i)
+ {
+ if (x == 0 || x == width - 1)
+ memcpy (&out[i * 4], &in[i * 4], 4);
+ else
+ {
+ if (x >= width - 2)
+ /* the last red pixel seems to be black at all times, use
+ R instead: */
+ nR = R;
+
+ if (R - ((Ga + Gb) / 2 + B) / 2 >
+ NO_LIGHTS + ((pR - ((pGa + pGb) / 2 + pB) / 2) +
+ (nR - ((nGa + nGb) / 2 + nB) / 2)))
+ out[i * 4 + 3] = (pR + nR) / 2;
+ else
+ out[i * 4 + 3] = R;
+
+ if (Ga - (R + B) / 2 > NO_LIGHTS + ((pGa - (pR + pB) / 2) +
+ (nGa - (nR + nB) / 2)))
+ out[i * 4 + 1] = (pGa + nGa) / 2;
+ else
+ out[i * 4 + 1] = Ga;
+
+ if (Gb - (R + B) / 2 > NO_LIGHTS + ((pGb - (pR + pB) / 2) +
+ (nGb - (nR + nB) / 2)))
+ out[i * 4 + 2] = (pGb + nGb) / 2;
+ else
+ out[i * 4 + 2] = Gb;
+
+ if (B - ((Ga + Gb) / 2 + R) / 2 >
+ NO_LIGHTS + ((pB - ((pGa + pGb) / 2 + pR) / 2) +
+ (nB - ((nGa + nGb) / 2 + nR) / 2)))
+ out[i * 4 + 0] = (pB + nB) / 2;
+ else
+ out[i * 4 + 0] = B;
+ }
+ if (++x >= width)
+ x = 0;
+ }
+# undef R
+# undef Ga
+# undef Gb
+# undef B
+# undef pR
+# undef pGa
+# undef pGb
+# undef pB
+# undef nR
+# undef nGa
+# undef nGb
+# undef nB
+}
+
+static int
+reader_process (QC_Scanner * s, int in_fd, int out_fd)
+{
+ static SANE_Byte *buffer = 0, *extra = 0;
+ static size_t buffer_size = 0;
+ size_t count, len, num_bytes;
+ QC_Device *q = s->hw;
+ QC_Scan_Request req;
+ int width, height;
+ SANE_Byte *src;
+ FILE *ofp;
+
+ DBG (5, "reader_process: enter\n");
+
+ enable_ports (q);
+
+ ofp = fdopen (out_fd, "w");
+ if (!ofp)
+ return 1;
+
+ while (1)
+ {
+ if (setjmp (env))
+ {
+ char ch;
+
+ /* acknowledge the signal: */
+ DBG (1, "reader_process: sending signal ACK\n");
+ fwrite (&ch, 1, 1, ofp);
+ fflush (ofp); /* force everything out the pipe */
+ continue;
+ }
+ signal (SIGINT, sighandler);
+
+ /* the main process gets us started by writing a size_t giving
+ the number of bytes we should expect: */
+ if (read (in_fd, &req, sizeof (req)) != sizeof (req))
+ {
+ perror ("read");
+ return 1;
+ }
+ num_bytes = req.num_bytes;
+
+ DBG (3, "reader_process: got request for %lu bytes\n",
+ (u_long) num_bytes);
+
+ /* Don't do this in sane_start() since there may be a long
+ timespan between it and the first sane_read(), which would
+ result in poor images. */
+ qc_send (q, QC_SEND_VIDEO_FRAME);
+ qc_send (q, req.mode);
+
+ if (req.despeckle
+ && (!extra || buffer_size < num_bytes
+ || buffer_size >= 2 * num_bytes))
+ {
+ if (extra)
+ extra = realloc (extra, num_bytes);
+ else
+ extra = malloc (num_bytes);
+ if (!extra)
+ {
+ DBG (1, "reader_process: malloc(%ld) failed\n",
+ (long) num_bytes);
+ exit (1);
+ }
+ }
+
+ if (buffer_size < num_bytes || buffer_size >= 2 * num_bytes)
+ {
+ if (buffer)
+ buffer = realloc (buffer, num_bytes);
+ else
+ buffer = malloc (num_bytes);
+ if (!buffer)
+ {
+ DBG (1, "reader_process: malloc(%ld) failed\n",
+ (long) num_bytes);
+ exit (1);
+ }
+ buffer_size = num_bytes;
+ }
+
+ if (q->port_mode == QC_BIDIR)
+ {
+ /* turn port into input port */
+ write_lpcontrol (q, Autofeed | Reset_N | PCAck | BiDir);
+ usleep (3);
+ write_lpcontrol (q, Autofeed | Reset_N | BiDir);
+ qc_waithand (q, CamRdy1);
+ write_lpcontrol (q, Autofeed | Reset_N | PCAck | BiDir);
+ qc_waithand (q, 0);
+ }
+
+ if (q->version == QC_COLOR)
+ for (len = 0; len < num_bytes; len += count)
+ count = qc_readbytes (s, buffer + len);
+ else
+ {
+ /* strange -- should be 15:63 below, but 4bpp is odd */
+ int shift, invert;
+ unsigned int i;
+ u_char val;
+
+ switch (s->val[OPT_DEPTH].w)
+ {
+ case 4:
+ invert = 16;
+ shift = 4;
+ break;
+
+ case 6:
+ invert = 63;
+ shift = 2;
+ break;
+
+ default:
+ DBG (1, "reader_process: unexpected depth %d\n",
+ s->val[OPT_DEPTH].w);
+ return 1;
+ }
+
+ for (len = 0; len < num_bytes; len += count)
+ {
+ count = qc_readbytes (s, buffer + len);
+ for (i = 0; i < count; ++i)
+ {
+ /* 4bpp is odd (again) -- inverter is 16, not 15,
+ but output must be 0-15 */
+ val = buffer[len + i];
+ if (val > 0 || invert != 16)
+ val = invert - val;
+ buffer[len + i] = (val << shift) | (val >> (8 - 2 * shift));
+ }
+ }
+ qc_readbytes (s, 0); /* reset state machine */
+ }
+ /* we're done reading this frame: */
+ DBG (2, "reader_process: frame complete\n");
+
+ if (q->port_mode == QC_BIDIR)
+ {
+ /* return port to output mode */
+ write_lpcontrol (q, Autofeed);
+ usleep (3);
+ write_lpcontrol (q, Autofeed | Reset_N);
+ usleep (3);
+ write_lpcontrol (q, Autofeed | Reset_N | PCAck);
+ }
+
+ if (req.resolution == QC_RES_HIGH)
+ {
+ SANE_Byte buf[6];
+ int x, y;
+
+ /* in billions mode, we need to oversample the data: */
+ src = buffer;
+ width = req.params.pixels_per_line;
+ height = req.params.lines;
+
+ if (req.despeckle)
+ {
+ despeckle32 (width / 2, req.params.lines / 2, buffer, extra);
+ src = extra;
+ }
+
+ assert (!(width & 1)); /* width must be even */
+
+ for (y = 0; y < height; ++y)
+ {
+ /* even line */
+
+ for (x = 0; x < width; x += 2)
+ {
+ int red1, green1, blue1, green2, blue2;
+
+ blue1 = src[0];
+ green1 = src[1];
+ red1 = src[3];
+ if (x >= width - 2)
+ {
+ red1 = src[-1]; /* last red seems to be missing */
+ blue2 = blue1;
+ green2 = green1;
+ }
+ else
+ {
+ blue2 = src[4];
+ green2 = src[5];
+ }
+ src += 4;
+
+ buf[0] = red1;
+ buf[1] = green1;
+ buf[2] = blue1;
+ buf[3] = red1;
+ buf[4] = green2;
+ buf[5] = blue2;
+ if (fwrite (buf, 1, 6, ofp) != 6)
+ {
+ perror ("fwrite: short write");
+ return 1;
+ }
+ }
+ if (++y >= height)
+ break;
+
+ src -= 2 * width; /* 4 bytes/pixel -> 2 pixels of 3 bytes each */
+
+ /* odd line */
+ for (x = 0; x < width; x += 2)
+ {
+ int red1, green3, blue3, green4, blue4;
+ int yoff;
+
+ if (x >= width - 2)
+ red1 = src[-1]; /* last red seems to be missing */
+ else
+ red1 = src[3];
+ yoff = 2 * width;
+ if (y >= height - 1)
+ yoff = 0;
+ green3 = src[yoff + 1];
+ blue3 = src[yoff + 0];
+ if (x >= width - 2)
+ {
+ blue4 = blue3;
+ green4 = green3;
+ }
+ else
+ {
+ blue4 = src[yoff + 4];
+ green4 = src[yoff + 5];
+ }
+ src += 4;
+
+ buf[0] = red1;
+ buf[1] = green3;
+ buf[2] = blue3;
+ buf[3] = red1;
+ buf[4] = green4;
+ buf[5] = blue4;
+ if (fwrite (buf, 1, 6, ofp) != 6)
+ {
+ perror ("fwrite: short write");
+ return 1;
+ }
+ }
+ }
+ }
+ else
+ {
+ src = buffer;
+ if (req.despeckle)
+ {
+ despeckle (req.params.pixels_per_line, req.params.lines,
+ buffer, extra);
+ src = extra;
+ }
+
+ /* now write the whole thing to the main process: */
+ if (fwrite (src, 1, num_bytes, ofp) != num_bytes)
+ {
+ perror ("fwrite: short write");
+ return 1;
+ }
+ }
+ fflush (ofp);
+ }
+ assert (SANE_FALSE); /* not reached */
+ DBG (5, "reader_process: exit\n");
+ return 1;
+}
+
+static SANE_Status
+attach (const char *devname, QC_Device ** devp)
+{
+ int i, n1, n2, s1, s2, cmd, port, force_unidir;
+ SANE_Status result, status;
+ QC_Device *q;
+ char *endp;
+
+ DBG (3, "attach: enter\n");
+ errno = 0;
+ force_unidir = 0;
+ if (devname[0] == 'u')
+ {
+ force_unidir = 1;
+ ++devname;
+ }
+ port = strtol (devname, &endp, 0);
+ if (endp == devname || errno == ERANGE)
+ {
+ DBG (1, "attach: invalid port address `%s'\n", devname);
+ return SANE_STATUS_INVAL;
+ }
+
+ for (q = first_dev; q; q = q->next)
+ if (port == q->port)
+ {
+ if (devp)
+ *devp = q;
+ return SANE_STATUS_GOOD;
+ }
+
+ q = malloc (sizeof (*q));
+ if (!q)
+ return SANE_STATUS_NO_MEM;
+
+ memset (q, 0, sizeof (*q));
+ q->port = port;
+ q->lock_fd = -1;
+
+ result = enable_ports (q);
+ if (result != SANE_STATUS_GOOD)
+ {
+ DBG (1, "attach: cannot enable ports (%s)\n", strerror (errno));
+ free (q);
+ return SANE_STATUS_INVAL;
+ }
+
+ /* lock camera while we determine its version: */
+ qc_lock (q);
+
+ qc_reset (q);
+
+ write_lpdata (q, QC_SEND_VERSION);
+ qc_wait (q);
+ write_lpcontrol (q, Autofeed | Reset_N); /* make PCAck inactive */
+ qc_wait (q);
+
+ for (i = 0; (i < 1000) && !(s1 = (n1 = read_lpstatus (q)) & CamRdy1); i++);
+ if (!s1)
+ {
+ DBG (2, "attach: failed to get CamRdy1 at port 0x%x\n", q->port);
+ goto unlock_and_fail;
+ }
+
+ write_lpcontrol (q, Autofeed | Reset_N | PCAck);
+ qc_wait (q);
+
+ for (i = 0; (i < 1000) && (s2 = (n2 = read_lpstatus (q)) & CamRdy1); i++);
+ if (s2)
+ {
+ DBG (2, "attach: CamRdy1 failed to clear at port 0x%x\n", q->port);
+ goto unlock_and_fail;
+ }
+
+ cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4);
+
+ if (cmd != QC_SEND_VERSION)
+ {
+ DBG (2, "attach: got 0x%02x instead of 0x%02x\n", cmd, QC_SEND_VERSION);
+ goto unlock_and_fail;
+ }
+
+ q->version = qc_readparam (q);
+ DBG (1, "attach: found QuickCam version 0x%02x\n", q->version);
+
+ q->port_mode = QC_UNIDIR;
+ if (!force_unidir)
+ {
+ write_lpcontrol (q, BiDir);
+ write_lpdata (q, 0x75);
+ if (read_lpdata (q) != 0x75)
+ q->port_mode = QC_BIDIR;
+ }
+
+ /* For some reason the color quickcam needs two set-black commands
+ after a reset. Thus, we now set the black-level to some
+ reasonable value (0) so that the next set-black level command
+ will really go through. */
+ if (q->version == QC_COLOR)
+ {
+ qc_send (q, QC_SET_BLACK);
+ qc_send (q, 0);
+
+ DBG (3, "attach: resetting black_level\n");
+
+ /* wait for set black level command to finish: */
+ while (qc_getstatus (q) & (CameraNotReady | BlackBalanceInProgress))
+ usleep (10000);
+ }
+
+ status = qc_unlock (q);
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (1, "attach: status qc_unlock NOK\n");
+ /* status = SANE_STATUS_GOOD; */
+ }
+ q->sane.name = strdup (devname);
+ q->sane.vendor = "Connectix";
+ q->sane.model =
+ (q->version == QC_COLOR) ? "Color QuickCam" : "B&W QuickCam";
+ q->sane.type = "video camera";
+
+ ++num_devices;
+ q->next = first_dev;
+ first_dev = q;
+
+ if (devp)
+ *devp = q;
+ DBG (3, "attach: exit status OK\n");
+ status = SANE_STATUS_GOOD;
+ return status;
+
+
+unlock_and_fail:
+ status = qc_unlock (q);
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (1, "attach: unlock_and_fail status qc_unlock NOK\n");
+ }
+ free (q);
+ DBG (3, "attach: exit status NOK\n");
+ status = SANE_STATUS_INVAL;
+ return status;
+}
+
+static SANE_Status
+init_options (QC_Scanner * s)
+{
+ int i;
+
+ DBG (3, "init_options: enter\n");
+
+ memset (s->opt, 0, sizeof (s->opt));
+ memset (s->val, 0, sizeof (s->val));
+
+ for (i = 0; i < NUM_OPTIONS; ++i)
+ {
+ s->opt[i].size = sizeof (SANE_Word);
+ s->opt[i].cap = (SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT);
+ }
+
+ s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
+ s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
+ s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
+ s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
+ s->val[OPT_NUM_OPTS].w = NUM_OPTIONS;
+
+ /* "Mode" group: */
+
+ s->opt[OPT_MODE_GROUP].title = "Scan Mode";
+ s->opt[OPT_MODE_GROUP].desc = "";
+ s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_MODE_GROUP].cap = 0;
+ s->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ /* resolution */
+ s->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
+ s->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
+ s->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
+ s->opt[OPT_RESOLUTION].type = SANE_TYPE_STRING;
+ s->opt[OPT_RESOLUTION].size = 5; /* sizeof("High") */
+ s->opt[OPT_RESOLUTION].unit = SANE_UNIT_NONE;
+ s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_RESOLUTION].constraint.string_list = resolution_list;
+ s->val[OPT_RESOLUTION].s = strdup (resolution_list[QC_RES_LOW]);
+
+ /* bit-depth */
+ s->opt[OPT_DEPTH].name = SANE_NAME_BIT_DEPTH;
+ s->opt[OPT_DEPTH].title = "Pixel depth";
+ s->opt[OPT_DEPTH].desc = "Number of bits per pixel.";
+ s->opt[OPT_DEPTH].type = SANE_TYPE_INT;
+ s->opt[OPT_DEPTH].unit = SANE_UNIT_BIT;
+ s->opt[OPT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_DEPTH].constraint.word_list = color_depth_list;
+ s->val[OPT_DEPTH].w = color_depth_list[NELEMS (color_depth_list) - 1];
+
+ /* test */
+ s->opt[OPT_TEST].name = "test-image";
+ s->opt[OPT_TEST].title = "Force test image";
+ s->opt[OPT_TEST].desc =
+ "Acquire a test-image instead of the image seen by the camera. "
+ "The test image consists of red, green, and blue squares of "
+ "32x32 pixels each. Use this to find out whether the "
+ "camera is connected properly.";
+ s->opt[OPT_TEST].type = SANE_TYPE_BOOL;
+ s->val[OPT_TEST].w = SANE_FALSE;
+
+ /* "Geometry" group: */
+ s->opt[OPT_GEOMETRY_GROUP].title = "Geometry";
+ s->opt[OPT_GEOMETRY_GROUP].desc = "";
+ s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED;
+ s->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ /* top-left x */
+ s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
+ s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
+ s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
+ s->opt[OPT_TL_X].type = SANE_TYPE_INT;
+ s->opt[OPT_TL_X].unit = SANE_UNIT_PIXEL;
+ s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_TL_X].constraint.range = &x_range[QC_RES_LOW];
+ s->val[OPT_TL_X].w = 10;
+
+ /* top-left y */
+ s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
+ s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
+ s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
+ s->opt[OPT_TL_Y].type = SANE_TYPE_INT;
+ s->opt[OPT_TL_Y].unit = SANE_UNIT_PIXEL;
+ s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_TL_Y].constraint.range = &y_range[QC_RES_LOW];
+ s->val[OPT_TL_Y].w = 0;
+
+ /* bottom-right x */
+ s->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X;
+ s->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
+ s->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X;
+ s->opt[OPT_BR_X].type = SANE_TYPE_INT;
+ s->opt[OPT_BR_X].unit = SANE_UNIT_PIXEL;
+ s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BR_X].constraint.range = &odd_x_range[QC_RES_LOW];
+ s->val[OPT_BR_X].w = 339;
+
+ /* bottom-right y */
+ s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
+ s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
+ s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
+ s->opt[OPT_BR_Y].type = SANE_TYPE_INT;
+ s->opt[OPT_BR_Y].unit = SANE_UNIT_PIXEL;
+ s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BR_Y].constraint.range = &odd_y_range[QC_RES_LOW];
+ s->val[OPT_BR_Y].w = 245;
+
+ /* xfer-scale */
+ s->opt[OPT_XFER_SCALE].name = "transfer-scale";
+ s->opt[OPT_XFER_SCALE].title = "Transfer scale";
+ s->opt[OPT_XFER_SCALE].desc =
+ "The transferscale determines how many of the acquired pixels actually "
+ "get sent to the computer. For example, a transfer scale of 2 would "
+ "request that every other acquired pixel would be omitted. That is, "
+ "when scanning a 200 pixel wide and 100 pixel tall area, the resulting "
+ "image would be only 100x50 pixels large. Using a large transfer scale "
+ "improves acquisition speed, but reduces resolution.";
+ s->opt[OPT_XFER_SCALE].type = SANE_TYPE_INT;
+ s->opt[OPT_XFER_SCALE].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_XFER_SCALE].constraint.word_list = xfer_scale_list;
+ s->val[OPT_XFER_SCALE].w = xfer_scale_list[1];
+
+ /* despeckle */
+ s->opt[OPT_DESPECKLE].name = "despeckle";
+ s->opt[OPT_DESPECKLE].title = "Speckle filter";
+ s->opt[OPT_DESPECKLE].desc = "Turning on this filter will remove the "
+ "christmas lights that are typically present in dark images.";
+ s->opt[OPT_DESPECKLE].type = SANE_TYPE_BOOL;
+ s->opt[OPT_DESPECKLE].constraint_type = SANE_CONSTRAINT_NONE;
+ s->val[OPT_DESPECKLE].w = 0;
+
+
+ /* "Enhancement" group: */
+
+ s->opt[OPT_ENHANCEMENT_GROUP].title = "Enhancement";
+ s->opt[OPT_ENHANCEMENT_GROUP].desc = "";
+ s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_ENHANCEMENT_GROUP].cap = 0;
+ s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
+
+ /* brightness */
+ s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
+ s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
+ s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS
+ " In a conventional camera, this control corresponds to the "
+ "exposure time.";
+ s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
+ s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_AUTOMATIC;
+ s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
+ s->val[OPT_BRIGHTNESS].w = 135;
+
+ /* contrast */
+ s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
+ s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
+ s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
+ s->opt[OPT_CONTRAST].type = SANE_TYPE_INT;
+ s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CONTRAST].constraint.range = &u8_range;
+ s->val[OPT_CONTRAST].w = 104;
+
+ /* black-level */
+ s->opt[OPT_BLACK_LEVEL].name = SANE_NAME_BLACK_LEVEL;
+ s->opt[OPT_BLACK_LEVEL].title = SANE_TITLE_BLACK_LEVEL;
+ s->opt[OPT_BLACK_LEVEL].desc = SANE_DESC_BLACK_LEVEL
+ " This value should be selected so that black areas just start "
+ "to look really black (not gray).";
+ s->opt[OPT_BLACK_LEVEL].type = SANE_TYPE_INT;
+ s->opt[OPT_BLACK_LEVEL].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BLACK_LEVEL].constraint.range = &u8_range;
+ s->val[OPT_BLACK_LEVEL].w = 0;
+
+ /* white-level */
+ s->opt[OPT_WHITE_LEVEL].name = SANE_NAME_WHITE_LEVEL;
+ s->opt[OPT_WHITE_LEVEL].title = SANE_TITLE_WHITE_LEVEL;
+ s->opt[OPT_WHITE_LEVEL].desc = SANE_DESC_WHITE_LEVEL
+ " This value should be selected so that white areas just start "
+ "to look really white (not gray).";
+ s->opt[OPT_WHITE_LEVEL].type = SANE_TYPE_INT;
+ s->opt[OPT_WHITE_LEVEL].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_WHITE_LEVEL].constraint.range = &u8_range;
+ s->val[OPT_WHITE_LEVEL].w = 150;
+
+ /* hue */
+ s->opt[OPT_HUE].name = SANE_NAME_HUE;
+ s->opt[OPT_HUE].title = SANE_TITLE_HUE;
+ s->opt[OPT_HUE].desc = SANE_DESC_HUE;
+ s->opt[OPT_HUE].type = SANE_TYPE_INT;
+ s->opt[OPT_HUE].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_HUE].constraint.range = &u8_range;
+ s->val[OPT_HUE].w = 128;
+
+ /* saturation */
+ s->opt[OPT_SATURATION].name = SANE_NAME_SATURATION;
+ s->opt[OPT_SATURATION].title = SANE_TITLE_SATURATION;
+ s->opt[OPT_SATURATION].desc = SANE_DESC_SATURATION;
+ s->opt[OPT_SATURATION].type = SANE_TYPE_INT;
+ s->opt[OPT_SATURATION].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_SATURATION].constraint.range = &u8_range;
+ s->val[OPT_SATURATION].w = 100;
+
+ DBG (3, "init_options: exit\n");
+
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
+{
+ char dev_name[PATH_MAX], *str;
+ size_t len;
+ FILE *fp;
+ authorize = authorize; /* silence compilation warnings */
+
+ DBG_INIT ();
+
+ DBG (1, "sane_init: enter\n");
+
+ if (version_code)
+ *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, 0);
+
+ fp = sanei_config_open (QCAM_CONFIG_FILE);
+ if (!fp)
+ {
+ DBG (1, "sane_init: file `%s' not accessible\n", QCAM_CONFIG_FILE);
+ return SANE_STATUS_INVAL;
+ }
+
+ while (sanei_config_read (dev_name, sizeof (dev_name), fp))
+ {
+ if (dev_name[0] == '#') /* ignore line comments */
+ continue;
+ len = strlen (dev_name);
+
+ if (!len)
+ continue; /* ignore empty lines */
+
+ for (str = dev_name; *str && !isspace (*str) && *str != '#'; ++str);
+ *str = '\0';
+
+ attach (dev_name, 0);
+ }
+ fclose (fp);
+
+ DBG (1, "sane_init: exit\n");
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_exit (void)
+{
+ QC_Device *dev, *next;
+ static const SANE_Device **devlist;
+
+ DBG (5, "sane_exit: enter\n");
+
+ for (dev = first_dev; dev; dev = next)
+ {
+ next = dev->next;
+ free ((void *) dev->sane.name);
+ disable_ports (dev);
+ free (dev);
+ }
+ if (devlist) {
+ free (devlist);
+ devlist = NULL;
+ }
+ DBG (5, "sane_exit: exit\n");
+}
+
+SANE_Status
+sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
+{
+ static const SANE_Device **devlist = 0;
+ QC_Device *dev;
+ int i;
+
+ DBG (5, "sane_get_devices: enter\n");
+
+ local_only = local_only; /* silence compilation warnings */
+
+ 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 (5, "sane_get_devices: exit\n");
+
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_open (SANE_String_Const devicename, SANE_Handle * handle)
+{
+ SANE_Status status;
+ QC_Device *dev;
+ QC_Scanner *s;
+
+ DBG (5, "sane_open: enter: (devicename = %s)\n", devicename);
+ if (devicename[0])
+ {
+ status = attach (devicename, &dev);
+ if (status != SANE_STATUS_GOOD)
+ return status;
+ }
+ else
+ /* empty devicname -> use first device */
+ dev = first_dev;
+
+ if (!dev)
+ return SANE_STATUS_INVAL;
+
+ s = malloc (sizeof (*s));
+ if (!s)
+ return SANE_STATUS_NO_MEM;
+ memset (s, 0, sizeof (*s));
+ s->user_corner = 0;
+ s->hw = dev;
+ s->value_changed = ~0; /* ensure all options get updated */
+ s->reader_pid = -1;
+ s->to_child = -1;
+ s->from_child = -1;
+ s->read_fd = -1;
+
+ init_options (s);
+
+ /* The contrast option seems to have an effect for b&w cameras only,
+ so don't give the user the impression that this is a useful thing
+ to set... */
+ if (s->hw->version == QC_COLOR)
+ s->opt[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE;
+ else
+ {
+ /* Black level, Hue and Saturation are things the b&w cameras
+ know nothing about. Despeckle might be useful, but this code
+ seems to work for color cameras only right now. The framesize
+ seems to work better in these ranges. */
+ s->opt[OPT_DESPECKLE].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_BLACK_LEVEL].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_HUE].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_SATURATION].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_RESOLUTION].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_TEST].cap |= SANE_CAP_INACTIVE;
+
+ s->opt[OPT_DEPTH].constraint.word_list = mono_depth_list;
+ s->val[OPT_DEPTH].w = mono_depth_list[NELEMS (mono_depth_list) - 1];
+ s->opt[OPT_TL_X].constraint.range = &bw_x_range;
+ s->val[OPT_TL_X].w = 14;
+ s->opt[OPT_TL_Y].constraint.range = &bw_y_range;
+ s->val[OPT_TL_Y].w = 0;
+ s->opt[OPT_BR_X].constraint.range = &odd_bw_x_range;
+ s->val[OPT_BR_X].w = 333;
+ s->opt[OPT_BR_Y].constraint.range = &odd_bw_y_range;
+ s->val[OPT_BR_Y].w = 239;
+
+ s->val[OPT_BRIGHTNESS].w = 170;
+ s->val[OPT_CONTRAST].w = 150;
+ s->val[OPT_WHITE_LEVEL].w = 150;
+ }
+
+ /* insert newly opened handle into list of open handles: */
+ s->next = first_handle;
+ first_handle = s;
+
+ *handle = s;
+
+ DBG (5, "sane_open: exit\n");
+
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_close (SANE_Handle handle)
+{
+ QC_Scanner *prev, *s;
+
+ DBG (5, "sane_close: enter\n");
+
+ /* remove handle from list of open handles: */
+ prev = 0;
+ for (s = first_handle; s; s = s->next)
+ {
+ if (s == handle)
+ break;
+ prev = s;
+ }
+ if (!s)
+ {
+ DBG (1, "sane_close: bad handle %p\n", handle);
+ return; /* oops, not a handle we know about */
+ }
+ if (prev)
+ prev->next = s->next;
+ else
+ first_handle = s->next;
+
+ if (s->scanning)
+ sane_cancel (handle);
+
+ if (s->reader_pid >= 0)
+ {
+ kill (s->reader_pid, SIGTERM);
+ waitpid (s->reader_pid, 0, 0);
+ s->reader_pid = 0;
+ }
+ if (s->to_child >= 0)
+ close (s->to_child);
+ if (s->from_child >= 0)
+ close (s->from_child);
+ if (s->read_fd >= 0)
+ close (s->read_fd);
+
+ free (s);
+
+ DBG (5, "sane_close: exit\n");
+
+}
+
+const SANE_Option_Descriptor *
+sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
+{
+ QC_Scanner *s = handle;
+
+ DBG (5, "sane_get_option_descriptor: enter\n");
+
+ if ((unsigned) option >= NUM_OPTIONS)
+ return 0;
+
+ DBG (5, "sane_get_option_descriptor: exit\n");
+
+ return s->opt + option;
+}
+
+SANE_Status
+sane_control_option (SANE_Handle handle, SANE_Int option,
+ SANE_Action action, void *val, SANE_Int * info)
+{
+ QC_Scanner *s = handle;
+ QC_Resolution old_res;
+ SANE_Status status;
+ SANE_Word cap;
+ char *old_val;
+ int i;
+
+ DBG (5, "sane_control_option: enter\n");
+
+ if (info)
+ *info = 0;
+
+ if (option >= NUM_OPTIONS)
+ return SANE_STATUS_INVAL;
+
+ cap = s->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_DEPTH:
+ case OPT_DESPECKLE:
+ case OPT_TEST:
+ case OPT_TL_X:
+ case OPT_TL_Y:
+ case OPT_BR_X:
+ case OPT_BR_Y:
+ case OPT_XFER_SCALE:
+ case OPT_BRIGHTNESS:
+ case OPT_CONTRAST:
+ case OPT_BLACK_LEVEL:
+ case OPT_WHITE_LEVEL:
+ case OPT_HUE:
+ case OPT_SATURATION:
+ *(SANE_Word *) val = s->val[option].w;
+ return SANE_STATUS_GOOD;
+
+ /* string options: */
+ case OPT_RESOLUTION:
+ strcpy (val, s->val[option].s);
+ return SANE_STATUS_GOOD;
+
+ default:
+ DBG (1, "control_option: option %d unknown\n", option);
+ }
+ }
+ else if (action == SANE_ACTION_SET_VALUE)
+ {
+ if (!SANE_OPTION_IS_SETTABLE (cap))
+ return SANE_STATUS_INVAL;
+
+ status = sanei_constrain_value (s->opt + option, val, info);
+ if (status != SANE_STATUS_GOOD)
+ return status;
+
+ if (option >= OPT_TL_X && option <= OPT_BR_Y)
+ s->user_corner |= 1 << (option - OPT_TL_X);
+
+ assert (option <= 31);
+ s->value_changed |= 1 << option;
+
+ switch (option)
+ {
+ /* (mostly) side-effect-free word options: */
+ case OPT_TL_X:
+ case OPT_TL_Y:
+ case OPT_BR_X:
+ case OPT_BR_Y:
+ case OPT_XFER_SCALE:
+ case OPT_DEPTH:
+ if (!s->scanning && info && s->val[option].w != *(SANE_Word *) val)
+ /* only signal the reload params if we're not scanning---no point
+ in creating the frontend useless work */
+ *info |= SANE_INFO_RELOAD_PARAMS;
+ /* fall through */
+ case OPT_NUM_OPTS:
+ case OPT_TEST:
+ case OPT_DESPECKLE:
+ case OPT_BRIGHTNESS:
+ case OPT_CONTRAST:
+ case OPT_BLACK_LEVEL:
+ case OPT_WHITE_LEVEL:
+ case OPT_HUE:
+ case OPT_SATURATION:
+ s->val[option].w = *(SANE_Word *) val;
+ return SANE_STATUS_GOOD;
+
+ /* options with side-effects: */
+ case OPT_RESOLUTION:
+ old_val = s->val[OPT_RESOLUTION].s;
+
+ if (strcmp (old_val, val) != 0)
+ return SANE_STATUS_GOOD; /* no change */
+
+ if (info)
+ {
+ *info |= SANE_INFO_RELOAD_OPTIONS;
+ if (!s->scanning)
+ *info |= SANE_INFO_RELOAD_PARAMS;
+ }
+ free (old_val);
+ s->val[OPT_RESOLUTION].s = strdup (val);
+
+ /* low-resolution mode: */
+ old_res = s->resolution;
+ s->resolution = QC_RES_LOW;
+ if (strcmp (val, resolution_list[QC_RES_HIGH]) == 0)
+ /* high-resolution mode: */
+ s->resolution = QC_RES_HIGH;
+ s->opt[OPT_TL_X].constraint.range = &x_range[s->resolution];
+ s->opt[OPT_BR_X].constraint.range = &odd_x_range[s->resolution];
+ s->opt[OPT_TL_Y].constraint.range = &y_range[s->resolution];
+ s->opt[OPT_BR_Y].constraint.range = &odd_y_range[s->resolution];
+
+ if (old_res == QC_RES_LOW && s->resolution == QC_RES_HIGH)
+ {
+ for (i = OPT_TL_X; i <= OPT_BR_Y; ++i)
+ s->val[i].w *= 2;
+ s->val[OPT_BR_X].w += 1;
+ s->val[OPT_BR_Y].w += 1;
+ s->opt[OPT_TEST].cap |= SANE_CAP_INACTIVE;
+ }
+ else if (old_res == QC_RES_HIGH && s->resolution == QC_RES_LOW)
+ {
+ for (i = OPT_TL_X; i <= OPT_BR_Y; ++i)
+ s->val[i].w /= 2;
+ s->opt[OPT_TEST].cap &= ~SANE_CAP_INACTIVE;
+ }
+
+ if (!(s->user_corner & 0x4))
+ s->val[OPT_BR_X].w = odd_x_range[s->resolution].max;
+ if (!(s->user_corner & 0x8))
+ s->val[OPT_BR_Y].w = odd_y_range[s->resolution].max - 4;
+
+ /* make sure the affected options have valid values: */
+ for (i = OPT_TL_X; i <= OPT_BR_Y; ++i)
+ if (s->val[i].w > s->opt[i].constraint.range->max)
+ s->val[i].w = s->opt[i].constraint.range->max;
+
+ DBG (5, "sane_control_option: exit\n");
+ return SANE_STATUS_GOOD;
+ }
+ }
+ else if (action == SANE_ACTION_SET_AUTO)
+ {
+ switch (option)
+ {
+ case OPT_BRIGHTNESS:
+ /* not implemented yet */
+ DBG (5, "sane_control_option: exit\n");
+ return SANE_STATUS_GOOD;
+
+ default:
+ break;
+ }
+ }
+
+ DBG (5, "sane_control_option: NOK exit\n");
+ return SANE_STATUS_INVAL;
+}
+
+SANE_Status
+sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
+{
+ QC_Scanner *s = handle;
+ QC_Device *q = s->hw;
+ int xfer_scale;
+ size_t Bpp = 3; /* # of bytes per pixel */
+
+ DBG (5, "sane_get_parameters: enter\n");
+
+ if (!s->scanning)
+ {
+ /* Only compute new parameters when not scanning---allows
+ changing width/height etc while scan is in progress. */
+ xfer_scale = s->val[OPT_XFER_SCALE].w;
+
+ s->params.format = SANE_FRAME_RGB;
+ if (q->version != QC_COLOR)
+ {
+ s->params.format = SANE_FRAME_GRAY;
+ Bpp = 1;
+ }
+ s->params.last_frame = SANE_TRUE;
+
+ s->params.pixels_per_line = s->val[OPT_BR_X].w - s->val[OPT_TL_X].w + 1;
+ s->params.pixels_per_line /= xfer_scale;
+ s->params.pixels_per_line &= ~1UL; /* ensure it's even */
+ if (s->params.pixels_per_line < 2)
+ s->params.pixels_per_line = 2;
+
+ s->params.lines = s->val[OPT_BR_Y].w - s->val[OPT_TL_Y].w + 1;
+ s->params.lines /= xfer_scale;
+ if (s->params.lines < 1)
+ s->params.lines = 1;
+
+ s->params.bytes_per_line = Bpp * s->params.pixels_per_line;
+ s->params.depth = 8;
+ }
+ if (params)
+ *params = s->params;
+
+ DBG (5, "sane_get_parameters: exit\n");
+
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_start (SANE_Handle handle)
+{
+ int top, left, width, height, undecimated_width, undecimated_height;
+ QC_Scanner *s = handle;
+ QC_Device *q = s->hw;
+ QC_Scan_Request req;
+
+ DBG (5, "sane_start: enter\n");
+
+ if (s->scanning)
+ return SANE_STATUS_DEVICE_BUSY;
+
+ if (s->reader_pid < 0)
+ {
+ int p2c_pipe[2]; /* parent->child pipe */
+ int c2p_pipe[2]; /* child->parent pipe */
+
+ if (pipe (p2c_pipe) < 0 || pipe (c2p_pipe) < 0)
+ {
+ DBG (3, "start: failed to create pipes\n");
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ s->reader_pid = fork ();
+ if (s->reader_pid == 0)
+ {
+ /* this is the child */
+ signal (SIGHUP, SIG_DFL);
+ signal (SIGINT, SIG_DFL);
+ signal (SIGPIPE, SIG_DFL);
+ signal (SIGTERM, SIG_DFL);
+ _exit (reader_process (s, p2c_pipe[0], c2p_pipe[1]));
+ }
+ close (p2c_pipe[0]);
+ close (c2p_pipe[1]);
+ s->to_child = p2c_pipe[1];
+ s->from_child = c2p_pipe[0];
+ }
+
+ s->read_fd = dup (s->from_child);
+ sane_get_parameters (s, 0); /* ensure uptodate parameters */
+
+ qc_lock (q);
+ s->holding_lock = SANE_TRUE;
+
+ if (q->version == QC_COLOR)
+ {
+ qc_send (q, QC_SET_SPEED);
+ qc_send (q, 2);
+
+ /* wait for camera to become ready: */
+ while (qc_getstatus (q) & CameraNotReady)
+ usleep (10000);
+
+ /* Only send black_level if necessary; this optimization may
+ fail if two applications access the camera in an interleaved
+ fashion; but the black-level command is slow enough that it
+ cannot be issued for every image acquisition. */
+ if (s->value_changed & (1 << OPT_BLACK_LEVEL))
+ {
+ s->value_changed &= ~(1 << OPT_BLACK_LEVEL);
+
+ qc_send (q, QC_SET_BLACK);
+ qc_send (q, s->val[OPT_BLACK_LEVEL].w);
+
+ DBG (3, "start: black_level=%d\n", s->val[OPT_BLACK_LEVEL].w);
+
+ /* wait for set black level command to finish: */
+ while (qc_getstatus (q) & (CameraNotReady | BlackBalanceInProgress))
+ usleep (10000);
+ }
+
+ if (s->value_changed & (1 << OPT_HUE))
+ {
+ s->value_changed &= ~(1 << OPT_HUE);
+ qc_send (q, QC_COL_SET_HUE);
+ qc_send (q, s->val[OPT_HUE].w);
+ }
+
+ if (s->value_changed & (1 << OPT_SATURATION))
+ {
+ s->value_changed &= ~(1 << OPT_SATURATION);
+ qc_send (q, QC_SET_SATURATION);
+ qc_send (q, s->val[OPT_SATURATION].w);
+ }
+ }
+
+ if (q->version != QC_COLOR)
+ qc_reset (q);
+
+ if (s->value_changed & (1 << OPT_CONTRAST))
+ {
+ s->value_changed &= ~(1 << OPT_CONTRAST);
+ qc_send (q, ((q->version == QC_COLOR)
+ ? QC_COL_SET_CONTRAST : QC_MONO_SET_CONTRAST));
+ qc_send (q, s->val[OPT_CONTRAST].w);
+ }
+
+ if (s->value_changed & (1 << OPT_BRIGHTNESS))
+ {
+ s->value_changed &= ~(1 << OPT_BRIGHTNESS);
+ qc_send (q, QC_SET_BRIGHTNESS);
+ qc_send (q, s->val[OPT_BRIGHTNESS].w);
+ }
+
+ width = s->params.pixels_per_line;
+ height = s->params.lines;
+ if (s->resolution == QC_RES_HIGH)
+ {
+ width /= 2; /* the expansion occurs through oversampling */
+ height /= 2; /* we acquire only half the lines that we generate */
+ }
+ undecimated_width = width * s->val[OPT_XFER_SCALE].w;
+ undecimated_height = height * s->val[OPT_XFER_SCALE].w;
+
+ s->num_bytes = 0;
+ s->bytes_per_frame = s->params.lines * s->params.bytes_per_line;
+
+ qc_send (q, QC_SET_NUM_V);
+ qc_send (q, undecimated_height);
+
+ if (q->version == QC_COLOR)
+ {
+ qc_send (q, QC_SET_NUM_H);
+ qc_send (q, undecimated_width / 2);
+ }
+ else
+ {
+ int val, val2;
+
+ if (q->port_mode == QC_UNIDIR && s->val[OPT_DEPTH].w == 6)
+ {
+ val = undecimated_width;
+ val2 = s->val[OPT_XFER_SCALE].w * 4;
+ }
+ else
+ {
+ val = undecimated_width * s->val[OPT_DEPTH].w;
+ val2 =
+ ((q->port_mode == QC_BIDIR) ? 24 : 8) * s->val[OPT_XFER_SCALE].w;
+ }
+ val = (val + val2 - 1) / val2;
+ qc_send (q, QC_SET_NUM_H);
+ qc_send (q, val);
+ }
+
+ left = s->val[OPT_TL_X].w / 2;
+ top = s->val[OPT_TL_Y].w;
+ if (s->resolution == QC_RES_HIGH)
+ {
+ left /= 2;
+ top /= 2;
+ }
+
+ DBG (3, "sane_start: top=%d, left=%d, white=%d, bright=%d, contr=%d\n",
+ top, left, s->val[OPT_WHITE_LEVEL].w, s->val[OPT_BRIGHTNESS].w,
+ s->val[OPT_CONTRAST].w);
+
+ qc_send (q, QC_SET_LEFT);
+ qc_send (q, left);
+
+ qc_send (q, QC_SET_TOP);
+ qc_send (q, top + 1); /* not sure why this is so... ;-( */
+
+ if (s->value_changed & (1 << OPT_WHITE_LEVEL))
+ {
+ s->value_changed &= ~(1 << OPT_WHITE_LEVEL);
+ qc_send (q, QC_SET_WHITE);
+ qc_send (q, s->val[OPT_WHITE_LEVEL].w);
+ }
+
+ DBG (2, "start: %s %d lines of %d pixels each (%ld bytes) => %dx%d\n",
+ (q->port_mode == QC_BIDIR) ? "bidir" : "unidir",
+ height, width, (long) s->bytes_per_frame,
+ s->params.pixels_per_line, s->params.lines);
+
+ /* send scan request to reader process: */
+ qc_setscanmode (s, &req.mode);
+ req.num_bytes = width * height;
+ if (q->version == QC_COLOR)
+ {
+ if (s->resolution == QC_RES_LOW)
+ req.num_bytes *= 3;
+ else
+ req.num_bytes *= 4;
+ }
+ req.resolution = s->resolution;
+ req.params = s->params;
+ req.despeckle = s->val[OPT_DESPECKLE].w;
+ write (s->to_child, &req, sizeof (req));
+
+ s->scanning = SANE_TRUE;
+ s->deliver_eof = 0;
+
+ DBG (5, "sane_start: exit\n");
+
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
+ SANE_Int * lenp)
+{
+ SANE_Status status;
+ QC_Scanner *s = handle;
+ QC_Device *q = s->hw;
+ ssize_t nread;
+ size_t len;
+
+ DBG (5, "sane_read: enter\n");
+
+ *lenp = 0;
+
+ if (s->deliver_eof)
+ {
+ s->deliver_eof = 0;
+ return SANE_STATUS_EOF;
+ }
+
+ if (!s->scanning)
+ return SANE_STATUS_CANCELLED;
+
+ len = max_len;
+ if (s->num_bytes + len > s->bytes_per_frame)
+ len = s->bytes_per_frame - s->num_bytes;
+
+ DBG (8, "read(buf=%p,num_bytes=%ld,max_len=%d,len=%ld)\n",
+ buf, (long) s->num_bytes, max_len, (long) len);
+
+ nread = read (s->read_fd, buf, len);
+ if (nread <= 0)
+ {
+ if (nread == 0 || errno == EAGAIN)
+ {
+ DBG (3, "read: no more data available\n");
+ return SANE_STATUS_GOOD;
+ }
+ DBG (3, "read: short read (%s)\n", strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (nread > 0 && s->holding_lock)
+ {
+ status = qc_unlock (q); /* now we can unlock the camera */
+ if (status != SANE_STATUS_GOOD)
+ DBG(3, "sane_read: qc_unlock error\n");
+ s->holding_lock = SANE_FALSE;
+ }
+
+ s->num_bytes += nread;
+ if (s->num_bytes >= s->bytes_per_frame)
+ {
+ s->scanning = SANE_FALSE;
+ close (s->read_fd);
+ s->read_fd = -1;
+ s->deliver_eof = 1;
+ }
+
+ if (lenp)
+ *lenp = nread;
+
+ DBG (5, "sane_read: exit, read got %d bytes\n", *lenp);
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_cancel (SANE_Handle handle)
+{
+ QC_Scanner *s = handle;
+ SANE_Bool was_scanning;
+ SANE_Status status;
+
+ DBG (5, "sane_cancel: enter\n");
+
+ was_scanning = s->scanning;
+ s->scanning = SANE_FALSE;
+ s->deliver_eof = 0;
+ if (s->read_fd >= 0)
+ {
+ close (s->read_fd);
+ s->read_fd = -1;
+ }
+
+ if (s->reader_pid >= 0 && was_scanning)
+ {
+ char buf[1024];
+ ssize_t nread;
+ int flags;
+
+ DBG (1, "cancel: cancelling read request\n");
+
+ kill (s->reader_pid, SIGINT); /* tell reader to stop reading */
+
+ /* save non-blocking i/o flags: */
+ flags = fcntl (s->from_child, F_GETFL, 0);
+
+ /* block until we read at least one byte: */
+ read (s->from_child, buf, 1);
+
+ /* put descriptor in non-blocking i/o: */
+ fcntl (s->from_child, F_SETFL, O_NONBLOCK);
+
+ /* read what's left over in the pipe/file buffer: */
+ do
+ {
+ while ((nread = read (s->from_child, buf, sizeof (buf))) > 0);
+ usleep (100000);
+ nread = read (s->from_child, buf, sizeof (buf));
+ }
+ while (nread > 0);
+
+ /* now restore non-blocking i/o flag: */
+ fcntl (s->from_child, F_SETFL, flags & O_NONBLOCK);
+
+ waitpid (s->reader_pid, 0, 0);
+ s->reader_pid = 0;
+
+ DBG (1, "cancel: cancellation completed\n");
+ }
+ if (s->holding_lock)
+ {
+ status = qc_unlock (s->hw);
+ if (status != SANE_STATUS_GOOD)
+ DBG(3, "sane_cancel: qc_unlock error\n");
+ s->holding_lock = SANE_FALSE;
+ }
+ DBG (5, "sane_cancel: exit\n");
+}
+
+SANE_Status
+sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
+{
+ QC_Scanner *s = handle;
+
+ DBG (5, "sane_set_io_mode: enter\n");
+
+ if (!s->scanning)
+ return SANE_STATUS_INVAL;
+
+ if (fcntl (s->read_fd, F_SETFL, non_blocking ? O_NONBLOCK : 0) < 0)
+ return SANE_STATUS_IO_ERROR;
+ DBG (5, "sane_set_io_mode: exit\n");
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
+{
+ QC_Scanner *s = handle;
+
+ DBG (5, "sane_get_select_fd: enter\n");
+ if (!s->scanning)
+ return SANE_STATUS_INVAL;
+
+ *fd = s->read_fd;
+ DBG (5, "sane_get_select_fd: exit\n");
+ return SANE_STATUS_GOOD;
+}