diff options
Diffstat (limited to 'backend/pieusb_specific.c')
-rw-r--r-- | backend/pieusb_specific.c | 2509 |
1 files changed, 2509 insertions, 0 deletions
diff --git a/backend/pieusb_specific.c b/backend/pieusb_specific.c new file mode 100644 index 0000000..8231075 --- /dev/null +++ b/backend/pieusb_specific.c @@ -0,0 +1,2509 @@ +/* sane - Scanner Access Now Easy. + + pieusb_specific.c + + Copyright (C) 2012-2015 Jan Vleeshouwers, Michael Rickmann, Klaus Kaempf + + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. */ + +/* ========================================================================= + * + * Various Pieusb backend specific functions + * + * Option handling, configuration file handling, post-processing + * + * ========================================================================= */ + +#define DEBUG_DECLARE_ONLY +#include "pieusb.h" + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include "../include/sane/sane.h" +#include "../include/sane/saneopts.h" +#include "../include/sane/sanei_config.h" + +#include <errno.h> +#include <math.h> +#include <time.h> + +#include "pieusb_usb.h" +#include "pieusb_scancmd.h" +#include "pieusb_buffer.h" +#include "pieusb_specific.h" + +/* Pieusb specific */ + +/* sub to sanei_pieusb_find_device_callback() */ +static SANE_Status pieusb_initialize_device_definition (Pieusb_Device_Definition* dev, Pieusb_Scanner_Properties* inq, const char* devicename, SANE_Word vendor_id, SANE_Word product_id); +static void pieusb_print_inquiry (Pieusb_Device_Definition * dev); + +/* sub to sane_start() */ +static void pieusb_calculate_shading(struct Pieusb_Scanner *scanner, SANE_Byte* buffer); + +/* MR */ +/* sub to sanei_pieusb_post() */ +static SANE_Status pieusb_write_pnm_file (char *filename, uint16_t *data, int depth, int channels, int pixels_per_line, int lines); + +/* Auxilary */ +static size_t max_string_size (SANE_String_Const const strings[]); +static double getGain(int gain); +static int getGainSetting(double gain); +/* +static void updateGain(Pieusb_Scanner *scanner, int color_index); +*/ +static void updateGain2(Pieusb_Scanner *scanner, int color_index, double gain_increase); + +/* -------------------------------------------------------------------------- + * + * SPECIFIC PIEUSB + * + * --------------------------------------------------------------------------*/ + +/* Settings for byte order */ +#define SCAN_IMG_FMT_OKLINE 0x08 +#define SCAN_IMG_FMT_BLK_ONE 0x04 +#define SCAN_IMG_FMT_MOTOROLA 0x02 +#define SCAN_IMG_FMT_INTEL 0x01 + +/* Settings for scanner capabilities */ +#define SCAN_CAP_PWRSAV 0x80 +#define SCAN_CAP_EXT_CAL 0x40 +#define SCAN_CAP_FAST_PREVIEW 0x10 +#define SCAN_CAP_DISABLE_CAL 0x08 +#define SCAN_CAP_SPEEDS 0x07 + +/* Available scanner options */ +#define SCAN_OPT_DEV_MPCL 0x80 +#define SCAN_OPT_DEV_TP1 0x04 +#define SCAN_OPT_DEV_TP 0x02 +#define SCAN_OPT_DEV_ADF 0x01 + +/* Options */ +#define SANE_NAME_EXPOSURE_R "exposure-time-r" +#define SANE_TITLE_EXPOSURE_R "Exposure time red" +#define SANE_DESC_EXPOSURE_R "The time the red color filter of the CCD is exposed" +#define SANE_NAME_EXPOSURE_G "exposure-time-g" +#define SANE_TITLE_EXPOSURE_G "Exposure time green" +#define SANE_DESC_EXPOSURE_G "The time the green color filter of the CCD is exposed" +#define SANE_NAME_EXPOSURE_B "exposure-time-b" +#define SANE_TITLE_EXPOSURE_B "Exposure time blue" +#define SANE_DESC_EXPOSURE_B "The time the blue color filter of the CCD is exposed" +#define SANE_NAME_EXPOSURE_I "exposure-time-i" +#define SANE_TITLE_EXPOSURE_I "Exposure time infrared" +#define SANE_DESC_EXPOSURE_I "The time the infrared color filter of the CCD is exposed" +#define SANE_EXPOSURE_DEFAULT DEFAULT_EXPOSURE +#if 1 +#define SANE_NAME_GAIN_R "gain-r" +#define SANE_TITLE_GAIN_R "Gain red" +#define SANE_DESC_GAIN_R "The gain of the signal processor for red" +#define SANE_NAME_GAIN_G "gain-g" +#define SANE_TITLE_GAIN_G "Gain green" +#define SANE_DESC_GAIN_G "The gain of the signal processor for green" +#define SANE_NAME_GAIN_B "gain-b" +#define SANE_TITLE_GAIN_B "Gain blue" +#define SANE_DESC_GAIN_B "The gain of the signal processor for blue" +#define SANE_NAME_GAIN_I "gain-i" +#define SANE_TITLE_GAIN_I "Gain infrared" +#define SANE_DESC_GAIN_I "The gain of the signal processor for infrared" +#define SANE_GAIN_DEFAULT DEFAULT_GAIN + +#define SANE_NAME_OFFSET_R "offset-r" +#define SANE_TITLE_OFFSET_R "Offset red" +#define SANE_DESC_OFFSET_R "The offset of the signal processor for red" +#define SANE_NAME_OFFSET_G "offset-g" +#define SANE_TITLE_OFFSET_G "Offset greed" +#define SANE_DESC_OFFSET_G "The offset of the signal processor for green" +#define SANE_NAME_OFFSET_B "offset-b" +#define SANE_TITLE_OFFSET_B "Offset blue" +#define SANE_DESC_OFFSET_B "The offset of the signal processor for blue" +#define SANE_NAME_OFFSET_I "offset-i" +#define SANE_TITLE_OFFSET_I "Offset infrared" +#define SANE_DESC_OFFSET_I "The offset of the signal processor for infrared" +#define SANE_OFFSET_DEFAULT DEFAULT_OFFSET +#else +#define SANE_NAME_GAIN "gain" +#define SANE_TITLE_GAIN "Gain" +#define SANE_DESC_GAIN "The gain of the signal processor for the 4 CCD color filters (R,G,B,I)" +#define SANE_GAIN_DEFAULT 0x13 + +#define SANE_NAME_OFFSET "offset" +#define SANE_TITLE_OFFSET "Offset" +#define SANE_DESC_OFFSET "The offset of the signal processor for the 4 CCD color filters (R,G,B,I)" +#define SANE_OFFSET_DEFAULT 0 +#endif +#define min(a,b) (((a)<(b))?(a):(b)) +#define max(a,b) (((a)>(b))?(a):(b)) + +static const SANE_Range percentage_range_100 = { + 0 << SANE_FIXED_SCALE_SHIFT, /* minimum */ + 100 << SANE_FIXED_SCALE_SHIFT, /* maximum */ + 0 << SANE_FIXED_SCALE_SHIFT /* quantization */ +}; + +/* From the firmware disassembly */ +static const SANE_Range gain_range = { + 0, /* minimum */ + 63, /* maximum */ + 0 /* quantization */ +}; + +/* From the firmware disassembly */ +static const SANE_Range offset_range = { + 0, /* minimum */ + 255, /* maximum */ + 0 /* quantization */ +}; + +static const double gains[] = { +1.000, 1.075, 1.154, 1.251, 1.362, 1.491, 1.653, /* 0, 5, 10, 15, 20, 25, 30 */ +1.858, 2.115, 2.458, 2.935, 3.638, 4.627 /* 35, 40, 45, 50, 55, 60 */ +}; + +/** + * Callback called whenever a connected USB device reports a supported vendor + * and product id combination. + * Used by sane_init() and by sane_open(). + * + * @param name Device name which has required vendor and product id + * @return SANE_STATUS_GOOD + */ +SANE_Status +sanei_pieusb_find_device_callback (const char *devicename) +{ + struct Pieusb_Command_Status status; + SANE_Status r; + Pieusb_Device_Definition *dev; + int device_number; /* index in usb devices list maintained by sani_usb */ + Pieusb_Scanner_Properties inq; + int retry; + + DBG (DBG_info_proc, "sanei_pieusb_find_device_callback: %s\n", devicename); + + /* Check if device is present in the Pieusb device list */ + for (dev = pieusb_definition_list_head; dev; dev = dev->next) { + if (strcmp (dev->sane.name, devicename) == 0) { + return SANE_STATUS_GOOD; + } + } + + /* If not, create a new device struct */ + dev = malloc (sizeof (*dev)); + if (!dev) { + return SANE_STATUS_NO_MEM; + } + + /* Get device number: index of the device in the sanei_usb devices list */ + r = sanei_usb_open (devicename, &device_number); + if (r != SANE_STATUS_GOOD) { + free (dev); + DBG (DBG_error, "sanei_pieusb_find_device_callback: sanei_usb_open failed for device %s: %s\n",devicename,sane_strstatus(r)); + return r; + } + + /* Get device properties */ + + retry = 2; + while (retry > 0) { + retry--; + /* get inquiry data length */ + sanei_pieusb_cmd_inquiry (device_number, &inq, 5, &status); + if (status.pieusb_status == PIEUSB_STATUS_GOOD) { + break; + } + else if (status.pieusb_status == PIEUSB_STATUS_IO_ERROR) { + if (retry > 0) { + DBG (DBG_info_proc, "inquiry failed, resetting usb\n"); + if (sanei_pieusb_usb_reset(device_number) == SANE_STATUS_GOOD) { + continue; /* retry after IEEE1284 reset */ + } + if (sanei_usb_reset(device_number) == SANE_STATUS_GOOD) { + continue; /* retry after USB reset */ + } + } + } + free (dev); + DBG (DBG_error, "sanei_pieusb_find_device_callback: get scanner properties (5 bytes) failed with %d\n", status.pieusb_status); + sanei_usb_close (device_number); + return status.pieusb_status; + } + /* get full inquiry data */ + sanei_pieusb_cmd_inquiry(device_number, &inq, inq.additionalLength+4, &status); + if (status.pieusb_status != PIEUSB_STATUS_GOOD) { + free (dev); + DBG (DBG_error, "sanei_pieusb_find_device_callback: get scanner properties failed\n"); + sanei_usb_close (device_number); + return status.pieusb_status; + } + + /* Close the device again */ + sanei_usb_close(device_number); + + /* Initialize device definition */ + r = pieusb_initialize_device_definition(dev, &inq, devicename, pieusb_supported_usb_device.vendor, pieusb_supported_usb_device.product); + if (r != SANE_STATUS_GOOD) { + return r; + } + + /* Output */ + pieusb_print_inquiry (dev); + + /* Check model number */ + if (inq.model != pieusb_supported_usb_device.model) { + free (dev); + DBG (DBG_error, "sanei_pieusb_find_device_callback: wrong model number %d\n", inq.model); + return SANE_STATUS_INVAL; + } + + /* Found a supported scanner, put it in the definitions list*/ + DBG (DBG_info_proc, "sanei_pieusb_find_device_callback: success\n"); + dev->next = pieusb_definition_list_head; + pieusb_definition_list_head = dev; + return SANE_STATUS_GOOD; +} + +/** + * Full initialization of a Pieusb_Device structure from INQUIRY data. + * The function is used in find_device_callback(), so when sane_init() or + * sane_open() is called. + * + * @param dev + */ +static SANE_Status +pieusb_initialize_device_definition (Pieusb_Device_Definition* dev, Pieusb_Scanner_Properties* inq, const char* devicename, + SANE_Word vendor_id, SANE_Word product_id) +{ + char *pp, *buf; + + /* Initialize device definition */ + dev->next = NULL; + dev->sane.name = strdup(devicename); + + /* Create 0-terminated string without trailing spaces for vendor */ + buf = malloc(9); + if (buf == NULL) + return SANE_STATUS_NO_MEM; + strncpy(buf, inq->vendor, 8); + pp = buf + 8; + *pp-- = '\0'; + while (*pp == ' ') *pp-- = '\0'; + dev->sane.vendor = buf; + + /* Create 0-terminated string without trailing spaces for model */ + buf = malloc(17); + if (buf == NULL) + return SANE_STATUS_NO_MEM; + strncpy(buf, inq->product, 16); + pp = buf + 16; + *pp-- = '\0'; + while (*pp == ' ') *pp-- = '\0'; + dev->sane.model = buf; + + dev->sane.type = "film scanner"; + dev->vendorId = vendor_id; + dev->productId = product_id; + + /* Create 0-terminated strings without trailing spaces for revision */ + buf = malloc(5); + if (buf == NULL) + return SANE_STATUS_NO_MEM; + strncpy(buf, inq->productRevision, 4); + pp = buf + 4; + *pp-- = '\0'; + while (*pp == ' ') *pp-- = '\0'; + dev->version = buf; + + dev->model = inq->model; + + /* Maximum resolution values */ + dev->maximum_resolution_x = inq->maxResolutionX; + dev->maximum_resolution_y = inq->maxResolutionY; + if (dev->maximum_resolution_y < 256) { + /* y res is a multiplier */ + dev->maximum_resolution = dev->maximum_resolution_x; + dev->maximum_resolution_x *= dev->maximum_resolution_y; + dev->maximum_resolution_y = dev->maximum_resolution_x; + } else { + /* y res really is resolution */ + dev->maximum_resolution = min (dev->maximum_resolution_x, dev->maximum_resolution_y); + } + + /* Geometry */ + dev->scan_bed_width = (double) inq->maxScanWidth / dev->maximum_resolution; + dev->scan_bed_height = (double) inq->maxScanHeight / dev->maximum_resolution; + dev->slide_top_left_x = inq->x0; + dev->slide_top_left_y = inq->y0; + dev->slide_width = (double) (inq->x1 - inq->x0) / dev->maximum_resolution; + dev->slide_height = (double) (inq->y1 - inq->y0) / dev->maximum_resolution; + + /* Integer and bit-encoded properties */ + dev->halftone_patterns = inq->halftones & 0x0f; + dev->color_filters = inq->filters; + dev->color_depths = inq->colorDepths; + dev->color_formats = inq->colorFormat; + dev->image_formats = inq->imageFormat; + dev->scan_capabilities = inq->scanCapability; + dev->optional_devices = inq->optionalDevices; + dev->enhancements = inq->enhancements; + dev->gamma_bits = inq->gammaBits; + dev->fast_preview_resolution = inq->previewScanResolution; + dev->minimum_highlight = inq->minumumHighlight; + dev->maximum_shadow = inq->maximumShadow; + dev->calibration_equation = inq->calibrationEquation; + dev->minimum_exposure = inq->minimumExposure; + dev->maximum_exposure = inq->maximumExposure*4; /* *4 to solve the strange situation that the default value is out of range */ + + dev->x0 = inq->x0; + dev->y0 = inq->y0; + dev->x1 = inq->x1; + dev->y1 = inq->y1; + dev->production = strndup(inq->production, 4); + dev->timestamp = strndup(inq->timestamp, 20); + dev->signature = (char *)strndup((char *)inq->signature, 40); + + /* Ranges for various quantities */ + dev->x_range.min = SANE_FIX (0); + dev->x_range.quant = SANE_FIX (0); + dev->x_range.max = SANE_FIX (dev->scan_bed_width * MM_PER_INCH); + + dev->y_range.min = SANE_FIX (0); + dev->y_range.quant = SANE_FIX (0); + dev->y_range.max = SANE_FIX (dev->scan_bed_height * MM_PER_INCH); + + dev->dpi_range.min = SANE_FIX (25); + dev->dpi_range.quant = SANE_FIX (1); + dev->dpi_range.max = SANE_FIX (max (dev->maximum_resolution_x, dev->maximum_resolution_y)); + + dev->shadow_range.min = SANE_FIX (0); + dev->shadow_range.quant = SANE_FIX (1); + dev->shadow_range.max = SANE_FIX (dev->maximum_shadow); + + dev->highlight_range.min = SANE_FIX (dev->minimum_highlight); + dev->highlight_range.quant = SANE_FIX (1); + dev->highlight_range.max = SANE_FIX (100); + + dev->exposure_range.min = dev->minimum_exposure; + dev->exposure_range.quant = 1; + dev->exposure_range.max = dev->maximum_exposure; + + dev->dust_range.min = 0; + dev->dust_range.quant = 1; + dev->dust_range.max = 100; + + /* Enumerated ranges vor various quantities */ + /*TODO: create from inq->filters */ + dev->scan_mode_list[0] = SANE_VALUE_SCAN_MODE_LINEART; + dev->scan_mode_list[1] = SANE_VALUE_SCAN_MODE_HALFTONE; + dev->scan_mode_list[2] = SANE_VALUE_SCAN_MODE_GRAY; + dev->scan_mode_list[3] = SANE_VALUE_SCAN_MODE_COLOR; + dev->scan_mode_list[4] = SANE_VALUE_SCAN_MODE_RGBI; + dev->scan_mode_list[5] = 0; + + dev->calibration_mode_list[0] = SCAN_CALIBRATION_DEFAULT; + dev->calibration_mode_list[1] = SCAN_CALIBRATION_AUTO; + dev->calibration_mode_list[2] = SCAN_CALIBRATION_PREVIEW; + dev->calibration_mode_list[3] = SCAN_CALIBRATION_OPTIONS; + dev->calibration_mode_list[4] = 0; + + dev->gain_adjust_list[0] = SCAN_GAIN_ADJUST_03; + dev->gain_adjust_list[1] = SCAN_GAIN_ADJUST_05; + dev->gain_adjust_list[2] = SCAN_GAIN_ADJUST_08; + dev->gain_adjust_list[3] = SCAN_GAIN_ADJUST_10; + dev->gain_adjust_list[4] = SCAN_GAIN_ADJUST_12; + dev->gain_adjust_list[5] = SCAN_GAIN_ADJUST_16; + dev->gain_adjust_list[6] = SCAN_GAIN_ADJUST_19; + dev->gain_adjust_list[7] = SCAN_GAIN_ADJUST_24; + dev->gain_adjust_list[8] = SCAN_GAIN_ADJUST_30; + dev->gain_adjust_list[9] = 0; + + /*TODO: create from inq->colorDepths? Maybe not: didn't experiment with + * 4 and 12 bit depths. Don;t know how they behave. */ + dev->bpp_list[0] = 3; /* count */ + dev->bpp_list[1] = 1; + dev->bpp_list[2] = 8; + dev->bpp_list[3] = 16; + + /* Infrared */ + dev->ir_sw_list[0] = "None"; + dev->ir_sw_list[1] = "Reduce red overlap"; + dev->ir_sw_list[2] = "Remove dirt"; + dev->ir_sw_list[3] = 0; + + dev->grain_sw_list[0] = 4; + dev->grain_sw_list[1] = 0; + dev->grain_sw_list[2] = 1; + dev->grain_sw_list[3] = 2; + dev->grain_sw_list[4] = 3; + dev->grain_sw_list[5] = 0; + + dev->crop_sw_list[0] = "None"; + dev->crop_sw_list[1] = "Outside"; + dev->crop_sw_list[2] = "Inside"; + dev->crop_sw_list[3] = 0; + + /* halftone_list */ + dev->halftone_list[0] = "53lpi 45d ROUND"; /* 8x8 pattern */ + dev->halftone_list[1] = "70lpi 45d ROUND"; /* 6x6 pattern */ + dev->halftone_list[2] = "75lpi Hori. Line"; /* 4x4 pattern */ + dev->halftone_list[3] = "4X4 BAYER"; /* 4x4 pattern */ + dev->halftone_list[4] = "4X4 SCROLL"; /* 4x4 pattern */ + dev->halftone_list[5] = "5x5 26 Levels"; /* 5x5 pattern */ + dev->halftone_list[6] = "4x4 SQUARE"; /* 4x4 pattern */ + dev->halftone_list[7] = "5x5 TILE"; /* 5x5 pattern */ + dev->halftone_list[8] = 0; + + return SANE_STATUS_GOOD; +} + +/** + * Output device definition. + * The function is used in find_device_callback(), so when sane_init() or + * sane_open() is called. + * + * @param dev Device to output + */ +static void +pieusb_print_inquiry (Pieusb_Device_Definition * dev) +{ + DBG (DBG_inquiry, "INQUIRY:\n"); + DBG (DBG_inquiry, "========\n"); + DBG (DBG_inquiry, "\n"); + DBG (DBG_inquiry, "vendor........................: '%s'\n", dev->sane.vendor); + DBG (DBG_inquiry, "product.......................: '%s'\n", dev->sane.model); + DBG (DBG_inquiry, "model .......................: 0x%04x\n", dev->model); + DBG (DBG_inquiry, "version.......................: '%s'\n", dev->version); + + DBG (DBG_inquiry, "X resolution..................: %d dpi\n", + dev->maximum_resolution_x); + DBG (DBG_inquiry, "Y resolution..................: %d dpi\n", + dev->maximum_resolution_y); + DBG (DBG_inquiry, "pixel resolution..............: %d dpi\n", + dev->maximum_resolution); + DBG (DBG_inquiry, "fb width......................: %f in\n", + dev->scan_bed_width); + DBG (DBG_inquiry, "fb length.....................: %f in\n", + dev->scan_bed_height); + + DBG (DBG_inquiry, "transparency width............: %f in\n", + dev->slide_width); + DBG (DBG_inquiry, "transparency length...........: %f in\n", + dev->slide_height); + DBG (DBG_inquiry, "transparency offset...........: %d,%d\n", + dev->slide_top_left_x, dev->slide_top_left_y); + + DBG (DBG_inquiry, "# of halftones................: %d\n", + dev->halftone_patterns); + + DBG (DBG_inquiry, "One pass color................: %s\n", + dev->color_filters & SCAN_ONE_PASS_COLOR ? "yes" : "no"); + + DBG (DBG_inquiry, "Filters.......................: %s%s%s%s%s (%02x)\n", + dev->color_filters & SCAN_FILTER_INFRARED ? "Infrared " : "", + dev->color_filters & SCAN_FILTER_RED ? "Red " : "", + dev->color_filters & SCAN_FILTER_GREEN ? "Green " : "", + dev->color_filters & SCAN_FILTER_BLUE ? "Blue " : "", + dev->color_filters & SCAN_FILTER_NEUTRAL ? "Neutral " : "", + dev->color_filters); + + DBG (DBG_inquiry, "Color depths..................: %s%s%s%s%s%s (%02x)\n", + dev->color_depths & SCAN_COLOR_DEPTH_16 ? "16 bit " : "", + dev->color_depths & SCAN_COLOR_DEPTH_12 ? "12 bit " : "", + dev->color_depths & SCAN_COLOR_DEPTH_10 ? "10 bit " : "", + dev->color_depths & SCAN_COLOR_DEPTH_8 ? "8 bit " : "", + dev->color_depths & SCAN_COLOR_DEPTH_4 ? "4 bit " : "", + dev->color_depths & SCAN_COLOR_DEPTH_1 ? "1 bit " : "", + dev->color_depths); + + DBG (DBG_inquiry, "Color Format..................: %s%s%s (%02x)\n", + dev->color_formats & SCAN_COLOR_FORMAT_INDEX ? "Indexed " : "", + dev->color_formats & SCAN_COLOR_FORMAT_LINE ? "Line " : "", + dev->color_formats & SCAN_COLOR_FORMAT_PIXEL ? "Pixel " : "", + dev->color_formats); + + DBG (DBG_inquiry, "Image Format..................: %s%s%s%s (%02x)\n", + dev->image_formats & SCAN_IMG_FMT_OKLINE ? "OKLine " : "", + dev->image_formats & SCAN_IMG_FMT_BLK_ONE ? "BlackOne " : "", + dev->image_formats & SCAN_IMG_FMT_MOTOROLA ? "Motorola " : "", + dev->image_formats & SCAN_IMG_FMT_INTEL ? "Intel" : "", + dev->image_formats); + + DBG (DBG_inquiry, + "Scan Capability...............: %s%s%s%s%d speeds (%02x)\n", + dev->scan_capabilities & SCAN_CAP_PWRSAV ? "PowerSave " : "", + dev->scan_capabilities & SCAN_CAP_EXT_CAL ? "ExtCal " : "", + dev->scan_capabilities & SCAN_CAP_FAST_PREVIEW ? "FastPreview" : + "", + dev->scan_capabilities & SCAN_CAP_DISABLE_CAL ? "DisCal " : "", + dev->scan_capabilities & SCAN_CAP_SPEEDS, + dev->scan_capabilities); + + DBG (DBG_inquiry, "Optional Devices..............: %s%s%s%s (%02x)\n", + dev->optional_devices & SCAN_OPT_DEV_MPCL ? "MultiPageLoad " : + "", + dev->optional_devices & SCAN_OPT_DEV_TP1 ? "TransModule1 " : "", + dev->optional_devices & SCAN_OPT_DEV_TP ? "TransModule " : "", + dev->optional_devices & SCAN_OPT_DEV_ADF ? "ADF " : "", + dev->optional_devices); + + DBG (DBG_inquiry, "Enhancement...................: %02x\n", + dev->enhancements); + DBG (DBG_inquiry, "Gamma bits....................: %d\n", + dev->gamma_bits); + + DBG (DBG_inquiry, "Fast Preview Resolution.......: %d\n", + dev->fast_preview_resolution); + DBG (DBG_inquiry, "Min Highlight.................: %d\n", + dev->minimum_highlight); + DBG (DBG_inquiry, "Max Shadow....................: %d\n", + dev->maximum_shadow); + DBG (DBG_inquiry, "Cal Eqn.......................: %d\n", + dev->calibration_equation); + DBG (DBG_inquiry, "Min Exposure..................: %d\n", + dev->minimum_exposure); + DBG (DBG_inquiry, "Max Exposure..................: %d\n", + dev->maximum_exposure); + + DBG (DBG_inquiry, "x0,y0 x1,y1...................: %d,%d %d,%d\n", + dev->x0, dev->y0, dev->x1, dev->y1); + DBG (DBG_inquiry, "production....................: '%s'\n", + dev->production); + DBG (DBG_inquiry, "timestamp.....................: '%s'\n", + dev->timestamp); + DBG (DBG_inquiry, "signature.....................: '%s'\n", + dev->signature); + +} + +/** + * Initiaize scanner options from the device definition and from exposure, + * gain and offset defaults. The function is called by sane_open(), when no + * optimized settings are available yet. The scanner object is fully + * initialized in sane_start(). + * + * @param scanner Scanner to initialize + * @return SANE_STATUS_GOOD + */ +SANE_Status +sanei_pieusb_init_options (Pieusb_Scanner* scanner) +{ + int i; + + DBG (DBG_info_proc, "sanei_pieusb_init_options\n"); + + memset (scanner->opt, 0, sizeof (scanner->opt)); + memset (scanner->val, 0, sizeof (scanner->val)); + + for (i = 0; i < NUM_OPTIONS; ++i) { + scanner->opt[i].size = sizeof (SANE_Word); + scanner->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + } + + /* Number of options (a pseudo-option) */ + scanner->opt[OPT_NUM_OPTS].name = SANE_NAME_NUM_OPTIONS; + scanner->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS; + scanner->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS; + scanner->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT; + scanner->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT; + scanner->val[OPT_NUM_OPTS].w = NUM_OPTIONS; + + /* "Mode" group: */ + scanner->opt[OPT_MODE_GROUP].title = "Scan Mode"; + scanner->opt[OPT_MODE_GROUP].desc = ""; + scanner->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP; + scanner->opt[OPT_MODE_GROUP].cap = 0; + scanner->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* scan mode */ + scanner->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE; + scanner->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE; + scanner->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE; + scanner->opt[OPT_MODE].type = SANE_TYPE_STRING; + scanner->opt[OPT_MODE].size = max_string_size ((SANE_String_Const const *) scanner->device->scan_mode_list); + scanner->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + scanner->opt[OPT_MODE].constraint.string_list = (SANE_String_Const const *) scanner->device->scan_mode_list; + scanner->val[OPT_MODE].s = (SANE_Char *) strdup (scanner->device->scan_mode_list[3]); /* default RGB */ + + /* bit depth */ + scanner->opt[OPT_BIT_DEPTH].name = SANE_NAME_BIT_DEPTH; + scanner->opt[OPT_BIT_DEPTH].title = SANE_TITLE_BIT_DEPTH; + scanner->opt[OPT_BIT_DEPTH].desc = SANE_DESC_BIT_DEPTH; + scanner->opt[OPT_BIT_DEPTH].type = SANE_TYPE_INT; + scanner->opt[OPT_BIT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST; + scanner->opt[OPT_BIT_DEPTH].size = sizeof (SANE_Word); + scanner->opt[OPT_BIT_DEPTH].constraint.word_list = scanner->device->bpp_list; + scanner->val[OPT_BIT_DEPTH].w = scanner->device->bpp_list[2]; + + /* resolution */ + scanner->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION; + scanner->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION; + scanner->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION; + scanner->opt[OPT_RESOLUTION].type = SANE_TYPE_FIXED; + scanner->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI; + scanner->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE; + scanner->opt[OPT_RESOLUTION].constraint.range = &scanner->device->dpi_range; + scanner->val[OPT_RESOLUTION].w = scanner->device->fast_preview_resolution << SANE_FIXED_SCALE_SHIFT; + + /* halftone pattern */ + scanner->opt[OPT_HALFTONE_PATTERN].name = SANE_NAME_HALFTONE_PATTERN; + scanner->opt[OPT_HALFTONE_PATTERN].title = SANE_TITLE_HALFTONE_PATTERN; + scanner->opt[OPT_HALFTONE_PATTERN].desc = SANE_DESC_HALFTONE_PATTERN; + scanner->opt[OPT_HALFTONE_PATTERN].type = SANE_TYPE_STRING; + scanner->opt[OPT_HALFTONE_PATTERN].size = max_string_size ((SANE_String_Const const *) scanner->device->halftone_list); + scanner->opt[OPT_HALFTONE_PATTERN].constraint_type = SANE_CONSTRAINT_STRING_LIST; + scanner->opt[OPT_HALFTONE_PATTERN].constraint.string_list = (SANE_String_Const const *) scanner->device->halftone_list; + scanner->val[OPT_HALFTONE_PATTERN].s = (SANE_Char *) strdup (scanner->device->halftone_list[6]); + scanner->opt[OPT_HALFTONE_PATTERN].cap |= SANE_CAP_INACTIVE; /* Not implemented, and only meaningful at depth 1 */ + + /* lineart threshold */ + scanner->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD; + scanner->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD; + scanner->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD; + scanner->opt[OPT_THRESHOLD].type = SANE_TYPE_FIXED; + scanner->opt[OPT_THRESHOLD].unit = SANE_UNIT_PERCENT; + scanner->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE; + scanner->opt[OPT_THRESHOLD].constraint.range = &percentage_range_100; + scanner->val[OPT_THRESHOLD].w = SANE_FIX (50); + /* scanner->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE; Not implemented, and only meaningful at depth 1 */ + + /* create a sharper scan at the cost of scan time */ + scanner->opt[OPT_SHARPEN].name = "sharpen"; + scanner->opt[OPT_SHARPEN].title = "Sharpen scan"; + scanner->opt[OPT_SHARPEN].desc = "Sharpen scan by taking more time to discharge the CCD."; + scanner->opt[OPT_SHARPEN].type = SANE_TYPE_BOOL; + scanner->opt[OPT_SHARPEN].unit = SANE_UNIT_NONE; + scanner->opt[OPT_SHARPEN].constraint_type = SANE_CONSTRAINT_NONE; + scanner->val[OPT_SHARPEN].b = SANE_FALSE; + scanner->opt[OPT_SHARPEN].cap |= SANE_CAP_SOFT_SELECT; + + /* skip the auto-calibration phase before the scan */ + scanner->opt[OPT_SHADING_ANALYSIS].name = "shading-analysis"; + scanner->opt[OPT_SHADING_ANALYSIS].title = "Perform shading analysis"; + scanner->opt[OPT_SHADING_ANALYSIS].desc = "Collect shading reference data before scanning the image. If set to 'no', this option may be overridden by the scanner."; + scanner->opt[OPT_SHADING_ANALYSIS].type = SANE_TYPE_BOOL; + scanner->opt[OPT_SHADING_ANALYSIS].unit = SANE_UNIT_NONE; + scanner->opt[OPT_SHADING_ANALYSIS].constraint_type = SANE_CONSTRAINT_NONE; + scanner->val[OPT_SHADING_ANALYSIS].b = SANE_FALSE; + scanner->opt[OPT_SHADING_ANALYSIS].cap |= SANE_CAP_SOFT_SELECT; + + /* use auto-calibration settings for scan */ + scanner->opt[OPT_CALIBRATION_MODE].name = "calibration"; + scanner->opt[OPT_CALIBRATION_MODE].title = "Calibration mode"; + scanner->opt[OPT_CALIBRATION_MODE].desc = "How to calibrate the scanner."; + scanner->opt[OPT_CALIBRATION_MODE].type = SANE_TYPE_STRING; + scanner->opt[OPT_CALIBRATION_MODE].size = max_string_size ((SANE_String_Const const *) scanner->device->calibration_mode_list); + scanner->opt[OPT_CALIBRATION_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + scanner->opt[OPT_CALIBRATION_MODE].constraint.string_list = (SANE_String_Const const *) scanner->device->calibration_mode_list; + scanner->val[OPT_CALIBRATION_MODE].s = (SANE_Char *) strdup (scanner->device->calibration_mode_list[1]); /* default auto */ + + /* OPT_GAIN_ADJUST */ + scanner->opt[OPT_GAIN_ADJUST].name = "gain-adjust"; + scanner->opt[OPT_GAIN_ADJUST].title = "Adjust gain"; + scanner->opt[OPT_GAIN_ADJUST].desc = "Adjust gain determined by calibration procedure."; + scanner->opt[OPT_GAIN_ADJUST].type = SANE_TYPE_STRING; + scanner->opt[OPT_GAIN_ADJUST].size = max_string_size ((SANE_String_Const const *) scanner->device->gain_adjust_list); + scanner->opt[OPT_GAIN_ADJUST].constraint_type = SANE_CONSTRAINT_STRING_LIST; + scanner->opt[OPT_GAIN_ADJUST].constraint.string_list = (SANE_String_Const const *) scanner->device->gain_adjust_list; + scanner->val[OPT_GAIN_ADJUST].s = (SANE_Char *) strdup (scanner->device->gain_adjust_list[2]); /* x 1.0 (no change) */ + + /* scan infrared channel faster but less accurate */ + scanner->opt[OPT_FAST_INFRARED].name = "fast-infrared"; + scanner->opt[OPT_FAST_INFRARED].title = "Fast infrared scan"; + scanner->opt[OPT_FAST_INFRARED].desc = "Do not reposition scan head before scanning infrared line. Results in an infrared offset which may deteriorate IR dust and scratch removal."; + scanner->opt[OPT_FAST_INFRARED].type = SANE_TYPE_BOOL; + scanner->opt[OPT_FAST_INFRARED].unit = SANE_UNIT_NONE; + scanner->opt[OPT_FAST_INFRARED].constraint_type = SANE_CONSTRAINT_NONE; + scanner->val[OPT_FAST_INFRARED].b = SANE_FALSE; + scanner->opt[OPT_FAST_INFRARED].cap |= SANE_CAP_SOFT_SELECT; + + /* automatically advance to next slide after scan */ + scanner->opt[OPT_ADVANCE_SLIDE].name = "advcane"; + scanner->opt[OPT_ADVANCE_SLIDE].title = "Advance slide"; + scanner->opt[OPT_ADVANCE_SLIDE].desc = "Automatically advance to next slide after scan"; + scanner->opt[OPT_ADVANCE_SLIDE].type = SANE_TYPE_BOOL; + scanner->opt[OPT_ADVANCE_SLIDE].unit = SANE_UNIT_NONE; + scanner->opt[OPT_ADVANCE_SLIDE].constraint_type = SANE_CONSTRAINT_NONE; + scanner->val[OPT_ADVANCE_SLIDE].w = SANE_TRUE; + scanner->opt[OPT_ADVANCE_SLIDE].cap |= SANE_CAP_SOFT_SELECT; + + /* "Geometry" group: */ + scanner->opt[OPT_GEOMETRY_GROUP].title = "Geometry"; + scanner->opt[OPT_GEOMETRY_GROUP].desc = ""; + scanner->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP; + scanner->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED; + scanner->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* top-left x */ + scanner->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X; + scanner->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X; + scanner->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X; + scanner->opt[OPT_TL_X].type = SANE_TYPE_FIXED; + scanner->opt[OPT_TL_X].unit = SANE_UNIT_MM; + scanner->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE; + scanner->opt[OPT_TL_X].constraint.range = &(scanner->device->x_range); + scanner->val[OPT_TL_X].w = 0; + + /* top-left y */ + scanner->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y; + scanner->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y; + scanner->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y; + scanner->opt[OPT_TL_Y].type = SANE_TYPE_FIXED; + scanner->opt[OPT_TL_Y].unit = SANE_UNIT_MM; + scanner->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE; + scanner->opt[OPT_TL_Y].constraint.range = &(scanner->device->y_range); + scanner->val[OPT_TL_Y].w = 0; + + /* bottom-right x */ + scanner->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X; + scanner->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X; + scanner->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X; + scanner->opt[OPT_BR_X].type = SANE_TYPE_FIXED; + scanner->opt[OPT_BR_X].unit = SANE_UNIT_MM; + scanner->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE; + scanner->opt[OPT_BR_X].constraint.range = &(scanner->device->x_range); + scanner->val[OPT_BR_X].w = scanner->device->x_range.max; + + /* bottom-right y */ + scanner->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y; + scanner->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y; + scanner->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y; + scanner->opt[OPT_BR_Y].type = SANE_TYPE_FIXED; + scanner->opt[OPT_BR_Y].unit = SANE_UNIT_MM; + scanner->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE; + scanner->opt[OPT_BR_Y].constraint.range = &(scanner->device->y_range); + scanner->val[OPT_BR_Y].w = scanner->device->y_range.max; + + /* "Enhancement" group: */ + scanner->opt[OPT_ENHANCEMENT_GROUP].title = "Enhancement"; + scanner->opt[OPT_ENHANCEMENT_GROUP].desc = ""; + scanner->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP; + scanner->opt[OPT_ENHANCEMENT_GROUP].cap = 0; + scanner->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* correct data for lamp variations (shading) */ + scanner->opt[OPT_CORRECT_SHADING].name = "correct-shading"; + scanner->opt[OPT_CORRECT_SHADING].title = "Correct shading"; + scanner->opt[OPT_CORRECT_SHADING].desc = "Correct data for lamp variations (shading)"; + scanner->opt[OPT_CORRECT_SHADING].type = SANE_TYPE_BOOL; + scanner->opt[OPT_CORRECT_SHADING].unit = SANE_UNIT_NONE; + scanner->val[OPT_CORRECT_SHADING].w = SANE_TRUE; + + /* correct infrared for red crosstalk */ + scanner->opt[OPT_CORRECT_INFRARED].name = "correct-infrared"; + scanner->opt[OPT_CORRECT_INFRARED].title = "Correct infrared"; + scanner->opt[OPT_CORRECT_INFRARED].desc = "Correct infrared for red crosstalk"; + scanner->opt[OPT_CORRECT_INFRARED].type = SANE_TYPE_BOOL; + scanner->opt[OPT_CORRECT_INFRARED].unit = SANE_UNIT_NONE; + scanner->val[OPT_CORRECT_INFRARED].w = SANE_FALSE; + + /* detect and remove dust and scratch artifacts */ + scanner->opt[OPT_CLEAN_IMAGE].name = "clean-image"; + scanner->opt[OPT_CLEAN_IMAGE].title = "Clean image"; + scanner->opt[OPT_CLEAN_IMAGE].desc = "Detect and remove dust and scratch artifacts"; + scanner->opt[OPT_CLEAN_IMAGE].type = SANE_TYPE_BOOL; + scanner->opt[OPT_CLEAN_IMAGE].unit = SANE_UNIT_NONE; + scanner->val[OPT_CLEAN_IMAGE].w = SANE_FALSE; + + /* strength of grain filtering */ + scanner->opt[OPT_SMOOTH_IMAGE].name = "smooth"; + scanner->opt[OPT_SMOOTH_IMAGE].title = "Attenuate film grain"; + scanner->opt[OPT_SMOOTH_IMAGE].desc = "Amount of smoothening"; + scanner->opt[OPT_SMOOTH_IMAGE].type = SANE_TYPE_INT; + scanner->opt[OPT_SMOOTH_IMAGE].constraint_type = SANE_CONSTRAINT_WORD_LIST; + scanner->opt[OPT_SMOOTH_IMAGE].size = sizeof (SANE_Word); + scanner->opt[OPT_SMOOTH_IMAGE].constraint.word_list = scanner->device->grain_sw_list; + scanner->val[OPT_SMOOTH_IMAGE].w = scanner->device->grain_sw_list[1]; + if (scanner->opt[OPT_SMOOTH_IMAGE].constraint.word_list[0] < 2) { + scanner->opt[OPT_SMOOTH_IMAGE].cap |= SANE_CAP_INACTIVE; + } + + /* gamma correction, to make image sRGB like */ + scanner->opt[OPT_TRANSFORM_TO_SRGB].name = "srgb"; + scanner->opt[OPT_TRANSFORM_TO_SRGB].title = "sRGB colors"; + scanner->opt[OPT_TRANSFORM_TO_SRGB].desc = "Transform image to approximate sRGB color space"; + scanner->opt[OPT_TRANSFORM_TO_SRGB].type = SANE_TYPE_BOOL; + scanner->opt[OPT_TRANSFORM_TO_SRGB].unit = SANE_UNIT_NONE; + scanner->val[OPT_TRANSFORM_TO_SRGB].w = SANE_FALSE; + scanner->opt[OPT_TRANSFORM_TO_SRGB].cap |= SANE_CAP_INACTIVE; + + /* color correction for generic negative film */ + scanner->opt[OPT_INVERT_IMAGE].name = "invert"; + scanner->opt[OPT_INVERT_IMAGE].title = "Invert colors"; + scanner->opt[OPT_INVERT_IMAGE].desc = "Correct for generic negative film"; + scanner->opt[OPT_INVERT_IMAGE].type = SANE_TYPE_BOOL; + scanner->opt[OPT_INVERT_IMAGE].unit = SANE_UNIT_NONE; + scanner->val[OPT_INVERT_IMAGE].w = SANE_FALSE; + scanner->opt[OPT_INVERT_IMAGE].cap |= SANE_CAP_INACTIVE; + + /* crop image */ + scanner->opt[OPT_CROP_IMAGE].name = "crop"; + scanner->opt[OPT_CROP_IMAGE].title = "Cropping"; + scanner->opt[OPT_CROP_IMAGE].desc = "How to crop the image"; + scanner->opt[OPT_CROP_IMAGE].type = SANE_TYPE_STRING; + scanner->opt[OPT_CROP_IMAGE].size = max_string_size ((SANE_String_Const const *)(void*) scanner->device->crop_sw_list); + scanner->opt[OPT_CROP_IMAGE].constraint_type = SANE_CONSTRAINT_STRING_LIST; + scanner->opt[OPT_CROP_IMAGE].constraint.string_list = (SANE_String_Const const *)(void*) scanner->device->crop_sw_list; + scanner->val[OPT_CROP_IMAGE].s = (SANE_Char *) strdup (scanner->device->crop_sw_list[2]); + + /* "Advanced" group: */ + scanner->opt[OPT_ADVANCED_GROUP].title = "Advanced"; + scanner->opt[OPT_ADVANCED_GROUP].desc = ""; + scanner->opt[OPT_ADVANCED_GROUP].type = SANE_TYPE_GROUP; + scanner->opt[OPT_ADVANCED_GROUP].cap = SANE_CAP_ADVANCED; + scanner->opt[OPT_ADVANCED_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + + /* preview */ + scanner->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW; + scanner->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW; + scanner->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW; + scanner->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL; + scanner->val[OPT_PREVIEW].w = SANE_FALSE; + + /* save shading data */ + scanner->opt[OPT_SAVE_SHADINGDATA].name = "save-shading-data"; + scanner->opt[OPT_SAVE_SHADINGDATA].title = "Save shading data"; + scanner->opt[OPT_SAVE_SHADINGDATA].desc = "Save shading data in 'pieusb.shading'"; + scanner->opt[OPT_SAVE_SHADINGDATA].type = SANE_TYPE_BOOL; + scanner->val[OPT_SAVE_SHADINGDATA].w = SANE_FALSE; + + /* save CCD mask */ + scanner->opt[OPT_SAVE_CCDMASK].name = "save-ccdmask"; + scanner->opt[OPT_SAVE_CCDMASK].title = "Save CCD mask"; + scanner->opt[OPT_SAVE_CCDMASK].desc = "Save CCD mask 'pieusb.ccd'"; + scanner->opt[OPT_SAVE_CCDMASK].type = SANE_TYPE_BOOL; + scanner->val[OPT_SAVE_CCDMASK].w = SANE_FALSE; + + scanner->opt[OPT_LIGHT].name = "light"; + scanner->opt[OPT_LIGHT].title = "Light"; + scanner->opt[OPT_LIGHT].desc = "Light"; + scanner->opt[OPT_LIGHT].type = SANE_TYPE_INT; + scanner->opt[OPT_LIGHT].unit = SANE_UNIT_MICROSECOND; + scanner->opt[OPT_LIGHT].cap |= SANE_CAP_SOFT_SELECT; + scanner->opt[OPT_LIGHT].size = sizeof(SANE_Word); + scanner->val[OPT_LIGHT].w = DEFAULT_LIGHT; + + scanner->opt[OPT_DOUBLE_TIMES].name = "double-times"; + scanner->opt[OPT_DOUBLE_TIMES].title = "Double times"; + scanner->opt[OPT_DOUBLE_TIMES].desc = "Double times"; + scanner->opt[OPT_DOUBLE_TIMES].type = SANE_TYPE_INT; + scanner->opt[OPT_DOUBLE_TIMES].unit = SANE_UNIT_MICROSECOND; + scanner->opt[OPT_DOUBLE_TIMES].cap |= SANE_CAP_SOFT_SELECT; + scanner->opt[OPT_DOUBLE_TIMES].size = sizeof(SANE_Word); + scanner->val[OPT_DOUBLE_TIMES].w = DEFAULT_DOUBLE_TIMES; + + /* exposure times for R, G, B and I */ + scanner->opt[OPT_SET_EXPOSURE_R].name = SANE_NAME_EXPOSURE_R; + scanner->opt[OPT_SET_EXPOSURE_R].title = SANE_TITLE_EXPOSURE_R; + scanner->opt[OPT_SET_EXPOSURE_R].desc = SANE_DESC_EXPOSURE_R; + scanner->opt[OPT_SET_EXPOSURE_G].name = SANE_NAME_EXPOSURE_G; + scanner->opt[OPT_SET_EXPOSURE_G].title = SANE_TITLE_EXPOSURE_G; + scanner->opt[OPT_SET_EXPOSURE_G].desc = SANE_DESC_EXPOSURE_G; + scanner->opt[OPT_SET_EXPOSURE_B].name = SANE_NAME_EXPOSURE_B; + scanner->opt[OPT_SET_EXPOSURE_B].title = SANE_TITLE_EXPOSURE_B; + scanner->opt[OPT_SET_EXPOSURE_B].desc = SANE_DESC_EXPOSURE_B; + scanner->opt[OPT_SET_EXPOSURE_I].name = SANE_NAME_EXPOSURE_I; + scanner->opt[OPT_SET_EXPOSURE_I].title = SANE_TITLE_EXPOSURE_I; + scanner->opt[OPT_SET_EXPOSURE_I].desc = SANE_DESC_EXPOSURE_I; + for (i = OPT_SET_EXPOSURE_R; i <= OPT_SET_EXPOSURE_I; ++i) { + scanner->opt[i].type = SANE_TYPE_INT; + scanner->opt[i].unit = SANE_UNIT_MICROSECOND; + scanner->opt[i].cap |= SANE_CAP_SOFT_SELECT; + scanner->opt[i].constraint_type = SANE_CONSTRAINT_RANGE; + scanner->opt[i].constraint.range = &(scanner->device->exposure_range); + scanner->opt[i].size = sizeof(SANE_Word); + scanner->val[i].w = SANE_EXPOSURE_DEFAULT; + } + + /* gain for R, G, B and I */ + scanner->opt[OPT_SET_GAIN_R].name = SANE_NAME_GAIN_R; + scanner->opt[OPT_SET_GAIN_R].title = SANE_TITLE_GAIN_R; + scanner->opt[OPT_SET_GAIN_R].desc = SANE_DESC_GAIN_R; + scanner->opt[OPT_SET_GAIN_G].name = SANE_NAME_GAIN_G; + scanner->opt[OPT_SET_GAIN_G].title = SANE_TITLE_GAIN_G; + scanner->opt[OPT_SET_GAIN_G].desc = SANE_DESC_GAIN_G; + scanner->opt[OPT_SET_GAIN_B].name = SANE_NAME_GAIN_B; + scanner->opt[OPT_SET_GAIN_B].title = SANE_TITLE_GAIN_B; + scanner->opt[OPT_SET_GAIN_B].desc = SANE_DESC_GAIN_B; + scanner->opt[OPT_SET_GAIN_I].name = SANE_NAME_GAIN_I; + scanner->opt[OPT_SET_GAIN_I].title = SANE_TITLE_GAIN_I; + scanner->opt[OPT_SET_GAIN_I].desc = SANE_DESC_GAIN_I; + for (i = OPT_SET_GAIN_R; i <= OPT_SET_GAIN_I; ++i) { + scanner->opt[i].type = SANE_TYPE_INT; + scanner->opt[i].unit = SANE_UNIT_NONE; + scanner->opt[i].constraint_type = SANE_CONSTRAINT_RANGE; + scanner->opt[i].constraint.range = &gain_range; + scanner->opt[i].size = sizeof(SANE_Word); + scanner->val[i].w = SANE_GAIN_DEFAULT; + } + /* offsets for R, G, B and I */ + scanner->opt[OPT_SET_OFFSET_R].name = SANE_NAME_OFFSET_R; + scanner->opt[OPT_SET_OFFSET_R].title = SANE_TITLE_OFFSET_R; + scanner->opt[OPT_SET_OFFSET_R].desc = SANE_DESC_OFFSET_R; + scanner->opt[OPT_SET_OFFSET_G].name = SANE_NAME_OFFSET_G; + scanner->opt[OPT_SET_OFFSET_G].title = SANE_TITLE_OFFSET_G; + scanner->opt[OPT_SET_OFFSET_G].desc = SANE_DESC_OFFSET_G; + scanner->opt[OPT_SET_OFFSET_B].name = SANE_NAME_OFFSET_B; + scanner->opt[OPT_SET_OFFSET_B].title = SANE_TITLE_OFFSET_B; + scanner->opt[OPT_SET_OFFSET_B].desc = SANE_DESC_OFFSET_B; + scanner->opt[OPT_SET_OFFSET_I].name = SANE_NAME_OFFSET_I; + scanner->opt[OPT_SET_OFFSET_I].title = SANE_TITLE_OFFSET_I; + scanner->opt[OPT_SET_OFFSET_I].desc = SANE_DESC_OFFSET_I; + for (i = OPT_SET_OFFSET_R; i <= OPT_SET_OFFSET_I; ++i) { + scanner->opt[i].type = SANE_TYPE_INT; + scanner->opt[i].unit = SANE_UNIT_NONE; + scanner->opt[i].constraint_type = SANE_CONSTRAINT_RANGE; + scanner->opt[i].constraint.range = &offset_range; + scanner->opt[i].size = sizeof(SANE_Word); + scanner->val[i].w = SANE_OFFSET_DEFAULT; + } + return SANE_STATUS_GOOD; +} + +/** + * Parse line from config file into a vendor id, product id and a model number + * + * @param config_line Text to parse + * @param vendor_id + * @param product_id + * @param model_number + * @return SANE_STATUS_GOOD, or SANE_STATUS_INVAL in case of a parse error + */ +SANE_Status +sanei_pieusb_parse_config_line(const char* config_line, SANE_Word* vendor_id, SANE_Word* product_id, SANE_Word* model_number) +{ + char *vendor_id_string, *product_id_string, *model_number_string; + + if (strncmp (config_line, "usb ", 4) != 0) { + return SANE_STATUS_INVAL; + } + /* Detect vendor-id */ + config_line += 4; + config_line = sanei_config_skip_whitespace (config_line); + if (*config_line) { + config_line = sanei_config_get_string (config_line, &vendor_id_string); + if (vendor_id_string) { + *vendor_id = strtol (vendor_id_string, 0, 0); + free (vendor_id_string); + } else { + return SANE_STATUS_INVAL; + } + config_line = sanei_config_skip_whitespace (config_line); + } else { + return SANE_STATUS_INVAL; + } + /* Detect product-id */ + config_line = sanei_config_skip_whitespace (config_line); + if (*config_line) { + config_line = sanei_config_get_string (config_line, &product_id_string); + if (product_id_string) { + *product_id = strtol (product_id_string, 0, 0); + free (product_id_string); + } else { + return SANE_STATUS_INVAL; + } + config_line = sanei_config_skip_whitespace (config_line); + } else { + return SANE_STATUS_INVAL; + } + /* Detect product-id */ + config_line = sanei_config_skip_whitespace (config_line); + if (*config_line) { + config_line = sanei_config_get_string (config_line, &model_number_string); + if (model_number_string) { + *model_number = strtol (model_number_string, 0, 0); + free (model_number_string); + } else { + return SANE_STATUS_INVAL; + } + config_line = sanei_config_skip_whitespace (config_line); + } else { + return SANE_STATUS_INVAL; + } + return SANE_STATUS_GOOD; +} + +/** + * Check if current list of supported devices contains the given specifications. + * + * @param vendor_id + * @param product_id + * @param model_number + * @return + */ +SANE_Bool +sanei_pieusb_supported_device_list_contains(SANE_Word vendor_id, SANE_Word product_id, SANE_Word model_number) +{ + int i = 0; + while (pieusb_supported_usb_device_list[i].vendor != 0) { + if (pieusb_supported_usb_device_list[i].vendor == vendor_id + && pieusb_supported_usb_device_list[i].product == product_id + && pieusb_supported_usb_device_list[i].model == model_number) { + return SANE_TRUE; + } + i++; + } + return SANE_FALSE; +} + +/** + * Add the given specifications to the current list of supported devices + * @param vendor_id + * @param product_id + * @param model_number + * @return + */ +SANE_Status +sanei_pieusb_supported_device_list_add(SANE_Word vendor_id, SANE_Word product_id, SANE_Word model_number) +{ + int i = 0, k; + struct Pieusb_USB_Device_Entry* dl; + + while (pieusb_supported_usb_device_list[i].vendor != 0) { + i++; + } + /* i is index of last entry */ + for (k=0; k<=i; k++) { + DBG(DBG_info_proc,"sanei_pieusb_supported_device_list_add(): current %03d: %04x %04x %02x\n", i, + pieusb_supported_usb_device_list[k].vendor, + pieusb_supported_usb_device_list[k].product, + pieusb_supported_usb_device_list[k].model); + } + + dl = realloc(pieusb_supported_usb_device_list,(i+2)*sizeof(struct Pieusb_USB_Device_Entry)); /* Add one entry to list */ + if (dl == NULL) { + return SANE_STATUS_INVAL; + } + /* Copy values */ + pieusb_supported_usb_device_list = dl; + pieusb_supported_usb_device_list[i].vendor = vendor_id; + pieusb_supported_usb_device_list[i].product = product_id; + pieusb_supported_usb_device_list[i].model = model_number; + pieusb_supported_usb_device_list[i+1].vendor = 0; + pieusb_supported_usb_device_list[i+1].product = 0; + pieusb_supported_usb_device_list[i+1].model = 0; + for (k=0; k<=i+1; k++) { + DBG(DBG_info_proc,"sanei_pieusb_supported_device_list_add() add: %03d: %04x %04x %02x\n", i, + pieusb_supported_usb_device_list[k].vendor, + pieusb_supported_usb_device_list[k].product, + pieusb_supported_usb_device_list[k].model); + } + return SANE_STATUS_GOOD; +} + +/** + * Actions to perform when a cancel request has been received. + * + * @param scanner scanner to stop scanning + * @return SANE_STATUS_CANCELLED + */ +SANE_Status +sanei_pieusb_on_cancel (Pieusb_Scanner * scanner) +{ + struct Pieusb_Command_Status status; + + DBG (DBG_info_proc, "sanei_pieusb_on_cancel()\n"); + + sanei_pieusb_cmd_stop_scan (scanner->device_number, &status); + sanei_pieusb_cmd_set_scan_head (scanner->device_number, 1, 0, &status); + sanei_pieusb_buffer_delete (&scanner->buffer); + scanner->scanning = SANE_FALSE; + return SANE_STATUS_CANCELLED; +} + +/** + * Determine maximum lengt of a set of strings. + * + * @param strings Set of strings + * @return maximum length + */ +static size_t +max_string_size (SANE_String_Const const strings[]) +{ + size_t size, max_size = 0; + int i; + + for (i = 0; strings[i]; ++i) { + size = strlen (strings[i]) + 1; + if (size > max_size) { + max_size = size; + } + } + + return max_size; +} + +/* From MR's pie.c */ + +/* ------------------------- PIEUSB_CORRECT_SHADING -------------------------- */ + +/** + * Correct the given buffer for shading using shading data in scanner. + * If the loop order is width->color->height, a 7200 dpi scan correction takes + * 45 minutes. If the loop order is color->height->width, this is less than 3 + * minutes. So it is worthwhile to find the used pixels first (array width_to_loc). + * + * @param scanner Scanner + * @param buffer Buffer to correct + */ +void +sanei_pieusb_correct_shading(struct Pieusb_Scanner *scanner, struct Pieusb_Read_Buffer *buffer) +{ + + int i, j, c, k; + SANE_Uint val, val_org, *p; + int *width_to_loc; + + DBG (DBG_info_proc, "sanei_pieusb_correct_shading()\n"); + + /* Loop through CCD-mask to find used pixels */ + width_to_loc = calloc(buffer->width,sizeof(int)); + j = 0; + for (i = 0; i < scanner->ccd_mask_size; i++) { + if (scanner->ccd_mask[i] == 0) { + width_to_loc[j++] = i; + } + } + /* Correct complete image */ + for (c = 0; c < buffer->colors; c++) { + DBG(DBG_info,"sanei_pieusb_correct_shading() correct color %d\n",c); + for (k = 0; k < buffer->height; k++) { + /* DBG(DBG_info,"Correct line %d\n",k); */ + p = buffer->data + c * buffer->width * buffer->height + k * buffer->width; + for (j = 0; j < buffer->width; j++) { + val_org = *p; + val = lround((double)scanner->shading_mean[c] / scanner->shading_ref[c][width_to_loc[j]] * val_org); + /* DBG(DBG_info,"Correct [%d,%d,%d] %d -> %d\n",k,j,c,val_org,val); */ + *p++ = val; + } + } + } + /* Free memory */ + free(width_to_loc); +} + +/* === functions copied from MR's code === */ + +/** + * + * @param scanner + * @param in_img + * @param planes + * @param out_planes + * @return + */ +SANE_Status +sanei_pieusb_post (Pieusb_Scanner *scanner, uint16_t **in_img, int planes) +{ + uint16_t *cplane[PLANES]; /* R, G, B, I gray scale planes */ + SANE_Parameters parameters; /* describes the image */ + int winsize_smooth; /* for adapting replaced pixels */ + char filename[64]; + SANE_Status status; + int smooth, i; + + memcpy (¶meters, &scanner->scan_parameters, sizeof (SANE_Parameters)); + parameters.format = SANE_FRAME_GRAY; + parameters.bytes_per_line = parameters.pixels_per_line; + if (parameters.depth > 8) + parameters.bytes_per_line *= 2; + parameters.last_frame = 0; + + DBG (DBG_info, "pie_usb_post: %d ppl, %d lines, %d bits, %d planes, %d dpi\n", + parameters.pixels_per_line, parameters.lines, + parameters.depth, planes, scanner->mode.resolution); + + if (planes > PLANES) { + DBG (DBG_error, "pie_usb_post: too many planes: %d (max %d)\n", planes, PLANES); + return SANE_STATUS_INVAL; + } + + for (i = 0; i < planes; i++) + cplane[i] = in_img[i]; + + /* dirt is rather resolution invariant, so + * setup resolution dependent parameters + */ + /* film grain reduction */ + smooth = scanner->val[OPT_SMOOTH_IMAGE].w; + winsize_smooth = (scanner->mode.resolution / 540) | 1; + /* smoothen whole image or only replaced pixels */ + if (smooth) + { + winsize_smooth += 2 * (smooth - 3); /* even */ + if (winsize_smooth < 3) + smooth = 0; + } + if (winsize_smooth < 3) + winsize_smooth = 3; + DBG (DBG_info, "pie_usb_sw_post: winsize_smooth %d\n", winsize_smooth); + + /* RGBI post-processing if selected: + * 1) remove spectral overlay from ired plane, + * 2) remove dirt, smoothen if, crop if */ + if (scanner->val[OPT_CORRECT_INFRARED].b) /* (scanner->processing & POST_SW_IRED_MASK) */ + { + /* remove spectral overlay from ired plane */ + status = sanei_ir_spectral_clean (¶meters, scanner->ln_lut, cplane[0], cplane[3]); + if (status != SANE_STATUS_GOOD) + return status; + if (DBG_LEVEL >= 15) + { + snprintf (filename, 63, "/tmp/ir-spectral.pnm"); + pieusb_write_pnm_file (filename, cplane[3], + parameters.depth, 1, + parameters.pixels_per_line, parameters.lines); + } + if (scanner->cancel_request) /* asynchronous cancel ? */ + return SANE_STATUS_CANCELLED; + } /* scanner-> processing & POST_SW_IRED_MASK */ + + /* remove dirt, smoothen if, crop if */ + if (scanner->val[OPT_CLEAN_IMAGE].b) /* (scanner->processing & POST_SW_DIRT) */ + { + double *norm_histo; + uint16_t *thresh_data; + int static_thresh, too_thresh; /* static thresholds */ + int winsize_filter; /* primary size of filtering window */ + int size_dilate; /* the dirt mask */ + + /* size of filter detecting dirt */ + winsize_filter = (int) (5.0 * (double) scanner->mode.resolution / 300.0) | 1; + if (winsize_filter < 3) + winsize_filter = 3; + /* dirt usually has smooth edges which also need correction */ + size_dilate = scanner->mode.resolution / 1000 + 1; + + /* first detect large dirt by a static threshold */ + status = sanei_ir_create_norm_histogram (¶meters, cplane[3], &norm_histo); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_error, "pie_usb_sw_post: no buffer\n"); + return SANE_STATUS_NO_MEM; + } + /* generate a "bimodal" static threshold */ + status = sanei_ir_threshold_yen (¶meters, norm_histo, &static_thresh); + if (status != SANE_STATUS_GOOD) + return status; + /* generate traditional static threshold */ + status = sanei_ir_threshold_otsu (¶meters, norm_histo, &too_thresh); + if (status != SANE_STATUS_GOOD) + return status; + /* choose lower one */ + if (too_thresh < static_thresh) + static_thresh = too_thresh; + free (norm_histo); + + /* then generate dirt mask with adaptive thresholding filter + * and add the dirt from the static threshold */ + /* last two parameters: 10, 50 detects more, 20, 75 less */ + status = sanei_ir_filter_madmean (¶meters, cplane[3], &thresh_data, winsize_filter, 20, 100); + if (status != SANE_STATUS_GOOD) { + free (thresh_data); + return status; + } + sanei_ir_add_threshold (¶meters, cplane[3], thresh_data, static_thresh); + if (DBG_LEVEL >= 15) + { + snprintf (filename, 63, "/tmp/ir-threshold.pnm"); + pieusb_write_pnm_file (filename, thresh_data, + 8, 1, parameters.pixels_per_line, + parameters.lines); + } + if (scanner->cancel_request) { /* asynchronous cancel ? */ + free (thresh_data); + return SANE_STATUS_CANCELLED; + } + /* replace the dirt and smoothen film grain and crop if possible */ + status = sanei_ir_dilate_mean (¶meters, cplane, thresh_data, + 500, size_dilate, winsize_smooth, smooth, + 0, NULL); + if (status != SANE_STATUS_GOOD) { + free (thresh_data); + return status; + } + smooth = 0; + free (thresh_data); + } + + if (DBG_LEVEL >= 15) + { + pieusb_write_pnm_file ("/tmp/RGBi-img.pnm", scanner->buffer.data, + scanner->scan_parameters.depth, 3, scanner->scan_parameters.pixels_per_line, + scanner->scan_parameters.lines); + } + + return status; +} + +/* ------------------------------ PIE_USB_WRITE_PNM_FILE ------------------------------- */ +static SANE_Status +pieusb_write_pnm_file (char *filename, SANE_Uint *data, int depth, + int channels, int pixels_per_line, int lines) +{ + FILE *out; + int r, c, ch; + SANE_Uint val; + uint8_t b = 0; + + DBG (DBG_info_proc, + "pie_usb_write_pnm_file: depth=%d, channels=%d, ppl=%d, lines=%d\n", + depth, channels, pixels_per_line, lines); + + out = fopen (filename, "w"); + if (!out) + { + DBG (DBG_error, + "pie_usb_write_pnm_file: could not open %s for writing: %s\n", + filename, strerror (errno)); + return SANE_STATUS_INVAL; + } + + switch (depth) { + case 1: + fprintf (out, "P4\n%d\n%d\n", pixels_per_line, lines); + for (r = 0; r < lines; r++) { + int i; + i = 0; + b = 0; + for (c = 0; c < pixels_per_line; c++) { + val = *(data + r * pixels_per_line + c); + if (val > 0) b |= (0x80 >> i); + i++; + if (i == 7) { + fputc(b, out); + i = 0; + b = 0; + } + } + if (i != 0) { + fputc(b, out); + } + } + break; + case 8: + fprintf (out, "P%c\n%d\n%d\n%d\n", channels == 1 ? '5' : '6', pixels_per_line, lines, 255); + for (r = 0; r < lines; r++) { + for (c = 0; c < pixels_per_line; c++) { + for (ch = 0; ch < channels; ch++) { + val = *(data + ch * lines * pixels_per_line + r * pixels_per_line + c); + b = val & 0xFF; + fputc(b, out); + } + } + } + break; + case 16: + fprintf (out, "P%c\n%d\n%d\n%d\n", channels == 1 ? '5' : '6', pixels_per_line, lines, 65535); + for (r = 0; r < lines; r++) { + for (c = 0; c < pixels_per_line; c++) { + for (ch = 0; ch < channels; ch++) { + val = *(data + ch * lines * pixels_per_line + r * pixels_per_line + c); + b = (val >> 8) & 0xFF; + fputc(b, out); + b = val & 0xFF; + fputc(b, out); + } + } + } + break; + default: + DBG (DBG_error, "pie_usb_write_pnm_file: depth %d not implemented\n", depth); + } + fclose (out); + + DBG (DBG_info, "pie_usb_write_pnm_file: finished\n"); + return SANE_STATUS_GOOD; +} + +/** + * Check option inconsistencies. + * In most cases an inconsistency can be solved by ignoring an option setting. + * Message these situations and return 1 to indicate we can work with the + * current set op options. If the settings are really inconsistent, return 0. + */ +int +sanei_pieusb_analyse_options(struct Pieusb_Scanner *scanner) +{ + /* Checks*/ + if (scanner->val[OPT_TL_X].w > scanner->val[OPT_BR_X].w) { + DBG (DBG_error, "sane_start: %s (%.1f mm) is bigger than %s (%.1f mm) -- aborting\n", + scanner->opt[OPT_TL_X].title, + SANE_UNFIX (scanner->val[OPT_TL_X].w), + scanner->opt[OPT_BR_X].title, + SANE_UNFIX (scanner->val[OPT_BR_X].w)); + return 0; + } + if (scanner->val[OPT_TL_Y].w > scanner->val[OPT_BR_Y].w) { + DBG (DBG_error, "sane_start: %s (%.1f mm) is bigger than %s (%.1f mm) -- aborting\n", + scanner->opt[OPT_TL_Y].title, + SANE_UNFIX (scanner->val[OPT_TL_Y].w), + scanner->opt[OPT_BR_Y].title, + SANE_UNFIX (scanner->val[OPT_BR_Y].w)); + return 0; + } + /* Modes sometimes limit other choices */ + if (scanner->val[OPT_PREVIEW].b) { + /* Preview uses its own specific settings */ + if (scanner->val[OPT_RESOLUTION].w != (scanner->device->fast_preview_resolution << SANE_FIXED_SCALE_SHIFT)) { + DBG (DBG_info_sane, "Option %s = %f ignored during preview\n", scanner->opt[OPT_RESOLUTION].name, SANE_UNFIX(scanner->val[OPT_RESOLUTION].w)); + } + if (scanner->val[OPT_SHARPEN].b) { + DBG (DBG_info_sane, "Option %s = %d ignored during preview\n", scanner->opt[OPT_SHARPEN].name, scanner->val[OPT_SHARPEN].b); + } + if (!scanner->val[OPT_FAST_INFRARED].b) { + DBG (DBG_info_sane, "Option %s = %d ignored during preview\n", scanner->opt[OPT_FAST_INFRARED].name, scanner->val[OPT_FAST_INFRARED].b); + } + if (scanner->val[OPT_CORRECT_INFRARED].b) { + DBG (DBG_info_sane, "Option %s = %d ignored during preview\n", scanner->opt[OPT_CORRECT_INFRARED].name, scanner->val[OPT_CORRECT_INFRARED].b); + } + if (scanner->val[OPT_CLEAN_IMAGE].b) { + DBG (DBG_info_sane, "Option %s = %d ignored during preview\n", scanner->opt[OPT_CLEAN_IMAGE].name, scanner->val[OPT_CLEAN_IMAGE].b); + } + if (scanner->val[OPT_SMOOTH_IMAGE].w != 0) { + DBG (DBG_info_sane, "Option %s = %d ignored during preview\n", scanner->opt[OPT_SMOOTH_IMAGE].name, scanner->val[OPT_SMOOTH_IMAGE].w); + } + if (strcmp(scanner->val[OPT_CROP_IMAGE].s, scanner->device->crop_sw_list[0]) != 0) { + DBG (DBG_info_sane, "Option %s = %s ignored during preview\n", scanner->opt[OPT_CROP_IMAGE].name, scanner->val[OPT_CROP_IMAGE].s); + } + if (scanner->val[OPT_TRANSFORM_TO_SRGB].b) { + DBG (DBG_info_sane, "Option %s = %d ignored during preview\n", scanner->opt[OPT_TRANSFORM_TO_SRGB].name, scanner->val[OPT_TRANSFORM_TO_SRGB].w); + } + if (scanner->val[OPT_INVERT_IMAGE].w) { + DBG (DBG_info_sane, "Option %s = %d ignored during preview\n", scanner->opt[OPT_INVERT_IMAGE].name, scanner->val[OPT_INVERT_IMAGE].w); + } + } else if (strcmp(scanner->val[OPT_MODE].s,SANE_VALUE_SCAN_MODE_LINEART)==0) { + /* Can we do any post processing in lineart? Needs testing to see what's possible */ + if (scanner->val[OPT_BIT_DEPTH].w != 1) { + DBG (DBG_info_sane, "Option %s = %d ignored in lineart mode (will use 1)\n", scanner->opt[OPT_BIT_DEPTH].name, scanner->val[OPT_BIT_DEPTH].w); + } + if (!scanner->val[OPT_FAST_INFRARED].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in lineart mode (irrelevant)\n", scanner->opt[OPT_FAST_INFRARED].name, scanner->val[OPT_FAST_INFRARED].b); + } + if (!scanner->val[OPT_CORRECT_SHADING].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in lineart mode (irrelevant)\n", scanner->opt[OPT_CORRECT_SHADING].name, scanner->val[OPT_CORRECT_SHADING].b); + } + if (!scanner->val[OPT_CORRECT_INFRARED].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in lineart mode (irrelevant)\n", scanner->opt[OPT_CORRECT_INFRARED].name, scanner->val[OPT_CORRECT_INFRARED].b); + } + if (scanner->val[OPT_CLEAN_IMAGE].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in lineart mode (irrelevant)\n", scanner->opt[OPT_CLEAN_IMAGE].name, scanner->val[OPT_CLEAN_IMAGE].b); + } + if (scanner->val[OPT_SMOOTH_IMAGE].w != 0) { + DBG (DBG_info_sane, "Option %s = %d ignored in lineart mode (irrelevant)\n", scanner->opt[OPT_SMOOTH_IMAGE].name, scanner->val[OPT_SMOOTH_IMAGE].w); + } + if (strcmp(scanner->val[OPT_CROP_IMAGE].s, scanner->device->crop_sw_list[0]) != 0) { + DBG (DBG_info_sane, "Option %s = %s ignored in lineart mode (irrelevant)\n", scanner->opt[OPT_CROP_IMAGE].name, scanner->val[OPT_CROP_IMAGE].s); + } + if (scanner->val[OPT_TRANSFORM_TO_SRGB].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in lineart mode (irrelevant)\n", scanner->opt[OPT_TRANSFORM_TO_SRGB].name, scanner->val[OPT_TRANSFORM_TO_SRGB].w); + } + } else if (strcmp(scanner->val[OPT_MODE].s,SANE_VALUE_SCAN_MODE_HALFTONE)==0) { + /* Can we do any post processing in halftone? Needs testing to see what's possible */ + if (scanner->val[OPT_BIT_DEPTH].w != 1) { + DBG (DBG_info_sane, "Option %s = %d ignored in halftone mode (will use 1)\n", scanner->opt[OPT_BIT_DEPTH].name, scanner->val[OPT_BIT_DEPTH].w); + } + if (!scanner->val[OPT_FAST_INFRARED].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in halftone mode (irrelevant)\n", scanner->opt[OPT_FAST_INFRARED].name, scanner->val[OPT_FAST_INFRARED].b); + } + if (!scanner->val[OPT_CORRECT_SHADING].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in halftone mode (irrelevant)\n", scanner->opt[OPT_CORRECT_SHADING].name, scanner->val[OPT_CORRECT_SHADING].b); + } + if (!scanner->val[OPT_CORRECT_INFRARED].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in halftone mode (irrelevant)\n", scanner->opt[OPT_CORRECT_INFRARED].name, scanner->val[OPT_CORRECT_INFRARED].b); + } + if (scanner->val[OPT_CLEAN_IMAGE].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in halftone mode (irrelevant)\n", scanner->opt[OPT_CLEAN_IMAGE].name, scanner->val[OPT_CLEAN_IMAGE].b); + } + if (scanner->val[OPT_SMOOTH_IMAGE].w != 0) { + DBG (DBG_info_sane, "Option %s = %d ignored in halftone mode (irrelevant)\n", scanner->opt[OPT_SMOOTH_IMAGE].name, scanner->val[OPT_SMOOTH_IMAGE].w); + } + if (strcmp(scanner->val[OPT_CROP_IMAGE].s, scanner->device->crop_sw_list[0]) != 0) { + DBG (DBG_info_sane, "Option %s = %s ignored in halftone mode (irrelevant)\n", scanner->opt[OPT_CROP_IMAGE].name, scanner->val[OPT_CROP_IMAGE].s); + } + if (scanner->val[OPT_TRANSFORM_TO_SRGB].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in halftone mode (irrelevant)\n", scanner->opt[OPT_TRANSFORM_TO_SRGB].name, scanner->val[OPT_TRANSFORM_TO_SRGB].w); + } + } else if (strcmp(scanner->val[OPT_MODE].s,SANE_VALUE_SCAN_MODE_GRAY)==0) { + /* Can we do any post processing in gray mode? */ + /* Can we obtain a single color channel in this mode? How? */ + /* Is this just RGB with luminance trasformation? */ + /* Needs testing to see what's possible */ + /* Only do 8 or 16 bit scans */ + if (scanner->val[OPT_BIT_DEPTH].w == 1) { + DBG (DBG_info_sane, "Option %s = %d ignored in gray mode (will use 8)\n", scanner->opt[OPT_BIT_DEPTH].name, scanner->val[OPT_BIT_DEPTH].w); + } + if (!scanner->val[OPT_FAST_INFRARED].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in gray mode (irrelevant)\n", scanner->opt[OPT_FAST_INFRARED].name, scanner->val[OPT_FAST_INFRARED].b); + } + if (!scanner->val[OPT_CORRECT_INFRARED].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in gray mode (irrelevant)\n", scanner->opt[OPT_CORRECT_INFRARED].name, scanner->val[OPT_CORRECT_INFRARED].b); + } + if (scanner->val[OPT_CLEAN_IMAGE].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in gray mode (irrelevant)\n", scanner->opt[OPT_CLEAN_IMAGE].name, scanner->val[OPT_CLEAN_IMAGE].b); + } + if (scanner->val[OPT_TRANSFORM_TO_SRGB].b) { + DBG (DBG_info_sane, "Option %s = %d ignored in gray mode (irrelevant)\n", scanner->opt[OPT_TRANSFORM_TO_SRGB].name, scanner->val[OPT_TRANSFORM_TO_SRGB].w); + } + } else if (strcmp(scanner->val[OPT_MODE].s,SANE_VALUE_SCAN_MODE_COLOR)==0) { + /* Some options require infrared data to be obtained, so all infrared options are relevant */ + /* Only do 8 or 16 bit scans */ + if (scanner->val[OPT_BIT_DEPTH].w == 1) { + DBG (DBG_info_sane, "Option %s = %d ignored in color mode (will use 8)\n", scanner->opt[OPT_BIT_DEPTH].name, scanner->val[OPT_BIT_DEPTH].w); + } + } else if (strcmp(scanner->val[OPT_MODE].s,SANE_VALUE_SCAN_MODE_RGBI)==0) { + /* Only do 8 or 16 bit scans */ + if (scanner->val[OPT_BIT_DEPTH].w == 1) { + DBG (DBG_info_sane, "Option %s = %d ignored in color mode (will use 8)\n", scanner->opt[OPT_BIT_DEPTH].name, scanner->val[OPT_BIT_DEPTH].w); + } + } + + return 1; +} + +/** + * Print options + * + * @param scanner + */ +void +sanei_pieusb_print_options(struct Pieusb_Scanner *scanner) +{ + int k; + /* List current options and values */ + DBG (DBG_info, "Num options = %d\n", scanner->val[OPT_NUM_OPTS].w); + for (k = 1; k < scanner->val[OPT_NUM_OPTS].w; k++) { + switch (scanner->opt[k].type) { + case SANE_TYPE_BOOL: + DBG(DBG_info," Option %d: %s = %d\n", k, scanner->opt[k].name, scanner->val[k].b); + break; + case SANE_TYPE_INT: + DBG(DBG_info," Option %d: %s = %d\n", k, scanner->opt[k].name, scanner->val[k].w); + break; + case SANE_TYPE_FIXED: + DBG(DBG_info," Option %d: %s = %f\n", k, scanner->opt[k].name, SANE_UNFIX (scanner->val[k].w)); + break; + case SANE_TYPE_STRING: + DBG(DBG_info," Option %d: %s = %s\n", k, scanner->opt[k].name, scanner->val[k].s); + break; + case SANE_TYPE_GROUP: + DBG(DBG_info," Option %d: %s = %s\n", k, scanner->opt[k].title, scanner->val[k].s); + break; + default: + DBG(DBG_info," Option %d: %s unknown type %d\n", k, scanner->opt[k].name, scanner->opt[k].type); + break; + } + } +} + +/** + * Calculate reference values for each pixel, line means and line maxima. + * We have got 45 lines for all four colors and for each CCD pixel. + * The reference value for each pixel is the 45-line average for that + * pixel, for each color separately. + * + * @param scanner + * @param buffer + */ +static void pieusb_calculate_shading(struct Pieusb_Scanner *scanner, SANE_Byte* buffer) +{ + int k, m; + SANE_Byte* p; + SANE_Int ci, val; + SANE_Int shading_width = scanner->device->shading_parameters[0].pixelsPerLine; + SANE_Int shading_height = scanner->device->shading_parameters[0].nLines; + + /* Initialze all to 0 */ + for (k = 0; k < SHADING_PARAMETERS_INFO_COUNT; k++) { + scanner->shading_max[k] = 0; + scanner->shading_mean[k] = 0; + memset(scanner->shading_ref[k], 0, shading_width * sizeof (SANE_Int)); + } + /* Process data from buffer */ + p = buffer; + switch (scanner->mode.colorFormat) { + case 0x01: /* Pixel */ + /* Process pixel by pixel */ + for (k = 0; k < shading_height; k++) { + for (m = 0; m < shading_width; m++) { + for (ci = 0; ci < SHADING_PARAMETERS_INFO_COUNT; ci++) { + val = *(p) + *(p+1) * 256; + scanner->shading_ref[ci][m] += val; + scanner->shading_max[ci] = scanner->shading_max[ci] < val ? val : scanner->shading_max[ci]; + p += 2; + } + } + } + break; + case 0x04: /* Indexed */ + /* Process each line in the sequence found in the buffer */ + for (k = 0; k < shading_height*4; k++) { + /* Save at right color */ + switch (*p) { + case 'R': ci = 0; break; + case 'G': ci = 1; break; + case 'B': ci = 2; break; + case 'I': ci = 3; break; + default: ci = -1; break; /* ignore line */ + } + /* Add scanned data to reference line and keep track of maximum */ + if (ci != -1) { + for (m = 0; m < shading_width; m++) { + val = *(p+2+2*m) + *(p+2+2*m+1) * 256; + scanner->shading_ref[ci][m] += val; + scanner->shading_max[ci] = scanner->shading_max[ci] < val ? val : scanner->shading_max[ci]; + /* DBG(DBG_error,"%02d Shading_ref[%d][%d] = %d\n",k,ci,m,scanner->shading_ref[ci][m]); */ + } + } + /* Next line */ + p += 2*shading_width+2; + } + break; + default: + DBG (DBG_error,"sane_start(): color format %d not implemented\n",scanner->mode.colorFormat); + return; + } + /* Mean reference value needs division */ + for (k = 0; k < SHADING_PARAMETERS_INFO_COUNT; k++) { + for (m = 0; m < shading_width; m++) { + scanner->shading_ref[k][m] = lround((double)scanner->shading_ref[k][m]/shading_height); + /* DBG(DBG_error,"Shading_ref[%d][%d] = %d\n",k,m,scanner->shading_ref[k][m]); */ + } + } + /* Overall means */ + for (k = 0; k < SHADING_PARAMETERS_INFO_COUNT; k++) { + for (m=0; m<shading_width; m++) { + scanner->shading_mean[k] += scanner->shading_ref[k][m]; + } + scanner->shading_mean[k] = lround((double)scanner->shading_mean[k]/shading_width); + DBG (DBG_error,"Shading_mean[%d] = %d\n",k,scanner->shading_mean[k]); + } + + /* Set shading data present */ + scanner->shading_data_present = SANE_TRUE; + + /* Export shading data as TIFF */ +#ifdef CAN_DO_4_CHANNEL_TIFF + if (scanner->val[OPT_SAVE_SHADINGDATA].b) { + struct Pieusb_Read_Buffer shading; + SANE_Byte* lboff = buffer; + SANE_Int bpl = shading_width*2; + SANE_Int n; + buffer_create(&shading, shading_width, shading_height, 0x0F, 16); + for (n=0; n<4*shading_height; n++) { + if (buffer_put_single_color_line(&shading, *lboff, lboff+2, bpl) == 0) { + break; + } + lboff += (bpl + 2); + } + FILE* fs = fopen("pieusb.shading", "w"); + /* write_tiff_rgbi_header (fs, shading_width, shading_height, 16, 3600, NULL); */ + fwrite(shading.data, 1, shading.image_size_bytes, fs); + fclose(fs); + buffer_delete(&shading); + } +#endif + +} + +/* + * Set frame (from scanner options) + */ + +SANE_Status +sanei_pieusb_set_frame_from_options(Pieusb_Scanner * scanner) +{ + double dpmm; + struct Pieusb_Command_Status status; + + dpmm = (double) scanner->device->maximum_resolution / MM_PER_INCH; + scanner->frame.x0 = SANE_UNFIX(scanner->val[OPT_TL_X].w) * dpmm; + scanner->frame.y0 = SANE_UNFIX(scanner->val[OPT_TL_Y].w) * dpmm; + scanner->frame.x1 = SANE_UNFIX(scanner->val[OPT_BR_X].w) * dpmm; + scanner->frame.y1 = SANE_UNFIX(scanner->val[OPT_BR_Y].w) * dpmm; + scanner->frame.index = 0x80; /* 0x80: value from cyberview */ + sanei_pieusb_cmd_set_scan_frame (scanner->device_number, scanner->frame.index, &(scanner->frame), &status); + DBG (DBG_info_sane, "sanei_pieusb_set_frame_from_options(): sanei_pieusb_cmd_set_scan_frame status %s\n", sane_strstatus (sanei_pieusb_convert_status (status.pieusb_status))); + return status.pieusb_status; +} + +/* + * Set mode (from scanner options) + */ + +SANE_Status +sanei_pieusb_set_mode_from_options(Pieusb_Scanner * scanner) +{ + struct Pieusb_Command_Status status; + const char *mode; + SANE_Status res; + + mode = scanner->val[OPT_MODE].s; + if (strcmp (mode, SANE_VALUE_SCAN_MODE_LINEART) == 0) { + scanner->mode.passes = SCAN_FILTER_GREEN; /* G */ + scanner->mode.colorFormat = SCAN_COLOR_FORMAT_PIXEL; + } else if(strcmp (mode, SANE_VALUE_SCAN_MODE_HALFTONE) == 0) { + scanner->mode.passes = SCAN_FILTER_GREEN; /* G */ + scanner->mode.colorFormat = SCAN_COLOR_FORMAT_PIXEL; + } else if(strcmp (mode, SANE_VALUE_SCAN_MODE_GRAY) == 0) { + scanner->mode.passes = SCAN_FILTER_GREEN; /* G=gray; unable to get R & B & I to work */ + scanner->mode.colorFormat = SCAN_COLOR_FORMAT_PIXEL; + } else if(scanner->val[OPT_PREVIEW].b) { + /* Catch preview here, otherwise next ifs get complicated */ + scanner->mode.passes = SCAN_ONE_PASS_COLOR; + scanner->mode.colorFormat = SCAN_COLOR_FORMAT_INDEX; /* pixel format might be an alternative */ + } else if(strcmp (mode, SANE_VALUE_SCAN_MODE_RGBI) == 0) { + scanner->mode.passes = SCAN_ONE_PASS_RGBI; + scanner->mode.colorFormat = SCAN_COLOR_FORMAT_INDEX; + } else if(strcmp (mode, SANE_VALUE_SCAN_MODE_COLOR) == 0 && scanner->val[OPT_CLEAN_IMAGE].b) { + scanner->mode.passes = SCAN_ONE_PASS_RGBI; /* Need infrared for cleaning */ + scanner->mode.colorFormat = SCAN_COLOR_FORMAT_INDEX; + } else { /* SANE_VALUE_SCAN_MODE_COLOR */ + scanner->mode.passes = SCAN_ONE_PASS_COLOR; + scanner->mode.colorFormat = SCAN_COLOR_FORMAT_INDEX; /* pixel format might be an alternative */ + } + /* Resolution */ + if (scanner->val[OPT_PREVIEW].b) { + scanner->mode.resolution = scanner->device->fast_preview_resolution; + DBG (DBG_info_sane, "sanei_pieusb_set_mode_from_options(): resolution fast preview (%d)\n", scanner->mode.resolution); + } else { + scanner->mode.resolution = SANE_UNFIX (scanner->val[OPT_RESOLUTION].w); + DBG (DBG_info_sane, "sanei_pieusb_set_mode_from_options(): resolution from option setting (%d)\n", scanner->mode.resolution); + } + /* Bit depth: exit on untested values */ + switch (scanner->val[OPT_BIT_DEPTH].w) { + case 1: scanner->mode.colorDepth = SCAN_COLOR_DEPTH_1; break; + case 8: scanner->mode.colorDepth = SCAN_COLOR_DEPTH_8; break; + case 16: scanner->mode.colorDepth = SCAN_COLOR_DEPTH_16; break; + default: /* 4, 10 & 12 */ + DBG (DBG_error, "sanei_pieusb_set_mode_from_options(): sanei_pieusb_cmd_set_scan_frame untested bit depth %d\n", scanner->val[OPT_BIT_DEPTH].w); + return SANE_STATUS_INVAL; + } + scanner->mode.byteOrder = 0x01; /* 0x01 = Intel; only bit 0 used */ + scanner->mode.sharpen = scanner->val[OPT_SHARPEN].b && !scanner->val[OPT_PREVIEW].b; + scanner->mode.skipShadingAnalysis = !scanner->val[OPT_SHADING_ANALYSIS].b; + scanner->mode.fastInfrared = scanner->val[OPT_FAST_INFRARED].b && !scanner->val[OPT_PREVIEW].b; + if (strcmp (scanner->val[OPT_HALFTONE_PATTERN].s, "53lpi 45d ROUND") == 0) { + scanner->mode.halftonePattern = 0; + } else { /*TODO: the others */ + scanner->mode.halftonePattern = 0; + } + scanner->mode.lineThreshold = SANE_UNFIX (scanner->val[OPT_THRESHOLD].w) / 100 * 0xFF; /* 0xFF = 100% */ + sanei_pieusb_cmd_set_mode (scanner->device_number, &(scanner->mode), &status); + res = sanei_pieusb_convert_status(status.pieusb_status); + if (res == SANE_STATUS_GOOD) { + res = sanei_pieusb_wait_ready (scanner, 0); + } + DBG (DBG_info_sane, "sanei_pieusb_set_mode_from_options(): sanei_pieusb_cmd_set_mode status %s\n", sane_strstatus(res)); + return res; +} + +/** + * Set gains, exposure and offset, to: + * - values default (pieusb_set_default_gain_offset) + * - values set by options + * - values set by auto-calibration procedure + * - values determined from preceeding preview + * + * @param scanner + * @return + */ +SANE_Status +sanei_pieusb_set_gain_offset(Pieusb_Scanner * scanner, const char *calibration_mode) +{ + struct Pieusb_Command_Status status; + SANE_Status ret; + double gain; + + DBG (DBG_info,"sanei_pieusb_set_gain_offset(): mode = %s\n", calibration_mode); + + if (strcmp (calibration_mode, SCAN_CALIBRATION_DEFAULT) == 0) { + /* Default values */ + DBG(DBG_info_sane,"sanei_pieusb_set_gain_offset(): get calibration data from defaults\n"); + scanner->settings.exposureTime[0] = DEFAULT_EXPOSURE; + scanner->settings.exposureTime[1] = DEFAULT_EXPOSURE; + scanner->settings.exposureTime[2] = DEFAULT_EXPOSURE; + scanner->settings.exposureTime[3] = DEFAULT_EXPOSURE; + scanner->settings.offset[0] = DEFAULT_OFFSET; + scanner->settings.offset[1] = DEFAULT_OFFSET; + scanner->settings.offset[2] = DEFAULT_OFFSET; + scanner->settings.offset[3] = DEFAULT_OFFSET; + scanner->settings.gain[0] = DEFAULT_GAIN; + scanner->settings.gain[1] = DEFAULT_GAIN; + scanner->settings.gain[2] = DEFAULT_GAIN; + scanner->settings.gain[3] = DEFAULT_GAIN; + scanner->settings.light = DEFAULT_LIGHT; + scanner->settings.extraEntries = DEFAULT_ADDITIONAL_ENTRIES; + scanner->settings.doubleTimes = DEFAULT_DOUBLE_TIMES; + status.pieusb_status = PIEUSB_STATUS_GOOD; + } else if ((strcmp(calibration_mode, SCAN_CALIBRATION_PREVIEW) == 0) + && scanner->preview_done) { + /* If no preview data availble, do the auto-calibration. */ + double dg, dgi; + DBG (DBG_info, "sanei_pieusb_set_gain_offset(): get calibration data from preview. scanner->mode.passes %d\n", scanner->mode.passes); + switch (scanner->mode.passes) { + case SCAN_ONE_PASS_RGBI: + dg = 3.00; + dgi = ((double)scanner->settings.saturationLevel[0] / 65536) / ((double)scanner->preview_upper_bound[0] / HISTOGRAM_SIZE); + if (dgi < dg) dg = dgi; + dgi = ((double)scanner->settings.saturationLevel[1] / 65536) / ((double)scanner->preview_upper_bound[1] / HISTOGRAM_SIZE); + if (dgi < dg) dg = dgi; + dgi = ((double)scanner->settings.saturationLevel[2] / 65536) / ((double)scanner->preview_upper_bound[2] / HISTOGRAM_SIZE); + if (dgi < dg) dg = dgi; + updateGain2(scanner, 0, dg); + updateGain2(scanner, 1, dg); + updateGain2(scanner, 2, dg); + break; + case SCAN_ONE_PASS_COLOR: + dg = 3.00; + dgi = ((double)scanner->settings.saturationLevel[0] / 65536) / ((double)scanner->preview_upper_bound[0] / HISTOGRAM_SIZE); + if (dgi < dg) dg = dgi; + dgi = ((double)scanner->settings.saturationLevel[1] / 65536) / ((double)scanner->preview_upper_bound[1] / HISTOGRAM_SIZE); + if (dgi < dg) dg = dgi; + dgi = ((double)scanner->settings.saturationLevel[2] / 65536) / ((double)scanner->preview_upper_bound[2] / HISTOGRAM_SIZE); + if (dgi < dg) dg = dgi; + updateGain2(scanner, 0, dg); + updateGain2(scanner, 1, dg); + updateGain2(scanner, 2, dg); + break; + case SCAN_FILTER_BLUE: + dg = 3.00; + dgi = ((double)scanner->settings.saturationLevel[2] / 65536) / ((double)scanner->preview_upper_bound[2] / HISTOGRAM_SIZE); + if (dgi < dg) dg = dgi; + updateGain2(scanner, 2, dg); + break; + case SCAN_FILTER_GREEN: + dg = 3.00; + dgi = ((double)scanner->settings.saturationLevel[1] / 65536) / ((double)scanner->preview_upper_bound[1] / HISTOGRAM_SIZE); + if (dgi < dg) dg = dgi; + updateGain2(scanner, 1, dg); + break; + case SCAN_FILTER_RED: + dg = 3.00; + dgi = ((double)scanner->settings.saturationLevel[0] / 65536) / ((double)scanner->preview_upper_bound[0] / HISTOGRAM_SIZE); + if (dgi < dg) dg = dgi; + updateGain2(scanner, 0, dg); + break; + case SCAN_FILTER_NEUTRAL: + break; + } + status.pieusb_status = PIEUSB_STATUS_GOOD; + } else if (strcmp (calibration_mode, SCAN_CALIBRATION_OPTIONS) == 0) { + DBG (DBG_info_sane, "sanei_pieusb_set_gain_offset(): get calibration data from options\n"); + /* Exposure times */ + scanner->settings.exposureTime[0] = scanner->val[OPT_SET_EXPOSURE_R].w; + scanner->settings.exposureTime[1] = scanner->val[OPT_SET_EXPOSURE_G].w; + scanner->settings.exposureTime[2] = scanner->val[OPT_SET_EXPOSURE_B].w; + scanner->settings.exposureTime[3] = scanner->val[OPT_SET_EXPOSURE_I].w; /* Infrared */ + /* Offsets */ + scanner->settings.offset[0] = scanner->val[OPT_SET_OFFSET_R].w; + scanner->settings.offset[1] = scanner->val[OPT_SET_OFFSET_G].w; + scanner->settings.offset[2] = scanner->val[OPT_SET_OFFSET_B].w; + scanner->settings.offset[3] = scanner->val[OPT_SET_OFFSET_I].w; /* Infrared */ + /* Gains */ + scanner->settings.gain[0] = scanner->val[OPT_SET_GAIN_R].w; + scanner->settings.gain[1] = scanner->val[OPT_SET_GAIN_G].w; + scanner->settings.gain[2] = scanner->val[OPT_SET_GAIN_B].w; + scanner->settings.gain[3] = scanner->val[OPT_SET_GAIN_I].w; /* Infrared */ + /* Light, extra entries and doubling */ + scanner->settings.light = scanner->val[OPT_LIGHT].w; + scanner->settings.extraEntries = DEFAULT_ADDITIONAL_ENTRIES; + scanner->settings.doubleTimes = scanner->val[OPT_DOUBLE_TIMES].w; + status.pieusb_status = PIEUSB_STATUS_GOOD; + } else { /* SCAN_CALIBRATION_AUTO */ + DBG (DBG_info_sane, "sanei_pieusb_set_gain_offset(): get calibration data from scanner\n"); + sanei_pieusb_cmd_get_gain_offset (scanner->device_number, &scanner->settings, &status); + } + /* Check status */ + if (status.pieusb_status == PIEUSB_STATUS_DEVICE_BUSY) { + ret = sanei_pieusb_wait_ready (scanner, 0); + if (ret != SANE_STATUS_GOOD) { + DBG (DBG_error,"sanei_pieusb_set_gain_offset(): not ready after sanei_pieusb_cmd_get_gain_offset(): %d\n", ret); + return ret; + } + } + else if (status.pieusb_status != PIEUSB_STATUS_GOOD) { + return SANE_STATUS_INVAL; + } + /* Adjust gain */ + gain = 1.0; + if (strcmp (scanner->val[OPT_GAIN_ADJUST].s, SCAN_GAIN_ADJUST_03) == 0) { + gain = 0.3; + } else if (strcmp (scanner->val[OPT_GAIN_ADJUST].s, SCAN_GAIN_ADJUST_05) == 0) { + gain = 0.5; + } else if (strcmp (scanner->val[OPT_GAIN_ADJUST].s, SCAN_GAIN_ADJUST_08) == 0) { + gain = 0.8; + } else if (strcmp (scanner->val[OPT_GAIN_ADJUST].s, SCAN_GAIN_ADJUST_10) == 0) { + gain = 1.0; + } else if (strcmp (scanner->val[OPT_GAIN_ADJUST].s, SCAN_GAIN_ADJUST_12) == 0) { + gain = 1.2; + } else if (strcmp (scanner->val[OPT_GAIN_ADJUST].s, SCAN_GAIN_ADJUST_16) == 0) { + gain = 1.6; + } else if (strcmp (scanner->val[OPT_GAIN_ADJUST].s, SCAN_GAIN_ADJUST_19) == 0) { + gain = 1.9; + } else if (strcmp (scanner->val[OPT_GAIN_ADJUST].s, SCAN_GAIN_ADJUST_24) == 0) { + gain = 2.4; + } else if (strcmp (scanner->val[OPT_GAIN_ADJUST].s, SCAN_GAIN_ADJUST_30) == 0) { + gain = 3.0; + } + switch (scanner->mode.passes) { + case SCAN_ONE_PASS_RGBI: + case SCAN_ONE_PASS_COLOR: + updateGain2 (scanner, 0, gain); + updateGain2 (scanner, 1, gain); + updateGain2 (scanner, 2, gain); + /* Don't correct IR, hampers cleaning process... */ + break; + case SCAN_FILTER_INFRARED: + updateGain2 (scanner, 3, gain); + break; + case SCAN_FILTER_BLUE: + updateGain2 (scanner, 2, gain); + break; + case SCAN_FILTER_GREEN: + updateGain2 (scanner, 1, gain); + break; + case SCAN_FILTER_RED: + updateGain2 (scanner, 0, gain); + break; + case SCAN_FILTER_NEUTRAL: + break; + } + /* Now set values for gain, offset and exposure */ + sanei_pieusb_cmd_set_gain_offset (scanner->device_number, &(scanner->settings), &status); + ret = sanei_pieusb_convert_status (status.pieusb_status); + DBG (DBG_info_sane, "sanei_pieusb_set_gain_offset(): status %s\n", sane_strstatus (ret)); + return ret; +} + +/* + * get shading data + * must be called immediately after sanei_pieusb_set_gain_offset + */ + +SANE_Status +sanei_pieusb_get_shading_data(Pieusb_Scanner * scanner) +{ + struct Pieusb_Command_Status status; + SANE_Int shading_width; + SANE_Int shading_height; + SANE_Byte* buffer; + SANE_Int lines; + SANE_Int cols; + SANE_Int size; + SANE_Status res = SANE_STATUS_GOOD; + + DBG (DBG_info_sane, "sanei_pieusb_get_shading_data()\n"); + shading_width = scanner->device->shading_parameters[0].pixelsPerLine; + shading_height = scanner->device->shading_parameters[0].nLines; + if (shading_height < 1) { + DBG (DBG_error, "shading_height < 1\n"); + return SANE_STATUS_INVAL; + } + switch (scanner->mode.colorFormat) { + case SCAN_COLOR_FORMAT_PIXEL: /* Pixel */ + lines = shading_height * 4; + cols = 2 * shading_width; + break; + case SCAN_COLOR_FORMAT_INDEX: /* Indexed */ + lines = shading_height * 4; + cols = (2 * shading_width + 2); + break; + default: + DBG (DBG_error, "sanei_pieusb_get_shading_data(): color format %d not implemented\n", scanner->mode.colorFormat); + return SANE_STATUS_INVAL; + } + + size = cols * lines; + buffer = malloc (size); + if (buffer == NULL) { + return SANE_STATUS_NO_MEM; + } + sanei_pieusb_cmd_get_scanned_lines (scanner->device_number, buffer, 4, cols * 4, &status); + if (status.pieusb_status == PIEUSB_STATUS_GOOD) { + res = sanei_pieusb_wait_ready (scanner, 0); + if (res == SANE_STATUS_GOOD) { + sanei_pieusb_cmd_get_scanned_lines (scanner->device_number, buffer + cols*4, lines - 4, (lines - 4) * cols, &status); + if (status.pieusb_status == PIEUSB_STATUS_GOOD) { + pieusb_calculate_shading (scanner, buffer); + } + res = sanei_pieusb_convert_status (status.pieusb_status); + } + } + else { + res = sanei_pieusb_convert_status (status.pieusb_status); + } + free (buffer); + return res; +} + +/* + * + */ + +SANE_Status +sanei_pieusb_get_ccd_mask(Pieusb_Scanner * scanner) +{ + struct Pieusb_Command_Status status; + + DBG(DBG_info_proc, "sanei_pieusb_get_ccd_mask()\n"); + + sanei_pieusb_cmd_get_ccd_mask(scanner->device_number, scanner->ccd_mask, scanner->ccd_mask_size, &status); + if (status.pieusb_status == PIEUSB_STATUS_GOOD) { + /* Save CCD mask */ + if (scanner->val[OPT_SAVE_CCDMASK].b) { + FILE* fs = fopen ("pieusb.ccd", "w"); + fwrite (scanner->ccd_mask, 1, scanner->ccd_mask_size, fs); + fclose (fs); + } + } + return sanei_pieusb_convert_status(status.pieusb_status); + +} + +/** + * Read parameters from scanner + * and initialize SANE parameters + * + * @param scanner + * @return parameter_bytes for use in get_scan_data() + */ +SANE_Status +sanei_pieusb_get_parameters(Pieusb_Scanner * scanner, SANE_Int *parameter_bytes) +{ + struct Pieusb_Command_Status status; + struct Pieusb_Scan_Parameters parameters; + const char *mode; + + DBG (DBG_info_proc, "sanei_pieusb_get_parameters()\n"); + + sanei_pieusb_cmd_get_parameters (scanner->device_number, ¶meters, &status); + if (status.pieusb_status != PIEUSB_STATUS_GOOD) { + return sanei_pieusb_convert_status (status.pieusb_status); + } + *parameter_bytes = parameters.bytes; + /* Use response from sanei_pieusb_cmd_get_parameters() for initialization of SANE parameters. + * Note the weird values of the bytes-field: this is because of the colorFormat + * setting in sanei_pieusb_cmd_set_mode(). The single-color modes all use the pixel format, + * which makes sanei_pieusb_cmd_get_parameters() return a full color line although just + * one color actually contains data. For the index format, the bytes field + * gives the size of a single color line. */ + mode = scanner->val[OPT_MODE].s; + if (strcmp (mode, SANE_VALUE_SCAN_MODE_LINEART) == 0) { + scanner->scan_parameters.format = SANE_FRAME_GRAY; + scanner->scan_parameters.depth = 1; + scanner->scan_parameters.bytes_per_line = parameters.bytes/3; + } else if (strcmp (mode, SANE_VALUE_SCAN_MODE_HALFTONE) == 0) { + scanner->scan_parameters.format = SANE_FRAME_GRAY; + scanner->scan_parameters.depth = 1; + scanner->scan_parameters.bytes_per_line = parameters.bytes/3; + } else if (strcmp (mode, SANE_VALUE_SCAN_MODE_GRAY) == 0) { + scanner->scan_parameters.format = SANE_FRAME_GRAY; + scanner->scan_parameters.depth = scanner->val[OPT_BIT_DEPTH].w; + scanner->scan_parameters.bytes_per_line = parameters.bytes/3; + } else if (strcmp (mode, SANE_VALUE_SCAN_MODE_RGBI) == 0) { + scanner->scan_parameters.format = SANE_FRAME_RGB; /* was: SANE_FRAME_RGBI */ + scanner->scan_parameters.depth = scanner->val[OPT_BIT_DEPTH].w; + scanner->scan_parameters.bytes_per_line = 4*parameters.bytes; + } else { /* SANE_VALUE_SCAN_MODE_COLOR, with and without option clean image set */ + scanner->scan_parameters.format = SANE_FRAME_RGB; + scanner->scan_parameters.depth = scanner->val[OPT_BIT_DEPTH].w; + scanner->scan_parameters.bytes_per_line = 3*parameters.bytes; + } + scanner->scan_parameters.lines = parameters.lines; + scanner->scan_parameters.pixels_per_line = parameters.width; + scanner->scan_parameters.last_frame = SANE_TRUE; + + DBG (DBG_info_sane,"sanei_pieusb_get_parameters(): mode '%s'\n", mode); + DBG (DBG_info_sane," format = %d\n", scanner->scan_parameters.format); + DBG (DBG_info_sane," depth = %d\n", scanner->scan_parameters.depth); + DBG (DBG_info_sane," bytes_per_line = %d\n", scanner->scan_parameters.bytes_per_line); + DBG (DBG_info_sane," lines = %d\n", scanner->scan_parameters.lines); + DBG (DBG_info_sane," pixels_per_line = %d\n", scanner->scan_parameters.pixels_per_line); + DBG (DBG_info_sane," last_frame = %d\n", scanner->scan_parameters.last_frame); + + return SANE_STATUS_GOOD; +} + +SANE_Status +sanei_pieusb_get_scan_data(Pieusb_Scanner * scanner, SANE_Int parameter_bytes) +{ + struct Pieusb_Command_Status status; + SANE_Parameters *parameters = &scanner->scan_parameters; + SANE_Int lines_to_read, lines_remaining; + SANE_Int ppl, bpl; + SANE_Byte *linebuf, *lboff; + SANE_Bool compress; + int n, k, i; + + switch (scanner->mode.colorFormat) { + case SCAN_COLOR_FORMAT_PIXEL: /* Pixel */ + lines_to_read = scanner->buffer.height; + break; + case SCAN_COLOR_FORMAT_INDEX: /* Indexed */ + lines_to_read = scanner->buffer.colors * scanner->buffer.height; + break; + default: + DBG(DBG_error, "sanei_pieusb_get_scan_data(): color format %d not implemented\n",scanner->mode.colorFormat); + return SANE_STATUS_INVAL; + } + lines_remaining = lines_to_read; + DBG (DBG_info_proc, "sanei_pieusb_get_scan_data(colorFormat %d), lines_to_read %d, bytes %d\n", scanner->mode.colorFormat, lines_to_read, parameter_bytes); + + /* + fdraw = open("/tmp/pieusb.raw", O_WRONLY | O_CREAT | O_TRUNC, (mode_t)0600); + if (fdraw == -1) { + perror("error opening raw image buffer file"); + } +*/ + while (lines_remaining > 0) { + SANE_Int lines; + /* Read lines */ + /* The amount of bytes_per_line varies with color format setting; only 'pixel' and 'index' implemented */ + ppl = parameters->pixels_per_line; + switch (scanner->mode.colorFormat) { + case SCAN_COLOR_FORMAT_PIXEL: /* Pixel */ + bpl = parameter_bytes; + break; + case SCAN_COLOR_FORMAT_INDEX: /* Indexed */ + bpl = parameter_bytes + 2; /* Index bytes! */ + break; + default: + DBG(DBG_error, "sanei_pieusb_get_scan_data(): color format %d not implemented\n", scanner->mode.colorFormat); + return SANE_STATUS_INVAL; + } + lines = (lines_remaining < 256) ? lines_remaining : 255; + DBG(DBG_info_sane, "sanei_pieusb_get_scan_data(): reading lines: now %d, bytes per line = %d\n", lines, bpl); + linebuf = malloc(lines * bpl); + sanei_pieusb_cmd_get_scanned_lines(scanner->device_number, linebuf, lines, lines * bpl, &status); + if (status.pieusb_status != PIEUSB_STATUS_GOOD ) { + /* Error, return */ + free(linebuf); + return SANE_STATUS_INVAL; + } + /* Save raw data */ +/* + if (fdraw != -1) { + wcnt = write(fdraw,linebuf,parameters.lines*bpl); + DBG(DBG_info_sane,"Raw written %d\n",wcnt); + } +*/ + /* Copy into official buffer + * Sometimes the scanner returns too many lines. Take care not to + * overflow the buffer. */ + lboff = linebuf; + switch (scanner->mode.colorFormat) { + case SCAN_COLOR_FORMAT_PIXEL: + /* The scanner may return lines with 3 colors even though only + * one color is actually scanned. Detect this situation and + * eliminate the excess samples from the line buffer before + * handing it to buffer_put_full_color_line(). */ + compress = SANE_FALSE; + if (scanner->buffer.colors == 1 + && (bpl * scanner->buffer.packing_density / ppl) == (3 * scanner->buffer.packet_size_bytes)) { + compress = SANE_TRUE; + } + for (n = 0; n < lines; n++) { + if (compress) { + /* Move samples to fill up all unused locations */ + int ps = scanner->buffer.packet_size_bytes; + for (k = 0; k < scanner->buffer.line_size_packets; k++) { + for (i = 0; i < ps; i++) { + lboff[ps*k+i] = lboff[3*ps*k+i]; + } + } + } + if (sanei_pieusb_buffer_put_full_color_line(&scanner->buffer, lboff, bpl/3) == 0) { + /* Error, return */ + return SANE_STATUS_INVAL; + } + lboff += bpl; + } + break; + case SCAN_COLOR_FORMAT_INDEX: + /* Indexed data */ + for (n = 0; n < lines; n++) { + if (sanei_pieusb_buffer_put_single_color_line(&scanner->buffer, *lboff, lboff+2, bpl-2) == 0) { + /* Error, return */ + return SANE_STATUS_INVAL; + } + lboff += bpl; + } + break; + default: + DBG(DBG_error, "sanei_pieusb_get_scan_data(): store color format %d not implemented\n", scanner->mode.colorFormat); + free(linebuf); + return SANE_STATUS_INVAL; + } + free(linebuf); + lines_remaining -= lines; /* Note: excess discarded */ + DBG(DBG_info_sane, "sanei_pieusb_get_scan_data(): reading lines: remaining %d\n", lines_remaining); + } +/* + if (fdraw != -1) close(fdraw); +*/ + return SANE_STATUS_GOOD; +} + +/** + * Wait for scanner to get ready + * + * loop of test_ready/read_state + * + * @param scanner + * @param device_number, used if scanner == NULL + * @return SANE_Status + */ + +SANE_Status +sanei_pieusb_wait_ready(Pieusb_Scanner * scanner, SANE_Int device_number) +{ + struct Pieusb_Command_Status status; + struct Pieusb_Scanner_State state; + time_t start, elapsed; + + DBG (DBG_info_proc, "sanei_pieusb_wait_ready()\n"); + start = time(NULL); + if (scanner) + device_number = scanner->device_number; + + for(;;) { + sanei_pieusb_cmd_test_unit_ready(device_number, &status); + DBG (DBG_info_proc, "-> sanei_pieusb_cmd_test_unit_ready: %d\n", status.pieusb_status); + if (status.pieusb_status == PIEUSB_STATUS_GOOD) + break; + if (status.pieusb_status == PIEUSB_STATUS_IO_ERROR) + break; + sanei_pieusb_cmd_read_state(device_number, &state, &status); + DBG (DBG_info_proc, "-> sanei_pieusb_cmd_read_state: %d\n", status.pieusb_status); + if (status.pieusb_status != PIEUSB_STATUS_DEVICE_BUSY) + break; + sleep(2); + elapsed = time(NULL) - start; + if (elapsed > 120) { /* 2 minute overall timeout */ + DBG (DBG_error, "scanner not ready after 2 minutes\n"); + break; + } + if (elapsed % 2) { + DBG (DBG_info, "still waiting for scanner to get ready\n"); + } + } + return sanei_pieusb_convert_status(status.pieusb_status); +} + + +SANE_Status sanei_pieusb_analyze_preview(Pieusb_Scanner * scanner) +{ + int k, n; + SANE_Parameters params; + SANE_Int N; + double *norm_histo; + double level; + + DBG(DBG_info, "sanei_pieusb_analyze_preview(): saving preview data\n"); + + /* Settings */ + scanner->preview_done = SANE_TRUE; + for (k = 0; k < 4; k++) { + scanner->preview_exposure[k] = scanner->settings.exposureTime[k]; + scanner->preview_gain[k] = scanner->settings.gain[k]; + scanner->preview_offset[k] = scanner->settings.offset[k]; + } + /* Analyze color planes */ + N = scanner->buffer.width * scanner->buffer.height; + params.format = SANE_FRAME_GRAY; + params.depth = scanner->buffer.depth; + params.pixels_per_line = scanner->buffer.width; + params.lines = scanner->buffer.height; + for (k = 0; k < scanner->buffer.colors; k++) { + /* Create histogram for color k */ + sanei_ir_create_norm_histogram (¶ms, scanner->buffer.data + k * N, &norm_histo); + /* Find 1% and 99% limits */ + level = 0; + for (n =0; n < HISTOGRAM_SIZE; n++) { + + level += norm_histo[n]; + if (level < 0.01) { + scanner->preview_lower_bound[k] = n; + } + if (level < 0.99) { + scanner->preview_upper_bound[k] = n; + } + } + DBG(DBG_info,"sanei_pieusb_analyze_preview(): 1%%-99%% levels for color %d: %d - %d\n", k, scanner->preview_lower_bound[k], scanner->preview_upper_bound[k]); + } + /* Disable remaining color planes */ + for (k = scanner->buffer.colors; k < 4; k++) { + scanner->preview_lower_bound[k] = 0; + scanner->preview_upper_bound[k] = 0; + } + return SANE_STATUS_GOOD; +} + + +/** + * Return actual gain at given gain setting + * + * @param gain Gain setting (0 - 63) + * @return + */ +static double getGain(int gain) +{ + int k; + + /* Actually an error, but don't be picky */ + if (gain <= 0) { + return gains[0]; + } + /* A gain > 63 is also an error, but don't be picky */ + if (gain >= 60) { + return (gain-55)*(gains[12]-gains[11])/5 + gains[11]; + } + /* Interpolate other values */ + k = gain/5; /* index of array value just below given gain */ + return (gain-5*k)*(gains[k+1]-gains[k])/5 + gains[k]; +} + +static int getGainSetting(double gain) +{ + int k, m; + + /* Out of bounds */ + if (gain < 1.0) { + return 0; + } + if (gain >= gains[12]) { + m = 60 + lround((gain-gains[11])/(gains[12]-gains[11])*5); + if (m > 63) m = 63; + return m; + } + /* Interpolate the rest */ + m = 0; + for (k = 0; k <= 11; k++) { + if (gains[k] <= gain && gain < gains[k+1]) { + m = 5*k + lround((gain-gains[k])/(gains[k+1]-gains[k])*5); + } + } + return m; +} + +/** + * Modify gain and exposure times in order to make maximal use of the scan depth. + * Each color treated separately, infrared excluded. + * + * This may be too aggressive => leads to a noisy whitish border instead of the orange. + * In a couuple of tries, gain was set to values of 60 and above, which introduces + * the noise? + * The whitish border is logical since the brightest parts of the negative, the + * unexposed borders, are amplified to values near CCD saturation, which is white. + * Maybe a uniform gain increase for each color is more appropriate? Somewhere + * between 2.5 and 3 seems worthwhile trying, see updateGain2(). + * + switch (scanner->mode.passes) { + case SCAN_ONE_PASS_RGBI: + updateGain(scanner,0); + updateGain(scanner,1); + updateGain(scanner,2); + updateGain(scanner,3); + break; + case SCAN_ONE_PASS_COLOR: + updateGain(scanner,0); + updateGain(scanner,1); + updateGain(scanner,2); + break; + case SCAN_FILTER_INFRARED: + updateGain(scanner,3); + break; + case SCAN_FILTER_BLUE: + updateGain(scanner,2); + break; + case SCAN_FILTER_GREEN: + updateGain(scanner,1); + break; + case SCAN_FILTER_RED: + updateGain(scanner,0); + break; + case SCAN_FILTER_NEUTRAL: + break; + } + * @param scanner + */ +/* +static void updateGain(Pieusb_Scanner *scanner, int color_index) +{ + double g, dg; + + DBG(DBG_info_sane,"updateGain(): color %d preview used G=%d Exp=%d\n", color_index, scanner->preview_gain[color_index], scanner->preview_exposure[color_index]); + // Additional gain to obtain + dg = ((double)scanner->settings.saturationLevel[color_index] / 65536) / ((double)scanner->preview_upper_bound[color_index] / HISTOGRAM_SIZE); + DBG(DBG_info_sane,"updateGain(): additional gain %f\n", dg); + // Achieve this by modifying gain and exposure + // Gain used for preview + g = getGain(scanner->preview_gain[color_index]); + DBG(DBG_info_sane,"updateGain(): preview had gain %d => %f\n",scanner->preview_gain[color_index],g); + // Look up new gain setting g*sqrt(dg) + DBG(DBG_info_sane,"updateGain(): optimized gain * %f = %f\n",sqrt(dg),sqrt(dg)*g); + scanner->settings.gain[color_index] = getGainSetting(g*sqrt(dg)); + DBG(DBG_info_sane,"updateGain(): optimized gain setting %d => %f\n",scanner->settings.gain[color_index],getGain(scanner->settings.gain[color_index])); + // Exposure change is straightforward + DBG(DBG_info_sane,"updateGain(): remains for exposure %f\n",dg/(getGain(scanner->settings.gain[color_index])/g)); + scanner->settings.exposureTime[color_index] = lround( g / getGain(scanner->settings.gain[color_index]) * dg * scanner->preview_exposure[color_index] ); + DBG(DBG_info_sane,"updateGain(): new setting G=%d Exp=%d\n", scanner->settings.gain[color_index], scanner->settings.exposureTime[color_index]); +} +*/ + +static void updateGain2(Pieusb_Scanner *scanner, int color_index, double gain_increase) +{ + double g; + + DBG(DBG_info,"updateGain2(): color %d preview used G=%d Exp=%d\n", color_index, scanner->settings.gain[color_index], scanner->settings.exposureTime[color_index]); + /* Additional gain to obtain */ + DBG(DBG_info,"updateGain2(): additional gain %f\n", gain_increase); + /* Achieve this by modifying gain and exposure */ + /* Gain used for preview */ + g = getGain(scanner->settings.gain[color_index]); + DBG(DBG_info,"updateGain2(): preview had gain %d => %f\n", scanner->settings.gain[color_index], g); + /* Look up new gain setting g*sqrt(dg) */ + DBG(DBG_info,"updateGain2(): optimized gain * %f = %f\n", sqrt(gain_increase), sqrt(gain_increase) * g); + scanner->settings.gain[color_index] = getGainSetting(g * sqrt(gain_increase)); + DBG(DBG_info,"updateGain2(): optimized gain setting %d => %f\n", scanner->settings.gain[color_index], getGain(scanner->settings.gain[color_index])); + /* Exposure change is straightforward */ + DBG(DBG_info,"updateGain2(): remains for exposure %f\n", gain_increase / (getGain(scanner->settings.gain[color_index]) / g)); + scanner->settings.exposureTime[color_index] = lround( g / getGain(scanner->settings.gain[color_index]) * gain_increase * scanner->settings.exposureTime[color_index] ); + DBG(DBG_info,"updateGain2(): new setting G=%d Exp=%d\n", scanner->settings.gain[color_index], scanner->settings.exposureTime[color_index]); +} |