kopia lustrzana https://gitlab.com/sane-project/backends
2498 wiersze
61 KiB
C
2498 wiersze
61 KiB
C
/***************************************************************************
|
|
* 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: psfales@earthling.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 "sane/config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
|
|
#include "sane/sane.h"
|
|
#include "sane/sanei.h"
|
|
#include "sane/saneopts.h"
|
|
|
|
#define BACKEND_NAME dc25
|
|
#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/dc25.XXXXXX";
|
|
|
|
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, int orientation)
|
|
{
|
|
FILE *ifp;
|
|
unsigned char pic[MAX_IMAGE_SIZE];
|
|
int res,
|
|
image_size,
|
|
image_width,
|
|
net_width,
|
|
camera_header,
|
|
components;
|
|
char file[1024],
|
|
*extp;
|
|
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 (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;
|
|
|
|
}
|
|
|
|
/*
|
|
* Remove the extension (.cmt) from the file name
|
|
*/
|
|
|
|
strcpy (file, base_name);
|
|
if ( (extp = strrchr (file, '.')) != NULL)
|
|
*extp = '\0';
|
|
|
|
save_pixmap (pp, file, orientation, format);
|
|
|
|
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
|
|
save_pixmap_pxm (struct pixmap *p, FILE *fp)
|
|
{
|
|
int result = 0;
|
|
|
|
if (p) {
|
|
fprintf (fp, "P%c\n%d %d\n255\n", (p->components == 1) ? '5' : '6', p->width, p->height);
|
|
fwrite (p->planes, p->height*p->width*p->components, 1, fp);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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 int
|
|
save_pixmap (struct pixmap *p, char *name, int UNUSEDARG orientation, int UNUSEDARG format)
|
|
{
|
|
struct pixmap *to_be_saved = p;
|
|
char fname[1024];
|
|
FILE *ofp = NULL;
|
|
int retval;
|
|
|
|
|
|
/*
|
|
* Build the image name
|
|
*/
|
|
|
|
strcpy (fname, name);
|
|
strcat (fname, ".");
|
|
strcat (fname, (to_be_saved->components == 3) ? PPM_EXT : PGM_EXT );
|
|
|
|
/*
|
|
* Open the output file, if necessary
|
|
*/
|
|
|
|
if ( (ofp = fopen (fname, "wb")) == NULL) {
|
|
DBG (4,"save_pixmap: cannot open %s for output\n", fname);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Save the image
|
|
*/
|
|
|
|
retval = save_pixmap_pxm (to_be_saved, ofp);
|
|
|
|
if (ofp) fclose (ofp);
|
|
|
|
return retval;
|
|
}
|
|
|
|
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 (V_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=%d\n",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 ) {
|
|
perror (tmpname);
|
|
DBG (1,"Unable to make temp file name\n");
|
|
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, 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 = 241;
|
|
}
|
|
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 = 241;
|
|
}
|
|
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) {
|
|
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;
|
|
|
|
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\n");
|
|
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.
|
|
*/
|
|
|
|
f = fopen (tmpname,"wb");
|
|
if ( f == NULL ) {
|
|
DBG (4,"Unable to open tmp file\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
strcpy (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 = 241;
|
|
|
|
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,ROT_STRAIGHT) == -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 ( 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);
|
|
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)
|
|
{
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
SANE_Status
|
|
sane_get_select_fd (SANE_Handle UNUSEDARG handle, SANE_Int * UNUSEDARG fd)
|
|
{
|
|
return SANE_STATUS_UNSUPPORTED;
|
|
}
|
|
|