sane-project-frontends/src/scanadf.c

1720 wiersze
42 KiB
C

/* saneadf - a SANE front end for document scanning
based on
bnhscan by tummy.com and
scanimage by Andreas Beck and David Mosberger
Copyright (C) 1999 Tom Martone
Copyright (C) 2005 Rene Rebe ([ -p | --pipe] script option)
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., 675 Mass Ave, Cambridge, MA 02139, USA. */
#ifdef _AIX
# include <lalloca.h> /* MUST come first for AIX! */
#endif
#include "sane/config.h"
#include <lalloca.h>
#include <assert.h>
#include <getopt.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "sane/sane.h"
#include "sane/sanei.h"
#include "sane/saneopts.h"
#ifndef PATH_MAX
#define PATH_MAX 1024
#endif
const char*
sane_strframe (SANE_Frame f)
{
switch (f)
{
case SANE_FRAME_GRAY:
return "gray";
case SANE_FRAME_RGB:
return "RBG";
case SANE_FRAME_RED:
return "red";
case SANE_FRAME_GREEN:
return "green";
case SANE_FRAME_BLUE:
return "blue";
#if 0 // Not currently support by SANE
case SANE_FRAME_TEXT:
return "text";
case SANE_FRAME_JPEG:
return "jpeg";
case SANE_FRAME_G31D:
return "g31d";
case SANE_FRAME_G32D:
return "g32d";
case SANE_FRAME_G42D:
return "g42d";
#endif
default:
return "unknown";
}
}
#define sane_isbasicframe(f) ( (f) == SANE_FRAME_GRAY || \
(f) == SANE_FRAME_RGB || \
(f) == SANE_FRAME_RED || \
(f) == SANE_FRAME_GREEN || \
(f) == SANE_FRAME_BLUE )
#ifndef HAVE_ATEXIT
# define atexit(func) on_exit(func, 0) /* works for SunOS, at least */
#endif
typedef struct
{
u_char *data;
int Bpp; /* bytes/pixel */
int width;
int height;
int x;
int y;
}
Image;
static struct option basic_options[] =
{
{"device-name", required_argument, NULL, 'd'},
{"list-devices", no_argument, NULL, 'L'},
{"help", no_argument, NULL, 'h'},
{"verbose", no_argument, NULL, 'v'},
{"version", no_argument, NULL, 'V'},
{"no-overwrite", no_argument, NULL, 'N'},
{ "output-file", required_argument, 0, 'o' },
{ "start-count", required_argument, 0, 's' },
{ "end-count", required_argument, 0, 'e' },
{ "scan-script", required_argument, 0, 'S' },
{ "script-wait", no_argument, 0, 128 },
{ "raw", no_argument, 0, 'r' },
{ "pipe", no_argument, 0, 'p' },
{0, }
};
#define BASE_OPTSTRING "d:hLvVNTo:s:e:S:pr"
#define STRIP_HEIGHT 256 /* # lines we increment image height */
static struct option * all_options;
static int option_number_len;
static int * option_number;
static SANE_Handle device;
static int verbose;
static int help;
static const char * prog_name;
static SANE_Option_Descriptor window_option[2];
static int window[4];
static int resolution_opt = -1, x_resolution_opt = -1, y_resolution_opt = -1;
static SANE_Word window_val[2];
static int window_val_user[2]; /* is width/height user-specified? */
static void scanadf_exit (int);
static const char usage[] = "Usage: %s [OPTION]...\n\
\n\
Start image acquisition on a scanner device and write image data to\n\
output files.\n\
\n\
[ -d | --device-name <device> ] use a given scanner device.\n\
[ -h | --help ] display this help message and exit.\n\
[ -L | --list-devices ] show available scanner devices.\n\
[ -v | --verbose ] give even more status messages.\n\
[ -V | --version ] print version information.\n\
[ -N | --no-overwrite ] don't overwrite existing files.\n\
\n\
[ -o | --output-file <name> ] name of file to write image data\n\
(%%d replacement in output file name).\n\
[ -S | --scan-script <name> ] name of script to run after every scan.\n\
[ --script-wait ] wait for scripts to finish before exit\n\
[ -s | --start-count <num> ] page count of first scanned image.\n\
[ -e | --end-count <num> ] last page number to scan.\n\
[ -r | --raw ] write raw image data to file.\n";
static RETSIGTYPE
sighandler (int signum)
{
if (device)
{
fprintf (stderr, "%s: stopping scanner...\n", prog_name);
sane_cancel (device);
}
}
static void
print_unit (SANE_Unit unit)
{
switch (unit)
{
case SANE_UNIT_NONE: break;
case SANE_UNIT_PIXEL: fputs ("pel", stdout); break;
case SANE_UNIT_BIT: fputs ("bit", stdout); break;
case SANE_UNIT_MM: fputs ("mm", stdout); break;
case SANE_UNIT_DPI: fputs ("dpi", stdout); break;
case SANE_UNIT_PERCENT: fputc ('%', stdout); break;
case SANE_UNIT_MICROSECOND: fputs ("us", stdout); break;
}
}
static void
print_option (SANE_Device *device, int opt_num, char short_name)
{
const char *str, *last_break, *start;
const SANE_Option_Descriptor *opt;
SANE_Bool not_first = SANE_FALSE;
int i, column;
opt = sane_get_option_descriptor (device, opt_num);
if (short_name)
printf (" -%c", short_name);
else
printf (" --%s", opt->name);
if (opt->type == SANE_TYPE_BOOL)
{
fputs ("[=(", stdout);
if (opt->cap & SANE_CAP_AUTOMATIC)
fputs ("auto|", stdout);
fputs ("yes|no)]", stdout);
}
else if (opt->type != SANE_TYPE_BUTTON)
{
fputc (' ', stdout);
if (opt->cap & SANE_CAP_AUTOMATIC)
{
fputs ("auto|", stdout);
not_first = SANE_TRUE;
}
switch (opt->constraint_type)
{
case SANE_CONSTRAINT_NONE:
switch (opt->type)
{
case SANE_TYPE_INT: fputs ("<int>", stdout); break;
case SANE_TYPE_FIXED: fputs ("<float>", stdout); break;
case SANE_TYPE_STRING: fputs ("<string>", stdout); break;
default:
break;
}
if (opt->type != SANE_TYPE_STRING && opt->size > sizeof (SANE_Word))
fputs (",...", stdout);
break;
case SANE_CONSTRAINT_RANGE:
if (opt->type == SANE_TYPE_INT)
{
printf ("%d..%d",
opt->constraint.range->min, opt->constraint.range->max);
print_unit (opt->unit);
if (opt->size > sizeof (SANE_Word))
fputs (",...", stdout);
if (opt->constraint.range->quant)
printf (" (in steps of %d)",
opt->constraint.range->quant);
}
else
{
printf ("%g..%g",
SANE_UNFIX(opt->constraint.range->min),
SANE_UNFIX(opt->constraint.range->max));
print_unit (opt->unit);
if (opt->size > sizeof (SANE_Word))
fputs (",...", stdout);
if (opt->constraint.range->quant)
printf (" (in steps of %g)",
SANE_UNFIX(opt->constraint.range->quant));
}
break;
case SANE_CONSTRAINT_WORD_LIST:
for (i = 0; i < opt->constraint.word_list[0]; ++i)
{
if (not_first)
fputc ('|', stdout);
not_first = SANE_TRUE;
if (opt->type == SANE_TYPE_INT)
printf ("%d", opt->constraint.word_list[i + 1]);
else
printf ("%g",
SANE_UNFIX(opt->constraint.word_list[i + 1]));
}
print_unit (opt->unit);
if (opt->size > sizeof (SANE_Word))
fputs (",...", stdout);
break;
case SANE_CONSTRAINT_STRING_LIST:
for (i = 0; opt->constraint.string_list[i]; ++i)
{
if (i > 0)
fputc ('|', stdout);
fputs (opt->constraint.string_list[i], stdout);
}
break;
}
}
if (opt->type == SANE_TYPE_STRING ||
opt->type == SANE_TYPE_BOOL ||
opt->type == SANE_TYPE_INT ||
opt->type == SANE_TYPE_FIXED)
{
/* print current option value */
fputs (" [", stdout);
if (SANE_OPTION_IS_ACTIVE(opt->cap))
{
void * val = alloca (opt->size);
sane_control_option (device, opt_num, SANE_ACTION_GET_VALUE, val, 0);
switch (opt->type)
{
case SANE_TYPE_BOOL:
fputs (*(SANE_Bool *)val ? "yes" : "no", stdout);
break;
case SANE_TYPE_INT:
printf ("%d", *(SANE_Int *)val);
break;
case SANE_TYPE_FIXED:
printf ("%g", SANE_UNFIX(*(SANE_Fixed *)val));
break;
case SANE_TYPE_STRING:
fputs ((char *) val, stdout);
break;
default:
break;
}
}
else
fputs ("inactive", stdout);
fputc (']', stdout);
}
fputs ("\n ", stdout);
switch (short_name)
{
case 'x':
fputs ("Width of scan-area.", stdout);
break;
case 'y':
fputs ("Height of scan-area.", stdout);
break;
default:
column = 8; last_break = 0; start = opt->desc;
for (str = opt->desc; *str; ++str)
{
++column;
if (*str == ' ')
last_break = str;
if (column >= 79 && last_break)
{
while (start < last_break)
fputc (*start++, stdout);
start = last_break + 1; /* skip blank */
fputs ("\n ", stdout);
column = 8 + (str - start);
}
}
while (*start)
fputc (*start++, stdout);
}
fputc ('\n', stdout);
}
/* A scalar has the following syntax:
V [ U ]
V is the value of the scalar. It is either an integer or a
floating point number, depending on the option type.
U is an optional unit. If not specified, the default unit is used.
The following table lists which units are supported depending on
what the option's default unit is:
Option's unit: Allowed units:
SANE_UNIT_NONE:
SANE_UNIT_PIXEL: pel
SANE_UNIT_BIT: b (bit), B (byte)
SANE_UNIT_MM: mm (millimeter), cm (centimeter), in or " (inches),
SANE_UNIT_DPI: dpi
SANE_UNIT_PERCENT: %
SANE_UNIT_PERCENT: us
*/
static const char *
parse_scalar (const SANE_Option_Descriptor * opt, const char * str,
SANE_Word * value)
{
char * end;
double v;
if (opt->type == SANE_TYPE_FIXED)
v = strtod (str, &end) * (1 << SANE_FIXED_SCALE_SHIFT);
else
v = strtol (str, &end, 10);
if (str == end)
{
fprintf (stderr,
"%s: option --%s: bad option value (rest of option: %s)\n",
prog_name, opt->name, str);
scanadf_exit (1);
}
str = end;
switch (opt->unit)
{
case SANE_UNIT_NONE:
case SANE_UNIT_PIXEL:
break;
case SANE_UNIT_BIT:
if (*str == 'b' || *str == 'B')
{
if (*str++ == 'B')
v *= 8;
}
break;
case SANE_UNIT_MM:
if (str[0] == '\0')
v *= 1.0; /* default to mm */
else if (strcmp (str, "mm") == 0)
str += sizeof ("mm") - 1;
else if (strcmp (str, "cm") == 0)
{
str += sizeof ("cm") - 1;
v *= 10.0;
}
else if (strcmp (str, "in") == 0 || *str == '"')
{
if (*str++ != '"')
++str;
v *= 25.4; /* 25.4 mm/inch */
}
else
{
fprintf (stderr,
"%s: option --%s: illegal unit (rest of option: %s)\n",
prog_name, opt->name, str);
return 0;
}
break;
case SANE_UNIT_DPI:
if (strcmp (str, "dpi") == 0)
str += sizeof ("dpi") - 1;
break;
case SANE_UNIT_PERCENT:
if (*str == '%')
++str;
break;
case SANE_UNIT_MICROSECOND:
if (strcmp (str, "us") == 0)
str += sizeof ("us") - 1;
break;
}
*value = v + 0.5;
return str;
}
/* A vector has the following syntax:
[ '[' I ']' ] S { [','|'-'] [ '[' I ']' S }
The number in brackets (I), if present, determines the index of the
vector element to be set next. If I is not present, the value of
last index used plus 1 is used. The first index value used is 0
unless I is present.
S is a scalar value as defined by parse_scalar().
If two consecutive value specs are separated by a comma (,) their
values are set independently. If they are separated by a dash (-),
they define the endpoints of a line and all vector values between
the two endpoints are set according to the value of the
interpolated line. For example, [0]15-[255]15 defines a vector of
256 elements whose value is 15. Similarly, [0]0-[255]255 defines a
vector of 256 elements whose value starts at 0 and increases to
255. */
static void
parse_vector (const SANE_Option_Descriptor * opt, const char * str,
SANE_Word * vector, size_t vector_length)
{
SANE_Word value, prev_value = 0;
int index = -1, prev_index = 0;
char * end, separator = '\0';
/* initialize vector to all zeroes: */
memset (vector, 0, vector_length * sizeof (SANE_Word));
do {
if (*str == '[')
{
/* read index */
index = strtol (++str, &end, 10);
if (str == end || *end != ']')
{
fprintf (stderr, "%s: option --%s: closing bracket missing "
"(rest of option: %s)\n", prog_name, opt->name, str);
scanadf_exit (1);
}
str = end + 1;
}
else
++index;
if (index < 0 || index >= vector_length)
{
fprintf (stderr, "%s: option --%s: index %d out of range [0..%ld]\n",
prog_name, opt->name, index, (long) vector_length - 1);
scanadf_exit (1);
}
/* read value */
str = parse_scalar (opt, str, &value);
if (!str)
scanadf_exit (1);
if (*str && *str != '-' && *str != ',')
{
fprintf (stderr,
"%s: option --%s: illegal separator (rest of option: %s)\n",
prog_name, opt->name, str);
scanadf_exit (1);
}
/* store value: */
vector[index] = value;
if (separator == '-')
{
/* interpolate */
double v, slope;
int i;
v = (double) prev_value;
slope = ((double) value - v) / (index - prev_index);
for (i = prev_index + 1; i < index; ++i)
{
v += slope;
vector[i] = (SANE_Word) v;
}
}
prev_index = index;
prev_value = value;
separator = *str++;
} while (separator == ',' || separator == '-');
if (verbose > 2)
{
int i;
fprintf (stderr, "%s: value for --%s is: ", prog_name, opt->name);
for (i = 0; i < vector_length; ++i)
if (opt->type == SANE_TYPE_FIXED)
fprintf (stderr, "%g ", SANE_UNFIX(vector[i]));
else
fprintf (stderr, "%d ", vector[i]);
fputc ('\n', stderr);
}
}
void
fetch_options (SANE_Device * device)
{
const SANE_Option_Descriptor * opt;
SANE_Int num_dev_options;
int i, option_count;
SANE_Status status;
/* and now build the full table of long options: */
opt = sane_get_option_descriptor (device, 0);
if (opt == NULL)
{
fprintf (stderr, "Could not get option descriptor for option 0\n");
exit (1);
}
status = sane_control_option (device, 0, SANE_ACTION_GET_VALUE, &num_dev_options, 0);
if (status != SANE_STATUS_GOOD)
{
fprintf (stderr, "Could not get value for option 0: %s\n", sane_strstatus (status));
exit (1);
}
option_count = 0;
for (i = 1; i < num_dev_options; ++i)
{
opt = sane_get_option_descriptor (device, i);
if (opt == NULL)
{
fprintf (stderr, "Could not get option descriptor for option %d\n", i);
exit (1);
}
if (!SANE_OPTION_IS_SETTABLE (opt->cap))
continue;
option_number[option_count] = i;
all_options[option_count].name = (char *) opt->name;
all_options[option_count].flag = 0;
all_options[option_count].val = 0;
if (opt->type == SANE_TYPE_BOOL)
all_options[option_count].has_arg = optional_argument;
else if (opt->type == SANE_TYPE_BUTTON)
all_options[option_count].has_arg = no_argument;
else
all_options[option_count].has_arg = required_argument;
/* Keep track of resolution options */
if (strcmp (opt->name, SANE_NAME_SCAN_RESOLUTION) == 0)
{
resolution_opt = i;
}
else if (strcmp (opt->name, SANE_NAME_SCAN_X_RESOLUTION) == 0)
{
x_resolution_opt = i;
}
if (strcmp (opt->name, SANE_NAME_SCAN_Y_RESOLUTION) == 0)
{
y_resolution_opt = i;
}
/* Keep track of top-left corner options (if they exist at
all) and replace the bottom-right corner options by a
width/height option (if they exist at all). */
if ((opt->type == SANE_TYPE_FIXED || opt->type == SANE_TYPE_INT)
&& opt->size == sizeof (SANE_Int)
&& (opt->unit == SANE_UNIT_MM || opt->unit == SANE_UNIT_PIXEL))
{
if (strcmp (opt->name, SANE_NAME_SCAN_TL_X) == 0)
{
window[2] = i;
all_options[option_count].val = 'l';
}
else if (strcmp (opt->name, SANE_NAME_SCAN_TL_Y) == 0)
{
window[3] = i;
all_options[option_count].val = 't';
}
else if (strcmp (opt->name, SANE_NAME_SCAN_BR_X) == 0)
{
window[0] = i;
all_options[option_count].name = "width";
all_options[option_count].val = 'x';
window_option[0] = *opt;
window_option[0].title = "Scan width";
window_option[0].desc = "Width of scanning area.";
if (!window_val_user[0])
sane_control_option (device, i, SANE_ACTION_GET_VALUE,
&window_val[0], 0);
}
else if (strcmp (opt->name, SANE_NAME_SCAN_BR_Y) == 0)
{
window[1] = i;
all_options[option_count].name = "height";
all_options[option_count].val = 'y';
window_option[1] = *opt;
window_option[1].title = "Scan height";
window_option[1].desc = "Height of scanning area.";
if (!window_val_user[1])
sane_control_option (device, i, SANE_ACTION_GET_VALUE,
&window_val[1], 0);
}
}
++option_count;
}
memcpy (all_options + option_count, basic_options, sizeof (basic_options));
option_count += NELEMS(basic_options);
memset (all_options + option_count, 0, sizeof (all_options[0]));
/* Initialize width & height options based on backend default
values for top-left x/y and bottom-right x/y: */
for (i = 0; i < 2; ++i)
{
if (window[i] && window[i + 2] && !window_val_user[i])
{
SANE_Word pos;
sane_control_option (device, window[i + 2],
SANE_ACTION_GET_VALUE, &pos, 0);
window_val[i] = window_val[i] - pos + 1;
}
}
}
static void
scanadf_exit (int status)
{
if (device)
{
if (verbose > 1)
fprintf (stderr, "Closing device\n");
sane_close (device);
}
if (verbose > 1)
fprintf (stderr, "Calling sane_exit\n");
sane_exit ();
if (all_options)
free (all_options);
if (option_number)
free (option_number);
if (verbose > 1)
fprintf (stderr, "scanimage: finished\n");
exit (status);
}
static void
set_option (SANE_Handle device, int optnum, void * valuep)
{
const SANE_Option_Descriptor * opt;
SANE_Status status;
SANE_Word orig = 0;
SANE_Int info;
opt = sane_get_option_descriptor (device, optnum);
if (opt->size == sizeof (SANE_Word) && opt->type != SANE_TYPE_STRING)
orig = *(SANE_Word *) valuep;
status = sane_control_option (device, optnum, SANE_ACTION_SET_VALUE,
valuep, &info);
if (status != SANE_STATUS_GOOD)
{
fprintf (stderr, "%s: setting of option --%s failed (%s)\n",
prog_name, opt->name, sane_strstatus (status));
scanadf_exit (1);
}
if ((info & SANE_INFO_INEXACT) && opt->size == sizeof (SANE_Word))
{
if (opt->type == SANE_TYPE_INT)
fprintf (stderr, "%s: rounded value of %s from %d to %d\n",
prog_name, opt->name, orig, *(SANE_Word *) valuep);
else if (opt->type == SANE_TYPE_FIXED)
fprintf (stderr, "%s: rounded value of %s from %g to %g\n",
prog_name, opt->name,
SANE_UNFIX(orig), SANE_UNFIX(*(SANE_Word *) valuep));
}
if (info & SANE_INFO_RELOAD_OPTIONS)
fetch_options (device);
}
static void
process_backend_option (SANE_Handle device, int optnum, const char * optarg)
{
static SANE_Word * vector = 0;
static size_t vector_size = 0;
const SANE_Option_Descriptor * opt;
size_t vector_length;
SANE_Status status;
SANE_Word value;
void * valuep;
opt = sane_get_option_descriptor (device, optnum);
if (!SANE_OPTION_IS_ACTIVE(opt->cap))
{
fprintf (stderr, "%s: attempted to set inactive option %s\n",
prog_name, opt->name);
scanadf_exit (1);
}
if ((opt->cap & SANE_CAP_AUTOMATIC) && strncasecmp (optarg, "auto", 4) == 0)
{
status = sane_control_option (device, optnum, SANE_ACTION_SET_AUTO,
0, 0);
if (status != SANE_STATUS_GOOD)
{
fprintf (stderr, "%s: failed to set option --%s to automatic (%s)\n",
prog_name, opt->name, sane_strstatus (status));
scanadf_exit (1);
}
return;
}
valuep = &value;
switch (opt->type)
{
case SANE_TYPE_BOOL:
value = 1; /* no argument means option is set */
if (optarg)
{
if (strncasecmp (optarg, "yes", strlen (optarg)) == 0)
value = 1;
else if (strncasecmp (optarg, "no", strlen (optarg)) == 0)
value = 0;
else
{
fprintf (stderr, "%s: option --%s: bad option value `%s'\n",
prog_name, opt->name, optarg);
scanadf_exit (1);
}
}
break;
case SANE_TYPE_INT:
case SANE_TYPE_FIXED:
/* ensure vector is long enough: */
vector_length = opt->size / sizeof (SANE_Word);
if (vector_size < vector_length)
{
vector_size = vector_length;
vector = realloc (vector, vector_length * sizeof (SANE_Word));
if (!vector)
{
fprintf (stderr, "%s: out of memory\n", prog_name);
scanadf_exit (1);
}
}
parse_vector (opt, optarg, vector, vector_length);
valuep = vector;
break;
case SANE_TYPE_STRING:
valuep = (void *) optarg;
break;
case SANE_TYPE_BUTTON:
value = 0; /* value doesn't matter */
break;
default:
fprintf (stderr, "%s: duh, got unknown option type %d\n",
prog_name, opt->type);
return;
}
set_option (device, optnum, valuep);
}
static void
write_pnm_header_to_file (FILE *fp, SANE_Frame format, int width, int height, int depth)
{
switch (format)
{
case SANE_FRAME_RED:
case SANE_FRAME_GREEN:
case SANE_FRAME_BLUE:
case SANE_FRAME_RGB:
fprintf (fp, "P6\n# SANE data follows\n%d %d\n255\n", width, height);
break;
case SANE_FRAME_GRAY:
if (depth == 1)
fprintf (fp, "P4\n# SANE data follows\n%d %d\n", width, height);
else
fprintf (fp, "P5\n# SANE data follows\n%d %d\n255\n", width, height);
break;
default:
/* default action for unknown frametypes; don't write a header */
break;
}
#ifdef __EMX__ /* OS2 - write in binary mode. */
_fsetmode(fp, "b");
#endif
}
static void *
advance (Image *image)
{
if (++image->x >= image->width)
{
image->x = 0;
if (++image->y >= image->height || !image->data)
{
size_t old_size = 0, new_size;
if (image->data)
old_size = image->height * image->width * image->Bpp;
image->height += STRIP_HEIGHT;
new_size = image->height * image->width * image->Bpp;
if (image->data)
image->data = realloc (image->data, new_size);
else
image->data = malloc (new_size);
if (image->data)
memset (image->data + old_size, 0, new_size - old_size);
}
}
if (!image->data)
fprintf (stderr, "%s: can't allocate image buffer (%dx%d)\n",
prog_name, image->width, image->height);
return image->data;
}
static SANE_Int
get_resolution(SANE_Device *device)
{
SANE_Int res = 200;
SANE_Word val;
const SANE_Option_Descriptor *opt;
if (resolution_opt >= 0)
{
opt = sane_get_option_descriptor (device, resolution_opt);
sane_control_option (device, resolution_opt,
SANE_ACTION_GET_VALUE, &val, 0);
switch (opt->type)
{
case SANE_TYPE_INT:
res = val;
break;
case SANE_TYPE_FIXED:
res = (SANE_Int) SANE_UNFIX(val);
break;
case SANE_TYPE_STRING:
case SANE_TYPE_BOOL:
default:
if (verbose)
fprintf(stderr,
"Peculiar option data type for resolution, "
"using default value.\n");
break;
}
}
else
{
if (verbose)
fprintf(stderr, "No resolution option found, using default value.\n");
}
return res;
}
static int
exec_script (const char *script, const char* fname, SANE_Bool use_pipe,
SANE_Parameters *parm, int *fd)
{
static char cmd[PATH_MAX * 2];
static char env[7][PATH_MAX * 2];
int pid;
SANE_Int res;
SANE_Frame format;
extern char **environ;
int pipefd[2];
res = get_resolution(device);
format = parm->format;
if (format == SANE_FRAME_RED ||
format == SANE_FRAME_GREEN ||
format == SANE_FRAME_BLUE)
{
/* the resultant format is RGB */
format = SANE_FRAME_RGB;
}
snprintf (env[0], sizeof(env[0]), "SCAN_RES=%d", res);
if (putenv (env[0]))
fprintf (stderr, "putenv:failed\n");
snprintf (env[1], sizeof(env[1]), "SCAN_WIDTH=%d", parm->pixels_per_line);
if (putenv (env[1]))
fprintf (stderr, "putenv:failed\n");
snprintf (env[2], sizeof(env[2]), "SCAN_HEIGHT=%d", parm->lines);
if (putenv (env[2]))
fprintf (stderr, "putenv:failed\n");
snprintf (env[3], sizeof(env[3]), "SCAN_FORMAT_ID=%d", (int) parm->format);
if (putenv (env[3]))
fprintf (stderr, "putenv:failed\n");
snprintf (env[4], sizeof(env[4]), "SCAN_FORMAT=%s",
sane_strframe (parm->format));
if (putenv (env[4]))
fprintf (stderr, "putenv:failed\n");
snprintf (env[5], sizeof(env[5]), "SCAN_DEPTH=%d", parm->depth);
if (putenv (env[5]))
fprintf (stderr, "putenv:failed\n");
snprintf (env[6], sizeof(env[6]), "SCAN_PIPE=%d", use_pipe);
if (putenv (env[6]))
fprintf (stderr, "putenv:failed\n");
if (use_pipe) {
pipe(pipefd);
}
/*signal(SIGCHLD, SIG_IGN);*/
switch ((pid = fork()))
{
case -1:
/* fork failed */
fprintf(stderr, "Error forking: %s (%d)\n", strerror(errno), errno);
break;
case 0:
/* in child process */
if (use_pipe) {
dup2(pipefd[0],0); close(pipefd[0]); close(pipefd[1]);
}
sprintf(cmd, "%s '%s'", script, fname);
execle(script, script, fname, NULL, environ);
exit(0);
default:
if (verbose)
fprintf(stderr, "Started script `%s' as pid=%d\n", script, pid);
break;
}
if (use_pipe) {
close(pipefd[0]);
*fd = pipefd[1];
}
return pid;
}
static SANE_Status
scan_it_raw (const char *fname, SANE_Bool raw, const char *script,
SANE_Bool use_pipe)
{
int i, len, first_frame = 1, offset = 0, must_buffer = 0;
SANE_Byte buffer[32*1024], min = 0xff, max = 0;
SANE_Parameters parm;
SANE_Status status;
Image image = {0, };
FILE *fp = NULL;
int pid = 0;
do
{
#ifdef SANE_STATUS_WARMING_UP
do
{
status = sane_start (device);
} while (status == SANE_STATUS_WARMING_UP);
#else
status = sane_start (device);
#endif
if (status != SANE_STATUS_GOOD)
{
if (status != SANE_STATUS_NO_DOCS)
{
fprintf (stderr, "%s: sane_start: %s\n",
prog_name, sane_strstatus (status));
}
goto cleanup;
}
status = sane_get_parameters (device, &parm);
if (status != SANE_STATUS_GOOD)
{
fprintf (stderr, "%s: sane_get_parameters: %s\n",
prog_name, sane_strstatus (status));
goto cleanup;
}
if (script && use_pipe)
{
int fd = 0;
pid = exec_script(script, fname, use_pipe, &parm, &fd);
fp = fdopen (fd, "wb");
}
else
fp = fopen(fname, "wb");
if (!fp) {
fprintf(stderr, "Error opening output `%s': %s (%d)\n",
fname, strerror(errno), errno);
status = SANE_STATUS_IO_ERROR;
goto cleanup;
}
if (verbose)
{
if (first_frame)
{
if (sane_isbasicframe(parm.format))
{
if (parm.lines >= 0)
fprintf (stderr, "%s: scanning image of size %dx%d pixels"
" at %d bits/pixel\n",
prog_name, parm.pixels_per_line, parm.lines,
8 * parm.bytes_per_line / parm.pixels_per_line);
else
fprintf (stderr, "%s: scanning image %d pixels wide and "
"variable height at %d bits/pixel\n",
prog_name, parm.pixels_per_line,
8 * parm.bytes_per_line / parm.pixels_per_line);
}
else
{
fprintf (stderr, "%s: receiving %s frame "
"bytes/line=%d, "
"pixels/line=%d, "
"lines=%d, "
"depth=%d\n",
prog_name, sane_strframe(parm.format),
parm.bytes_per_line,
parm.pixels_per_line,
parm.lines,
parm.depth);
}
}
fprintf (stderr, "%s: acquiring %s frame\n", prog_name,
sane_strframe(parm.format));
}
if (first_frame)
{
switch (parm.format)
{
case SANE_FRAME_RED:
case SANE_FRAME_GREEN:
case SANE_FRAME_BLUE:
assert (parm.depth == 8);
must_buffer = 1;
offset = parm.format - SANE_FRAME_RED;
break;
case SANE_FRAME_RGB:
assert (parm.depth == 8);
case SANE_FRAME_GRAY:
assert (parm.depth == 1 || parm.depth == 8);
/* if we're writing raw, we skip the header and never
* have to buffer a single frame format.
*/
if (raw == SANE_FALSE)
{
if (parm.lines < 0)
{
must_buffer = 1;
offset = 0;
}
else
{
write_pnm_header_to_file (fp, parm.format,
parm.pixels_per_line,
parm.lines, parm.depth);
}
}
break;
#if 0 // Not currently supported by SANE
case SANE_FRAME_TEXT:
case SANE_FRAME_JPEG:
case SANE_FRAME_G31D:
case SANE_FRAME_G32D:
case SANE_FRAME_G42D:
if (!parm.last_frame)
{
status = SANE_STATUS_INVAL;
fprintf (stderr, "%s: bad %s frame: must be last_frame\n",
prog_name, sane_strframe (parm.format));
goto cleanup;
}
/* write them out without a header; don't buffer */
break;
#endif
default:
/* Default action for unknown frametypes; write them out
* without a header; issue a warning in verbose mode.
* Since we're not writing a header, there's no need to
* buffer.
*/
if (verbose)
{
fprintf(stderr, "scanadf: unknown frame format %d\n",
(int) parm.format);
}
if (!parm.last_frame)
{
status = SANE_STATUS_INVAL;
fprintf (stderr, "%s: bad %s frame: must be last_frame\n",
prog_name, sane_strframe (parm.format));
goto cleanup;
}
break;
}
if (must_buffer)
{
/* We're either scanning a multi-frame image or the
scanner doesn't know what the eventual image height
will be (common for hand-held scanners). In either
case, we need to buffer all data before we can write
the image. */
image.width = parm.pixels_per_line;
if (parm.lines >= 0)
/* See advance(); we allocate one extra line so we
don't end up realloc'ing in when the image has been
filled in. */
image.height = parm.lines - STRIP_HEIGHT + 1;
else
image.height = 0;
image.Bpp = 3;
if (parm.format == SANE_FRAME_GRAY ||
!sane_isbasicframe(parm.format))
image.Bpp = 1;
image.x = image.width - 1;
image.y = -1;
if (!advance (&image))
goto cleanup;
}
}
else
{
assert (parm.format >= SANE_FRAME_RED
&& parm.format <= SANE_FRAME_BLUE);
offset = parm.format - SANE_FRAME_RED;
image.x = image.y = 0;
}
while (1)
{
status = sane_read (device, buffer, sizeof (buffer), &len);
if (status != SANE_STATUS_GOOD)
{
if (verbose && parm.depth == 8)
fprintf (stderr, "%s: min/max graylevel value = %d/%d\n",
prog_name, min, max);
if (status != SANE_STATUS_EOF)
{
fprintf (stderr, "%s: sane_read: %s\n",
prog_name, sane_strstatus (status));
goto cleanup;
}
break;
}
if (must_buffer)
{
switch (parm.format)
{
case SANE_FRAME_RED:
case SANE_FRAME_GREEN:
case SANE_FRAME_BLUE:
for (i = 0; i < len; ++i)
{
image.data[offset + 3*i] = buffer[i];
if (!advance (&image))
goto cleanup;
}
offset += 3*len;
break;
case SANE_FRAME_RGB:
for (i = 0; i < len; ++i)
{
image.data[offset + i] = buffer[i];
if ((offset + i) % 3 == 0 && !advance (&image))
goto cleanup;
}
offset += len;
break;
case SANE_FRAME_GRAY:
for (i = 0; i < len; ++i)
{
image.data[offset + i] = buffer[i];
if (!advance (&image))
goto cleanup;
}
offset += len;
break;
default:
/* optional frametypes are never buffered */
fprintf(stderr, "%s: ERROR: trying to buffer %s"
" frametype\n",
prog_name,
sane_strframe(parm.format));
break;
}
}
else
fwrite (buffer, 1, len, fp);
if (verbose && parm.depth == 8)
{
for (i = 0; i < len; ++i)
if (buffer[i] >= max)
max = buffer[i];
else if (buffer[i] < min)
min = buffer[i];
}
}
first_frame = 0;
}
while (!parm.last_frame);
if (must_buffer)
{
image.height = image.y;
if (raw == SANE_FALSE)
{
/* if we're writing raw, we skip the header */
write_pnm_header_to_file (fp, parm.format, image.width,
image.height, parm.depth);
}
fwrite (image.data, image.Bpp, image.height * image.width, fp);
}
if (fp)
{
fclose(fp);
fp = NULL;
}
if (script && !use_pipe)
pid = exec_script (script, fname, use_pipe, &parm, NULL);
if (script) {
int exit_status = 0;
waitpid (pid, &exit_status, 0);
if (exit_status && verbose)
fprintf(stderr, "%s: WARNING: child exited with %d\n",
prog_name, exit_status);
}
cleanup:
if (image.data)
free (image.data);
if (fp) fclose(fp);
return status;
}
static SANE_Int
scan_docs (int start, int end, int no_overwrite, SANE_Bool raw,
const char *outfmt, const char *script, SANE_Bool use_pipe)
{
SANE_Status status = SANE_STATUS_GOOD;
SANE_Int scannedPages = 0;
SANE_Char fname[PATH_MAX];
struct stat statbuf;
int res;
while (end < 0 || start <= end)
{
snprintf(fname, sizeof (fname), outfmt, start);
/* does the filename already exist? */
if (no_overwrite)
{
res = stat (fname, &statbuf);
if (res == 0)
{
status = SANE_STATUS_INVAL;
fprintf (stderr, "Filename %s already exists; will not overwrite\n", fname);
}
}
/* Scan the document */
if (status == SANE_STATUS_GOOD)
status = scan_it_raw(fname, raw, script, use_pipe);
/* Any scan errors? */
if (status == SANE_STATUS_NO_DOCS)
{
/* out of paper in the hopper; this is our normal exit */
status = SANE_STATUS_GOOD;
break;
}
else if (status == SANE_STATUS_EOF)
{
/* done with this doc */
status = SANE_STATUS_GOOD;
fprintf(stderr, "Scanned document %s\n", fname);
scannedPages++;
start++;
}
else
{
/* unexpected error */
fprintf(stderr, "%s\n", sane_strstatus(status));
break;
}
}
fprintf(stderr, "Scanned %d pages\n", scannedPages);
return status;
}
int
main (int argc, char **argv)
{
int ch, i, index, all_options_len;
const SANE_Option_Descriptor * opt;
const SANE_Device ** device_list;
SANE_Int num_dev_options = 0;
const char * devname = 0;
SANE_Status status;
char *full_optstring;
SANE_Bool raw = SANE_FALSE;
SANE_Bool use_pipe = SANE_FALSE;
const char *scanScript = NULL; /* script to run at end of scan */
int scriptWait = 0;
const char *outputFile = "image-%04d"; /* file name(format) to write output to */
int startNum = 1, endNum = -1; /* start/end numbers of pages to scan */
int no_overwrite = 0;
atexit (sane_exit);
prog_name = strrchr (argv[0], '/');
if (prog_name)
++prog_name;
else
prog_name = argv[0];
sane_init (0, 0);
/* make a first pass through the options with error printing and argument
permutation disabled: */
opterr = 0;
while ((ch = getopt_long (argc, argv, "-" BASE_OPTSTRING, basic_options,
&index))
!= EOF)
{
switch (ch)
{
case ':':
case '?':
break; /* may be an option that we'll parse later on */
case 'd': devname = optarg; break;
case 'h': help = 1; break;
case 'N': no_overwrite = 1; break;
case 'v': ++verbose; break;
case 'L':
{
int i;
status = sane_get_devices (&device_list, SANE_FALSE);
if (status != SANE_STATUS_GOOD)
{
fprintf (stderr, "%s: sane_get_devices() failed: %s\n",
prog_name, sane_strstatus (status));
scanadf_exit (1);
}
for (i = 0; device_list[i]; ++i)
{
printf ("device `%s' is a %s %s %s\n",
device_list[i]->name, device_list[i]->vendor,
device_list[i]->model, device_list[i]->type);
}
scanadf_exit (0);
}
case 'o': outputFile = optarg; break;
case 'S': scanScript = optarg; break;
case 128: scriptWait = 1; break;
case 's': startNum = atoi(optarg); break;
case 'e': endNum = atoi(optarg); break;
case 'r': raw = SANE_TRUE; break;
case 'p': use_pipe = SANE_TRUE; break;
case 'V':
printf ("scanadf (%s) %s\n", PACKAGE, VERSION);
exit (0);
default:
break; /* ignore device specific options for now */
}
}
if (scriptWait && !scanScript)
scriptWait = 0;
if (help)
printf (usage, prog_name);
if (!devname)
{
/* If no device name was specified explicitly, we open the first
device we find (if any): */
status = sane_get_devices (&device_list, SANE_FALSE);
if (status != SANE_STATUS_GOOD)
{
fprintf (stderr, "%s: sane_get_devices() failed: %s\n",
prog_name, sane_strstatus (status));
scanadf_exit (1);
}
if (!device_list[0])
{
fprintf (stderr, "%s: no SANE devices found\n", prog_name);
scanadf_exit (1);
}
devname = device_list[0]->name;
}
status = sane_open (devname, &device);
if (status != SANE_STATUS_GOOD)
{
fprintf (stderr, "%s: open of device %s failed: %s\n",
prog_name, devname, sane_strstatus (status));
if (help)
device = 0;
else
scanadf_exit (1);
}
if (device)
{
/* We got a device, find out how many options it has: */
status = sane_control_option (device, 0, SANE_ACTION_GET_VALUE,
&num_dev_options, 0);
if (status != SANE_STATUS_GOOD)
{
fprintf (stderr, "%s: unable to determine option count\n",
prog_name);
scanadf_exit (1);
}
all_options_len = num_dev_options + NELEMS(basic_options) + 1;
all_options = malloc (all_options_len * sizeof (all_options[0]));
option_number_len = num_dev_options;
option_number = malloc (option_number_len * sizeof (option_number[0]));
if (!all_options || !option_number)
{
fprintf (stderr, "%s: out of memory in fetch_options()\n",
prog_name);
scanadf_exit (1);
}
fetch_options (device);
{
char *larg, *targ, *xarg, *yarg;
larg = targ = xarg = yarg = "";
/* Maybe accept t, l, x, and y options. */
if (window[0])
xarg = "x:";
if (window[1])
yarg = "y:";
if (window[2])
larg = "l:";
if (window[3])
targ = "t:";
/* Now allocate the full option list. */
full_optstring = malloc (strlen (BASE_OPTSTRING)
+ strlen (larg) + strlen (targ)
+ strlen (xarg) + strlen (yarg) + 1);
if (!full_optstring)
{
fprintf (stderr, "%s: out of memory\n", prog_name);
scanadf_exit (1);
}
strcpy (full_optstring, BASE_OPTSTRING);
strcat (full_optstring, larg);
strcat (full_optstring, targ);
strcat (full_optstring, xarg);
strcat (full_optstring, yarg);
}
optind = 0;
opterr = 1; /* re-enable error printing and arg permutation */
while ((ch = getopt_long (argc, argv, full_optstring, all_options,
&index))
!= EOF)
{
switch (ch)
{
case ':':
case '?':
scanadf_exit (1); /* error message is printed by getopt_long() */
case 'd': case 'h': case 'v': case 'V': case 'T':
case 'o': case 'S': case 's': case 'e': case 'p': case 'r':
/* previously handled options */
break;
case 'x':
window_val_user[0] = 1;
parse_vector (&window_option[0], optarg, &window_val[0], 1);
break;
case 'y':
window_val_user[1] = 1;
parse_vector (&window_option[1], optarg, &window_val[1], 1);
break;
case 'l': /* tl-x */
process_backend_option (device, window[2], optarg);
break;
case 't': /* tl-y */
process_backend_option (device, window[3], optarg);
break;
case 0:
process_backend_option (device, option_number[index], optarg);
break;
}
}
free (full_optstring);
for (index = 0; index < 2; ++index)
if (window[index])
{
SANE_Word val, pos;
if (window[index + 2])
sane_control_option (device, window[index + 2],
SANE_ACTION_GET_VALUE, &pos, 0);
val = pos + window_val[index] - 1;
set_option (device, window[index], &val);
}
if (help)
{
printf ("\nOptions specific to device `%s':\n", devname);
for (i = 0; i < num_dev_options; ++i)
{
char short_name = '\0';
int j;
opt = 0;
for (j = 0; j < 4; ++j)
if (i == window[j])
{
short_name = "xylt"[j];
if (j < 2)
opt = window_option + j;
}
if (!opt)
opt = sane_get_option_descriptor (device, i);
if (opt->type == SANE_TYPE_GROUP)
printf (" %s:\n", opt->title);
if (!SANE_OPTION_IS_SETTABLE (opt->cap))
continue;
print_option (device, i, short_name);
}
if (num_dev_options)
fputc ('\n', stdout);
}
}
if (help)
{
printf ("\
Type ``%s --help -d DEVICE'' to get list of all options for DEVICE.\n\
\n\
List of available devices:", prog_name);
if (device)
sane_close(device);
status = sane_get_devices (&device_list, SANE_FALSE);
if (status == SANE_STATUS_GOOD)
{
int column = 80;
for (i = 0; device_list[i]; ++i)
{
if (column + strlen (device_list[i]->name) + 1 >= 80)
{
printf ("\n ");
column = 4;
}
if (column > 4)
{
fputc (' ', stdout);
column += 1;
}
fputs (device_list[i]->name, stdout);
column += strlen (device_list[i]->name);
}
}
fputc ('\n', stdout);
scanadf_exit (0);
}
signal (SIGHUP, sighandler);
signal (SIGINT, sighandler);
signal (SIGPIPE, sighandler);
signal (SIGTERM, sighandler);
status = scan_docs (startNum, endNum, no_overwrite, raw,
outputFile, scanScript, use_pipe);
sane_cancel (device);
sane_close (device);
sane_exit();
if (scriptWait)
while (wait(NULL) != -1);
return (status == SANE_STATUS_GOOD) ? 0 : 1;
}