From cfd27ef2ad8b005fd47ab41ef29b71d9e3d48201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Thu, 8 Oct 2015 12:32:49 +0200 Subject: Imported Upstream version 1.0.25 --- backend/fujitsu.c | 839 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 609 insertions(+), 230 deletions(-) (limited to 'backend/fujitsu.c') diff --git a/backend/fujitsu.c b/backend/fujitsu.c index 48d55dc..9fddb1c 100644 --- a/backend/fujitsu.c +++ b/backend/fujitsu.c @@ -6,7 +6,7 @@ Copyright (C) 2000 Randolph Bentson Copyright (C) 2001 Frederik Ramm Copyright (C) 2001-2004 Oliver Schirrmeister - Copyright (C) 2003-2011 m. allan noah + Copyright (C) 2003-2014 m. allan noah JPEG output and low memory usage support funded by: Archivista GmbH, www.archivista.ch @@ -14,7 +14,7 @@ O A S Oilfield Accounting Service Ltd, www.oas.ca Automatic length detection support funded by: Martin G. Miller, mgmiller at optonline.net - Software image enhancement routines funded by: + Software image enhancement routines and recent scanner support funded by: Fujitsu Computer Products of America, Inc. www.fcpa.com -------------------------------------------------------------------------- @@ -533,11 +533,50 @@ v116 2013-03-23, MAN - call set_mode() in init_interlace - add swskip option - v117 2013-06-11, MAN + v117 2013-06-11, MAN (SANE 1.0.24) - default buffer-mode to off - improved error handling in sane_start - image width must be multiple of 8 when swcrop is used before binarization (iX500) - check hopper sensor before calling object_position(load) on iX500 + v118 2013-12-09, MAN + - support fi-7160, fi-7260, fi-7180 and fi-7280 + - remove unused var from do_scsi_cmd() + - added more request_sense options + - add adv_paper_protect option + - enable paper protection by default + - increase max_x_fb for fi-6240 and fi-6230 + v119 2013-12-18, MAN + - call get_pixelsize after start_scan, not before + - extend get_pixelsize to request backside data + - stop using backup/restore_params + - don't use extended get_pixelsize on M3091 or M3092 + - call software crop code on backside images too + v120 2014-01-29, MAN + - only call hopper_before_op code at batch start + - remove unused backup/restore_params + v121 2014-04-07, MAN + - add JFIF APP0 marker with resolution to jpeg images + - improve jpeg duplex parsing code + - simplify jpeg ifdefs + - add offtimer option for more recent scanners + - don't print 0 length line in hexdump + v122 2014-10-28, MAN + - add support for object_position halt + - call object_position halt in check_for_cancel when requested + v123 2014-11-06, MAN + - workaround Linux USB3 bugs by adding command counting code and + sending an even number of reads and writes during disconnect_fd + v124 2014-12-09, MAN + - support resolution controlled max page-height (fi-6/7xxx scanners) + - reorder scanner sections in init_model chronologically + v125 2014-12-16, MAN + - remove USB packet counting code from v123, fix sanei_usb instead + v126 2015-08-23, MAN + - initial support for iX100 + - add late_lut support for iX500/iX100 + v127 2015-08-25, MAN + - separate iX100 from iX500 settings + - iX100 has gray and lineart SANE FLOW DIAGRAM @@ -587,7 +626,7 @@ #include "fujitsu.h" #define DEBUG 1 -#define BUILD 117 +#define BUILD 127 /* values for SANE_DEBUG_FUJITSU env var: - errors 5 @@ -600,6 +639,12 @@ - useless noise 35 */ +/* ------------------------------------------------------------------------- */ +/* if JPEG support is not enabled in sane.h, we setup our own defines */ +#ifndef SANE_FRAME_JPEG +#define SANE_FRAME_JPEG 0x0B +#define SANE_JPEG_DISABLED 1 +#endif /* ------------------------------------------------------------------------- */ #define STRING_FLATBED SANE_I18N("Flatbed") #define STRING_ADFFRONT SANE_I18N("ADF Front") @@ -1577,9 +1622,8 @@ init_vpd (struct fujitsu *s) s->has_comp_JPG1 = get_IN_compression_JPG_BASE (in); DBG (15, " compression JPG1: %d\n", s->has_comp_JPG1); -#ifndef SANE_FRAME_JPEG +#ifdef SANE_JPEG_DISABLED DBG (15, " (Disabled)\n"); - s->has_comp_JPG1 = 0; #endif s->has_comp_JPG2 = get_IN_compression_JPG_EXT (in); @@ -1671,7 +1715,12 @@ init_vpd (struct fujitsu *s) DBG (15, " rgb lut: %d\n", get_IN_rgb_lut(in)); DBG (15, " num lut dl: %d\n", get_IN_num_lut_dl(in)); + s->has_off_mode = get_IN_erp_lot6_supp(in); + DBG (15, " ErP Lot6 (power off timer): %d\n", s->has_off_mode); DBG (15, " sync next feed: %d\n", get_IN_sync_next_feed(in)); + + s->has_op_halt = get_IN_op_halt(in); + DBG (15, " object postion halt: %d\n", s->has_op_halt); } ret = SANE_STATUS_GOOD; @@ -2012,6 +2061,10 @@ init_model (struct fujitsu *s) s->max_x = s->max_x_basic * 1200 / s->basic_x_res; s->max_y = s->max_y_basic * 1200 / s->basic_y_res; + /* setup the list with a single choice, in 1200dpi units, at max res */ + s->max_y_by_res[0].res = s->max_y_res; + s->max_y_by_res[0].len = s->max_y; + /* assume these are same as adf, override below */ s->max_x_fb = s->max_x; s->max_y_fb = s->max_y; @@ -2062,6 +2115,7 @@ init_model (struct fujitsu *s) s->has_vuid_3091 = 1; s->has_vuid_color = 0; s->has_vuid_mono = 0; + s->has_short_pixelsize = 1; s->color_interlace = COLOR_INTERLACE_3091; s->duplex_interlace = DUPLEX_INTERLACE_3091; @@ -2106,6 +2160,38 @@ init_model (struct fujitsu *s) s->max_y_fb = 14032; } + else if (strstr (s->model_name, "fi-4750") ) { + /* weirdness */ + s->broken_diag_serial = 1; + } + + /* some firmware versions use capital f? */ + else if (strstr (s->model_name, "Fi-4860") + || strstr (s->model_name, "fi-4860") ) { + + /* weirdness */ + s->broken_diag_serial = 1; + + s->ppl_mod_by_mode[MODE_LINEART] = 32; + s->ppl_mod_by_mode[MODE_HALFTONE] = 32; + s->ppl_mod_by_mode[MODE_GRAYSCALE] = 4; + s->ppl_mod_by_mode[MODE_COLOR] = 4; + } + + /* some firmware versions use capital f? */ + else if (strstr (s->model_name, "Fi-4990") + || strstr (s->model_name, "fi-4990") ) { + + /* weirdness */ + s->duplex_interlace = DUPLEX_INTERLACE_NONE; + s->color_interlace = COLOR_INTERLACE_RRGGBB; + + s->ppl_mod_by_mode[MODE_LINEART] = 32; + s->ppl_mod_by_mode[MODE_HALFTONE] = 32; + s->ppl_mod_by_mode[MODE_GRAYSCALE] = 4; + s->ppl_mod_by_mode[MODE_COLOR] = 4; + } + else if (strstr (s->model_name,"fi-5110C")){ /* missing from vpd */ @@ -2135,49 +2221,44 @@ init_model (struct fujitsu *s) s->adbits = 8; } - /* some firmware versions use capital f? */ - else if (strstr (s->model_name, "Fi-4990") - || strstr (s->model_name, "fi-4990") ) { - - /* weirdness */ - s->duplex_interlace = DUPLEX_INTERLACE_NONE; - s->color_interlace = COLOR_INTERLACE_RRGGBB; + else if (strstr (s->model_name,"S1500")){ - s->ppl_mod_by_mode[MODE_LINEART] = 32; - s->ppl_mod_by_mode[MODE_HALFTONE] = 32; - s->ppl_mod_by_mode[MODE_GRAYSCALE] = 4; - s->ppl_mod_by_mode[MODE_COLOR] = 4; - } - - else if (strstr (s->model_name, "fi-4750") ) { - /* weirdness */ - s->broken_diag_serial = 1; + /*lies*/ + s->has_MS_bg=0; + s->has_MS_prepick=0; } - /* some firmware versions use capital f? */ - else if (strstr (s->model_name, "Fi-4860") - || strstr (s->model_name, "fi-4860") ) { + /* also includes the 'Z' models */ + else if (strstr (s->model_name,"fi-6130") + || strstr (s->model_name,"fi-6140")){ /* weirdness */ - s->broken_diag_serial = 1; - - s->ppl_mod_by_mode[MODE_LINEART] = 32; - s->ppl_mod_by_mode[MODE_HALFTONE] = 32; - s->ppl_mod_by_mode[MODE_GRAYSCALE] = 4; - s->ppl_mod_by_mode[MODE_COLOR] = 4; + /* these machines have longer max paper at lower res */ + s->max_y_by_res[1].res = 200; + s->max_y_by_res[1].len = 151512; } /* also includes the 'Z' models */ else if (strstr (s->model_name,"fi-6230") || strstr (s->model_name,"fi-6240")){ + /* weirdness */ + /* these machines have longer max paper at lower res */ + s->max_y_by_res[1].res = 200; + s->max_y_by_res[1].len = 151512; + /* missing from vpd */ - s->max_x_fb = 10488; + s->max_x_fb = 10764; /* was previously 10488 */ s->max_y_fb = 14032; /* some scanners can be slightly more? */ } - else if (strstr (s->model_name,"S1500") - || strstr (s->model_name,"fi-6110")){ + else if (strstr (s->model_name,"fi-6110")){ + + /* weirdness */ + /* these machines have longer max paper at lower res */ + s->max_y_by_res[1].res = 200; + s->max_y_by_res[1].len = 151512; + /*lies*/ s->has_MS_bg=0; s->has_MS_prepick=0; @@ -2196,6 +2277,7 @@ init_model (struct fujitsu *s) /* weirdness */ s->need_q_table = 1; + s->late_lut = 1; s->need_diag_preread = 1; s->ppl_mod_by_mode[MODE_COLOR] = 2; s->hopper_before_op = 1; @@ -2212,6 +2294,62 @@ init_model (struct fujitsu *s) s->can_mode[MODE_HALFTONE] = 0; } + /*mostly copied from iX500*/ + else if (strstr (s->model_name,"iX100")){ + /* locks up scanner if we try to auto detect */ + s->has_MS_lamp = 0; + + /* weirdness */ + s->need_q_table = 1; + s->late_lut = 1; + s->need_diag_preread = 1; + s->ppl_mod_by_mode[MODE_COLOR] = 2; + s->hopper_before_op = 1; + s->no_wait_after_op = 1; + + /* lies */ + s->adbits = 8; + + /* dont bother with this one */ + s->can_mode[MODE_HALFTONE] = 0; + } + + else if (strstr (s->model_name,"fi-7180") + || strstr (s->model_name,"fi-7160")){ + + /* weirdness */ + /* these machines have longer max paper at lower res */ + s->max_y_by_res[1].res = 400; + s->max_y_by_res[1].len = 194268; + s->max_y_by_res[2].res = 300; + s->max_y_by_res[2].len = 260268; + s->max_y_by_res[3].res = 200; + s->max_y_by_res[3].len = 266268; + + /* missing from vpd */ + s->has_df_recovery=1; + s->has_adv_paper_prot=1; + } + + else if (strstr (s->model_name,"fi-7280") + || strstr (s->model_name,"fi-7260")){ + + /* weirdness */ + /* these machines have longer max paper at lower res */ + s->max_y_by_res[1].res = 400; + s->max_y_by_res[1].len = 194268; + s->max_y_by_res[2].res = 300; + s->max_y_by_res[2].len = 260268; + s->max_y_by_res[3].res = 200; + s->max_y_by_res[3].len = 266268; + + /* missing from vpd */ + s->has_df_recovery=1; + s->has_adv_paper_prot=1; + s->max_x_fb = 10764; + s->max_y_fb = 14032; /* some scanners can be slightly more? */ + } + DBG (10, "init_model: finish\n"); return SANE_STATUS_GOOD; @@ -2277,6 +2415,7 @@ init_user (struct fujitsu *s) /* page height US-Letter */ s->page_height = 11 * 1200; + set_max_y(s); if(s->page_height > s->max_y){ s->page_height = s->max_y; } @@ -2310,6 +2449,23 @@ init_user (struct fujitsu *s) * even when the user only wants one */ s->buff_mode = MSEL_OFF; + /* useful features of newer scanners which we turn on, + * even though the scanner defaults to off */ + if(s->has_paper_protect){ + s->paper_protect = MSEL_ON; + } + if(s->has_staple_detect){ + s->staple_detect = MSEL_ON; + } + if(s->has_df_recovery){ + s->df_recovery = MSEL_ON; + } + if(s->has_adv_paper_prot){ + s->adv_paper_prot = MSEL_ON; + } + + s->off_time = 240; + DBG (10, "init_user: finish\n"); return SANE_STATUS_GOOD; @@ -3377,7 +3533,9 @@ sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) s->compress_list[i++]=STRING_NONE; if(s->has_comp_JPG1){ +#ifndef SANE_JPEG_DISABLED s->compress_list[i++]=STRING_JPEG; +#endif } s->compress_list[i]=NULL; @@ -3528,7 +3686,7 @@ sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) opt->cap = SANE_CAP_INACTIVE; } - /*df recovery*/ + /*df_recovery*/ if(option==OPT_DF_RECOVERY){ s->df_recovery_list[0] = STRING_DEFAULT; s->df_recovery_list[1] = STRING_OFF; @@ -3548,7 +3706,7 @@ sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) opt->cap = SANE_CAP_INACTIVE; } - /*paper protection*/ + /*paper_protect*/ if(option==OPT_PAPER_PROTECT){ s->paper_protect_list[0] = STRING_DEFAULT; s->paper_protect_list[1] = STRING_OFF; @@ -3568,6 +3726,26 @@ sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) opt->cap = SANE_CAP_INACTIVE; } + /*adv_paper_prot*/ + if(option==OPT_ADV_PAPER_PROT){ + s->adv_paper_prot_list[0] = STRING_DEFAULT; + s->adv_paper_prot_list[1] = STRING_OFF; + s->adv_paper_prot_list[2] = STRING_ON; + s->adv_paper_prot_list[3] = NULL; + + opt->name = "adv-paper-protect"; + opt->title = "Advanced paper protection"; + opt->desc = "Request scanner to predict jams in the ADF using improved sensors"; + opt->type = SANE_TYPE_STRING; + opt->constraint_type = SANE_CONSTRAINT_STRING_LIST; + opt->constraint.string_list = s->adv_paper_prot_list; + opt->size = maxStringSize (opt->constraint.string_list); + if (s->has_MS_df && s->has_adv_paper_prot) + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + else + opt->cap = SANE_CAP_INACTIVE; + } + /*staple detection*/ if(option==OPT_STAPLE_DETECT){ s->staple_detect_list[0] = STRING_DEFAULT; @@ -3693,7 +3871,7 @@ sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) opt->cap = SANE_CAP_INACTIVE; } - /*sleep time*/ + /*sleep_time*/ if(option==OPT_SLEEP_TIME){ s->sleep_time_range.min = 0; s->sleep_time_range.max = 60; @@ -3712,6 +3890,25 @@ sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) opt->cap = SANE_CAP_INACTIVE; } + /*off_time*/ + if(option==OPT_OFF_TIME){ + s->off_time_range.min = 0; + s->off_time_range.max = 960; + s->off_time_range.quant = 1; + + opt->name = "offtimer"; + opt->title = "Off timer"; + opt->desc = "Time in minutes until the internal power supply switches the scanner off. Will be rounded to nearest 15 minutes. Zero means never power off."; + opt->type = SANE_TYPE_INT; + opt->unit = SANE_UNIT_NONE; + opt->constraint_type = SANE_CONSTRAINT_RANGE; + opt->constraint.range=&s->off_time_range; + if(s->has_off_mode) + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + else + opt->cap = SANE_CAP_INACTIVE; + } + /*duplex offset*/ if(option==OPT_DUPLEX_OFFSET){ s->duplex_offset_range.min = -16; @@ -3862,13 +4059,25 @@ sane_get_option_descriptor (SANE_Handle handle, SANE_Int option) opt->constraint_type = SANE_CONSTRAINT_RANGE; opt->constraint.range = &s->swskip_range; - s->swskip_range.quant=1; + s->swskip_range.quant=SANE_FIX(0.10001); s->swskip_range.min=SANE_FIX(0); s->swskip_range.max=SANE_FIX(100); opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; } + /*halt scanner feeder when cancelling*/ + if(option==OPT_HALT_ON_CANCEL){ + opt->name = "halt-on-cancel"; + opt->title = "Halt on Cancel"; + opt->desc = "Request driver to halt the paper feed instead of eject during a cancel."; + opt->type = SANE_TYPE_BOOL; + if (s->has_op_halt) + opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; + else + opt->cap = SANE_CAP_INACTIVE; + } + /* "Endorser" group ------------------------------------------------------ */ if(option==OPT_ENDORSER_GROUP){ opt->name = "endorser-options"; @@ -4689,6 +4898,20 @@ sane_control_option (SANE_Handle handle, SANE_Int option, } return SANE_STATUS_GOOD; + case OPT_ADV_PAPER_PROT: + switch (s->adv_paper_prot) { + case MSEL_DEFAULT: + strcpy (val, STRING_DEFAULT); + break; + case MSEL_ON: + strcpy (val, STRING_ON); + break; + case MSEL_OFF: + strcpy (val, STRING_OFF); + break; + } + return SANE_STATUS_GOOD; + case OPT_STAPLE_DETECT: switch (s->staple_detect) { case MSEL_DEFAULT: @@ -4780,6 +5003,10 @@ sane_control_option (SANE_Handle handle, SANE_Int option, *val_p = s->sleep_time; return SANE_STATUS_GOOD; + case OPT_OFF_TIME: + *val_p = s->off_time; + return SANE_STATUS_GOOD; + case OPT_DUPLEX_OFFSET: *val_p = s->duplex_offset; return SANE_STATUS_GOOD; @@ -4820,6 +5047,10 @@ sane_control_option (SANE_Handle handle, SANE_Int option, *val_p = SANE_FIX(s->swskip); return SANE_STATUS_GOOD; + case OPT_HALT_ON_CANCEL: + *val_p = s->halt_on_cancel; + return SANE_STATUS_GOOD; + /* Endorser Group */ case OPT_ENDORSER: *val_p = s->u_endorser; @@ -5080,6 +5311,7 @@ sane_control_option (SANE_Handle handle, SANE_Int option, s->resolution_x = val_c; s->resolution_y = val_c; + set_max_y(s); *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; return SANE_STATUS_GOOD; @@ -5341,6 +5573,15 @@ sane_control_option (SANE_Handle handle, SANE_Int option, s->paper_protect = MSEL_OFF; return SANE_STATUS_GOOD; + case OPT_ADV_PAPER_PROT: + if (!strcmp(val, STRING_DEFAULT)) + s->adv_paper_prot = MSEL_DEFAULT; + else if (!strcmp(val, STRING_ON)) + s->adv_paper_prot = MSEL_ON; + else if (!strcmp(val, STRING_OFF)) + s->adv_paper_prot = MSEL_OFF; + return SANE_STATUS_GOOD; + case OPT_STAPLE_DETECT: if (!strcmp(val, STRING_DEFAULT)) s->staple_detect = MSEL_DEFAULT; @@ -5403,6 +5644,14 @@ sane_control_option (SANE_Handle handle, SANE_Int option, s->sleep_time = val_c; return set_sleep_mode(s); + case OPT_OFF_TIME: + /* do our own constrain, because we want to round up */ + s->off_time = (val_c + 14)/15*15; + if(s->off_time != val_c){ + *info |= SANE_INFO_INEXACT; + } + return set_off_mode(s); + case OPT_DUPLEX_OFFSET: s->duplex_offset = val_c; return SANE_STATUS_GOOD; @@ -5439,6 +5688,10 @@ sane_control_option (SANE_Handle handle, SANE_Int option, s->swskip = SANE_UNFIX(val_c); return SANE_STATUS_GOOD; + case OPT_HALT_ON_CANCEL: + s->halt_on_cancel = val_c; + return SANE_STATUS_GOOD; + /* Endorser Group */ case OPT_ENDORSER: s->u_endorser = val_c; @@ -5549,6 +5802,50 @@ set_sleep_mode(struct fujitsu *s) return ret; } +static SANE_Status +set_off_mode(struct fujitsu *s) +{ + SANE_Status ret = SANE_STATUS_GOOD; + + unsigned char cmd[SEND_DIAGNOSTIC_len]; /*also big enough for READ_DIAG*/ + size_t cmdLen = SEND_DIAGNOSTIC_len; + + unsigned char out[SD_powoff_len]; + size_t outLen = SD_powoff_len; + + DBG (10, "set_off_mode: start\n"); + + if (!s->has_cmd_sdiag || !s->has_cmd_rdiag || !s->has_off_mode){ + DBG (5, "set_off_mode: not supported, returning\n"); + return ret; + } + + memset(cmd,0,cmdLen); + set_SCSI_opcode(cmd, SEND_DIAGNOSTIC_code); + set_SD_slftst(cmd, 0); + set_SD_xferlen(cmd, outLen); + + memcpy(out,SD_powoff_string,SD_powoff_stringlen); + set_SD_powoff_disable(out,!s->off_time); + set_SD_powoff_interval(out,s->off_time/15); + + ret = do_cmd ( + s, 1, 0, + cmd, cmdLen, + out, outLen, + NULL, NULL + ); + + if (ret != SANE_STATUS_GOOD){ + DBG (5, "set_off_mode: send diag error: %d\n", ret); + return ret; + } + + DBG (10, "set_off_mode: finish\n"); + + return SANE_STATUS_GOOD; +} + static SANE_Status get_hardware_status (struct fujitsu *s, SANE_Int option) { @@ -6092,6 +6389,7 @@ mode_select_df (struct fujitsu *s) set_MSEL_df_paperprot(page,s->paper_protect); set_MSEL_df_stapledet(page,s->staple_detect); set_MSEL_df_recovery(page,s->df_recovery); + set_MSEL_df_paperprot2(page,s->adv_paper_prot); ret = do_cmd ( s, 1, 0, @@ -6408,7 +6706,6 @@ update_params (struct fujitsu * s) if (s->s_mode == MODE_COLOR) { params->depth = 8; -#ifdef SANE_FRAME_JPEG /* jpeg requires 8x8 squares */ if(s->compress == COMP_JPEG){ params->format = SANE_FRAME_JPEG; @@ -6416,20 +6713,16 @@ update_params (struct fujitsu * s) params->lines -= params->lines % 8; } else{ -#endif params->format = SANE_FRAME_RGB; params->pixels_per_line -= params->pixels_per_line % max(s->ppl_mod_by_mode[s->s_mode], s->ppl_mod_by_mode[s->u_mode]); -#ifdef SANE_FRAME_JPEG } -#endif params->bytes_per_line = params->pixels_per_line * 3; } else if (s->s_mode == MODE_GRAYSCALE) { params->depth = 8; -#ifdef SANE_FRAME_JPEG /* jpeg requires 8x8 squares */ if(s->compress == COMP_JPEG){ params->format = SANE_FRAME_JPEG; @@ -6437,13 +6730,10 @@ update_params (struct fujitsu * s) params->lines -= params->lines % 8; } else{ -#endif params->format = SANE_FRAME_GRAY; params->pixels_per_line -= params->pixels_per_line % max(s->ppl_mod_by_mode[s->s_mode], s->ppl_mod_by_mode[s->u_mode]); -#ifdef SANE_FRAME_JPEG } -#endif params->bytes_per_line = params->pixels_per_line; } @@ -6526,70 +6816,6 @@ update_u_params (struct fujitsu * s) return ret; } -/* make backup of param data, in case original is overwritten */ -SANE_Status -backup_params (struct fujitsu * s) -{ - SANE_Status ret = SANE_STATUS_GOOD; - SANE_Parameters * params = &(s->s_params); - SANE_Parameters * params_bk = &(s->s_params_bk); - - DBG (10, "backup_params: start\n"); - - params_bk->format = params->format; - params_bk->last_frame = params->last_frame; - params_bk->bytes_per_line = params->bytes_per_line; - params_bk->pixels_per_line = params->pixels_per_line; - params_bk->lines = params->lines; - params_bk->depth = params->depth; - - /* also have to save the user params */ - params = &(s->u_params); - params_bk = &(s->u_params_bk); - - params_bk->format = params->format; - params_bk->last_frame = params->last_frame; - params_bk->bytes_per_line = params->bytes_per_line; - params_bk->pixels_per_line = params->pixels_per_line; - params_bk->lines = params->lines; - params_bk->depth = params->depth; - - DBG (10, "backup_params: finish\n"); - return ret; -} - -/* restore backup of param data, in case original was overwritten */ -SANE_Status -restore_params (struct fujitsu * s) -{ - SANE_Status ret = SANE_STATUS_GOOD; - SANE_Parameters * params = &(s->s_params); - SANE_Parameters * params_bk = &(s->s_params_bk); - - DBG (10, "restore_params: start\n"); - - params->format = params_bk->format; - params->last_frame = params_bk->last_frame; - params->bytes_per_line = params_bk->bytes_per_line; - params->pixels_per_line = params_bk->pixels_per_line; - params->lines = params_bk->lines; - params->depth = params_bk->depth; - - /* also have to restore the user params */ - params = &(s->u_params); - params_bk = &(s->u_params_bk); - - params->format = params_bk->format; - params->last_frame = params_bk->last_frame; - params->bytes_per_line = params_bk->bytes_per_line; - params->pixels_per_line = params_bk->pixels_per_line; - params->lines = params_bk->lines; - params->depth = params_bk->depth; - - DBG (10, "restore_params: finish\n"); - return ret; -} - /* * Called by SANE when a page acquisition operation is to be started. * commands: scanner control (lampon), send (lut), send (dither), @@ -6700,6 +6926,13 @@ sane_start (SANE_Handle handle) if (ret != SANE_STATUS_GOOD) DBG (5, "sane_start: WARNING: cannot send_endorser %d\n", ret); + /* send lut if scanner has no hardware brightness/contrast */ + if (!s->late_lut && (!s->brightness_steps || !s->contrast_steps)){ + ret = send_lut(s); + if (ret != SANE_STATUS_GOOD) + DBG (5, "sane_start: WARNING: cannot early send_lut %d\n", ret); + } + /* set window command */ ret = set_window(s); if (ret != SANE_STATUS_GOOD) { @@ -6708,10 +6941,10 @@ sane_start (SANE_Handle handle) } /* send lut if scanner has no hardware brightness/contrast */ - if (!s->brightness_steps || !s->contrast_steps){ + if (s->late_lut && (!s->brightness_steps || !s->contrast_steps)){ ret = send_lut(s); if (ret != SANE_STATUS_GOOD) - DBG (5, "sane_start: WARNING: cannot send_lut %d\n", ret); + DBG (5, "sane_start: WARNING: cannot late send_lut %d\n", ret); } /* some scanners need the q table sent, even when not scanning jpeg */ @@ -6721,20 +6954,6 @@ sane_start (SANE_Handle handle) DBG (5, "sane_start: WARNING: cannot send_q_table %d\n", ret); } - /* try to read scan size from scanner */ - ret = get_pixelsize(s,0); - if (ret != SANE_STATUS_GOOD) { - DBG (5, "sane_start: ERROR: cannot get pixelsize\n"); - goto errors; - } - - /* make backup copy of params because later functions overwrite */ - ret = backup_params(s); - if (ret != SANE_STATUS_GOOD) { - DBG (5, "sane_start: ERROR: cannot backup params\n"); - goto errors; - } - /* start/stop endorser */ ret = endorser(s); if (ret != SANE_STATUS_GOOD) { @@ -6747,19 +6966,23 @@ sane_start (SANE_Handle handle) if (ret != SANE_STATUS_GOOD) { DBG (5, "sane_start: WARNING: cannot start lamp, ignoring\n"); } + + /* iX500 errors if op is called with no paper + * at the beginning of a batch, so we check */ + if(s->hopper_before_op && s->source != SOURCE_FLATBED){ + ret = get_hardware_status(s,0); + if(!s->hw_hopper){ + ret = SANE_STATUS_NO_DOCS; + DBG (5, "sane_start: ERROR: hopper empty\n"); + goto errors; + } + } } /* if already running, duplex needs to switch sides */ else if(s->source == SOURCE_ADF_DUPLEX){ s->side = !s->side; } - /* restore backup copy of params at the start of each image */ - ret = restore_params(s); - if (ret != SANE_STATUS_GOOD) { - DBG (5, "sane_start: ERROR: cannot restore params\n"); - goto errors; - } - /* set clean defaults with new sheet of paper */ /* dont reset the transfer vars on backside of duplex page */ /* otherwise buffered back page will be lost */ @@ -6788,11 +7011,30 @@ sane_start (SANE_Handle handle) s->buff_tx[1]=0; /* reset jpeg just in case... */ - s->jpeg_stage = JPEG_STAGE_HEAD; - s->jpeg_ff_offset = 0; + s->jpeg_stage = JPEG_STAGE_NONE; + s->jpeg_ff_offset = -1; s->jpeg_front_rst = 0; s->jpeg_back_rst = 0; + ret = object_position (s, OP_Feed); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot load page\n"); + goto errors; + } + + ret = start_scan (s); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot start_scan\n"); + goto errors; + } + + /* try to read scan size from scanner */ + ret = get_pixelsize(s,0); + if (ret != SANE_STATUS_GOOD) { + DBG (5, "sane_start: ERROR: cannot get pixelsize\n"); + goto errors; + } + /* store the number of front bytes */ if ( s->source != SOURCE_ADF_BACK ){ s->bytes_tot[SIDE_FRONT] = s->s_params.bytes_per_line * s->s_params.lines; @@ -6840,16 +7082,12 @@ sane_start (SANE_Handle handle) s->started=1; } - - ret = object_position (s, SANE_TRUE); - if (ret != SANE_STATUS_GOOD) { - DBG (5, "sane_start: ERROR: cannot load page\n"); - goto errors; - } - - ret = start_scan (s); + } + else{ + /* try to read scan size from scanner */ + ret = get_pixelsize(s,0); if (ret != SANE_STATUS_GOOD) { - DBG (5, "sane_start: ERROR: cannot start_scan\n"); + DBG (5, "sane_start: ERROR: cannot get pixelsize\n"); goto errors; } } @@ -7249,13 +7487,11 @@ set_window (struct fujitsu *s) set_WD_compress_type(desc1, COMP_NONE); set_WD_compress_arg(desc1, 0); -#ifdef SANE_FRAME_JPEG /* some scanners support jpeg image compression, for color/gs only */ if(s->s_params.format == SANE_FRAME_JPEG){ set_WD_compress_type(desc1, COMP_JPEG); set_WD_compress_arg(desc1, s->compress_arg); } -#endif /* the remainder of the block varies based on model and mode, * except for gamma and paper size, those are in the same place */ @@ -7435,7 +7671,8 @@ get_pixelsize(struct fujitsu *s, int actual) memset(cmd,0,cmdLen); set_SCSI_opcode(cmd, READ_code); set_R_datatype_code (cmd, R_datatype_pixelsize); - if(s->source == SOURCE_ADF_BACK){ + + if(s->side == SIDE_BACK){ set_R_window_id (cmd, WD_wid_back); } else{ @@ -7453,7 +7690,7 @@ get_pixelsize(struct fujitsu *s, int actual) /* when we are called post-scan, the scanner may give * more accurate data in other fields */ - if(actual && get_PSIZE_paper_w(in)){ + if(actual && !s->has_short_pixelsize && get_PSIZE_paper_w(in)){ s->s_params.pixels_per_line = get_PSIZE_paper_w(in); DBG(5,"get_pixelsize: Actual width\n"); } @@ -7469,7 +7706,7 @@ get_pixelsize(struct fujitsu *s, int actual) } /* when we are called post-scan, the scanner may give * more accurate data in other fields */ - else if(actual && get_PSIZE_paper_l(in)){ + else if(actual && !s->has_short_pixelsize && get_PSIZE_paper_l(in)){ s->s_params.lines = get_PSIZE_paper_l(in); DBG(5,"get_pixelsize: Actual length\n"); } @@ -7489,7 +7726,7 @@ get_pixelsize(struct fujitsu *s, int actual) } /* some scanners can request that the driver clean img */ - if(get_PSIZE_req_driv_valid(in)){ + if(!s->has_short_pixelsize && get_PSIZE_req_driv_valid(in)){ s->req_driv_crop = get_PSIZE_req_driv_crop(in); s->req_driv_lut = get_PSIZE_req_driv_lut(in); DBG(5,"get_pixelsize: scanner requests: crop=%d, lut=%d\n", @@ -7530,38 +7767,23 @@ get_pixelsize(struct fujitsu *s, int actual) * Issues the SCSI OBJECT POSITION command if an ADF is in use. */ static SANE_Status -object_position (struct fujitsu *s, int i_load) +object_position (struct fujitsu *s, int action) { SANE_Status ret = SANE_STATUS_GOOD; unsigned char cmd[OBJECT_POSITION_len]; size_t cmdLen = OBJECT_POSITION_len; - DBG (10, "object_position: start\n"); + DBG (10, "object_position: start %d\n", action); - if (s->source == SOURCE_FLATBED) { + if (s->source == SOURCE_FLATBED && action < OP_Halt) { DBG (10, "object_position: flatbed no-op\n"); return SANE_STATUS_GOOD; } - if(s->hopper_before_op && i_load){ - ret = get_hardware_status(s,0); - if(!s->hw_hopper){ - return SANE_STATUS_NO_DOCS; - } - } - memset(cmd,0,cmdLen); set_SCSI_opcode(cmd, OBJECT_POSITION_code); - - if (i_load) { - DBG (15, "object_position: load\n"); - set_OP_autofeed (cmd, OP_Feed); - } - else { - DBG (15, "object_position: eject\n"); - set_OP_autofeed (cmd, OP_Discharge); - } + set_OP_action (cmd, action); ret = do_cmd ( s, 1, 0, @@ -7630,22 +7852,30 @@ check_for_cancel(struct fujitsu *s) { SANE_Status ret=SANE_STATUS_GOOD; - DBG (10, "check_for_cancel: start\n"); + DBG (10, "check_for_cancel: start %d %d\n",s->started,s->cancelled); if(s->started && s->cancelled){ - DBG (15, "check_for_cancel: cancelling\n"); - /* cancel scan */ + /* halt scan */ + if(s->halt_on_cancel){ + DBG (15, "check_for_cancel: halting\n"); + ret = object_position (s, OP_Halt); + } + /* cancel scan */ + else{ + DBG (15, "check_for_cancel: cancelling\n"); ret = scanner_control(s, SC_function_cancel); - if (ret == SANE_STATUS_GOOD) { - ret = SANE_STATUS_CANCELLED; - } - else{ - DBG (5, "check_for_cancel: ERROR: cannot cancel\n"); - } + } + + if (ret == SANE_STATUS_GOOD || ret == SANE_STATUS_CANCELLED) { + ret = SANE_STATUS_CANCELLED; + } + else{ + DBG (5, "check_for_cancel: ERROR: cannot cancel\n"); + } - s->started = 0; - s->cancelled = 0; + s->started = 0; + s->cancelled = 0; } else if(s->cancelled){ DBG (15, "check_for_cancel: already cancelled\n"); @@ -7723,7 +7953,6 @@ sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int * len } } /* end 3091 */ -#ifdef SANE_FRAME_JPEG /* alternating jpeg duplex interlacing */ else if(s->source == SOURCE_ADF_DUPLEX && s->s_params.format == SANE_FRAME_JPEG @@ -7735,11 +7964,10 @@ sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int * len return ret; } } /* end alt jpeg */ -#endif /* alternating pnm duplex interlacing */ else if(s->source == SOURCE_ADF_DUPLEX - && s->s_params.format <= SANE_FRAME_RGB + && s->s_params.format != SANE_FRAME_JPEG && s->duplex_interlace == DUPLEX_INTERLACE_ALT ){ @@ -7808,7 +8036,33 @@ sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int * len return ret; } -#ifdef SANE_FRAME_JPEG +/* bare jpeg images dont contain resolution, but JFIF APP0 does, so we add */ +static SANE_Status +inject_jfif_header(struct fujitsu *s, int side) +{ + SANE_Status ret=SANE_STATUS_GOOD; + + unsigned char out[] = { + 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, + 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, + 0x00, 0x00 + }; + size_t outLen=JFIF_APP0_LENGTH; + + DBG (10, "inject_jfif_header: start %d\n", side); + + putnbyte(out + 12, s->resolution_x, 2); + putnbyte(out + 14, s->resolution_y, 2); + + memcpy(s->buffers[side]+s->buff_rx[side], out, outLen); + s->buff_rx[side] += outLen; + s->bytes_rx[side] += outLen; + + DBG (10, "inject_jfif_header: finish %d\n", ret); + + return ret; +} + static SANE_Status read_from_JPEGduplex(struct fujitsu *s) { @@ -7834,13 +8088,21 @@ read_from_JPEGduplex(struct fujitsu *s) * so we only get enough to fill whichever is smaller (and not yet done) */ if(!s->eof_rx[SIDE_FRONT]){ int avail = s->buff_tot[SIDE_FRONT] - s->buff_rx[SIDE_FRONT]; - if(bytes > avail) + if(bytes > avail){ bytes = avail; + /* leave space for JFIF header at start of image */ + if(s->bytes_rx[SIDE_FRONT] < 2) + bytes -= JFIF_APP0_LENGTH; + } } if(!s->eof_rx[SIDE_BACK]){ int avail = s->buff_tot[SIDE_BACK] - s->buff_rx[SIDE_BACK]; - if(bytes > avail) + if(bytes > avail){ bytes = avail; + /* leave space for JFIF header at start of image */ + if(s->bytes_rx[SIDE_BACK] < 2) + bytes -= JFIF_APP0_LENGTH; + } } DBG(15, "read_from_JPEGduplex: fto:%d frx:%d bto:%d brx:%d pa:%d\n", @@ -7901,7 +8163,7 @@ read_from_JPEGduplex(struct fujitsu *s) for(i=0;i<(int)inLen;i++){ /* about to change stage */ - if(in[i] == 0xff){ + if(in[i] == 0xff && s->jpeg_ff_offset != 0){ s->jpeg_ff_offset=0; continue; } @@ -7909,9 +8171,21 @@ read_from_JPEGduplex(struct fujitsu *s) /* last byte was an ff, this byte will change stage */ if(s->jpeg_ff_offset == 0){ - /* headers (SOI/HuffTab/QTab/DRI), in both sides */ - if(in[i] == 0xd8 || in[i] == 0xc4 - || in[i] == 0xdb || in[i] == 0xdd){ + /* first marker after SOI is not APP0, add one */ + if(s->jpeg_stage == JPEG_STAGE_SOI && in[i] != 0xe0){ + inject_jfif_header(s,SIDE_FRONT); + inject_jfif_header(s,SIDE_BACK); + s->jpeg_stage = JPEG_STAGE_HEAD; + } + + /* SOI header, in both sides */ + if(in[i] == 0xd8){ + s->jpeg_stage = JPEG_STAGE_SOI; + DBG(15, "read_from_JPEGduplex: stage SOI\n"); + } + + /* headers (HuffTab/QTab/DRI), in both sides */ + else if(in[i] == 0xc4 || in[i] == 0xdb || in[i] == 0xdd){ s->jpeg_stage = JPEG_STAGE_HEAD; DBG(15, "read_from_JPEGduplex: stage head\n"); } @@ -7972,6 +8246,11 @@ read_from_JPEGduplex(struct fujitsu *s) s->jpeg_stage = JPEG_STAGE_EOI; DBG(15, "read_from_JPEGduplex: stage eoi %d %d\n",(int)inLen,i); } + + /* unknown, warn */ + else if(in[i] != 0xff){ + DBG(15, "read_from_JPEGduplex: unknown %02x\n", in[i]); + } } s->jpeg_ff_offset++; @@ -8025,7 +8304,8 @@ read_from_JPEGduplex(struct fujitsu *s) } /* copy these stages to front */ - if(s->jpeg_stage == JPEG_STAGE_HEAD + if(s->jpeg_stage == JPEG_STAGE_SOI + || s->jpeg_stage == JPEG_STAGE_HEAD || s->jpeg_stage == JPEG_STAGE_SOF || s->jpeg_stage == JPEG_STAGE_SOS || s->jpeg_stage == JPEG_STAGE_EOI @@ -8043,7 +8323,8 @@ read_from_JPEGduplex(struct fujitsu *s) /* copy these stages to back */ if( s->jpeg_interlace == JPEG_INTERLACE_ALT && - ( s->jpeg_stage == JPEG_STAGE_HEAD + ( s->jpeg_stage == JPEG_STAGE_SOI + || s->jpeg_stage == JPEG_STAGE_HEAD || s->jpeg_stage == JPEG_STAGE_SOF || s->jpeg_stage == JPEG_STAGE_SOS || s->jpeg_stage == JPEG_STAGE_EOI @@ -8086,7 +8367,6 @@ read_from_JPEGduplex(struct fujitsu *s) return ret; } -#endif static SANE_Status read_from_3091duplex(struct fujitsu *s) @@ -8267,6 +8547,10 @@ read_from_scanner(struct fujitsu *s, int side) if(bytes % 2 && bytes < remain){ bytes -= s->s_params.bytes_per_line; } + + /* jpeg scans leave space for JFIF header at start of image */ + if(s->s_params.format == SANE_FRAME_JPEG && s->bytes_rx[side] < 2) + bytes -= JFIF_APP0_LENGTH; DBG(15, "read_from_scanner: si:%d re:%d bs:%d by:%d av:%d\n", side, remain, s->buffer_size, bytes, avail); @@ -8341,6 +8625,9 @@ read_from_scanner(struct fujitsu *s, int side) if(s->s_mode==MODE_COLOR && s->color_interlace == COLOR_INTERLACE_3091){ copy_3091 (s, in, inLen, side); } + else if(s->s_params.format == SANE_FRAME_JPEG){ + copy_JPEG (s, in, inLen, side); + } else{ copy_buffer (s, in, inLen, side); } @@ -8453,6 +8740,48 @@ copy_3091(struct fujitsu *s, unsigned char * buf, int len, int side) return ret; } +static SANE_Status +copy_JPEG(struct fujitsu *s, unsigned char * buf, int len, int side) +{ + SANE_Status ret=SANE_STATUS_GOOD; + int i, seen = 0; + + DBG (10, "copy_JPEG: start\n"); + + /* A jpeg image starts with the SOI marker, FF D8. + * This is optionally followed by the JFIF APP0 + * marker, FF E0. If that marker is not present, + * we add it, so we can insert the resolution */ + + if(!s->bytes_rx[side] && len >= 4 + && buf[0] == 0xFF && buf[1] == 0xD8 + && buf[2] == 0xFF && buf[3] != 0xE0 + ){ + /* SOI marker */ + for (i=0; i<2; i++){ + s->buffers[side][s->buff_rx[side]++] = buf[i]; + s->bytes_rx[side]++; + seen++; + } + + /* JFIF header after SOI */ + inject_jfif_header(s,side); + } + + memcpy(s->buffers[side]+s->buff_rx[side],buf+seen,len-seen); + s->buff_rx[side] += len-seen; + s->bytes_rx[side] += len-seen; + + /* should never happen with jpeg */ + if(s->bytes_rx[side] == s->bytes_tot[side]){ + s->eof_rx[side] = 1; + } + + DBG (10, "copy_JPEG: finish\n"); + + return ret; +} + static SANE_Status copy_buffer(struct fujitsu *s, unsigned char * buf, int len, int side) { @@ -8465,7 +8794,7 @@ copy_buffer(struct fujitsu *s, unsigned char * buf, int len, int side) /* invert image if scanner needs it for this mode */ /* jpeg data does not use inverting */ - if(s->s_params.format <= SANE_FRAME_RGB && s->reverse_by_mode[s->s_mode]){ + if(s->s_params.format != SANE_FRAME_JPEG && s->reverse_by_mode[s->s_mode]){ for(i=0; ifd, cmdBuff, cmdLen, outBuff, outLen, inBuff, inLen); @@ -9408,9 +9787,7 @@ must_fully_buffer(struct fujitsu *s) if( (s->swdeskew || s->swdespeck || s->swcrop || s->swskip) -#ifdef SANE_FRAME_JPEG && s->s_params.format != SANE_FRAME_JPEG -#endif ){ return 1; } @@ -9424,9 +9801,7 @@ static int must_downsample(struct fujitsu *s) { if(s->s_mode != s->u_mode -#ifdef SANE_FRAME_JPEG && s->compress != COMP_JPEG -#endif ){ return 1; } @@ -9492,6 +9867,23 @@ get_page_height(struct fujitsu *s) return height; } +/* s->max_y gives the maximum height of paper which can be scanned + * this actually varies by resolution, so a helper to change it */ +static int +set_max_y(struct fujitsu *s) +{ + int i; + + for(i=0;i<4;i++){ + if(!s->max_y_by_res[i].res) + break; + if(s->resolution_x <= s->max_y_by_res[i].res){ + s->max_y = s->max_y_by_res[i].len; + } + } + + return s->max_y; +} /** * Convenience method to determine longest string size in a list. @@ -9561,7 +9953,8 @@ hexdump (int level, char *comment, unsigned char *p, int l) } /* print last (partial) line */ - DBG (level, "%s\n", line); + if (i) + DBG (level, "%s\n", line); } /** @@ -9654,37 +10047,23 @@ buffer_crop(struct fujitsu *s, int side) DBG (10, "buffer_crop: start\n"); - /*only find edges on first image from a page, or if first image had error */ - if(s->side == SIDE_FRONT || s->source == SOURCE_ADF_BACK || s->crop_stat){ - - s->crop_stat = sanei_magic_findEdges( - &s->s_params,s->buffers[side],s->resolution_x,s->resolution_y, - &s->crop_vals[0],&s->crop_vals[1],&s->crop_vals[2],&s->crop_vals[3]); - - if(s->crop_stat){ - DBG (5, "buffer_crop: bad edges, bailing\n"); - goto cleanup; - } - - DBG (15, "buffer_crop: t:%d b:%d l:%d r:%d\n", - s->crop_vals[0],s->crop_vals[1],s->crop_vals[2],s->crop_vals[3]); - - /* we dont listen to the 'top' value, since fujitsu does not pad the top */ - s->crop_vals[0] = 0; + ret = sanei_magic_findEdges( + &s->s_params,s->buffers[side],s->resolution_x,s->resolution_y, + &s->crop_vals[0],&s->crop_vals[1],&s->crop_vals[2],&s->crop_vals[3]); - /* if we will later binarize this image, make sure the width - * is a multiple of 8 pixels, by adjusting the right side */ - if ( must_downsample(s) && s->u_mode < MODE_GRAYSCALE ){ - s->crop_vals[3] -= (s->crop_vals[3]-s->crop_vals[2]) % 8; - } + if(ret){ + DBG (5, "buffer_crop: bad edges, bailing\n"); + ret = SANE_STATUS_GOOD; + goto cleanup; } - /* backside images can use a 'flipped' version of frontside data */ - else{ - int left = s->crop_vals[2]; - int right = s->crop_vals[3]; + + DBG (15, "buffer_crop: t:%d b:%d l:%d r:%d\n", + s->crop_vals[0],s->crop_vals[1],s->crop_vals[2],s->crop_vals[3]); - s->crop_vals[2] = s->s_params.pixels_per_line - right; - s->crop_vals[3] = s->s_params.pixels_per_line - left; + /* if we will later binarize this image, make sure the width + * is a multiple of 8 pixels, by adjusting the right side */ + if ( must_downsample(s) && s->u_mode < MODE_GRAYSCALE ){ + s->crop_vals[3] -= (s->crop_vals[3]-s->crop_vals[2]) % 8; } /* now crop the image */ -- cgit v1.2.3