sane-project-backends/frontend/scanimage.c

2962 wiersze
76 KiB
C

/* scanimage -- command line scanning utility
Uses the SANE library.
Copyright (C) 2015 Rolf Bensch <rolf at bensch hyphen online dot de>
Copyright (C) 1996, 1997, 1998 Andreas Beck and David Mosberger
Copyright (C) 1999 - 2009 by the SANE Project -- See AUTHORS and ChangeLog
for details.
For questions and comments contact the sane-devel mailinglist (see
http://www.sane-project.org/mailing-lists.html).
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, see <https://www.gnu.org/licenses/>.
*/
#ifdef _AIX
# include "../include/lalloca.h" /* MUST come first for AIX! */
#endif
#include "../include/sane/config.h"
#include "../include/lalloca.h"
#include <assert.h>
#include "lgetopt.h"
#include <inttypes.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_LIBPNG
#include <png.h>
#endif
#ifdef HAVE_LIBJPEG
#include <jpeglib.h>
#endif
#include "../include/_stdint.h"
#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/saneopts.h"
#include "sicc.h"
#include "stiff.h"
#ifdef HAVE_LIBJPEG
#include "jpegtopdf.h"
#endif
#include "../include/md5.h"
#ifndef PATH_MAX
#define PATH_MAX 1024
#endif
typedef struct
{
uint8_t *data;
int width; /*WARNING: this is in bytes, get pixel width from param*/
int height;
int x;
int y;
int num_channels;
}
Image;
#define OPTION_FORMAT 1001
#define OPTION_MD5 1002
#define OPTION_BATCH_COUNT 1003
#define OPTION_BATCH_START_AT 1004
#define OPTION_BATCH_DOUBLE 1005
#define OPTION_BATCH_INCREMENT 1006
#define OPTION_BATCH_PROMPT 1007
#define OPTION_BATCH_PRINT 1008
#define BATCH_COUNT_UNLIMITED -1
static struct option basic_options[] = {
{"device-name", required_argument, NULL, 'd'},
{"list-devices", no_argument, NULL, 'L'},
{"formatted-device-list", required_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"verbose", no_argument, NULL, 'v'},
{"progress", no_argument, NULL, 'p'},
{"output-file", required_argument, NULL, 'o'},
{"test", no_argument, NULL, 'T'},
{"all-options", no_argument, NULL, 'A'},
{"version", no_argument, NULL, 'V'},
{"buffer-size", optional_argument, NULL, 'B'},
{"batch", optional_argument, NULL, 'b'},
{"batch-count", required_argument, NULL, OPTION_BATCH_COUNT},
{"batch-start", required_argument, NULL, OPTION_BATCH_START_AT},
{"batch-double", no_argument, NULL, OPTION_BATCH_DOUBLE},
{"batch-increment", required_argument, NULL, OPTION_BATCH_INCREMENT},
{"batch-print", no_argument, NULL, OPTION_BATCH_PRINT},
{"batch-prompt", no_argument, NULL, OPTION_BATCH_PROMPT},
{"format", required_argument, NULL, OPTION_FORMAT},
{"accept-md5-only", no_argument, NULL, OPTION_MD5},
{"icc-profile", required_argument, NULL, 'i'},
{"dont-scan", no_argument, NULL, 'n'},
{0, 0, NULL, 0}
};
#define OUTPUT_UNKNOWN 0
#define OUTPUT_PNM 1
#define OUTPUT_TIFF 2
#define OUTPUT_PNG 3
#define OUTPUT_JPEG 4
#define OUTPUT_PDF 5
#define BASE_OPTSTRING "d:hi:Lf:o:B::nvVTAbp"
#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 progress = 0;
static const char* output_file = NULL;
static int test;
static int all;
static int output_format = OUTPUT_UNKNOWN;
static int help;
static int dont_scan = 0;
static const char *prog_name;
static int resolution_optind = -1, resolution_value = 0;
/* window (area) related options */
static SANE_Option_Descriptor window_option[4]; /*updated descs for x,y,l,t*/
static int window[4]; /*index into backend options for x,y,l,t*/
static SANE_Word window_val[2]; /*the value for x,y options*/
static int window_val_user[2]; /* is x,y user-specified? */
static int accept_only_md5_auth = 0;
static const char *icc_profile = NULL;
static void fetch_options (SANE_Device * device);
static void scanimage_exit (int);
static SANE_Word tl_x = 0;
static SANE_Word tl_y = 0;
static SANE_Word br_x = 0;
static SANE_Word br_y = 0;
static SANE_Byte *buffer;
static size_t buffer_size;
static void
auth_callback (SANE_String_Const resource,
SANE_Char * username, SANE_Char * password)
{
char tmp[3 + 128 + SANE_MAX_USERNAME_LEN + SANE_MAX_PASSWORD_LEN], *wipe;
unsigned char md5digest[16];
int md5mode = 0, len, query_user = 1;
FILE *pass_file;
struct stat stat_buf;
char * uname = NULL;
*tmp = 0;
if (getenv ("HOME") != NULL)
{
if (strlen (getenv ("HOME")) < 500)
{
sprintf (tmp, "%s/.sane/pass", getenv ("HOME"));
}
}
if ((strlen (tmp) > 0) && (stat (tmp, &stat_buf) == 0))
{
if ((stat_buf.st_mode & 63) != 0)
{
fprintf (stderr, "%s has wrong permissions (use at least 0600)\n",
tmp);
}
else
{
if ((pass_file = fopen (tmp, "r")) != NULL)
{
if (strstr (resource, "$MD5$") != NULL)
len = (strstr (resource, "$MD5$") - resource);
else
len = strlen (resource);
while (fgets (tmp, sizeof(tmp), pass_file))
{
if ((strlen (tmp) > 0) && (tmp[strlen (tmp) - 1] == '\n'))
tmp[strlen (tmp) - 1] = 0;
if ((strlen (tmp) > 0) && (tmp[strlen (tmp) - 1] == '\r'))
tmp[strlen (tmp) - 1] = 0;
char *colon1 = strchr (tmp, ':');
if (colon1 != NULL)
{
char *tmp_username = tmp;
*colon1 = '\0';
char *colon2 = strchr (colon1 + 1, ':');
if (colon2 != NULL)
{
char *tmp_password = colon1 + 1;
*colon2 = '\0';
if ((strncmp (colon2 + 1, resource, len) == 0)
&& ((int) strlen (colon2 + 1) == len))
{
if ((strlen (tmp_username) < SANE_MAX_USERNAME_LEN) &&
(strlen (tmp_password) < SANE_MAX_PASSWORD_LEN))
{
strncpy (username, tmp_username, SANE_MAX_USERNAME_LEN);
strncpy (password, tmp_password, SANE_MAX_PASSWORD_LEN);
query_user = 0;
break;
}
}
}
}
}
fclose (pass_file);
}
}
}
if (strstr (resource, "$MD5$") != NULL)
{
md5mode = 1;
len = (strstr (resource, "$MD5$") - resource);
if (query_user == 1)
fprintf (stderr, "Authentication required for resource %*.*s. "
"Enter username: ", len, len, resource);
}
else
{
if (accept_only_md5_auth != 0)
{
fprintf (stderr, "ERROR: backend requested plain-text password\n");
return;
}
else
{
fprintf (stderr,
"WARNING: backend requested plain-text password\n");
query_user = 1;
}
if (query_user == 1)
fprintf (stderr,
"Authentication required for resource %s. Enter username: ",
resource);
}
if (query_user == 1)
uname = fgets (username, SANE_MAX_USERNAME_LEN, stdin);
if (uname != NULL && (strlen (username)) && (username[strlen (username) - 1] == '\n'))
username[strlen (username) - 1] = 0;
if (query_user == 1)
{
#ifdef HAVE_GETPASS
strcpy (password, (wipe = getpass ("Enter password: ")));
memset (wipe, 0, strlen (password));
#else
printf("OS has no getpass(). User Queries will not work\n");
#endif
}
if (md5mode)
{
sprintf (tmp, "%.128s%.*s", (strstr (resource, "$MD5$")) + 5,
SANE_MAX_PASSWORD_LEN - 1, password);
md5_buffer (tmp, strlen (tmp), md5digest);
memset (password, 0, SANE_MAX_PASSWORD_LEN);
sprintf (password, "$MD5$%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x",
md5digest[0], md5digest[1],
md5digest[2], md5digest[3],
md5digest[4], md5digest[5],
md5digest[6], md5digest[7],
md5digest[8], md5digest[9],
md5digest[10], md5digest[11],
md5digest[12], md5digest[13], md5digest[14], md5digest[15]);
}
}
static void
sighandler (int signum)
{
static SANE_Bool first_time = SANE_TRUE;
if (device)
{
fprintf (stderr, "%s: received signal %d\n", prog_name, signum);
if (first_time)
{
first_time = SANE_FALSE;
fprintf (stderr, "%s: trying to stop scanner\n", prog_name);
sane_cancel (device);
}
else
{
fprintf (stderr, "%s: aborting\n", prog_name);
_exit (0);
}
}
}
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, const SANE_Option_Descriptor *opt)
{
const char *str, *last_break, *start;
SANE_Bool not_first = SANE_FALSE;
int i, column;
if (opt->type == SANE_TYPE_GROUP){
printf (" %s:\n", opt->title);
return;
}
/* if both of these are set, option is invalid */
if((opt->cap & SANE_CAP_SOFT_SELECT) && (opt->cap & SANE_CAP_HARD_SELECT)){
fprintf (stderr, "%s: invalid option caps, SS+HS\n", prog_name);
return;
}
/* invalid to select but not detect */
if((opt->cap & SANE_CAP_SOFT_SELECT) && !(opt->cap & SANE_CAP_SOFT_DETECT)){
fprintf (stderr, "%s: invalid option caps, SS!SD\n", prog_name);
return;
}
/* standard allows this, though it makes little sense
if(opt->cap & SANE_CAP_HARD_SELECT && !(opt->cap & SANE_CAP_SOFT_DETECT)){
fprintf (stderr, "%s: invalid option caps, HS!SD\n", prog_name);
return;
}*/
/* if one of these three is not set, option is useless, skip it */
if(!(opt->cap &
(SANE_CAP_SOFT_SELECT | SANE_CAP_HARD_SELECT | SANE_CAP_SOFT_DETECT)
)){
return;
}
/* print the option */
if ( !strcmp (opt->name, "x")
|| !strcmp (opt->name, "y")
|| !strcmp (opt->name, "t")
|| !strcmp (opt->name, "l"))
printf (" -%s", opt->name);
else
printf (" --%s", opt->name);
/* print the option choices */
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 > (SANE_Int) sizeof (SANE_Word))
fputs (",...", stdout);
break;
case SANE_CONSTRAINT_RANGE:
// Check for no range - some buggy backends can miss this out.
if (!opt->constraint.range)
{
fputs ("{no_range}", stdout);
}
else
{
if (opt->type == SANE_TYPE_INT)
{
if (!strcmp (opt->name, "x"))
{
printf ("%d..%d", opt->constraint.range->min,
opt->constraint.range->max - tl_x);
}
else if (!strcmp (opt->name, "y"))
{
printf ("%d..%d", opt->constraint.range->min,
opt->constraint.range->max - tl_y);
}
else
{
printf ("%d..%d", opt->constraint.range->min,
opt->constraint.range->max);
}
print_unit (opt->unit);
if (opt->size > (SANE_Int) sizeof(SANE_Word))
fputs (",...", stdout);
if (opt->constraint.range->quant)
printf (" (in steps of %d)", opt->constraint.range->quant);
}
else
{
if (!strcmp (opt->name, "x"))
{
printf ("%g..%g", SANE_UNFIX(opt->constraint.range->min),
SANE_UNFIX(opt->constraint.range->max - tl_x));
}
else if (!strcmp (opt->name, "y"))
{
printf ("%g..%g", SANE_UNFIX(opt->constraint.range->min),
SANE_UNFIX(opt->constraint.range->max - tl_y));
}
else
{
printf ("%g..%g", SANE_UNFIX(opt->constraint.range->min),
SANE_UNFIX(opt->constraint.range->max));
}
print_unit (opt->unit);
if (opt->size > (SANE_Int) 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:
// Check no words in list or no list - - some buggy backends can miss this out.
// Note the check on < 1 as SANE_Int is signed.
if (!opt->constraint.word_list || (opt->constraint.word_list[0] < 1))
{
fputs ("{no_wordlist}", stdout);
}
else
{
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 > (SANE_Int) sizeof (SANE_Word))
fputs (",...", stdout);
break;
case SANE_CONSTRAINT_STRING_LIST:
// Check for missing strings - some buggy backends can miss this out.
if (!opt->constraint.string_list || !opt->constraint.string_list[0])
{
fputs ("{no_stringlist}", stdout);
}
else
{
for (i = 0; opt->constraint.string_list[i]; ++i)
{
if (i > 0)
fputc ('|', stdout);
fputs (opt->constraint.string_list[i], stdout);
}
}
break;
}
}
/* print current option value */
if (opt->type == SANE_TYPE_STRING || opt->size == sizeof (SANE_Word))
{
if (SANE_OPTION_IS_ACTIVE (opt->cap))
{
void *val = alloca (opt->size);
sane_control_option (device, opt_num, SANE_ACTION_GET_VALUE, val,
0);
fputs (" [", stdout);
switch (opt->type)
{
case SANE_TYPE_BOOL:
fputs (*(SANE_Bool *) val ? "yes" : "no", stdout);
break;
case SANE_TYPE_INT:
if (strcmp (opt->name, "l") == 0)
{
tl_x = (*(SANE_Fixed *) val);
printf ("%d", tl_x);
}
else if (strcmp (opt->name, "t") == 0)
{
tl_y = (*(SANE_Fixed *) val);
printf ("%d", tl_y);
}
else if (strcmp (opt->name, "x") == 0)
{
br_x = (*(SANE_Fixed *) val);
printf ("%d", br_x - tl_x);
}
else if (strcmp (opt->name, "y") == 0)
{
br_y = (*(SANE_Fixed *) val);
printf ("%d", br_y - tl_y);
}
else
printf ("%d", *(SANE_Int *) val);
break;
case SANE_TYPE_FIXED:
if (strcmp (opt->name, "l") == 0)
{
tl_x = (*(SANE_Fixed *) val);
printf ("%g", SANE_UNFIX (tl_x));
}
else if (strcmp (opt->name, "t") == 0)
{
tl_y = (*(SANE_Fixed *) val);
printf ("%g", SANE_UNFIX (tl_y));
}
else if (strcmp (opt->name, "x") == 0)
{
br_x = (*(SANE_Fixed *) val);
printf ("%g", SANE_UNFIX (br_x - tl_x));
}
else if (strcmp (opt->name, "y") == 0)
{
br_y = (*(SANE_Fixed *) val);
printf ("%g", SANE_UNFIX (br_y - tl_y));
}
else
printf ("%g", SANE_UNFIX (*(SANE_Fixed *) val));
break;
case SANE_TYPE_STRING:
fputs ((char *) val, stdout);
break;
default:
break;
}
fputc (']', stdout);
}
}
if (!SANE_OPTION_IS_ACTIVE (opt->cap))
fputs (" [inactive]", stdout);
else if(opt->cap & SANE_CAP_HARD_SELECT)
fputs (" [hardware]", stdout);
else if(!(opt->cap & SANE_CAP_SOFT_SELECT) && (opt->cap & SANE_CAP_SOFT_DETECT))
fputs (" [read-only]", stdout);
else if (opt->cap & SANE_CAP_ADVANCED)
fputs (" [advanced]", stdout);
fputs ("\n ", stdout);
column = 8;
last_break = 0;
start = opt->desc;
for (str = opt->desc; *str; ++str)
{
++column;
if (*str == ' ')
last_break = str;
else if (*str == '\n'){
column=80;
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);
scanimage_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;
}
if(v < 0){
*value = v - 0.5;
}
else{
*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);
scanimage_exit (1);
}
str = end + 1;
}
else
++index;
if (index < 0 || index >= (int) vector_length)
{
fprintf (stderr,
"%s: option --%s: index %d out of range [0..%ld]\n",
prog_name, opt->name, index, (long) vector_length - 1);
scanimage_exit (1);
}
/* read value */
str = parse_scalar (opt, str, &value);
if (!str)
scanimage_exit (1);
if (*str && *str != '-' && *str != ',')
{
fprintf (stderr,
"%s: option --%s: illegal separator (rest of option: %s)\n",
prog_name, opt->name, str);
scanimage_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 < (int) 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);
}
}
static void
fetch_options (SANE_Device * device)
{
const SANE_Option_Descriptor *opt;
SANE_Int num_dev_options;
int i, option_count;
SANE_Status status;
opt = sane_get_option_descriptor (device, 0);
if (opt == NULL)
{
fprintf (stderr, "Could not get option descriptor for option 0\n");
scanimage_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));
scanimage_exit (1);
}
/* build the full table of long options */
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);
scanimage_exit (1);
}
/* create command line option only for non-group options */
if (opt->type == SANE_TYPE_GROUP)
continue;
option_number[option_count] = i;
all_options[option_count].name = (const 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;
/* Look for scan resolution */
if ((opt->type == SANE_TYPE_FIXED || opt->type == SANE_TYPE_INT)
&& opt->size == sizeof (SANE_Int)
&& (opt->unit == SANE_UNIT_DPI)
&& (strcmp (opt->name, SANE_NAME_SCAN_RESOLUTION) == 0))
resolution_optind = 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_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 scan-area.";
window_option[0].name = "x";
}
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 scan-area.";
window_option[1].name = "y";
}
else if (strcmp (opt->name, SANE_NAME_SCAN_TL_X) == 0)
{
window[2] = i;
all_options[option_count].val = 'l';
window_option[2] = *opt;
window_option[2].name = "l";
}
else if (strcmp (opt->name, SANE_NAME_SCAN_TL_Y) == 0)
{
window[3] = i;
all_options[option_count].val = 't';
window_option[3] = *opt;
window_option[3].name = "t";
}
}
++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_val_user[i])
{
sane_control_option (device, window[i],
SANE_ACTION_GET_VALUE, &window_val[i], 0);
if (window[i + 2]){
SANE_Word pos;
sane_control_option (device, window[i + 2],
SANE_ACTION_GET_VALUE, &pos, 0);
window_val[i] -= pos;
}
}
}
}
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 = 0;
opt = sane_get_option_descriptor (device, optnum);
if (!opt)
{
if (verbose > 0)
fprintf (stderr, "%s: ignored request to set invalid option %d\n",
prog_name, optnum);
return;
}
if (!SANE_OPTION_IS_ACTIVE (opt->cap))
{
if (verbose > 0)
fprintf (stderr, "%s: ignored request to set inactive option %s\n",
prog_name, opt->name);
return;
}
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));
scanimage_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_SETTABLE (opt->cap))
{
fprintf (stderr, "%s: attempted to set readonly option %s\n",
prog_name, opt->name);
scanimage_exit (1);
}
if (!SANE_OPTION_IS_ACTIVE (opt->cap))
{
fprintf (stderr, "%s: attempted to set inactive option %s\n",
prog_name, opt->name);
scanimage_exit (1);
}
if ((opt->cap & SANE_CAP_AUTOMATIC) && optarg &&
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));
scanimage_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);
scanimage_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);
scanimage_exit (1);
}
}
parse_vector (opt, optarg, vector, vector_length);
valuep = vector;
break;
case SANE_TYPE_STRING:
valuep = malloc (opt->size);
if (!valuep)
{
fprintf (stderr, "%s: out of memory\n", prog_name);
scanimage_exit (1);
}
strncpy (valuep, optarg, opt->size);
((char *) valuep)[opt->size - 1] = 0;
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);
if (opt->type == SANE_TYPE_STRING && valuep)
free(valuep);
}
static void
write_pnm_header (SANE_Frame format, int width, int height, int depth, FILE *ofp)
{
/* The netpbm-package does not define raw image data with maxval > 255. */
/* But writing maxval 65535 for 16bit data gives at least a chance */
/* to read the image. */
switch (format)
{
case SANE_FRAME_RED:
case SANE_FRAME_GREEN:
case SANE_FRAME_BLUE:
case SANE_FRAME_RGB:
fprintf (ofp, "P6\n# SANE data follows\n%d %d\n%d\n", width, height,
(depth <= 8) ? 255 : 65535);
break;
default:
if (depth == 1)
fprintf (ofp, "P4\n# SANE data follows\n%d %d\n", width, height);
else
fprintf (ofp, "P5\n# SANE data follows\n%d %d\n%d\n", width, height,
(depth <= 8) ? 255 : 65535);
break;
}
#ifdef __EMX__ /* OS2 - write in binary mode. */
_fsetmode (ofp, "b");
#endif
}
#ifdef HAVE_LIBPNG
static void
write_png_header (SANE_Frame format, int width, int height, int depth, int dpi, const char * icc_profile, FILE *ofp, png_structp* png_ptr, png_infop* info_ptr)
{
int color_type;
/* PNG does not have imperial reference units, so we must convert to metric. */
/* There are nominally 39.3700787401575 inches in a meter. */
const double pixels_per_meter = dpi * 39.3700787401575;
size_t icc_size = 0;
void *icc_buffer;
*png_ptr = png_create_write_struct
(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!*png_ptr) {
fprintf(stderr, "png_create_write_struct failed\n");
exit(1);
}
*info_ptr = png_create_info_struct(*png_ptr);
if (!*info_ptr) {
fprintf(stderr, "png_create_info_struct failed\n");
exit(1);
}
png_init_io(*png_ptr, ofp);
switch (format)
{
case SANE_FRAME_RED:
case SANE_FRAME_GREEN:
case SANE_FRAME_BLUE:
case SANE_FRAME_RGB:
color_type = PNG_COLOR_TYPE_RGB;
break;
default:
color_type = PNG_COLOR_TYPE_GRAY;
break;
}
png_set_IHDR(*png_ptr, *info_ptr, width, height,
depth, color_type, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_set_pHYs(*png_ptr, *info_ptr,
pixels_per_meter, pixels_per_meter,
PNG_RESOLUTION_METER);
if (icc_profile)
{
icc_buffer = sanei_load_icc_profile(icc_profile, &icc_size);
if (icc_size > 0)
{
/* libpng will abort if the profile and image colour spaces do not match*/
/* The data colour space field is at bytes 16 to 20 in an ICC profile */
/* see: ICC.1:2010 § 7.2.6 */
int is_gray_profile = strncmp((char *) icc_buffer + 16, "GRAY", 4) == 0;
int is_rgb_profile = strncmp((char *) icc_buffer + 16, "RGB ", 4) == 0;
if ((is_gray_profile && color_type == PNG_COLOR_TYPE_GRAY) ||
(is_rgb_profile && color_type == PNG_COLOR_TYPE_RGB))
{
png_set_iCCP(*png_ptr, *info_ptr, basename(icc_profile), PNG_COMPRESSION_TYPE_BASE, icc_buffer, icc_size);
}
else
{
if (is_gray_profile)
{
fprintf(stderr, "Ignoring 'GRAY' space ICC profile because the image is RGB.\n");
}
if (is_rgb_profile)
{
fprintf(stderr, "Ignoring 'RGB ' space ICC profile because the image is Grayscale.\n");
}
}
free(icc_buffer);
}
}
png_write_info(*png_ptr, *info_ptr);
}
#endif
#ifdef HAVE_LIBJPEG
static void
write_jpeg_header (SANE_Frame format, int width, int height, int dpi, FILE *ofp,
struct jpeg_compress_struct *cinfo,
struct jpeg_error_mgr *jerr)
{
cinfo->err = jpeg_std_error(jerr);
jpeg_create_compress(cinfo);
jpeg_stdio_dest(cinfo, ofp);
cinfo->image_width = width;
cinfo->image_height = height;
switch (format)
{
case SANE_FRAME_RED:
case SANE_FRAME_GREEN:
case SANE_FRAME_BLUE:
case SANE_FRAME_RGB:
cinfo->in_color_space = JCS_RGB;
cinfo->input_components = 3;
break;
default:
cinfo->in_color_space = JCS_GRAYSCALE;
cinfo->input_components = 1;
break;
}
jpeg_set_defaults(cinfo);
/* jpeg_set_defaults overrides density, be careful. */
cinfo->density_unit = 1; /* Inches */
cinfo->X_density = cinfo->Y_density = dpi;
cinfo->write_JFIF_header = TRUE;
jpeg_set_quality(cinfo, 75, TRUE);
jpeg_start_compress(cinfo, TRUE);
}
#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->num_channels;
image->height += STRIP_HEIGHT;
new_size = image->height * image->width * image->num_channels;
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_Status
scan_it (FILE *ofp, void* pw)
{
int i, len, first_frame = 1, offset = 0, must_buffer = 0;
uint64_t hundred_percent = 0;
SANE_Byte min = 0xff, max = 0;
SANE_Parameters parm;
SANE_Status status;
Image image = { 0, 0, 0, 0, 0, 0 };
static const char *format_name[] = {
"gray", "RGB", "red", "green", "blue"
};
uint64_t total_bytes = 0, expected_bytes;
SANE_Int hang_over = -1;
#ifdef HAVE_LIBPNG
int pngrow = 0;
png_bytep pngbuf = NULL;
png_structp png_ptr;
png_infop info_ptr;
#endif
#ifdef HAVE_LIBJPEG
int jpegrow = 0;
JSAMPLE *jpegbuf = NULL;
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
#endif
(void)pw;
do
{
if (!first_frame)
{
#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)
{
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 (verbose)
{
if (first_frame)
{
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,
parm.depth * (SANE_FRAME_RGB == parm.format ? 3 : 1));
else
fprintf (stderr, "%s: scanning image %d pixels wide and "
"variable height at %d bits/pixel\n",
prog_name, parm.pixels_per_line,
parm.depth * (SANE_FRAME_RGB == parm.format ? 3 : 1));
}
fprintf (stderr, "%s: acquiring %s frame\n", prog_name,
parm.format <= SANE_FRAME_BLUE ? format_name[parm.format]:"Unknown");
}
if (first_frame)
{
image.num_channels = 1;
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;
image.num_channels = 3;
break;
case SANE_FRAME_RGB:
assert ((parm.depth == 8) || (parm.depth == 16));
case SANE_FRAME_GRAY:
assert ((parm.depth == 1) || (parm.depth == 8)
|| (parm.depth == 16));
if (parm.lines < 0)
{
must_buffer = 1;
offset = 0;
}
else
switch(output_format)
{
case OUTPUT_TIFF:
sanei_write_tiff_header (parm.format,
parm.pixels_per_line, parm.lines,
parm.depth, resolution_value,
icc_profile, ofp);
break;
case OUTPUT_PNM:
write_pnm_header (parm.format, parm.pixels_per_line,
parm.lines, parm.depth, ofp);
break;
#ifdef HAVE_LIBPNG
case OUTPUT_PNG:
write_png_header (parm.format, parm.pixels_per_line,
parm.lines, parm.depth, resolution_value,
icc_profile, ofp, &png_ptr, &info_ptr);
break;
#endif
#ifdef HAVE_LIBJPEG
case OUTPUT_PDF:
sane_pdf_start_page ( pw, parm.pixels_per_line, parm.lines,
resolution_value, SANE_PDF_IMAGE_COLOR,
SANE_PDF_ROTATE_OFF);
write_jpeg_header (parm.format, parm.pixels_per_line,
parm.lines, resolution_value,
ofp, &cinfo, &jerr);
break;
case OUTPUT_JPEG:
write_jpeg_header (parm.format, parm.pixels_per_line,
parm.lines, resolution_value,
ofp, &cinfo, &jerr);
break;
#endif
}
break;
default:
break;
}
#ifdef HAVE_LIBPNG
if(output_format == OUTPUT_PNG)
pngbuf = malloc(parm.bytes_per_line);
#endif
#ifdef HAVE_LIBJPEG
if(output_format == OUTPUT_JPEG || output_format == OUTPUT_PDF)
jpegbuf = malloc(parm.bytes_per_line);
#endif
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.bytes_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.x = image.width - 1;
image.y = -1;
if (!advance (&image))
{
status = SANE_STATUS_NO_MEM;
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;
}
hundred_percent = ((uint64_t)parm.bytes_per_line) * parm.lines
* ((parm.format == SANE_FRAME_RGB || parm.format == SANE_FRAME_GRAY) ? 1:3);
while (1)
{
double progr;
status = sane_read (device, buffer, buffer_size, &len);
total_bytes += (SANE_Word) len;
progr = ((total_bytes * 100.) / (double) hundred_percent);
if (progr > 100.)
progr = 100.;
if (progress)
{
if (parm.lines >= 0)
fprintf(stderr, "Progress: %3.1f%%\r", progr);
else
fprintf(stderr, "Progress: (unknown)\r");
}
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));
return status;
}
break;
}
if (must_buffer)
{
switch (parm.format)
{
case SANE_FRAME_RED:
case SANE_FRAME_GREEN:
case SANE_FRAME_BLUE:
image.num_channels = 3;
for (i = 0; i < len; ++i)
{
image.data[offset + 3 * i] = buffer[i];
if (!advance (&image))
{
status = SANE_STATUS_NO_MEM;
goto cleanup;
}
}
offset += 3 * len;
break;
case SANE_FRAME_RGB:
image.num_channels = 1;
for (i = 0; i < len; ++i)
{
image.data[offset + i] = buffer[i];
if (!advance (&image))
{
status = SANE_STATUS_NO_MEM;
goto cleanup;
}
}
offset += len;
break;
case SANE_FRAME_GRAY:
image.num_channels = 1;
for (i = 0; i < len; ++i)
{
image.data[offset + i] = buffer[i];
if (!advance (&image))
{
status = SANE_STATUS_NO_MEM;
goto cleanup;
}
}
offset += len;
break;
default:
break;
}
}
else /* ! must_buffer */
{
#ifdef HAVE_LIBPNG
if (output_format == OUTPUT_PNG)
{
int i = 0;
int left = len;
while(pngrow + left >= parm.bytes_per_line)
{
memcpy(pngbuf + pngrow, buffer + i, parm.bytes_per_line - pngrow);
if(parm.depth == 1)
{
int j;
for(j = 0; j < parm.bytes_per_line; j++)
pngbuf[j] = ~pngbuf[j];
}
#ifndef WORDS_BIGENDIAN
/* SANE is endian-native, PNG is big-endian, */
/* see: https://www.w3.org/TR/2003/REC-PNG-20031110/#7Integers-and-byte-order */
if (parm.depth == 16)
{
int j;
for (j = 0; j < parm.bytes_per_line; j += 2)
{
SANE_Byte LSB;
LSB = pngbuf[j];
pngbuf[j] = pngbuf[j + 1];
pngbuf[j + 1] = LSB;
}
}
#endif
png_write_row(png_ptr, pngbuf);
i += parm.bytes_per_line - pngrow;
left -= parm.bytes_per_line - pngrow;
pngrow = 0;
}
memcpy(pngbuf + pngrow, buffer + i, left);
pngrow += left;
}
else
#endif
#ifdef HAVE_LIBJPEG
if (output_format == OUTPUT_JPEG || output_format == OUTPUT_PDF)
{
int i = 0;
int left = len;
while(jpegrow + left >= parm.bytes_per_line)
{
memcpy(jpegbuf + jpegrow, buffer + i, parm.bytes_per_line - jpegrow);
if(parm.depth == 1)
{
int col1, col8;
JSAMPLE *buf8 = malloc(parm.bytes_per_line * 8);
for(col1 = 0; col1 < parm.bytes_per_line; col1++)
for(col8 = 0; col8 < 8; col8++)
buf8[col1 * 8 + col8] = jpegbuf[col1] & (1 << (8 - col8 - 1)) ? 0 : 0xff;
jpeg_write_scanlines(&cinfo, &buf8, 1);
free(buf8);
} else {
jpeg_write_scanlines(&cinfo, &jpegbuf, 1);
}
i += parm.bytes_per_line - jpegrow;
left -= parm.bytes_per_line - jpegrow;
jpegrow = 0;
}
memcpy(jpegbuf + jpegrow, buffer + i, left);
jpegrow += left;
}
else
#endif
if ((output_format == OUTPUT_TIFF) || (parm.depth != 16))
fwrite (buffer, 1, len, ofp);
else
{
#if !defined(WORDS_BIGENDIAN)
int i, start = 0;
/* check if we have saved one byte from the last sane_read */
if (hang_over > -1)
{
if (len > 0)
{
fwrite (buffer, 1, 1, ofp);
buffer[0] = (SANE_Byte) hang_over;
hang_over = -1;
start = 1;
}
}
/* now do the byte-swapping */
for (i = start; i < (len - 1); i += 2)
{
unsigned char LSB;
LSB = buffer[i];
buffer[i] = buffer[i + 1];
buffer[i + 1] = LSB;
}
/* check if we have an odd number of bytes */
if (((len - start) % 2) != 0)
{
hang_over = buffer[len - 1];
len--;
}
#endif
fwrite (buffer, 1, len, ofp);
}
}
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;
switch(output_format) {
case OUTPUT_TIFF:
sanei_write_tiff_header (parm.format, parm.pixels_per_line,
image.height, parm.depth, resolution_value,
icc_profile, ofp);
break;
case OUTPUT_PNM:
write_pnm_header (parm.format, parm.pixels_per_line,
image.height, parm.depth, ofp);
break;
#ifdef HAVE_LIBPNG
case OUTPUT_PNG:
write_png_header (parm.format, parm.pixels_per_line,
image.height, parm.depth, resolution_value,
icc_profile, ofp, &png_ptr, &info_ptr);
break;
#endif
#ifdef HAVE_LIBJPEG
case OUTPUT_PDF:
sane_pdf_start_page ( pw, parm.pixels_per_line, parm.lines,
resolution_value, SANE_PDF_IMAGE_COLOR,
SANE_PDF_ROTATE_OFF);
write_jpeg_header (parm.format, parm.pixels_per_line,
parm.lines, resolution_value,
ofp, &cinfo, &jerr);
break;
case OUTPUT_JPEG:
write_jpeg_header (parm.format, parm.pixels_per_line,
parm.lines, resolution_value,
ofp, &cinfo, &jerr);
break;
#endif
}
#if !defined(WORDS_BIGENDIAN)
/* multibyte pnm file may need byte swap to LE */
/* FIXME: other bit depths? */
if (output_format != OUTPUT_TIFF && parm.depth == 16)
{
int i;
for (i = 0; i < image.height * image.width; i += 2)
{
unsigned char LSB;
LSB = image.data[i];
image.data[i] = image.data[i + 1];
image.data[i + 1] = LSB;
}
}
#endif
fwrite (image.data, 1, image.height * image.width * image.num_channels, ofp);
}
#ifdef HAVE_LIBPNG
if(output_format == OUTPUT_PNG)
png_write_end(png_ptr, info_ptr);
#endif
#ifdef HAVE_LIBJPEG
if(output_format == OUTPUT_JPEG || output_format == OUTPUT_PDF)
jpeg_finish_compress(&cinfo);
#endif
/* flush the output buffer */
fflush( ofp );
cleanup:
#ifdef HAVE_LIBPNG
if(output_format == OUTPUT_PNG) {
png_destroy_write_struct(&png_ptr, &info_ptr);
free(pngbuf);
}
#endif
#ifdef HAVE_LIBJPEG
if(output_format == OUTPUT_JPEG || output_format == OUTPUT_PDF) {
jpeg_destroy_compress(&cinfo);
free(jpegbuf);
}
#endif
if (image.data)
free (image.data);
expected_bytes = ((uint64_t)parm.bytes_per_line) * parm.lines *
((parm.format == SANE_FRAME_RGB
|| parm.format == SANE_FRAME_GRAY) ? 1 : 3);
if (parm.lines < 0)
expected_bytes = 0;
if (total_bytes > expected_bytes && expected_bytes != 0)
{
fprintf (stderr,
"%s: WARNING: read more data than announced by backend "
"(%" PRIu64 "/%" PRIu64 ")\n", prog_name, total_bytes, expected_bytes);
}
else if (verbose)
fprintf (stderr, "%s: read %" PRIu64 " bytes in total\n", prog_name, total_bytes);
return status;
}
#define clean_buffer(buf,size) memset ((buf), 0x23, size)
static void
pass_fail (int max, int len, SANE_Byte * buffer, SANE_Status status)
{
if (status != SANE_STATUS_GOOD)
fprintf (stderr, "FAIL Error: %s\n", sane_strstatus (status));
else if (buffer[len] != 0x23)
{
while (len <= max && buffer[len] != 0x23)
++len;
fprintf (stderr, "FAIL Cheat: %d bytes\n", len);
}
else if (len > max)
fprintf (stderr, "FAIL Overflow: %d bytes\n", len);
else if (len == 0)
fprintf (stderr, "FAIL No data\n");
else
fprintf (stderr, "PASS\n");
}
static SANE_Status
test_it (void)
{
int i, len;
SANE_Parameters parm;
SANE_Status status;
Image image = { 0, 0, 0, 0, 0, 0 };
static const char *format_name[] =
{ "gray", "RGB", "red", "green", "blue" };
#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)
{
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 (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,
parm.depth * (SANE_FRAME_RGB == parm.format ? 3 : 1));
else
fprintf (stderr, "%s: scanning image %d pixels wide and "
"variable height at %d bits/pixel\n",
prog_name, parm.pixels_per_line,
parm.depth * (SANE_FRAME_RGB == parm.format ? 3 : 1));
fprintf (stderr, "%s: acquiring %s frame, %d bits/sample\n", prog_name,
parm.format <= SANE_FRAME_BLUE ? format_name[parm.format]:"Unknown",
parm.depth);
image.data = malloc (parm.bytes_per_line * 2);
clean_buffer (image.data, parm.bytes_per_line * 2);
fprintf (stderr, "%s: reading one scanline, %d bytes...\t", prog_name,
parm.bytes_per_line);
status = sane_read (device, image.data, parm.bytes_per_line, &len);
pass_fail (parm.bytes_per_line, len, image.data, status);
if (status != SANE_STATUS_GOOD)
goto cleanup;
clean_buffer (image.data, parm.bytes_per_line * 2);
fprintf (stderr, "%s: reading one byte...\t\t", prog_name);
status = sane_read (device, image.data, 1, &len);
pass_fail (1, len, image.data, status);
if (status != SANE_STATUS_GOOD)
goto cleanup;
for (i = 2; i < parm.bytes_per_line * 2; i *= 2)
{
clean_buffer (image.data, parm.bytes_per_line * 2);
fprintf (stderr, "%s: stepped read, %d bytes... \t", prog_name, i);
status = sane_read (device, image.data, i, &len);
pass_fail (i, len, image.data, status);
if (status != SANE_STATUS_GOOD)
goto cleanup;
}
for (i /= 2; i > 2; i /= 2)
{
clean_buffer (image.data, parm.bytes_per_line * 2);
fprintf (stderr, "%s: stepped read, %d bytes... \t", prog_name, i - 1);
status = sane_read (device, image.data, i - 1, &len);
pass_fail (i - 1, len, image.data, status);
if (status != SANE_STATUS_GOOD)
goto cleanup;
}
cleanup:
sane_cancel (device);
if (image.data)
free (image.data);
return status;
}
static int
get_resolution (void)
{
const SANE_Option_Descriptor *resopt;
int resol = 0;
void *val;
if (resolution_optind < 0)
return 0;
resopt = sane_get_option_descriptor (device, resolution_optind);
if (!resopt)
return 0;
val = alloca (resopt->size);
if (!val)
return 0;
sane_control_option (device, resolution_optind, SANE_ACTION_GET_VALUE, val,
0);
if (resopt->type == SANE_TYPE_INT)
resol = *(SANE_Int *) val;
else
resol = (int) (SANE_UNFIX (*(SANE_Fixed *) val) + 0.5);
return resol;
}
static void
scanimage_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);
}
/** @brief print device options to stdout
*
* @param device struct of the opened device to describe
* @param num_dev_options number of device options
* @param ro SANE_TRUE to print read-only options
*/
static void print_options(SANE_Device * device, SANE_Int num_dev_options, SANE_Bool ro)
{
int i, j;
const SANE_Option_Descriptor *opt;
for (i = 1; i < num_dev_options; ++i)
{
opt = 0;
/* scan area uses modified option struct */
for (j = 0; j < 4; ++j)
if (i == window[j])
opt = window_option + j;
if (!opt)
opt = sane_get_option_descriptor (device, i);
if (ro || SANE_OPTION_IS_SETTABLE (opt->cap)
|| opt->type == SANE_TYPE_GROUP)
print_option (device, i, opt);
}
if (num_dev_options)
fputc ('\n', stdout);
}
static int guess_output_format(const char* output_file)
{
if (output_file == NULL)
{
fprintf(stderr, "Output format is not set, using pnm as a default.\n");
return OUTPUT_PNM;
}
// if the user passes us a path with a known extension then he won't be surprised if we figure
// out correct --format option. No warning is necessary in that case.
const char* extension = strrchr(output_file, '.');
if (extension != NULL)
{
struct {
const char* extension;
int output_format;
} formats[] = {
{ ".pnm", OUTPUT_PNM },
{ ".png", OUTPUT_PNG },
{ ".jpg", OUTPUT_JPEG },
{ ".jpeg", OUTPUT_JPEG },
{ ".tiff", OUTPUT_TIFF },
{ ".tif", OUTPUT_TIFF },
{ ".pdf", OUTPUT_PDF }
};
for (unsigned i = 0; i < sizeof(formats) / sizeof(formats[0]); ++i)
{
if (strcmp(extension, formats[i].extension) == 0)
return formats[i].output_format;
}
}
// it would be very confusing if user makes a typo in the filename and the output format changes.
// This is most likely not what the user wanted.
fprintf(stderr, "Could not guess output format from the given path and no --format given.\n");
exit(1);
}
int
main (int argc, char **argv)
{
int ch, i, index, all_options_len;
const SANE_Device **device_list;
SANE_Int num_dev_options = 0;
const char *devname = 0;
const char *defdevname = 0;
const char *format = 0;
char readbuf[2];
char *readbuf2;
int batch = 0;
int batch_print = 0;
int batch_prompt = 0;
int batch_count = BATCH_COUNT_UNLIMITED;
int batch_start_at = 1;
int batch_increment = 1;
SANE_Status status;
char *full_optstring;
SANE_Int version_code;
void *pw = NULL;
FILE *ofp = NULL;
buffer_size = (32 * 1024); /* default size */
prog_name = strrchr (argv[0], '/');
if (prog_name)
++prog_name;
else
prog_name = argv[0];
defdevname = getenv ("SANE_DEFAULT_DEVICE");
sane_init (&version_code, auth_callback);
/* 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 'b':
/* This may have already been set by the batch-count flag */
batch = 1;
format = optarg;
break;
case 'h':
help = 1;
break;
case 'i': /* icc profile */
icc_profile = optarg;
break;
case 'v':
++verbose;
break;
case 'p':
progress = 1;
break;
case 'o':
output_file = optarg;
break;
case 'B':
if (optarg)
buffer_size = 1024 * atoi(optarg);
else
buffer_size = (1024 * 1024);
break;
case 'T':
test = 1;
break;
case 'A':
all = 1;
break;
case 'n':
dont_scan = 1;
break;
case OPTION_BATCH_PRINT:
batch_print = 1;
break;
case OPTION_BATCH_PROMPT:
batch_prompt = 1;
break;
case OPTION_BATCH_INCREMENT:
batch_increment = atoi (optarg);
break;
case OPTION_BATCH_START_AT:
batch_start_at = atoi (optarg);
break;
case OPTION_BATCH_DOUBLE:
batch_increment = 2;
break;
case OPTION_BATCH_COUNT:
batch_count = atoi (optarg);
batch = 1;
break;
case OPTION_FORMAT:
if (strcmp (optarg, "tiff") == 0)
output_format = OUTPUT_TIFF;
else if (strcmp (optarg, "png") == 0)
{
#ifdef HAVE_LIBPNG
output_format = OUTPUT_PNG;
#else
fprintf(stderr, "PNG support not compiled in\n");
exit(1);
#endif
}
else if (strcmp (optarg, "jpeg") == 0)
{
#ifdef HAVE_LIBJPEG
output_format = OUTPUT_JPEG;
#else
fprintf(stderr, "JPEG support not compiled in\n");
exit(1);
#endif
}
else if (strcmp (optarg, "pdf") == 0)
{
#ifdef HAVE_LIBJPEG
output_format = OUTPUT_PDF;
#else
fprintf(stderr, "PDF support not compiled in\n");
exit(1);
#endif
}
else if (strcmp (optarg, "pnm") == 0)
{
output_format = OUTPUT_PNM;
}
else
{
fprintf(stderr, "Unknown output image format '%s'.\n", optarg);
fprintf(stderr, "Supported formats: pnm, tiff");
#ifdef HAVE_LIBPNG
fprintf(stderr, ", png");
#endif
#ifdef HAVE_LIBJPEG
fprintf(stderr, ", jpeg");
#endif
fprintf(stderr, ".\n");
exit(1);
}
break;
case OPTION_MD5:
accept_only_md5_auth = 1;
break;
case 'L':
case 'f':
{
int i = 0;
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));
scanimage_exit (1);
}
if (ch == 'L')
{
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);
}
}
else
{
int i = 0, int_arg = 0;
const char *percent, *start;
const char *text_arg = 0;
char ftype;
for (i = 0; device_list[i]; ++i)
{
start = optarg;
while (*start && (percent = strchr (start, '%')))
{
int start_len = percent - start;
percent++;
if (*percent)
{
switch (*percent)
{
case 'd':
text_arg = device_list[i]->name;
ftype = 's';
break;
case 'v':
text_arg = device_list[i]->vendor;
ftype = 's';
break;
case 'm':
text_arg = device_list[i]->model;
ftype = 's';
break;
case 't':
text_arg = device_list[i]->type;
ftype = 's';
break;
case 'i':
int_arg = i;
ftype = 'i';
break;
case 'n':
text_arg = "\n";
ftype = 's';
break;
case '%':
text_arg = "%";
ftype = 's';
break;
default:
fprintf (stderr,
"%s: unknown format specifier %%%c\n",
prog_name, *percent);
text_arg = "%";
ftype = 's';
}
printf ("%.*s", start_len, start);
switch (ftype)
{
case 's':
printf ("%s", text_arg);
break;
case 'i':
printf ("%i", int_arg);
break;
}
start = percent + 1;
}
else
{
/* last char of the string is a '%', ignore it */
start++;
break;
}
}
if (*start)
printf ("%s", start);
}
}
if (i == 0 && ch != 'f')
printf ("\nNo scanners were identified. If you were expecting "
"something different,\ncheck that the scanner is plugged "
"in, turned on and detected by the\nsane-find-scanner tool "
"(if appropriate). Please read the documentation\nwhich came "
"with this software (README, FAQ, manpages).\n");
if (defdevname)
printf ("default device is `%s'\n", defdevname);
scanimage_exit (0);
break;
}
case 'V':
printf ("scanimage (%s) %s; backend version %d.%d.%d\n", PACKAGE,
VERSION, SANE_VERSION_MAJOR (version_code),
SANE_VERSION_MINOR (version_code),
SANE_VERSION_BUILD (version_code));
scanimage_exit (0);
break;
default:
break; /* ignore device specific options for now */
}
}
if (help)
{
printf ("Usage: %s [OPTION]...\n\
\n\
Start image acquisition on a scanner device and write image data to\n\
standard output.\n\
\n\
Parameters are separated by a blank from single-character options (e.g.\n\
-d epson) and by a \"=\" from multi-character options (e.g. --device-name=epson).\n\
-d, --device-name=DEVICE use a given scanner device (e.g. hp:/dev/scanner)\n\
--format=pnm|tiff|png|jpeg|pdf file format of output file\n\
-i, --icc-profile=PROFILE include this ICC profile into TIFF file\n", prog_name);
printf ("\
-L, --list-devices show available scanner devices\n\
-f, --formatted-device-list=FORMAT similar to -L, but the FORMAT of the output\n\
can be specified: %%d (device name), %%v (vendor),\n\
%%m (model), %%t (type), %%i (index number), and\n\
%%n (newline)\n\
-b, --batch[=FORMAT] working in batch mode, FORMAT is `out%%d.pnm' `out%%d.tif'\n\
`out%%d.png' or `out%%d.jpg' by default depending on --format\n\
This option is incompatible with --output-file.");
printf ("\
--batch-start=# page number to start naming files with\n\
--batch-count=# how many pages to scan in batch mode\n\
--batch-increment=# increase page number in filename by #\n\
--batch-double increment page number by two, same as\n\
--batch-increment=2\n\
--batch-print print image filenames to stdout\n\
--batch-prompt ask for pressing a key before scanning a page\n");
printf ("\
--accept-md5-only only accept authorization requests using md5\n\
-p, --progress print progress messages\n\
-o, --output-file=PATH save output to the given file instead of stdout.\n\
This option is incompatible with --batch.\n\
-n, --dont-scan only set options, don't actually scan\n\
-T, --test test backend thoroughly\n\
-A, --all-options list all available backend options\n\
-h, --help display this help message and exit\n\
-v, --verbose give even more status messages\n\
-B, --buffer-size=# change input buffer size (in kB, default 32)\n");
printf ("\
-V, --version print version information\n");
}
if (batch && output_file != NULL)
{
fprintf(stderr, "--batch and --output-file can't be used together.\n");
exit(1);
}
if (output_format == OUTPUT_UNKNOWN)
output_format = guess_output_format(output_file);
if (!devname)
{
/* If no device name was specified explicitly, we look at the
environment variable SANE_DEFAULT_DEVICE. If this variable
is not set, we open the first device we find (if any): */
devname = defdevname;
if (!devname)
{
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));
scanimage_exit (1);
}
if (!device_list[0])
{
fprintf (stderr, "%s: no SANE devices found\n", prog_name);
scanimage_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 (devname[0] == '/')
fprintf (stderr, "\nYou seem to have specified a UNIX device name, "
"or filename instead of selecting\nthe SANE scanner or "
"image acquisition device you want to use. As an example,\n"
"you might want \"epson:/dev/sg0\" or "
"\"hp:/dev/usbscanner0\". If any supported\ndevices are "
"installed in your system, you should be able to see a "
"list with\n\"scanimage --list-devices\".\n");
if (help)
device = 0;
else
scanimage_exit (1);
}
if (device)
{
const SANE_Option_Descriptor * desc_ptr;
/* Good form to always get the descriptor once before value */
desc_ptr = sane_get_option_descriptor(device, 0);
if (!desc_ptr)
{
fprintf (stderr, "%s: unable to get option count descriptor\n",
prog_name);
scanimage_exit (1);
}
/* 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);
scanimage_exit (1);
}
/* malloc global option lists */
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 main()\n",
prog_name);
scanimage_exit (1);
}
/* load global option lists */
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);
scanimage_exit (1);
}
strcpy (full_optstring, BASE_OPTSTRING);
strcat (full_optstring, larg);
strcat (full_optstring, targ);
strcat (full_optstring, xarg);
strcat (full_optstring, yarg);
}
/* re-run argument processing with backend-specific options included
* this time, enable error printing and arg permutation */
optind = 0;
opterr = 1;
while ((ch = getopt_long (argc, argv, full_optstring, all_options,
&index)) != EOF)
{
switch (ch)
{
case ':':
case '?':
scanimage_exit (1); /* error message is printed by getopt_long() */
case 'd':
case 'h':
case 'p':
case 'o':
case 'v':
case 'V':
case 'T':
case 'B':
/* 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;
}
}
if (optind < argc)
{
fprintf (stderr, "%s: argument without option: `%s'; ", prog_name,
argv[argc - 1]);
fprintf (stderr, "try %s --help\n", prog_name);
scanimage_exit (1);
}
free (full_optstring);
/* convert x/y to br_x/br_y */
for (index = 0; index < 2; ++index)
if (window[index])
{
SANE_Word pos = 0;
SANE_Word val = window_val[index];
if (window[index + 2])
{
sane_control_option (device, window[index + 2],
SANE_ACTION_GET_VALUE, &pos, 0);
val += pos;
}
set_option (device, window[index], &val);
}
/* output device-specific help */
if (help)
{
printf ("\nOptions specific to device `%s':\n", devname);
print_options(device, num_dev_options, SANE_FALSE);
}
/* list all device-specific options */
if (all)
{
printf ("\nAll options specific to device `%s':\n", devname);
print_options(device, num_dev_options, SANE_TRUE);
scanimage_exit (0);
}
}
/* output device list */
if (help)
{
printf ("\
Type ``%s --help -d DEVICE'' to get list of all options for DEVICE.\n\
\n\
List of available devices:", prog_name);
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);
scanimage_exit (0);
}
if (dont_scan)
scanimage_exit (0);
if (output_format != OUTPUT_PNM)
resolution_value = get_resolution ();
#ifdef SIGHUP
signal (SIGHUP, sighandler);
#endif
#ifdef SIGPIPE
signal (SIGPIPE, sighandler);
#endif
signal (SIGINT, sighandler);
signal (SIGTERM, sighandler);
if (test == 0)
{
int n = batch_start_at;
if (batch && NULL == format)
{
switch(output_format) {
case OUTPUT_TIFF:
format = "out%d.tif";
break;
case OUTPUT_PNM:
format = "out%d.pnm";
break;
#ifdef HAVE_LIBPNG
case OUTPUT_PNG:
format = "out%d.png";
break;
#endif
#ifdef HAVE_LIBJPEG
case OUTPUT_PDF:
format = "out%d.pdf";
break;
case OUTPUT_JPEG:
format = "out%d.jpg";
break;
#endif
}
}
if (!batch)
{
ofp = stdout;
if (output_file != NULL)
{
ofp = fopen(output_file, "w");
if (ofp == NULL)
{
fprintf(stderr, "%s: could not open output file '%s', "
"exiting\n", prog_name, output_file);
scanimage_exit(1);
}
}
#ifdef HAVE_LIBJPEG
if (output_format == OUTPUT_PDF)
{
sane_pdf_open(&pw, ofp );
sane_pdf_start_doc( pw );
}
#endif
}
if (batch)
{
fputs("Scanning ", stderr);
if (batch_count == BATCH_COUNT_UNLIMITED)
fputs("infinity", stderr);
else
fprintf(stderr, "%d", batch_count);
fprintf (stderr,
" page%s, incrementing by %d, numbering from %d\n",
batch_count == 1 ? "" : "s", batch_increment, batch_start_at);
}
else if(isatty(fileno(ofp))){
fprintf (stderr,"%s: output is not a file, exiting\n", prog_name);
scanimage_exit (1);
}
buffer = malloc (buffer_size);
do
{
char path[PATH_MAX];
char part_path[PATH_MAX];
if (batch) /* format is NULL unless batch mode */
{
sprintf (path, format, n); /* love --(C++) */
strcpy (part_path, path);
#ifdef HAVE_LIBJPEG
if (output_format != OUTPUT_PDF)
#endif
strcat (part_path, ".part");
}
if (batch)
{
if (batch_prompt)
{
fprintf (stderr, "Place document no. %d on the scanner.\n",
n);
fprintf (stderr, "Press <RETURN> to continue.\n");
fprintf (stderr, "Press Ctrl + D to terminate.\n");
readbuf2 = fgets (readbuf, 2, stdin);
if (readbuf2 == NULL)
{
if (ofp)
{
#ifdef HAVE_LIBJPEG
if (output_format == OUTPUT_PDF)
{
sane_pdf_end_doc( pw );
sane_pdf_close ( pw );
}
#endif
fclose (ofp);
ofp = NULL;
}
break; /* get out of this loop */
}
}
fprintf (stderr, "Scanning page %d\n", n);
}
#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)
{
fprintf (stderr, "%s: sane_start: %s\n",
prog_name, sane_strstatus (status));
if (ofp )
{
#ifdef HAVE_LIBJPEG
if (output_format == OUTPUT_PDF)
{
sane_pdf_end_doc( pw );
sane_pdf_close ( pw );
}
#endif
fclose (ofp);
ofp = NULL;
}
break;
}
/* write to .part file while scanning is in progress */
if (batch)
{
#ifdef HAVE_LIBJPEG
SANE_Bool init_pdf = SANE_FALSE;
#endif
if (ofp == NULL)
{
ofp = fopen (part_path, "w");
#ifdef HAVE_LIBJPEG
if (output_format == OUTPUT_PDF && ofp != NULL)
init_pdf = SANE_TRUE;
#endif
}
if (NULL == ofp)
{
fprintf (stderr, "cannot open %s\n", part_path);
sane_cancel (device);
return SANE_STATUS_ACCESS_DENIED;
}
#ifdef HAVE_LIBJPEG
if (init_pdf )
{
sane_pdf_open( &pw, ofp );
sane_pdf_start_doc ( pw );
}
#endif
}
status = scan_it (ofp, pw);
#ifdef HAVE_LIBJPEG
if (output_format == OUTPUT_PDF)
{
sane_pdf_end_page( pw );
fflush( ofp );
}
#endif
if (batch)
{
fprintf (stderr, "Scanned page %d.", n);
fprintf (stderr, " (scanner status = %d)\n", status);
}
switch (status)
{
case SANE_STATUS_GOOD:
case SANE_STATUS_EOF:
status = SANE_STATUS_GOOD;
if (batch)
{
#ifdef HAVE_LIBJPEG
if (output_format != OUTPUT_PDF)
{
#endif
if (!ofp || 0 != fclose(ofp))
{
fprintf (stderr, "cannot close image file\n");
sane_cancel (device);
return SANE_STATUS_ACCESS_DENIED;
}
else
{
ofp = NULL;
/* let the fully scanned file show up */
if (rename (part_path, path))
{
fprintf (stderr, "cannot rename %s to %s\n",
part_path, path);
sane_cancel (device);
return SANE_STATUS_ACCESS_DENIED;
}
if (batch_print)
{
fprintf (stdout, "%s\n", path);
fflush (stdout);
}
}
#ifdef HAVE_LIBJPEG
}
#endif
}
else
{
#ifdef HAVE_LIBJPEG
if (output_format == OUTPUT_PDF)
{
sane_pdf_end_doc( pw );
fflush( ofp );
sane_pdf_close ( pw );
}
#endif
if (output_file && ofp)
{
fclose(ofp);
ofp = NULL;
}
}
break;
default:
if (batch)
{
if (ofp)
{
fclose (ofp);
ofp = NULL;
}
unlink (part_path);
}
else
{
#ifdef HAVE_LIBJPEG
if (output_format == OUTPUT_PDF)
{
sane_pdf_end_doc( pw );
sane_pdf_close ( pw );
}
#endif
if (output_file && ofp)
{
fclose(ofp);
ofp = NULL;
}
unlink (output_file);
}
break;
} /* switch */
n += batch_increment;
}
while ((batch
&& (batch_count == BATCH_COUNT_UNLIMITED || --batch_count))
&& SANE_STATUS_GOOD == status);
if (batch)
{
#ifdef HAVE_LIBJPEG
if (output_format == OUTPUT_PDF)
{
if (output_file && ofp)
{
sane_pdf_end_doc( pw );
fflush( ofp );
sane_pdf_close ( pw );
fclose(ofp);
ofp = NULL;
}
}
#endif
int num_pgs = (n - batch_start_at) / batch_increment;
fprintf (stderr, "Batch terminated, %d page%s scanned\n",
num_pgs, num_pgs == 1 ? "" : "s");
}
if (batch
&& SANE_STATUS_NO_DOCS == status
&& (batch_count == BATCH_COUNT_UNLIMITED)
&& n > batch_start_at)
status = SANE_STATUS_GOOD;
sane_cancel (device);
}
else
status = test_it ();
scanimage_exit (status);
/* the line below avoids compiler warnings */
return status;
}