summaryrefslogtreecommitdiff
path: root/backend/dc25.c
diff options
context:
space:
mode:
Diffstat (limited to 'backend/dc25.c')
-rw-r--r--backend/dc25.c2760
1 files changed, 2760 insertions, 0 deletions
diff --git a/backend/dc25.c b/backend/dc25.c
new file mode 100644
index 0000000..2c9e78c
--- /dev/null
+++ b/backend/dc25.c
@@ -0,0 +1,2760 @@
+/***************************************************************************
+ * SANE - Scanner Access Now Easy.
+
+ dc25.c
+
+ $Id$
+
+ This file (C) 1998 Peter Fales
+
+ 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 Kodak DC-25 (and
+ probably the DC-20) digital cameras. THIS IS EXTREMELY ALPHA CODE!
+ USE AT YOUR OWN RISK!!
+
+ (feedback to: dc25-devel@fales-lorenz.net)
+
+ This backend is based heavily on the dc20ctrl package by Ugo
+ Paternostro <paterno@dsi.unifi.it>. I've attached his header below:
+
+ ***************************************************************************
+
+ * Copyright (C) 1998 Ugo Paternostro <paterno@dsi.unifi.it>
+ *
+ * This file is part of the dc20ctrl package. The complete package can be
+ * downloaded from:
+ * http://aguirre.dsi.unifi.it/~paterno/binaries/dc20ctrl.tar.gz
+ *
+ * This package is derived from the dc20 package, built by Karl Hakimian
+ * <hakimian@aha.com> that you can find it at ftp.eecs.wsu.edu in the
+ * /pub/hakimian directory. The complete URL is:
+ * ftp://ftp.eecs.wsu.edu/pub/hakimian/dc20.tar.gz
+ *
+ * This package also includes a sligthly modified version of the Comet to ppm
+ * conversion routine written by YOSHIDA Hideki <hideki@yk.rim.or.jp>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+
+ ***************************************************************************/
+
+#include "../include/sane/config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include "../include/sane/sane.h"
+#include "../include/sane/sanei.h"
+#include "../include/sane/saneopts.h"
+
+#define BACKEND_NAME dc25
+#include "../include/sane/sanei_backend.h"
+
+#include "dc25.h"
+
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+#define MAGIC (void *)0xab730324
+#define DC25_CONFIG_FILE "dc25.conf"
+#define THUMBSIZE ( (CameraInfo.model == 0x25 ) ? 14400 : 5120 )
+
+static SANE_Bool is_open = 0;
+
+static SANE_Byte dc25_opt_image_number = 1; /* Image to load */
+static SANE_Bool dc25_opt_thumbnails; /* Load thumbnails */
+static SANE_Bool dc25_opt_snap; /* Take new picture */
+static SANE_Bool dc25_opt_lowres; /* Use low resoluiton */
+#define DC25_OPT_CONTRAST_DEFAULT 1.6
+ /* Contrast enhancement */
+static SANE_Fixed dc25_opt_contrast = SANE_FIX (DC25_OPT_CONTRAST_DEFAULT);
+#define DC25_OPT_GAMMA_DEFAULT 4.5
+ /* Gamma correction (10x) */
+static SANE_Fixed dc25_opt_gamma = SANE_FIX (DC25_OPT_GAMMA_DEFAULT);
+static SANE_Bool dc25_opt_erase; /* Erase all after download */
+static SANE_Bool dc25_opt_erase_one; /* Erase one after download */
+static SANE_Bool dumpinquiry;
+
+static SANE_Int info_flags;
+
+static int tfd; /* Camera File Descriptor */
+static char tty_name[PATH_MAX];
+#define DEF_TTY_NAME "/dev/ttyS0"
+
+static speed_t tty_baud = DEFAULT_TTY_BAUD;
+static char *tmpname;
+static char tmpnamebuf[] = "/tmp/dc25XXXXXX";
+
+static Dc20Info *dc20_info;
+static Dc20Info CameraInfo;
+
+static SANE_Byte contrast_table[256];
+
+static struct pixmap *pp;
+
+static const SANE_Range percentage_range = {
+ -100 << SANE_FIXED_SCALE_SHIFT, /* minimum */
+ 100 << SANE_FIXED_SCALE_SHIFT, /* maximum */
+ 0 << SANE_FIXED_SCALE_SHIFT /* quantization */
+};
+
+static const SANE_Range contrast_range = {
+ 0 << SANE_FIXED_SCALE_SHIFT, /* minimum */
+ 3 << SANE_FIXED_SCALE_SHIFT, /* maximum */
+ 16384 /* quantization ~ 0.025 */
+};
+
+static const SANE_Range gamma_range = {
+ 0 << SANE_FIXED_SCALE_SHIFT, /* minimum */
+ 10 << SANE_FIXED_SCALE_SHIFT, /* maximum */
+ 16384 /* quantization ~ 0.025 */
+};
+
+static SANE_Range image_range = {
+ 0,
+ 14,
+ 0
+};
+
+static SANE_Option_Descriptor sod[] = {
+ {
+ SANE_NAME_NUM_OPTIONS,
+ SANE_TITLE_NUM_OPTIONS,
+ SANE_DESC_NUM_OPTIONS,
+ SANE_TYPE_INT,
+ SANE_UNIT_NONE,
+ sizeof (SANE_Word),
+ SANE_CAP_SOFT_DETECT,
+ SANE_CONSTRAINT_NONE,
+ {NULL}
+ }
+ ,
+
+#define D25_OPT_IMAGE_SELECTION 1
+ {
+ "",
+ "Image Selection",
+ "Selection of the image to load.",
+ SANE_TYPE_GROUP,
+ SANE_UNIT_NONE,
+ 0,
+ 0,
+ SANE_CONSTRAINT_NONE,
+ {NULL}
+ }
+ ,
+
+#define DC25_OPT_IMAGE_NUMBER 2
+ {
+ "image",
+ "Image Number",
+ "Select Image Number to load from camera",
+ SANE_TYPE_INT,
+ SANE_UNIT_NONE,
+ 4,
+ SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT,
+ SANE_CONSTRAINT_RANGE,
+ {(SANE_String_Const *) & image_range} /* this is ANSI conformant! */
+ }
+ ,
+
+#define DC25_OPT_THUMBS 3
+ {
+ "thumbs",
+ "Load Thumbnail",
+ "Load the image as thumbnail.",
+ SANE_TYPE_BOOL,
+ SANE_UNIT_NONE,
+ sizeof (SANE_Word),
+ SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT,
+ SANE_CONSTRAINT_NONE,
+ {NULL}
+ }
+ ,
+#define DC25_OPT_SNAP 4
+ {
+ "snap",
+ "Snap new picture",
+ "Take new picture and download it",
+ SANE_TYPE_BOOL,
+ SANE_UNIT_NONE,
+ sizeof (SANE_Word),
+ SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED,
+ SANE_CONSTRAINT_NONE,
+ {NULL}
+ }
+ ,
+#define DC25_OPT_LOWRES 5
+ {
+ "lowres",
+ "Low Resolution",
+ "New pictures taken in low resolution",
+ SANE_TYPE_BOOL,
+ SANE_UNIT_NONE,
+ sizeof (SANE_Word),
+ SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_INACTIVE |
+ SANE_CAP_ADVANCED,
+ SANE_CONSTRAINT_NONE,
+ {NULL}
+ }
+ ,
+
+#define DC25_OPT_ERASE 6
+ {
+ "erase",
+ "Erase",
+ "Erase all pictures after downloading",
+ SANE_TYPE_BOOL,
+ SANE_UNIT_NONE,
+ sizeof (SANE_Word),
+ SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT,
+ SANE_CONSTRAINT_NONE,
+ {NULL}
+ }
+ ,
+
+#define DC25_OPT_ERASE_ONE 7
+ {
+ "erase-one",
+ "Erase One",
+ "Erase downloaded picture after downloading",
+ SANE_TYPE_BOOL,
+ SANE_UNIT_NONE,
+ sizeof (SANE_Word),
+ SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT,
+ SANE_CONSTRAINT_NONE,
+ {NULL}
+ }
+ ,
+
+#define DC25_OPT_ENHANCE 8
+ {
+ "",
+ "Image Parameters",
+ "Modifications to image parameters",
+ SANE_TYPE_GROUP,
+ SANE_UNIT_NONE,
+ 0,
+ 0,
+ SANE_CONSTRAINT_NONE,
+ {NULL}
+ }
+ ,
+
+#define DC25_OPT_CONTRAST 9
+ {
+ "contrast",
+ "Contrast Adjustment",
+ "Values > 1 enhance contrast",
+ SANE_TYPE_FIXED,
+ SANE_UNIT_NONE,
+ sizeof (SANE_Word),
+ SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT,
+ SANE_CONSTRAINT_RANGE,
+ {(const SANE_String_Const *) &contrast_range} /* this is ANSI conformant! */
+ },
+
+#define DC25_OPT_GAMMA 10
+ {
+ "gamma",
+ "Gamma Adjustment",
+ "Larger values make image darker",
+ SANE_TYPE_FIXED,
+ SANE_UNIT_NONE,
+ sizeof (SANE_Word),
+ SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT,
+ SANE_CONSTRAINT_RANGE,
+ {(const SANE_String_Const *) &gamma_range} /* this is ANSI conformant! */
+ },
+
+#define DC25_OPT_DEFAULT 11
+ {
+ "default-enhancements",
+ "Defaults",
+ "Set default values for enhancement controls (i.e. contrast).",
+ SANE_TYPE_BUTTON,
+ SANE_UNIT_NONE,
+ 0,
+ SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT,
+ SANE_CONSTRAINT_NONE,
+ {NULL}
+ }
+};
+
+static SANE_Parameters parms = {
+ SANE_FRAME_RGB,
+ 1,
+ 500, /* Number of bytes returned per scan line: */
+ 500, /* Number of pixels per scan line. */
+ 373, /* Number of lines for the current scan. */
+ 8, /* Number of bits per sample. */
+};
+
+
+
+
+static unsigned char init_pck[] = INIT_PCK;
+
+/*
+ * List of speeds to try to establish connection with the camera.
+ * Check 9600 first, as it's the speed the camera comes up in, then
+ * 115200, as that is the one most likely to be configured from a
+ * previous run
+ */
+static struct pkt_speed speeds[] = { {B9600, {0x96, 0x00}},
+#ifdef B115200
+{B115200, {0x11, 0x52}},
+#endif
+#ifdef B57600
+{B57600, {0x57, 0x60}},
+#endif
+{B38400, {0x38, 0x40}},
+{B19200, {0x19, 0x20}},
+};
+#define NUM_OF_SPEEDS ((int)(sizeof(speeds) / sizeof(struct pkt_speed)))
+
+static struct termios tty_orig;
+
+static int
+send_pck (int fd, unsigned char *pck)
+{
+ int n;
+ unsigned char r;
+
+ /*
+ * Not quite sure why we need this, but the program works a whole
+ * lot better (at least on the DC25) with this short delay.
+ */
+#ifdef HAVE_USLEEP
+ usleep (10);
+#else
+ sleep (1);
+#endif
+ if (write (fd, (char *) pck, 8) != 8)
+ {
+ DBG (2, "send_pck: error: write returned -1\n");
+ return -1;
+ }
+
+ if ((n = read (fd, (char *) &r, 1)) != 1)
+ {
+ DBG (2, "send_pck: error: read returned -1\n");
+ return -1;
+ }
+
+ return (r == 0xd1) ? 0 : -1;
+}
+
+static int
+init_dc20 (char *device, speed_t speed)
+{
+ struct termios tty_new;
+ int speed_index;
+
+ DBG (1, "DC-20/25 Backend 05/07/01\n");
+
+ for (speed_index = 0; speed_index < NUM_OF_SPEEDS; speed_index++)
+ {
+ if (speeds[speed_index].baud == speed)
+ {
+ init_pck[2] = speeds[speed_index].pkt_code[0];
+ init_pck[3] = speeds[speed_index].pkt_code[1];
+ break;
+ }
+ }
+
+ if (init_pck[2] == 0)
+ {
+ DBG (2, "unsupported baud rate.\n");
+ return -1;
+ }
+
+ /*
+ Open device file.
+ */
+ if ((tfd = open (device, O_RDWR)) == -1)
+ {
+ DBG (2, "init_dc20: error: could not open %s for read/write\n", device);
+ return -1;
+ }
+ /*
+ Save old device information to restore when we are done.
+ */
+ if (tcgetattr (tfd, &tty_orig) == -1)
+ {
+ DBG (2, "init_dc20: error: could not get attributes\n");
+ return -1;
+ }
+
+ memcpy ((char *) &tty_new, (char *) &tty_orig, sizeof (struct termios));
+ /*
+ We need the device to be raw. 8 bits even parity on 9600 baud to start.
+ */
+#ifdef HAVE_CFMAKERAW
+ cfmakeraw (&tty_new);
+#else
+ tty_new.c_lflag &= ~(ICANON | ECHO | ISIG);
+#endif
+ tty_new.c_oflag &= ~CSTOPB;
+ tty_new.c_cflag |= PARENB;
+ tty_new.c_cflag &= ~PARODD;
+ tty_new.c_cc[VMIN] = 0;
+ tty_new.c_cc[VTIME] = 50;
+ cfsetospeed (&tty_new, B9600);
+ cfsetispeed (&tty_new, B9600);
+
+ if (tcsetattr (tfd, TCSANOW, &tty_new) == -1)
+ {
+ DBG (2, "init_dc20: error: could not set attributes\n");
+ return -1;
+ }
+
+ if (send_pck (tfd, init_pck) == -1)
+ {
+ /*
+ * The camera always powers up at 9600, so we try
+ * that first. However, it may be already set to
+ * a different speed. Try the entries in the table:
+ */
+
+ for (speed_index = NUM_OF_SPEEDS - 1; speed_index > 0; speed_index--)
+ {
+ DBG (3, "init_dc20: changing speed to %d\n",
+ (int) speeds[speed_index].baud);
+
+ cfsetospeed (&tty_new, speeds[speed_index].baud);
+ cfsetispeed (&tty_new, speeds[speed_index].baud);
+
+ if (tcsetattr (tfd, TCSANOW, &tty_new) == -1)
+ {
+ DBG (2, "init_dc20: error: could not set attributes\n");
+ return -1;
+ }
+ if (send_pck (tfd, init_pck) != -1)
+ break;
+ }
+
+ if (speed_index == 0)
+ {
+ tcsetattr (tfd, TCSANOW, &tty_orig);
+ DBG (2, "init_dc20: error: no suitable baud rate\n");
+ return -1;
+ }
+ }
+ /*
+ Set speed to requested speed. Also, make a long timeout (we need this for
+ erase and shoot operations)
+ */
+ tty_new.c_cc[VTIME] = 150;
+ cfsetospeed (&tty_new, speed);
+ cfsetispeed (&tty_new, speed);
+
+ if (tcsetattr (tfd, TCSANOW, &tty_new) == -1)
+ {
+ DBG (2, "init_dc20: error: could not set attributes\n");
+ return -1;
+ }
+
+ return tfd;
+}
+
+static void
+close_dc20 (int fd)
+{
+ DBG (127, "close_dc20() called\n");
+ /*
+ * Put the camera back to 9600 baud
+ */
+
+ init_pck[2] = speeds[0].pkt_code[0];
+ init_pck[3] = speeds[0].pkt_code[1];
+ if (send_pck (fd, init_pck) == -1)
+ {
+ DBG (4, "close_dc20: error: could not set attributes\n");
+ }
+
+ /*
+ Restore original device settings.
+ */
+ if (tcsetattr (fd, TCSANOW, &tty_orig) == -1)
+ {
+ DBG (4, "close_dc20: error: could not set attributes\n");
+ }
+
+ if (close (fd) == -1)
+ {
+ DBG (4, "close_dc20: error: could not close device\n");
+ }
+}
+
+static unsigned char info_pck[] = INFO_PCK;
+
+static Dc20Info *
+get_info (int fd)
+{
+ unsigned char buf[256];
+
+ if (send_pck (fd, info_pck) == -1)
+ {
+ DBG (2, "get_info: error: send_pck returned -1\n");
+ return NULL;
+ }
+
+ DBG (9, "get_info: read info packet\n");
+
+ if (read_data (fd, buf, 256) == -1)
+ {
+ DBG (2, "get_info: error: read_data returned -1\n");
+ return NULL;
+ }
+
+ if (end_of_data (fd) == -1)
+ {
+ DBG (2, "get_info: error: end_of_data returned -1\n");
+ return NULL;
+ }
+
+ CameraInfo.model = buf[1];
+ CameraInfo.ver_major = buf[2];
+ CameraInfo.ver_minor = buf[3];
+ CameraInfo.pic_taken = buf[8] << 8 | buf[9];
+ if (CameraInfo.model == 0x25)
+ {
+
+ /* Not sure where the previous line came from. All the
+ * information I have says that even on the DC20 the number of
+ * standard res pics is in byte 17 and the number of high res pics
+ * is in byte 19. This is definitely true on my DC25.
+ */
+ CameraInfo.pic_taken = buf[17] + buf[19];
+ }
+
+ image_range.max = CameraInfo.pic_taken;
+ image_range.min = CameraInfo.pic_taken ? 1 : 0;
+
+ CameraInfo.pic_left = buf[10] << 8 | buf[11];
+
+ if (CameraInfo.model == 0x25)
+ {
+ /* Not sure where the previous line came from. All the
+ * information I have says that even on the DC20 the number of
+ * standard res pics left is in byte 23 and the number of high res
+ * pics left is in byte 21. It seems to me that the conservative
+ * approach is to report the number of high res pics left.
+ */
+ CameraInfo.pic_left = buf[21];
+ }
+ CameraInfo.flags.low_res = buf[23];
+
+ if (CameraInfo.model == 0x25)
+ {
+ /* Not sure where the previous line came from. All the
+ * information I have says that even on the DC20 the low_res
+ * byte is 11.
+ */
+ CameraInfo.flags.low_res = buf[11];
+ }
+ CameraInfo.flags.low_batt = buf[29];
+
+ return &CameraInfo;
+}
+
+static int
+read_data (int fd, unsigned char *buf, int sz)
+{
+ unsigned char ccsum;
+ unsigned char rcsum;
+ unsigned char c;
+ int retries = 0;
+ int n;
+ int r = 0;
+ int i;
+
+ while (retries++ < 5)
+ {
+
+ /*
+ * If this is not the first time through, then it must be
+ * a retry - signal the camera that we didn't like what
+ * we got. In either case, start filling the packet
+ */
+ if (retries != 1)
+ {
+
+ DBG (2, "Attempt retry %d\n", retries);
+ c = 0xe3;
+ if (write (fd, (char *) &c, 1) != 1)
+ {
+ DBG (2, "read_data: error: write ack\n");
+ return -1;
+ }
+
+ }
+
+ for (n = 0; n < sz && (r = read (fd, (char *) &buf[n], sz - n)) > 0;
+ n += r)
+ ;
+
+ if (r <= 0)
+ {
+ DBG (2, "read_data: error: read returned -1\n");
+ continue;
+ }
+
+ if (n < sz || read (fd, &rcsum, 1) != 1)
+ {
+ DBG (2, "read_data: error: buffer underrun or no checksum\n");
+ continue;
+ }
+
+ for (i = 0, ccsum = 0; i < n; i++)
+ ccsum ^= buf[i];
+
+ if (ccsum != rcsum)
+ {
+ DBG (2, "read_data: error: bad checksum (%02x != %02x)\n", rcsum,
+ ccsum);
+ continue;
+ }
+
+ /* If we got this far, then the packet is OK */
+ break;
+ }
+
+ c = 0xd2;
+
+ if (write (fd, (char *) &c, 1) != 1)
+ {
+ DBG (2, "read_data: error: write ack\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+end_of_data (int fd)
+{
+ char c;
+
+ if (read (fd, &c, 1) != 1)
+ {
+ DBG (2, "end_of_data: error: read returned -1\n");
+ return -1;
+ }
+
+ if (c != 0)
+ {
+ DBG (2, "end_of_data: error: bad EOD from camera (%02x)\n",
+ (unsigned) c);
+ return -1;
+ }
+
+ return 0;
+}
+
+#include <math.h>
+
+#define BIDIM_ARRAY(name, x, y, width) (name[((x) + ((y) * (width)))])
+
+/*
+ * These definitions depend on the resolution of the image
+ */
+
+#define MY_LOW_RIGHT_MARGIN 6
+
+/*
+ * These definitions are constant with resolution
+ */
+
+#define MY_LEFT_MARGIN 2
+
+#define NET_COLUMNS (columns - MY_LEFT_MARGIN - right_margin)
+#define NET_LINES (HEIGHT - TOP_MARGIN - BOTTOM_MARGIN)
+#define NET_PIXELS (NET_COLUMNS * NET_LINES)
+
+
+#define SCALE 64
+#define SMAX (256 * SCALE - 1)
+#define HORIZONTAL_INTERPOLATIONS 3
+#define HISTOGRAM_STEPS 4096
+
+#define RFACTOR 0.64
+#define GFACTOR 0.58
+#define BFACTOR 1.00
+#define RINTENSITY 0.476
+#define GINTENSITY 0.299
+#define BINTENSITY 0.175
+
+#define SATURATION 1.0
+#define NORM_PERCENTAGE 3
+
+static int columns = HIGH_WIDTH,
+ right_margin = HIGH_RIGHT_MARGIN, camera_header_size = HIGH_CAMERA_HEADER;
+static int low_i = -1, high_i = -1, norm_percentage = NORM_PERCENTAGE;
+static float saturation = SATURATION,
+ rfactor = RFACTOR, gfactor = GFACTOR, bfactor = BFACTOR;
+
+static void
+set_initial_interpolation (const unsigned char ccd[],
+ short horizontal_interpolation[])
+{
+ int column, line;
+ for (line = 0; line < HEIGHT; line++)
+ {
+ BIDIM_ARRAY (horizontal_interpolation, MY_LEFT_MARGIN, line, columns) =
+ BIDIM_ARRAY (ccd, MY_LEFT_MARGIN + 1, line, columns) * SCALE;
+ BIDIM_ARRAY (horizontal_interpolation, columns - right_margin - 1, line,
+ columns) =
+ BIDIM_ARRAY (ccd, columns - right_margin - 2, line, columns) * SCALE;
+ for (column = MY_LEFT_MARGIN + 1; column < columns - right_margin - 1;
+ column++)
+ {
+ BIDIM_ARRAY (horizontal_interpolation, column, line, columns) =
+ (BIDIM_ARRAY (ccd, column - 1, line, columns) +
+ BIDIM_ARRAY (ccd, column + 1, line, columns)) * (SCALE / 2);
+ }
+ }
+}
+
+static void
+interpolate_horizontally (const unsigned char ccd[],
+ short horizontal_interpolation[])
+{
+ int column, line, i, initial_column;
+ for (line = TOP_MARGIN - 1; line < HEIGHT - BOTTOM_MARGIN + 1; line++)
+ {
+ for (i = 0; i < HORIZONTAL_INTERPOLATIONS; i++)
+ {
+ for (initial_column = MY_LEFT_MARGIN + 1;
+ initial_column <= MY_LEFT_MARGIN + 2; initial_column++)
+ {
+ for (column = initial_column;
+ column < columns - right_margin - 1; column += 2)
+ {
+ BIDIM_ARRAY (horizontal_interpolation, column, line,
+ columns) =
+ ((float) BIDIM_ARRAY (ccd, column - 1, line, columns) /
+ BIDIM_ARRAY (horizontal_interpolation, column - 1, line,
+ columns) + (float) BIDIM_ARRAY (ccd,
+ column + 1,
+ line,
+ columns) /
+ BIDIM_ARRAY (horizontal_interpolation, column + 1, line,
+ columns)) * BIDIM_ARRAY (ccd, column, line,
+ columns) * (SCALE *
+ SCALE /
+ 2) +
+ 0.5;
+ }
+ }
+ }
+ }
+}
+
+static void
+interpolate_vertically (const unsigned char ccd[],
+ const short horizontal_interpolation[],
+ short red[], short green[], short blue[])
+{
+ int column, line;
+ for (line = TOP_MARGIN; line < HEIGHT - BOTTOM_MARGIN; line++)
+ {
+ for (column = MY_LEFT_MARGIN; column < columns - right_margin; column++)
+ {
+ int r2gb, g2b, rg2, rgb2, r, g, b;
+ int this_ccd = BIDIM_ARRAY (ccd, column, line, columns) * SCALE;
+ int up_ccd = BIDIM_ARRAY (ccd, column, line - 1, columns) * SCALE;
+ int down_ccd = BIDIM_ARRAY (ccd, column, line + 1, columns) * SCALE;
+ int this_horizontal_interpolation =
+ BIDIM_ARRAY (horizontal_interpolation, column, line, columns);
+ int this_intensity = this_ccd + this_horizontal_interpolation;
+ int up_intensity =
+ BIDIM_ARRAY (horizontal_interpolation, column, line - 1,
+ columns) + up_ccd;
+ int down_intensity =
+ BIDIM_ARRAY (horizontal_interpolation, column, line + 1,
+ columns) + down_ccd;
+ int this_vertical_interpolation;
+
+ /*
+ * PSF: I don't understand all this code, but I've found pictures
+ * where up_intensity or down_intensity are zero, resulting in a
+ * divide by zero error. It looks like this only happens when
+ * up_ccd or down_ccd are also zero, so we just set the intensity
+ * value to non-zero to prevent the error.
+ */
+ if (down_ccd == 0)
+ DBG (10, "down_ccd==0 at %d,%d\n", line, column);
+ if (up_ccd == 0)
+ DBG (10, "up_ccd==0 at %d,%d\n", line, column);
+ if (down_intensity == 0)
+ {
+ DBG (9, "Found down_intensity==0 at %d,%d down_ccd=%d\n", line,
+ column, down_ccd);
+ down_intensity = 1;
+ }
+ if (up_intensity == 0)
+ {
+ DBG (9, "Found up_intensity==0 at %d,%d up_ccd=%d\n", line,
+ column, up_ccd);
+ up_intensity = 1;
+ }
+
+ if (line == TOP_MARGIN)
+ {
+ this_vertical_interpolation =
+ (float) down_ccd / down_intensity * this_intensity + 0.5;
+ }
+ else if (line == HEIGHT - BOTTOM_MARGIN - 1)
+ {
+ this_vertical_interpolation =
+ (float) up_ccd / up_intensity * this_intensity + 0.5;
+ }
+ else
+ {
+ this_vertical_interpolation =
+ ((float) up_ccd / up_intensity +
+ (float) down_ccd / down_intensity) * this_intensity / 2.0 +
+ 0.5;
+ }
+ if (line & 1)
+ {
+ if (column & 1)
+ {
+ r2gb = this_ccd;
+ g2b = this_horizontal_interpolation;
+ rg2 = this_vertical_interpolation;
+ r = (2 * (r2gb - g2b) + rg2) / 5;
+ g = (rg2 - r) / 2;
+ b = g2b - 2 * g;
+ }
+ else
+ {
+ g2b = this_ccd;
+ r2gb = this_horizontal_interpolation;
+ rgb2 = this_vertical_interpolation;
+ r = (3 * r2gb - g2b - rgb2) / 5;
+ g = 2 * r - r2gb + g2b;
+ b = g2b - 2 * g;
+ }
+ }
+ else
+ {
+ if (column & 1)
+ {
+ rg2 = this_ccd;
+ rgb2 = this_horizontal_interpolation;
+ r2gb = this_vertical_interpolation;
+ b = (3 * rgb2 - r2gb - rg2) / 5;
+ g = (rgb2 - r2gb + rg2 - b) / 2;
+ r = rg2 - 2 * g;
+ }
+ else
+ {
+ rgb2 = this_ccd;
+ rg2 = this_horizontal_interpolation;
+ g2b = this_vertical_interpolation;
+ b = (g2b - 2 * (rg2 - rgb2)) / 5;
+ g = (g2b - b) / 2;
+ r = rg2 - 2 * g;
+ }
+ }
+ if (r < 0)
+ r = 0;
+ if (g < 0)
+ g = 0;
+ if (b < 0)
+ b = 0;
+ BIDIM_ARRAY (red, column, line, columns) = r;
+ BIDIM_ARRAY (green, column, line, columns) = g;
+ BIDIM_ARRAY (blue, column, line, columns) = b;
+ }
+ }
+}
+
+static void
+adjust_color_and_saturation (short red[], short green[], short blue[])
+{
+ int line, column;
+ int r_min = SMAX, g_min = SMAX, b_min = SMAX;
+ int r_max = 0, g_max = 0, b_max = 0;
+ int r_sum = 0, g_sum = 0, b_sum = 0;
+ float sqr_saturation = sqrt (saturation);
+ for (line = TOP_MARGIN; line < HEIGHT - BOTTOM_MARGIN; line++)
+ {
+ for (column = MY_LEFT_MARGIN; column < columns - right_margin; column++)
+ {
+ float r = BIDIM_ARRAY (red, column, line, columns) * rfactor;
+ float g = BIDIM_ARRAY (green, column, line, columns) * gfactor;
+ float b = BIDIM_ARRAY (blue, column, line, columns) * bfactor;
+ if (saturation != 1.0)
+ {
+ float *min, *mid, *max, new_intensity;
+ float intensity =
+ r * RINTENSITY + g * GINTENSITY + b * BINTENSITY;
+ if (r > g)
+ {
+ if (r > b)
+ {
+ max = &r;
+ if (g > b)
+ {
+ min = &b;
+ mid = &g;
+ }
+ else
+ {
+ min = &g;
+ mid = &b;
+ }
+ }
+ else
+ {
+ min = &g;
+ mid = &r;
+ max = &b;
+ }
+ }
+ else
+ {
+ if (g > b)
+ {
+ max = &g;
+ if (r > b)
+ {
+ min = &b;
+ mid = &r;
+ }
+ else
+ {
+ min = &r;
+ mid = &b;
+ }
+ }
+ else
+ {
+ min = &r;
+ mid = &g;
+ max = &b;
+ }
+ }
+ *mid = *min + sqr_saturation * (*mid - *min);
+ *max = *min + saturation * (*max - *min);
+ new_intensity =
+ r * RINTENSITY + g * GINTENSITY + b * BINTENSITY;
+ r *= intensity / new_intensity;
+ g *= intensity / new_intensity;
+ b *= intensity / new_intensity;
+ }
+ r += 0.5;
+ g += 0.5;
+ b += 0.5;
+ if (r_min > r)
+ r_min = r;
+ if (g_min > g)
+ g_min = g;
+ if (b_min > b)
+ b_min = b;
+ if (r_max < r)
+ r_max = r;
+ if (g_max < g)
+ g_max = g;
+ if (b_max < b)
+ b_max = b;
+ r_sum += r;
+ g_sum += g;
+ b_sum += b;
+ BIDIM_ARRAY (red, column, line, columns) = r;
+ BIDIM_ARRAY (green, column, line, columns) = g;
+ BIDIM_ARRAY (blue, column, line, columns) = b;
+ }
+ }
+}
+
+static int
+min3 (int x, int y, int z)
+{
+ return (x < y ? (x < z ? x : z) : (y < z ? y : z));
+}
+
+static int
+max3 (int x, int y, int z)
+{
+ return (x > y ? (x > z ? x : z) : (y > z ? y : z));
+}
+
+static void
+determine_limits (const short red[],
+ const short green[],
+ const short blue[], int *low_i_ptr, int *high_i_ptr)
+{
+ unsigned int histogram[HISTOGRAM_STEPS + 1];
+ int column, line, i, s;
+ int low_i = *low_i_ptr, high_i = *high_i_ptr;
+ int max_i = 0;
+ for (line = TOP_MARGIN; line < HEIGHT - BOTTOM_MARGIN; line++)
+ {
+ for (column = MY_LEFT_MARGIN; column < columns - right_margin; column++)
+ {
+ i = max3 (BIDIM_ARRAY (red, column, line, columns),
+ BIDIM_ARRAY (green, column, line, columns),
+ BIDIM_ARRAY (blue, column, line, columns));
+ if (i > max_i)
+ max_i = i;
+ }
+ }
+ if (low_i == -1)
+ {
+ for (i = 0; i <= HISTOGRAM_STEPS; i++)
+ histogram[i] = 0;
+ for (line = TOP_MARGIN; line < HEIGHT - BOTTOM_MARGIN; line++)
+ {
+ for (column = MY_LEFT_MARGIN; column < columns - right_margin;
+ column++)
+ {
+ i = min3 (BIDIM_ARRAY (red, column, line, columns),
+ BIDIM_ARRAY (green, column, line, columns),
+ BIDIM_ARRAY (blue, column, line, columns));
+ histogram[i * HISTOGRAM_STEPS / max_i]++;
+ }
+ }
+ for (low_i = 0, s = 0;
+ low_i <= HISTOGRAM_STEPS && s < NET_PIXELS * norm_percentage / 100;
+ low_i++)
+ {
+ s += histogram[low_i];
+ }
+ low_i = (low_i * max_i + HISTOGRAM_STEPS / 2) / HISTOGRAM_STEPS;
+ *low_i_ptr = low_i;
+ }
+ if (high_i == -1)
+ {
+ for (i = 0; i <= HISTOGRAM_STEPS; i++)
+ histogram[i] = 0;
+ for (line = TOP_MARGIN; line < HEIGHT - BOTTOM_MARGIN; line++)
+ {
+ for (column = MY_LEFT_MARGIN; column < columns - right_margin;
+ column++)
+ {
+ i = max3 (BIDIM_ARRAY (red, column, line, columns),
+ BIDIM_ARRAY (green, column, line, columns),
+ BIDIM_ARRAY (blue, column, line, columns));
+ histogram[i * HISTOGRAM_STEPS / max_i]++;
+ }
+ }
+ for (high_i = HISTOGRAM_STEPS, s = 0;
+ high_i >= 0 && s < NET_PIXELS * norm_percentage / 100; high_i--)
+ {
+ s += histogram[high_i];
+ }
+ high_i = (high_i * max_i + HISTOGRAM_STEPS / 2) / HISTOGRAM_STEPS;
+ *high_i_ptr = high_i;
+ }
+/*
+if (verbose) printf ("%s: determine_limits: low_i = %d, high_i = %d\n", __progname, low_i, high_i);
+*/
+}
+
+/*
+ * The original dc20ctrl program used a default gamma of 0.35, but I thougt
+ * 0.45 looks better. In addition, since xscanimage seems to always force
+ * a resolution of 0.1, I multiply everything by 10 and make the default
+ * 4.5.
+ */
+
+static unsigned char *
+make_gamma_table (int range)
+{
+ int i;
+ double factor =
+ pow (256.0, 1.0 / (SANE_UNFIX (dc25_opt_gamma) / 10.0)) / range;
+ unsigned char *gamma_table;
+ if ((gamma_table = malloc (range * sizeof (unsigned char))) == NULL)
+ {
+ DBG (1, "make_gamma_table: can't allocate memory for gamma table\n");
+ return NULL;
+ }
+ for (i = 0; i < range; i++)
+ {
+ int g =
+ pow ((double) i * factor, (SANE_UNFIX (dc25_opt_gamma) / 10.0)) + 0.5;
+/*
+ if (verbose) fprintf (stderr, "%s: make_gamma_table: gamma[%4d] = %3d\n", __progname, i, g);
+*/
+ if (g > 255)
+ g = 255;
+ gamma_table[i] = g;
+ }
+ return gamma_table;
+}
+
+static int
+lookup_gamma_table (int i, int low_i, int high_i,
+ const unsigned char gamma_table[])
+{
+ if (i <= low_i)
+ return 0;
+ if (i >= high_i)
+ return 255;
+ return gamma_table[i - low_i];
+}
+
+static int
+output_rgb (const short red[],
+ const short green[],
+ const short blue[], int low_i, int high_i, struct pixmap *pp)
+{
+ int r_min = 255, g_min = 255, b_min = 255;
+ int r_max = 0, g_max = 0, b_max = 0;
+ int r_sum = 0, g_sum = 0, b_sum = 0;
+ int column, line;
+ unsigned char *gamma_table = make_gamma_table (high_i - low_i);
+
+ if (gamma_table == NULL)
+ {
+ DBG (10, "output_rgb: error: cannot make gamma table\n");
+ return -1;
+ }
+
+ for (line = TOP_MARGIN; line < HEIGHT - BOTTOM_MARGIN; line++)
+ {
+ for (column = MY_LEFT_MARGIN; column < columns - right_margin; column++)
+ {
+ int r =
+ lookup_gamma_table (BIDIM_ARRAY (red, column, line, columns),
+ low_i, high_i, gamma_table);
+ int g =
+ lookup_gamma_table (BIDIM_ARRAY (green, column, line, columns),
+ low_i, high_i, gamma_table);
+ int b =
+ lookup_gamma_table (BIDIM_ARRAY (blue, column, line, columns),
+ low_i, high_i, gamma_table);
+ if (r > 255)
+ r = 255;
+ else if (r < 0)
+ r = 0;
+ if (g > 255)
+ g = 255;
+ else if (g < 0)
+ g = 0;
+ if (b > 255)
+ b = 255;
+ else if (b < 0)
+ b = 0;
+ set_pixel_rgb (pp, column - MY_LEFT_MARGIN, line - TOP_MARGIN, r, g,
+ b);
+ if (r_min > r)
+ r_min = r;
+ if (g_min > g)
+ g_min = g;
+ if (b_min > b)
+ b_min = b;
+ if (r_max < r)
+ r_max = r;
+ if (g_max < g)
+ g_max = g;
+ if (b_max < b)
+ b_max = b;
+ r_sum += r;
+ g_sum += g;
+ b_sum += b;
+ }
+ }
+ free (gamma_table);
+/*
+ {
+ fprintf (stderr, "%s: output_rgb: r: min = %d, max = %d, ave = %d\n", __progname, r_min, r_max, r_sum / NET_PIXELS);
+ fprintf (stderr, "%s: output_rgb: g: min = %d, max = %d, ave = %d\n", __progname, g_min, g_max, g_sum / NET_PIXELS);
+ fprintf (stderr, "%s: output_rgb: b: min = %d, max = %d, ave = %d\n", __progname, b_min, b_max, b_sum / NET_PIXELS);
+ }
+*/
+ return 0;
+}
+
+static int
+comet_to_pixmap (unsigned char *pic, struct pixmap *pp)
+{
+ unsigned char *ccd;
+ short *horizontal_interpolation, *red, *green, *blue;
+ int retval = 0;
+
+ if (pic == NULL)
+ {
+ DBG (1, "cmttoppm: error: no input image\n");
+ return -1;
+ }
+
+ if (pic[4] == 0x01)
+ {
+ /* Low resolution mode */
+ columns = LOW_WIDTH;
+ right_margin = MY_LOW_RIGHT_MARGIN;
+ camera_header_size = LOW_CAMERA_HEADER;
+ }
+ else
+ {
+ /* High resolution mode */
+ columns = HIGH_WIDTH;
+ right_margin = HIGH_RIGHT_MARGIN;
+ camera_header_size = HIGH_CAMERA_HEADER;
+ }
+ ccd = pic + camera_header_size;
+
+ if ((horizontal_interpolation =
+ malloc (sizeof (short) * HEIGHT * columns)) == NULL)
+ {
+ DBG (1,
+ "cmttoppm: error: not enough memory for horizontal_interpolation\n");
+ return -1;
+ }
+
+
+ if ((red = malloc (sizeof (short) * HEIGHT * columns)) == NULL)
+ {
+ DBG (1, "error: not enough memory for red\n");
+ return -1;
+ }
+
+ if ((green = malloc (sizeof (short) * HEIGHT * columns)) == NULL)
+ {
+ DBG (1, "error: not enough memory for green\n");
+ return -1;
+ }
+
+ if ((blue = malloc (sizeof (short) * HEIGHT * columns)) == NULL)
+ {
+ DBG (1, "error: not enough memory for blue\n");
+ return -1;
+ }
+
+ /* Decode raw CCD data to RGB */
+ set_initial_interpolation (ccd, horizontal_interpolation);
+ interpolate_horizontally (ccd, horizontal_interpolation);
+ interpolate_vertically (ccd, horizontal_interpolation, red, green, blue);
+
+ adjust_color_and_saturation (red, green, blue);
+
+ /* Determine lower and upper limit using histogram */
+ if (low_i == -1 || high_i == -1)
+ {
+ determine_limits (red, green, blue, &low_i, &high_i);
+ }
+
+ /* Output pixmap structure */
+ retval = output_rgb (red, green, blue, low_i, high_i, pp);
+
+ return retval;
+}
+
+static int
+convert_pic (char *base_name, int format)
+{
+ FILE *ifp;
+ unsigned char pic[MAX_IMAGE_SIZE];
+ int res, image_size, image_width, net_width, camera_header, components;
+ struct pixmap *pp2;
+
+ DBG (127, "convert_pic() called\n");
+
+ /*
+ * Read the image in memory
+ */
+
+ if ((ifp = fopen (base_name, "rb")) == NULL)
+ {
+ DBG (10, "convert_pic: error: cannot open %s for reading\n", base_name);
+ return -1;
+ }
+
+ if (fread (pic, COMET_HEADER_SIZE, 1, ifp) != 1)
+ {
+ DBG (10, "convert_pic: error: cannot read COMET header\n");
+ fclose (ifp);
+ return -1;
+ }
+
+ if (strncmp ((char *) pic, COMET_MAGIC, sizeof (COMET_MAGIC)) != 0)
+ {
+ DBG (10, "convert_pic: error: file %s is not in COMET format\n",
+ base_name);
+ fclose (ifp);
+ return -1;
+ }
+
+ if (fread (pic, LOW_CAMERA_HEADER, 1, ifp) != 1)
+ {
+ DBG (10, "convert_pic: error: cannot read camera header\n");
+ fclose (ifp);
+ return -1;
+ }
+
+ res = pic[4];
+ if (res == 0)
+ {
+ /*
+ * We just read a LOW_CAMERA_HEADER block, so resync with the
+ * HIGH_CAMERA_HEADER length by reading once more one of this.
+ */
+ if (fread (pic + LOW_CAMERA_HEADER, LOW_CAMERA_HEADER, 1, ifp) != 1)
+ {
+ DBG (10,
+ "convert_pic: error: cannot resync with high resolution header\n");
+ fclose (ifp);
+ return -1;
+ }
+ }
+
+ if (fread (pic + CAMERA_HEADER (res), WIDTH (res), HEIGHT, ifp) != HEIGHT)
+ {
+ DBG (9, "convert_pic: error: cannot read picture\n");
+ fclose (ifp);
+ return -1;
+ }
+
+ fclose (ifp);
+
+ /*
+ * Setup image size with resolution
+ */
+
+ image_size = IMAGE_SIZE (res);
+ image_width = WIDTH (res);
+ net_width = image_width - LEFT_MARGIN - RIGHT_MARGIN (res);
+ camera_header = CAMERA_HEADER (res);
+ components = (format & SAVE_24BITS) ? 3 : 1;
+
+ /*
+ * Convert the image to 24 bits
+ */
+
+ if ((pp =
+ alloc_pixmap (net_width - 1, HEIGHT - BOTTOM_MARGIN - 1,
+ components)) == NULL)
+ {
+ DBG (1, "convert_pic: error: alloc_pixmap\n");
+ return -1;
+ }
+
+ comet_to_pixmap (pic, pp);
+
+ if (format & SAVE_ADJASPECT)
+ {
+ /*
+ * Strech image
+ */
+
+ if (res)
+ pp2 = alloc_pixmap (320, HEIGHT - BOTTOM_MARGIN - 1, components);
+ else
+ pp2 = alloc_pixmap (net_width - 1, 373, components);
+
+ if (pp2 == NULL)
+ {
+ DBG (2, "convert_pic: error: alloc_pixmap\n");
+ free_pixmap (pp);
+ return -1;
+ }
+
+ if (res)
+ zoom_x (pp, pp2);
+ else
+ zoom_y (pp, pp2);
+
+ free_pixmap (pp);
+ pp = pp2;
+ pp2 = NULL;
+
+ }
+
+ return 0;
+}
+
+#define PGM_EXT "pgm"
+#define PPM_EXT "ppm"
+
+#define RED 0.30
+#define GREEN 0.59
+#define BLUE 0.11
+
+#define RED_OFFSET 0
+#define GREEN_OFFSET 1
+#define BLUE_OFFSET 2
+
+#define GET_COMP(pp, x, y, c) (pp->planes[((x) + (y)*pp->width)*pp->components + (c)])
+
+#define GET_R(pp, x, y) (GET_COMP(pp, x, y, RED_OFFSET))
+#define GET_G(pp, x, y) (GET_COMP(pp, x, y, GREEN_OFFSET))
+#define GET_B(pp, x, y) (GET_COMP(pp, x, y, BLUE_OFFSET))
+
+static struct pixmap *
+alloc_pixmap (int x, int y, int d)
+{
+ struct pixmap *result = NULL;
+
+ if (d == 1 || d == 3)
+ {
+ if (x > 0)
+ {
+ if (y > 0)
+ {
+ if ((result = malloc (sizeof (struct pixmap))) != NULL)
+ {
+ result->width = x;
+ result->height = y;
+ result->components = d;
+ if (!(result->planes = malloc (x * y * d)))
+ {
+ DBG (10,
+ "alloc_pixmap: error: not enough memory for bitplanes\n");
+ free (result);
+ result = NULL;
+ }
+ }
+ else
+ DBG (10,
+ "alloc_pixmap: error: not enough memory for pixmap\n");
+ }
+ else
+ DBG (10, "alloc_pixmap: error: y is out of range\n");
+ }
+ else
+ DBG (10, "alloc_pixmap: error: x is out of range\n");
+ }
+ else
+ DBG (10, "alloc_pixmap: error: cannot handle %d components\n", d);
+
+ return result;
+}
+
+static void
+free_pixmap (struct pixmap *p)
+{
+ if (p)
+ {
+ free (p->planes);
+ free (p);
+ }
+}
+
+static int
+set_pixel_rgb (struct pixmap *p, int x, int y, unsigned char r,
+ unsigned char g, unsigned char b)
+{
+ int result = 0;
+
+ if (p)
+ {
+ if (x >= 0 && x < p->width)
+ {
+ if (y >= 0 && y < p->height)
+ {
+ if (p->components == 1)
+ {
+ GET_R (p, x, y) = RED * r + GREEN * g + BLUE * b;
+ }
+ else
+ {
+ GET_R (p, x, y) = r;
+ GET_G (p, x, y) = g;
+ GET_B (p, x, y) = b;
+ }
+ }
+ else
+ {
+ DBG (10, "set_pixel_rgb: error: y out of range\n");
+ result = -1;
+ }
+ }
+ else
+ {
+ DBG (10, "set_pixel_rgb: error: x out of range\n");
+ result = -1;
+ }
+ }
+
+ return result;
+}
+
+static int
+zoom_x (struct pixmap *source, struct pixmap *dest)
+{
+ int result = 0, dest_col, row, component, src_index;
+ float ratio, src_ptr, delta;
+ unsigned char src_component;
+
+ if (source && dest)
+ {
+ /*
+ * We could think of resizing a pixmap and changing the number of
+ * components at the same time. Maybe this will be implemented later.
+ */
+ if (source->height == dest->height
+ && source->components == dest->components)
+ {
+ if (source->width < dest->width)
+ {
+ ratio = ((float) source->width / (float) dest->width);
+
+ for (src_ptr = 0, dest_col = 0; dest_col < dest->width;
+ src_ptr += ratio, dest_col++)
+ {
+ /*
+ * dest[dest_col] = source[(int)src_ptr] +
+ * (source[((int)src_ptr) + 1] - source[(int)src_ptr])
+ * * (src_ptr - (int)src_ptr);
+ */
+ src_index = (int) src_ptr;
+ delta = src_ptr - src_index;
+
+ for (row = 0; row < source->height; row++)
+ {
+ for (component = 0; component < source->components;
+ component++)
+ {
+ src_component =
+ GET_COMP (source, src_index, row, component);
+
+ GET_COMP (dest, dest_col, row, component) =
+ src_component +
+ (GET_COMP (source, src_index + 1, row,
+ component) - src_component) * delta;
+ }
+ }
+ }
+ }
+ else
+ {
+ DBG (10, "zoom_x: error: can only zoom out\n");
+ result = -1;
+ }
+ }
+ else
+ {
+ DBG (10, "zoom_x: error: incompatible pixmaps\n");
+ result = -1;
+ }
+ }
+
+ return result;
+}
+
+static int
+zoom_y (struct pixmap *source, struct pixmap *dest)
+{
+ int result = 0, dest_row, column, component, src_index;
+ float ratio, src_ptr, delta;
+ unsigned char src_component;
+
+ if (source && dest)
+ {
+ /*
+ * We could think of resizing a pixmap and changing the number of
+ * components at the same time. Maybe this will be implemented later.
+ */
+ if (source->width == dest->width
+ && source->components == dest->components)
+ {
+ if (source->height < dest->height)
+ {
+ ratio = ((float) source->height / (float) dest->height);
+
+ for (src_ptr = 0, dest_row = 0; dest_row < dest->height;
+ src_ptr += ratio, dest_row++)
+ {
+ /*
+ * dest[dest_row] = source[(int)src_ptr] +
+ * (source[((int)src_ptr) + 1] - source[(int)src_ptr])
+ * * (src_ptr - (int)src_ptr);
+ */
+ src_index = (int) src_ptr;
+ delta = src_ptr - src_index;
+
+ for (column = 0; column < source->width; column++)
+ {
+ for (component = 0; component < source->components;
+ component++)
+ {
+ src_component =
+ GET_COMP (source, column, src_index, component);
+
+ GET_COMP (dest, column, dest_row, component) =
+ src_component +
+ (GET_COMP (source, column, src_index + 1,
+ component) - src_component) * delta;
+ }
+ }
+ }
+ }
+ else
+ {
+ DBG (10, "zoom_y: error: can only zoom out\n");
+ result = -1;
+ }
+ }
+ else
+ {
+ DBG (10, "zoom_y: error: incompatible pixmaps\n");
+ result = -1;
+ }
+ }
+
+ return result;
+}
+
+static unsigned char shoot_pck[] = SHOOT_PCK;
+
+static int
+shoot (int fd)
+{
+ struct termios tty_temp, tty_old;
+ int result = 0;
+
+ DBG (127, "shoot() called\n");
+
+ if (write (fd, (char *) shoot_pck, 8) != 8)
+ {
+ DBG (3, "shoot: error: write error\n");
+ return -1;
+ }
+
+ if (CameraInfo.model != 0x25)
+ {
+ /*
+ * WARNING: now we set the serial port to 9600 baud!
+ */
+
+ if (tcgetattr (fd, &tty_old) == -1)
+ {
+ DBG (3, "shoot: error: could not get attributes\n");
+ return -1;
+ }
+
+ memcpy ((char *) &tty_temp, (char *) &tty_old, sizeof (struct termios));
+
+ cfsetispeed (&tty_temp, B9600);
+ cfsetospeed (&tty_temp, B9600);
+
+ /*
+ * Apparently there is a bug in the DC20 where the response to
+ * the shoot request is always at 9600. The DC25 does not have
+ * this bug, so we skip this block.
+ */
+ if (tcsetattr (fd, TCSANOW, &tty_temp) == -1)
+ {
+ DBG (3, "shoot: error: could not set attributes\n");
+ return -1;
+ }
+ }
+
+ if (read (fd, (char *) &result, 1) != 1)
+ {
+ DBG (3, "shoot: error: read returned -1\n");
+ result = -1;
+ }
+ else
+ {
+ result = (result == 0xD1) ? 0 : -1;
+ }
+
+ if (CameraInfo.model != 0x25)
+ {
+ /*
+ * We reset the serial to its original speed.
+ * We can skip this on the DC25 also.
+ */
+ if (tcsetattr (fd, TCSANOW, &tty_old) == -1)
+ {
+ DBG (3, "shoot: error: could not reset attributes\n");
+ result = -1;
+ }
+ }
+
+ if (result == 0)
+ {
+ if (CameraInfo.model == 0x25)
+ {
+ /*
+ * If we don't put this in, the next read will time out
+ * and return failure. Does the DC-20 need it too?
+ */
+ sleep (3);
+ }
+ if (end_of_data (fd) == -1)
+ {
+ DBG (3, "shoot: error: end_of_data returned -1\n");
+ result = -1;
+ }
+ }
+
+ return result;
+}
+
+
+static unsigned char erase_pck[] = ERASE_PCK;
+
+static int
+erase (int fd)
+{
+ int count = 0;
+
+ DBG (127, "erase() called for image %d\n", dc25_opt_image_number);
+ erase_pck[3] = dc25_opt_image_number;
+ if (dc25_opt_erase)
+ {
+ erase_pck[3] = 0;
+ }
+
+ if (send_pck (fd, erase_pck) == -1)
+ {
+ DBG (3, "erase: error: send_pck returned -1\n");
+ return -1;
+ }
+
+ if (CameraInfo.model == 0x25)
+ {
+ /*
+ * This block may really apply to the DC20 also, but since I
+ * don't have one, it's hard to say for sure. On the DC25, erase
+ * takes long enought that the read may timeout without returning
+ * any data before the erase is complete. We let this happen
+ * up to 4 times, then give up.
+ */
+ while (count < 4)
+ {
+ if (end_of_data (fd) == -1)
+ {
+ count++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (count == 4)
+ {
+ DBG (3, "erase: error: end_of_data returned -1\n");
+ return -1;
+ }
+ }
+ else
+ { /* Assume DC-20 */
+
+ if (end_of_data (fd) == -1)
+ {
+ DBG (3, "erase: error: end_of_data returned -1\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static unsigned char res_pck[] = RES_PCK;
+
+static int
+change_res (int fd, unsigned char res)
+{
+ DBG (127, "change_res called\n");
+ if (res != 0 && res != 1)
+ {
+ DBG (3, "change_res: error: unsupported resolution\n");
+ return -1;
+ }
+
+ res_pck[2] = res;
+
+ if (send_pck (fd, res_pck) == -1)
+ {
+ DBG (4, "change_res: error: send_pck returned -1\n");
+ }
+
+ if (end_of_data (fd) == -1)
+ {
+ DBG (4, "change_res: error: end_of_data returned -1\n");
+ }
+ return 0;
+}
+
+SANE_Status
+sane_init (SANE_Int * version_code, SANE_Auth_Callback UNUSEDARG authorize)
+{
+ char dev_name[PATH_MAX], *p;
+ size_t len;
+ FILE *fp;
+ int baud;
+
+ strcpy (tty_name, DEF_TTY_NAME);
+
+ DBG_INIT ();
+
+ if (version_code)
+ *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, 0);
+
+ fp = sanei_config_open (DC25_CONFIG_FILE);
+
+ DBG (127,
+ "sane_init() $Id$\n");
+
+ if (!fp)
+ {
+ /* default to /dev/ttyS0 instead of insisting on config file */
+ DBG (1, "sane_init: missing config file '%s'\n", DC25_CONFIG_FILE);
+ }
+ else
+ {
+ while (sanei_config_read (dev_name, sizeof (dev_name), fp))
+ {
+ dev_name[sizeof (dev_name) - 1] = '\0';
+ DBG (20, "sane_init: config- %s", dev_name);
+
+ if (dev_name[0] == '#')
+ continue; /* ignore line comments */
+ len = strlen (dev_name);
+ if (!len)
+ continue; /* ignore empty lines */
+ if (strncmp (dev_name, "port=", 5) == 0)
+ {
+ p = strchr (dev_name, '/');
+ if (p)
+ {
+ strcpy (tty_name, p);
+ }
+ DBG (20, "Config file port=%s\n", tty_name);
+ }
+ else if (strncmp (dev_name, "baud=", 5) == 0)
+ {
+ baud = atoi (&dev_name[5]);
+ switch (baud)
+ {
+ case 9600:
+ tty_baud = B9600;
+ break;
+ case 19200:
+ tty_baud = B19200;
+ break;
+ case 38400:
+ tty_baud = B38400;
+ break;
+#ifdef B57600
+ case 57600:
+ tty_baud = B57600;
+ break;
+#endif
+#ifdef B115200
+ case 115200:
+ tty_baud = B115200;
+ break;
+#endif
+ default:
+ DBG (20, "Unknown baud=%d\n", baud);
+ tty_baud = DEFAULT_TTY_BAUD;
+ break;
+ }
+ DBG (20, "Config file baud=%lu\n", (u_long) tty_baud);
+ }
+ else if (strcmp (dev_name, "dumpinquiry") == 0)
+ {
+ dumpinquiry = SANE_TRUE;
+ }
+ }
+ fclose (fp);
+ }
+
+ if ((tfd = init_dc20 (tty_name, tty_baud)) == -1)
+ {
+ return SANE_STATUS_INVAL;
+ }
+
+ if ((dc20_info = get_info (tfd)) == NULL)
+ {
+ DBG (2, "error: could not get info\n");
+ close_dc20 (tfd);
+ return SANE_STATUS_INVAL;
+ }
+
+ if (dumpinquiry)
+ {
+ DBG (0, "\nCamera information:\n~~~~~~~~~~~~~~~~~\n\n");
+ DBG (0, "Model...........: DC%x\n", dc20_info->model);
+ DBG (0, "Firmware version: %d.%d\n", dc20_info->ver_major,
+ dc20_info->ver_minor);
+ DBG (0, "Pictures........: %d/%d\n", dc20_info->pic_taken,
+ dc20_info->pic_taken + dc20_info->pic_left);
+ DBG (0, "Resolution......: %s\n",
+ dc20_info->flags.low_res ? "low" : "high");
+ DBG (0, "Battery state...: %s\n",
+ dc20_info->flags.low_batt ? "low" : "good");
+ }
+
+ if (CameraInfo.pic_taken == 0)
+ {
+/*
+ sod[DC25_OPT_IMAGE_NUMBER].cap |= SANE_CAP_INACTIVE;
+*/
+ image_range.min = 0;
+ dc25_opt_image_number = 0;
+
+ }
+ else
+ {
+/*
+ sod[DC25_OPT_IMAGE_NUMBER].cap &= ~SANE_CAP_INACTIVE;
+*/
+ image_range.min = 1;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_exit (void)
+{
+}
+
+/* Device select/open/close */
+
+static const SANE_Device dev[] = {
+ {
+ "0",
+ "Kodak",
+ "DC-25",
+ "still camera"},
+};
+
+SANE_Status
+sane_get_devices (const SANE_Device *** device_list,
+ SANE_Bool UNUSEDARG local_only)
+{
+ static const SANE_Device *devlist[] = {
+ dev + 0, 0
+ };
+
+ DBG (127, "sane_get_devices called\n");
+
+ if (dc20_info == NULL)
+ {
+ return SANE_STATUS_INVAL;
+ }
+ *device_list = devlist;
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_open (SANE_String_Const devicename, SANE_Handle * handle)
+{
+ int i;
+
+ DBG (127, "sane_open for device %s\n", devicename);
+ if (!devicename[0])
+ {
+ i = 0;
+ }
+ else
+ {
+ for (i = 0; i < NELEMS (dev); ++i)
+ {
+ if (strcmp (devicename, dev[i].name) == 0)
+ {
+ break;
+ }
+ }
+ }
+
+ if (i >= NELEMS (dev))
+ {
+ return SANE_STATUS_INVAL;
+ }
+
+ if (is_open)
+ {
+ return SANE_STATUS_DEVICE_BUSY;
+ }
+
+ is_open = 1;
+ *handle = MAGIC;
+
+ if (dc20_info == NULL)
+ {
+ DBG (1, "No device info\n");
+ }
+
+ if (tmpname == NULL)
+ {
+ tmpname = tmpnamebuf;
+ if (mktemp (tmpname) == NULL)
+ {
+ DBG (1, "Unable to make temp file %s\n", tmpname);
+ return SANE_STATUS_INVAL;
+ }
+ }
+
+ DBG (3, "sane_open: pictures taken=%d\n", dc20_info->pic_taken);
+
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_close (SANE_Handle handle)
+{
+ DBG (127, "sane_close called\n");
+ if (handle == MAGIC)
+ is_open = 0;
+
+ if (pp)
+ {
+ free_pixmap (pp);
+ pp = NULL;
+ }
+
+ close_dc20 (tfd);
+
+ DBG (127, "sane_close returning\n");
+}
+
+const SANE_Option_Descriptor *
+sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
+{
+ if (handle != MAGIC || !is_open)
+ return NULL; /* wrong device */
+ if (option < 0 || option >= NELEMS (sod))
+ return NULL;
+ return &sod[option];
+}
+
+SANE_Status
+sane_control_option (SANE_Handle handle, SANE_Int option,
+ SANE_Action action, void *value, SANE_Int * info)
+{
+ SANE_Int myinfo = info_flags;
+ SANE_Status status;
+
+ info_flags = 0;
+
+ DBG (127, "control_option(handle=%p,opt=%s,act=%s,val=%p,info=%p)\n",
+ handle, sod[option].title,
+ (action ==
+ SANE_ACTION_SET_VALUE ? "SET" : (action ==
+ SANE_ACTION_GET_VALUE ? "GET" :
+ "SETAUTO")), value, (void *)info);
+
+ if (handle != MAGIC || !is_open)
+ return SANE_STATUS_INVAL; /* Unknown handle ... */
+
+ if (option < 0 || option >= NELEMS (sod))
+ return SANE_STATUS_INVAL; /* Unknown option ... */
+
+ switch (action)
+ {
+ case SANE_ACTION_SET_VALUE:
+ status = sanei_constrain_value (sod + option, value, &myinfo);
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (1, "Constraint error in control_option\n");
+ return status;
+ }
+
+ switch (option)
+ {
+ case DC25_OPT_IMAGE_NUMBER:
+ dc25_opt_image_number = *(SANE_Word *) value;
+/* myinfo |= SANE_INFO_RELOAD_OPTIONS; */
+ break;
+
+ case DC25_OPT_THUMBS:
+ dc25_opt_thumbnails = !!*(SANE_Word *) value;
+ myinfo |= SANE_INFO_RELOAD_PARAMS;
+
+ if (dc25_opt_thumbnails)
+ {
+ /*
+ * DC20 thumbnail are 80x60 grayscale, DC25
+ * thumbnails are color.
+ */
+ parms.format =
+ (CameraInfo.model == 0x25) ? SANE_FRAME_RGB : SANE_FRAME_GRAY;
+ parms.bytes_per_line = 80 * 3;
+ parms.pixels_per_line = 80;
+ parms.lines = 60;
+ }
+ else
+ {
+ parms.format = SANE_FRAME_RGB;
+ if (dc20_info->flags.low_res)
+ {
+ parms.bytes_per_line = 320 * 3;
+ parms.pixels_per_line = 320;
+ parms.lines = 243;
+ }
+ else
+ {
+ parms.bytes_per_line = 500 * 3;
+ parms.pixels_per_line = 500;
+ parms.lines = 373;
+ }
+ }
+ break;
+
+ case DC25_OPT_SNAP:
+ dc25_opt_snap = !!*(SANE_Word *) value;
+ myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
+ if (dc25_opt_snap)
+ {
+ sod[DC25_OPT_LOWRES].cap &= ~SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ sod[DC25_OPT_LOWRES].cap |= SANE_CAP_INACTIVE;
+ }
+ break;
+
+ case DC25_OPT_LOWRES:
+ dc25_opt_lowres = !!*(SANE_Word *) value;
+ myinfo |= SANE_INFO_RELOAD_PARAMS;
+
+ if (!dc25_opt_thumbnails)
+ {
+
+ parms.format = SANE_FRAME_RGB;
+
+ if (dc20_info->flags.low_res)
+ {
+ parms.bytes_per_line = 320 * 3;
+ parms.pixels_per_line = 320;
+ parms.lines = 243;
+ }
+ else
+ {
+ parms.bytes_per_line = 500 * 3;
+ parms.pixels_per_line = 500;
+ parms.lines = 373;
+ }
+
+ }
+ break;
+
+ case DC25_OPT_CONTRAST:
+ dc25_opt_contrast = *(SANE_Word *) value;
+ break;
+
+ case DC25_OPT_GAMMA:
+ dc25_opt_gamma = *(SANE_Word *) value;
+ break;
+
+ case DC25_OPT_ERASE:
+ dc25_opt_erase = !!*(SANE_Word *) value;
+
+ /*
+ * erase and erase_one are mutually exclusive. If
+ * this one is turned on, the other must be off
+ */
+ if (dc25_opt_erase && dc25_opt_erase_one)
+ {
+ dc25_opt_erase_one = SANE_FALSE;
+ myinfo |= SANE_INFO_RELOAD_OPTIONS;
+ }
+ break;
+
+ case DC25_OPT_ERASE_ONE:
+ dc25_opt_erase_one = !!*(SANE_Word *) value;
+
+ /*
+ * erase and erase_one are mutually exclusive. If
+ * this one is turned on, the other must be off
+ */
+ if (dc25_opt_erase_one && dc25_opt_erase)
+ {
+ dc25_opt_erase = SANE_FALSE;
+ myinfo |= SANE_INFO_RELOAD_OPTIONS;
+ }
+ break;
+
+ case DC25_OPT_DEFAULT:
+
+ dc25_opt_contrast = SANE_FIX (DC25_OPT_CONTRAST_DEFAULT);
+ dc25_opt_gamma = SANE_FIX (DC25_OPT_GAMMA_DEFAULT);
+ myinfo |= SANE_INFO_RELOAD_OPTIONS;
+ break;
+
+ default:
+ return SANE_STATUS_INVAL;
+ }
+ break;
+
+ case SANE_ACTION_GET_VALUE:
+ switch (option)
+ {
+ case 0:
+ *(SANE_Word *) value = NELEMS (sod);
+ break;
+
+ case DC25_OPT_IMAGE_NUMBER:
+ *(SANE_Word *) value = dc25_opt_image_number;
+ break;
+
+ case DC25_OPT_THUMBS:
+ *(SANE_Word *) value = dc25_opt_thumbnails;
+ break;
+
+ case DC25_OPT_SNAP:
+ *(SANE_Word *) value = dc25_opt_snap;
+ break;
+
+ case DC25_OPT_LOWRES:
+ *(SANE_Word *) value = dc25_opt_lowres;
+ break;
+
+ case DC25_OPT_CONTRAST:
+ *(SANE_Word *) value = dc25_opt_contrast;
+ break;
+
+ case DC25_OPT_GAMMA:
+ *(SANE_Word *) value = dc25_opt_gamma;
+ break;
+
+ case DC25_OPT_ERASE:
+ *(SANE_Word *) value = dc25_opt_erase;
+ break;
+
+ case DC25_OPT_ERASE_ONE:
+ *(SANE_Word *) value = dc25_opt_erase_one;
+ break;
+
+ default:
+ return SANE_STATUS_INVAL;
+ }
+ break;
+
+ case SANE_ACTION_SET_AUTO:
+ switch (option)
+ {
+#if 0
+ case DC25_OPT_CONTRAST:
+ dc25_opt_contrast = SANE_FIX (DC25_OPT_CONTRAST_DEFAULT);
+ break;
+
+ case DC25_OPT_GAMMA:
+ dc25_opt_gamma = SANE_FIX (DC25_OPT_GAMMA_DEFAULT);
+ break;
+#endif
+
+ default:
+ return SANE_STATUS_UNSUPPORTED; /* We are DUMB */
+ }
+ }
+
+ if (info)
+ *info = myinfo;
+
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
+{
+ int rc = SANE_STATUS_GOOD;
+
+ DBG (127, "sane_get_params called\n");
+
+ if (handle != MAGIC || !is_open)
+ rc = SANE_STATUS_INVAL; /* Unknown handle ... */
+
+
+ *params = parms;
+ return rc;
+}
+
+static unsigned char thumb_pck[] = THUMBS_PCK;
+
+static unsigned char pic_pck[] = PICS_PCK;
+
+static int bytes_in_buffer;
+static int bytes_read_from_buffer;
+static SANE_Byte buffer[1024];
+static int total_bytes_read;
+static SANE_Bool started = SANE_FALSE;
+static int outbytes;
+
+SANE_Status
+sane_start (SANE_Handle handle)
+{
+ int n, i;
+ FILE *f;
+
+ DBG (127, "sane_start called, handle=%lx\n", (u_long) handle);
+
+ if (handle != MAGIC || !is_open ||
+ (dc25_opt_image_number == 0 && dc25_opt_snap == SANE_FALSE))
+ return SANE_STATUS_INVAL; /* Unknown handle ... */
+
+ if (started)
+ {
+ return SANE_STATUS_EOF;
+ }
+
+ if (dc25_opt_snap)
+ {
+
+ /*
+ * Don't allow picture unless there is room in the
+ * camera.
+ */
+ if (CameraInfo.pic_left == 0)
+ {
+ DBG (3, "No room to store new picture\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ /*
+ * DC-20 can only change resolution when camer is empty.
+ * DC-25 can do it any time.
+ */
+ if (CameraInfo.model != 0x20 || CameraInfo.pic_taken == 0)
+ {
+ if (change_res (tfd, dc25_opt_lowres) == -1)
+ {
+ DBG (1, "Failed to set resolution\n");
+ return SANE_STATUS_INVAL;
+ }
+ }
+
+ /*
+ * Not sure why this delay is needed, but it seems to help:
+ */
+#ifdef HAVE_USLEEP
+ usleep (10);
+#else
+ sleep (1);
+#endif
+ if (shoot (tfd) == -1)
+ {
+ DBG (1, "Failed to snap new picture\n");
+ return SANE_STATUS_INVAL;
+ }
+ else
+ {
+ info_flags |= SANE_INFO_RELOAD_OPTIONS;
+ CameraInfo.pic_taken++;
+ CameraInfo.pic_left--;
+ dc25_opt_image_number = CameraInfo.pic_taken;
+ if (image_range.min == 0)
+ image_range.min = 1;
+ image_range.max++;
+ sod[DC25_OPT_IMAGE_NUMBER].cap &= ~SANE_CAP_INACTIVE;
+ }
+ }
+
+ if (dc25_opt_thumbnails)
+ {
+
+ /*
+ * For thumbnails, we can do things right where we
+ * start the download, and grab the first block
+ * from the camera. The reamining blocks will be
+ * fetched as necessary by sane_read().
+ */
+ thumb_pck[3] = (unsigned char) dc25_opt_image_number;
+
+ if (send_pck (tfd, thumb_pck) == -1)
+ {
+ DBG (4, "sane_start: error: send_pck returned -1\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ if (read_data (tfd, buffer, 1024) == -1)
+ {
+ DBG (4, "sane_start: read_data failed\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ /*
+ * DC20 thumbnail are 80x60 grayscale, DC25
+ * thumbnails are color.
+ */
+ parms.format =
+ (CameraInfo.model == 0x25) ? SANE_FRAME_RGB : SANE_FRAME_GRAY;
+ parms.bytes_per_line = 80 * 3; /* 80 pixels, 3 colors */
+ parms.pixels_per_line = 80;
+ parms.lines = 60;
+
+ bytes_in_buffer = 1024;
+ bytes_read_from_buffer = 0;
+
+ }
+ else
+ {
+ /*
+ * We do something a little messy, and violates the SANE
+ * philosophy. However, since it is fairly tricky to
+ * convert the DC2x "comet" files on the fly, we read in
+ * the entire data stream in sane_open(), and use convert_pic
+ * to convert it to an in-memory pixpmap. Then when
+ * sane_read() is called, we fill the requests from
+ * memory. A good project for me (or some kind volunteer)
+ * would be to rewrite this and move the actual download
+ * to sane_read(). However, one argument for keeping it
+ * this way is that the data comes down pretty fast, and
+ * it helps to dedicate the processor to this task. We
+ * might get serial port overruns if we try to do other
+ * things at the same time.
+ *
+ * Also, as a side note, I was constantly getting serial
+ * port overruns on a 90MHz pentium until I used hdparm
+ * to set the "-u1" flag on the system drives.
+ */
+ int fd;
+
+ fd = open (tmpname, O_CREAT | O_EXCL | O_WRONLY, 0600);
+ if (fd == -1)
+ {
+ DBG (0, "Unable to open tmp file\n");
+ return SANE_STATUS_INVAL;
+ }
+ f = fdopen (fd, "wb");
+ if (f == NULL)
+ {
+ DBG (0, "Unable to fdopen tmp file\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ strcpy ((char *) buffer, COMET_MAGIC);
+ fwrite (buffer, 1, COMET_HEADER_SIZE, f);
+
+ pic_pck[3] = (unsigned char) dc25_opt_image_number;
+
+ if (send_pck (tfd, pic_pck) == -1)
+ {
+ DBG (4, "sane_start: error: send_pck returned -1\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ if (read_data (tfd, buffer, 1024) == -1)
+ {
+ DBG (5, "sane_start: read_data failed\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ if (buffer[4] == 0)
+ { /* hi-res image */
+ DBG (5, "sane_start: hi-res image\n");
+ n = 122;
+
+ parms.bytes_per_line = 500 * 3; /* 3 colors */
+ parms.pixels_per_line = 500;
+ parms.lines = 373;
+
+ bytes_in_buffer = 1024;
+ bytes_read_from_buffer = 0;
+ }
+ else
+ {
+ n = 61;
+ DBG (5, "sane_start: low-res image\n");
+
+ parms.bytes_per_line = 320 * 3; /* 3 Colors */
+ parms.pixels_per_line = 320;
+ parms.lines = 243;
+
+ bytes_in_buffer = 1024;
+ bytes_read_from_buffer = 0;
+ }
+
+
+ fwrite (buffer, 1, 1024, f);
+
+ for (i = 1; i < n; i++)
+ {
+ if (read_data (tfd, buffer, 1024) == -1)
+ {
+ DBG (5, "sane_start: read_data failed\n");
+ return SANE_STATUS_INVAL;
+ }
+ fwrite (buffer, 1, 1024, f);
+ }
+
+ if (end_of_data (tfd) == -1)
+ {
+ fclose (f);
+ DBG (4, "sane_open: end_of_data error\n");
+ return SANE_STATUS_INVAL;
+ }
+ else
+ {
+ fclose (f);
+ if (convert_pic (tmpname, SAVE_ADJASPECT | SAVE_24BITS) == -1)
+ {
+ DBG (3, "sane_open: unable to convert\n");
+ return SANE_STATUS_INVAL;
+ }
+ unlink (tmpname);
+ outbytes = 0;
+ }
+ }
+
+ started = SANE_TRUE;
+ total_bytes_read = 0;
+
+ return SANE_STATUS_GOOD;
+}
+
+
+SANE_Status
+sane_read (SANE_Handle UNUSEDARG handle, SANE_Byte * data,
+ SANE_Int max_length, SANE_Int * length)
+{
+ DBG (127, "sane_read called, maxlen=%d\n", max_length);
+
+ if ( ! started ) {
+ return SANE_STATUS_INVAL;
+ }
+
+ if (dc25_opt_thumbnails)
+ {
+ if (total_bytes_read == THUMBSIZE)
+ {
+ if (dc25_opt_erase || dc25_opt_erase_one)
+ {
+
+ if (erase (tfd) == -1)
+ {
+ DBG (1, "Failed to erase memory\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ dc25_opt_erase = SANE_FALSE;
+ dc25_opt_erase_one = SANE_FALSE;
+ info_flags |= SANE_INFO_RELOAD_OPTIONS;
+
+ if (get_info (tfd) == NULL)
+ {
+ DBG (2, "error: could not get info\n");
+ close_dc20 (tfd);
+ return SANE_STATUS_INVAL;
+ }
+ DBG (10, "Call get_info!, image range=%d,%d\n", image_range.min,
+ image_range.max);
+ }
+ return SANE_STATUS_EOF;
+ }
+
+ *length = 0;
+ if (!(bytes_in_buffer - bytes_read_from_buffer))
+ {
+ if (read_data (tfd, buffer, 1024) == -1)
+ {
+ DBG (5, "sane_read: read_data failed\n");
+ return SANE_STATUS_INVAL;
+ }
+ bytes_in_buffer = 1024;
+ bytes_read_from_buffer = 0;
+ }
+
+ while (bytes_read_from_buffer < bytes_in_buffer &&
+ max_length && total_bytes_read < THUMBSIZE)
+ {
+ *data++ = buffer[bytes_read_from_buffer++];
+ (*length)++;
+ max_length--;
+ total_bytes_read++;
+ }
+
+ if (total_bytes_read == THUMBSIZE)
+ {
+ if (end_of_data (tfd) == -1)
+ {
+ DBG (4, "sane_read: end_of_data error\n");
+ return SANE_STATUS_INVAL;
+ }
+ else
+ {
+ return SANE_STATUS_GOOD;
+ }
+ }
+ else
+ {
+ return SANE_STATUS_GOOD;
+ }
+ }
+ else
+ {
+ int i;
+ int filesize = parms.bytes_per_line * parms.lines;
+
+ /*
+ * If outbytes is zero, then this is the first time
+ * we've been called, so update the contrast table.
+ * The formula is something I came up with that has the
+ * following prooperties:
+ * 1) It's a smooth curve that provides the effect I wanted
+ * (bright pixels are made brighter, dim pixels are made
+ * dimmer)
+ * 2) The contrast parameter can be adjusted to provide
+ * different amounts of contrast.
+ * 3) A parameter of 1.0 can be used to pass the data
+ * through unchanged (but values around 1.75 look
+ * a lot better
+ */
+ if (outbytes == 0)
+ {
+ double d;
+ double cont = SANE_UNFIX (dc25_opt_contrast);
+
+ for (i = 0; i < 256; i++)
+ {
+ d = (i * 2.0) / 255 - 1.0;
+ d =
+ ((-pow (1 - d, cont)) + 1) * (d >=
+ 0) + (((pow (d + 1, cont)) -
+ 1)) * (d < 0);
+ contrast_table[i] = (d * 127.5) + 127.5;
+/*
+ fprintf (stderr,"%03d %03d\n",i,contrast_table[i]);
+*/
+ }
+ }
+
+ /* We're done, so return EOF */
+ if (outbytes >= filesize)
+ {
+ free_pixmap (pp);
+ pp = NULL;
+
+ if (dc25_opt_erase || dc25_opt_erase_one)
+ {
+ if (erase (tfd) == -1)
+ {
+ DBG (1, "Failed to erase memory\n");
+ return SANE_STATUS_INVAL;
+ }
+ }
+
+ if (get_info (tfd) == NULL)
+ {
+ DBG (2, "error: could not get info\n");
+ close_dc20 (tfd);
+ return SANE_STATUS_INVAL;
+ }
+ DBG (10, "Call get_info!, image range=%d,%d\n", image_range.min,
+ image_range.max);
+
+ get_info (tfd);
+
+ *length=0;
+
+ return SANE_STATUS_EOF;
+ }
+
+ if (max_length > filesize - outbytes)
+ {
+ *length = filesize - outbytes;
+ }
+ else
+ {
+ *length = max_length;
+ }
+
+ memcpy (data, pp->planes + outbytes, *length);
+ outbytes += *length;
+
+
+ for (i = 0; i < *length; i++)
+ {
+ data[i] = contrast_table[data[i]];
+ }
+
+ return SANE_STATUS_GOOD;
+
+ }
+}
+
+void
+sane_cancel (SANE_Handle UNUSEDARG handle)
+{
+ DBG (127, "sane_cancel() called\n");
+ started = SANE_FALSE;
+}
+
+SANE_Status
+sane_set_io_mode (SANE_Handle UNUSEDARG handle,
+ SANE_Bool UNUSEDARG non_blocking)
+{
+ /* sane_set_io_mode() is only valid during a scan */
+ if (started)
+ {
+ if (non_blocking == SANE_FALSE)
+ {
+ return SANE_STATUS_GOOD;
+ }
+ else
+ {
+ return SANE_STATUS_UNSUPPORTED;
+ }
+ }
+ else
+ {
+ /* We aren't currently scanning */
+ return SANE_STATUS_INVAL;
+ }
+}
+
+SANE_Status
+sane_get_select_fd (SANE_Handle UNUSEDARG handle, SANE_Int UNUSEDARG * fd)
+{
+ return SANE_STATUS_UNSUPPORTED;
+}