/* scanimage -- command line scanning utility Uses the SANE library. Copyright (C) 2015 Rolf Bensch 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef _AIX # include "../include/lalloca.h" /* MUST come first for AIX! */ #endif #include "../include/sane/config.h" #include "../include/lalloca.h" #include #include "lgetopt.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBPNG #include #endif #ifdef HAVE_LIBJPEG #include #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" #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; } 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 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 ("", stdout); break; case SANE_TYPE_FIXED: fputs ("", stdout); break; case SANE_TYPE_STRING: fputs ("", stdout); break; default: break; } if (opt->type != SANE_TYPE_STRING && opt->size > (SANE_Int) sizeof (SANE_Word)) fputs (",...", stdout); break; case SANE_CONSTRAINT_RANGE: 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: 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: 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); 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 settable options */ if (!SANE_OPTION_IS_SETTABLE (opt->cap) || 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_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); } 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->height += STRIP_HEIGHT; new_size = image->height * image->width; 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) { 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 }; 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 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) { 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) || (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_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) 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: 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: 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: 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) { 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_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, 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) 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) { 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 }; 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 } }; 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; 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, "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 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_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); } } } 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); strcat (part_path, ".part"); } if (batch) { if (batch_prompt) { fprintf (stderr, "Place document no. %d on the scanner.\n", n); fprintf (stderr, "Press to continue.\n"); fprintf (stderr, "Press Ctrl + D to terminate.\n"); readbuf2 = fgets (readbuf, 2, stdin); if (readbuf2 == NULL) { if (ofp) { 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) { fclose (ofp); ofp = NULL; } break; } /* write to .part file while scanning is in progress */ if (batch) { if (NULL == (ofp = fopen (part_path, "w"))) { fprintf (stderr, "cannot open %s\n", part_path); sane_cancel (device); return SANE_STATUS_ACCESS_DENIED; } } status = scan_it (ofp); 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) { 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); } } } else { if (output_file && ofp) { fclose(ofp); ofp = NULL; } } break; default: if (batch) { if (ofp) { fclose (ofp); ofp = NULL; } unlink (part_path); } else { 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) { 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; }