diff options
Diffstat (limited to 'backend/genesys/genesys.cpp')
| -rw-r--r-- | backend/genesys/genesys.cpp | 6172 | 
1 files changed, 6172 insertions, 0 deletions
| diff --git a/backend/genesys/genesys.cpp b/backend/genesys/genesys.cpp new file mode 100644 index 0000000..7c25168 --- /dev/null +++ b/backend/genesys/genesys.cpp @@ -0,0 +1,6172 @@ +/* sane - Scanner Access Now Easy. + +   Copyright (C) 2003, 2004 Henning Meier-Geinitz <henning@meier-geinitz.de> +   Copyright (C) 2004, 2005 Gerhard Jaeger <gerhard@gjaeger.de> +   Copyright (C) 2004-2016 Stéphane Voltz <stef.dev@free.fr> +   Copyright (C) 2005-2009 Pierre Willenbrock <pierre@pirsoft.dnsalias.org> +   Copyright (C) 2006 Laurent Charpentier <laurent_pubs@yahoo.com> +   Copyright (C) 2007 Luke <iceyfor@gmail.com> +   Copyright (C) 2010 Chris Berry <s0457957@sms.ed.ac.uk> and Michael Rickmann <mrickma@gwdg.de> +                 for Plustek Opticbook 3600 support + +   Dynamic rasterization code was taken from the epjistsu backend by +   m. allan noah <kitno455 at gmail dot com> + +   Software processing for deskew, crop and dspeckle are inspired by allan's +   noah work in the fujitsu backend + +   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. +*/ + +/* + * SANE backend for Genesys Logic GL646/GL841/GL842/GL843/GL846/GL847/GL124 based scanners + */ + +#define DEBUG_NOT_STATIC + +#include "genesys.h" +#include "conv.h" +#include "gl124_registers.h" +#include "gl841_registers.h" +#include "gl843_registers.h" +#include "gl846_registers.h" +#include "gl847_registers.h" +#include "usb_device.h" +#include "utilities.h" +#include "scanner_interface_usb.h" +#include "test_scanner_interface.h" +#include "test_settings.h" +#include "../include/sane/sanei_config.h" +#include "../include/sane/sanei_magic.h" + +#include <array> +#include <cmath> +#include <cstring> +#include <fstream> +#include <iterator> +#include <list> +#include <numeric> +#include <exception> +#include <vector> + +#ifndef SANE_GENESYS_API_LINKAGE +#define SANE_GENESYS_API_LINKAGE extern "C" +#endif + +namespace genesys { + +// Data that we allocate to back SANE_Device objects in s_sane_devices +struct SANE_Device_Data +{ +    std::string name; +}; + +namespace { +    StaticInit<std::list<Genesys_Scanner>> s_scanners; +    StaticInit<std::vector<SANE_Device>> s_sane_devices; +    StaticInit<std::vector<SANE_Device_Data>> s_sane_devices_data; +    StaticInit<std::vector<SANE_Device*>> s_sane_devices_ptrs; +    StaticInit<std::list<Genesys_Device>> s_devices; + +    // Maximum time for lamp warm-up +    constexpr unsigned WARMUP_TIME = 65; +} // namespace + +static SANE_String_Const mode_list[] = { +  SANE_VALUE_SCAN_MODE_COLOR, +  SANE_VALUE_SCAN_MODE_GRAY, +  /* SANE_TITLE_HALFTONE,  currently unused */ +  SANE_VALUE_SCAN_MODE_LINEART, +    nullptr +}; + +static SANE_String_Const color_filter_list[] = { +  SANE_I18N ("Red"), +  SANE_I18N ("Green"), +  SANE_I18N ("Blue"), +    nullptr +}; + +static SANE_String_Const cis_color_filter_list[] = { +  SANE_I18N ("Red"), +  SANE_I18N ("Green"), +  SANE_I18N ("Blue"), +  SANE_I18N ("None"), +    nullptr +}; + +static SANE_Range swdespeck_range = { +  1, +  9, +  1 +}; + +static SANE_Range time_range = { +  0,				/* minimum */ +  60,				/* maximum */ +  0				/* quantization */ +}; + +static const SANE_Range u12_range = { +  0,				/* minimum */ +  4095,				/* maximum */ +  0				/* quantization */ +}; + +static const SANE_Range u14_range = { +  0,				/* minimum */ +  16383,			/* maximum */ +  0				/* quantization */ +}; + +static const SANE_Range u16_range = { +  0,				/* minimum */ +  65535,			/* maximum */ +  0				/* quantization */ +}; + +static const SANE_Range percentage_range = { +  SANE_FIX (0),			/* minimum */ +  SANE_FIX (100),		/* maximum */ +  SANE_FIX (1)			/* quantization */ +}; + +static const SANE_Range threshold_curve_range = { +  0,			/* minimum */ +  127,		        /* maximum */ +  1			/* quantization */ +}; + +/** + * range for brightness and contrast + */ +static const SANE_Range enhance_range = { +  -100,	/* minimum */ +  100,		/* maximum */ +  1		/* quantization */ +}; + +/** + * range for expiration time + */ +static const SANE_Range expiration_range = { +  -1,	        /* minimum */ +  30000,	/* maximum */ +  1		/* quantization */ +}; + +const Genesys_Sensor& sanei_genesys_find_sensor_any(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +    for (const auto& sensor : *s_sensors) { +        if (dev->model->sensor_id == sensor.sensor_id) { +            return sensor; +        } +    } +    throw std::runtime_error("Given device does not have sensor defined"); +} + +Genesys_Sensor* find_sensor_impl(Genesys_Device* dev, unsigned dpi, unsigned channels, +                                 ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels, +                    static_cast<unsigned>(scan_method)); +    for (auto& sensor : *s_sensors) { +        if (dev->model->sensor_id == sensor.sensor_id && sensor.resolutions.matches(dpi) && +            sensor.matches_channel_count(channels) && sensor.method == scan_method) +        { +            return &sensor; +        } +    } +    return nullptr; +} + +bool sanei_genesys_has_sensor(Genesys_Device* dev, unsigned dpi, unsigned channels, +                              ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels, +                    static_cast<unsigned>(scan_method)); +    return find_sensor_impl(dev, dpi, channels, scan_method) != nullptr; +} + +const Genesys_Sensor& sanei_genesys_find_sensor(Genesys_Device* dev, unsigned dpi, unsigned channels, +                                                ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels, +                    static_cast<unsigned>(scan_method)); +    const auto* sensor = find_sensor_impl(dev, dpi, channels, scan_method); +    if (sensor) +        return *sensor; +    throw std::runtime_error("Given device does not have sensor defined"); +} + +Genesys_Sensor& sanei_genesys_find_sensor_for_write(Genesys_Device* dev, unsigned dpi, +                                                    unsigned channels, +                                                    ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels, +                    static_cast<unsigned>(scan_method)); +    auto* sensor = find_sensor_impl(dev, dpi, channels, scan_method); +    if (sensor) +        return *sensor; +    throw std::runtime_error("Given device does not have sensor defined"); +} + + +std::vector<std::reference_wrapper<const Genesys_Sensor>> +    sanei_genesys_find_sensors_all(Genesys_Device* dev, ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "scan_method: %d", static_cast<unsigned>(scan_method)); +    std::vector<std::reference_wrapper<const Genesys_Sensor>> ret; +    for (const Genesys_Sensor& sensor : sanei_genesys_find_sensors_all_for_write(dev, scan_method)) { +        ret.push_back(sensor); +    } +    return ret; +} + +std::vector<std::reference_wrapper<Genesys_Sensor>> +    sanei_genesys_find_sensors_all_for_write(Genesys_Device* dev, ScanMethod scan_method) +{ +    DBG_HELPER_ARGS(dbg, "scan_method: %d", static_cast<unsigned>(scan_method)); +    std::vector<std::reference_wrapper<Genesys_Sensor>> ret; +    for (auto& sensor : *s_sensors) { +        if (dev->model->sensor_id == sensor.sensor_id && sensor.method == scan_method) { +            ret.push_back(sensor); +        } +    } +    return ret; +} + +void sanei_genesys_init_structs (Genesys_Device * dev) +{ +    DBG_HELPER(dbg); + +    bool gpo_ok = false; +    bool motor_ok = false; +    bool fe_ok = false; + +  /* initialize the GPO data stuff */ +    for (const auto& gpo : *s_gpo) { +        if (dev->model->gpio_id == gpo.id) { +            dev->gpo = gpo; +            gpo_ok = true; +            break; +        } +    } + +    // initialize the motor data stuff +    for (const auto& motor : *s_motors) { +        if (dev->model->motor_id == motor.id) { +            dev->motor = motor; +            motor_ok = true; +            break; +        } +    } + +    for (const auto& frontend : *s_frontends) { +        if (dev->model->adc_id == frontend.id) { +            dev->frontend_initial = frontend; +            dev->frontend = frontend; +            fe_ok = true; +            break; +        } +    } + +    if (!motor_ok || !gpo_ok || !fe_ok) { +        throw SaneException("bad description(s) for fe/gpo/motor=%d/%d/%d\n", +                            static_cast<unsigned>(dev->model->sensor_id), +                            static_cast<unsigned>(dev->model->gpio_id), +                            static_cast<unsigned>(dev->model->motor_id)); +    } +} + +/* Generate slope table for motor movement */ +/** + * This function generates a slope table using the slope from the motor struct + * truncated at the given exposure time or step count, whichever comes first. + * The summed time of the acceleration steps is returned, and the + * number of accerelation steps is put into used_steps. + * + * @param dev            Device struct + * @param slope_table    Table to write to + * @param step_type      Generate table for this step_type. 0=>full, 1=>half, + *                       2=>quarter + * @param exposure_time  Minimum exposure time of a scan line + * @param yres           Resolution of a scan line + * @param used_steps     Final number of steps is stored here + * @return               Motor slope table + * @note  all times in pixel time + */ +MotorSlopeTable sanei_genesys_create_slope_table3(AsicType asic_type, const Genesys_Motor& motor, +                                                  StepType step_type, int exposure_time, +                                                  unsigned yres) +{ +    unsigned target_speed_w = (exposure_time * yres) / motor.base_ydpi; + +    return create_slope_table(motor.get_slope(step_type), target_speed_w, step_type, 1, 1, +                              get_slope_table_max_size(asic_type)); +} + +/** @brief computes gamma table + * Generates a gamma table of the given length within 0 and the given + * maximum value + * @param gamma_table gamma table to fill + * @param size size of the table + * @param maximum value allowed for gamma + * @param gamma_max maximum gamma value + * @param gamma gamma to compute values + * @return a gamma table filled with the computed values + * */ +void +sanei_genesys_create_gamma_table (std::vector<uint16_t>& gamma_table, int size, +                                  float maximum, float gamma_max, float gamma) +{ +    gamma_table.clear(); +    gamma_table.resize(size, 0); + +  int i; +  float value; + +  DBG(DBG_proc, "%s: size = %d, ""maximum = %g, gamma_max = %g, gamma = %g\n", __func__, size, +      maximum, gamma_max, gamma); +  for (i = 0; i < size; i++) +    { +        value = static_cast<float>(gamma_max * std::pow(static_cast<double>(i) / size, 1.0 / gamma)); +        if (value > maximum) { +            value = maximum; +        } +        gamma_table[i] = static_cast<std::uint16_t>(value); +    } +  DBG(DBG_proc, "%s: completed\n", __func__); +} + +void sanei_genesys_create_default_gamma_table(Genesys_Device* dev, +                                              std::vector<uint16_t>& gamma_table, float gamma) +{ +    int size = 0; +    int max = 0; +    if (dev->model->asic_type == AsicType::GL646) { +        if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) { +            size = 16384; +        } else { +            size = 4096; +        } +        max = size - 1; +    } else if (dev->model->asic_type == AsicType::GL124 || +               dev->model->asic_type == AsicType::GL846 || +               dev->model->asic_type == AsicType::GL847) { +        size = 257; +        max = 65535; +    } else { +        size = 256; +        max = 65535; +    } +    sanei_genesys_create_gamma_table(gamma_table, size, max, max, gamma); +} + +/* computes the exposure_time on the basis of the given vertical dpi, +   the number of pixels the ccd needs to send, +   the step_type and the corresponding maximum speed from the motor struct */ +/* +  Currently considers maximum motor speed at given step_type, minimum +  line exposure needed for conversion and led exposure time. + +  TODO: Should also consider maximum transfer rate: ~6.5MB/s. +    Note: The enhance option of the scanners does _not_ help. It only halves +          the amount of pixels transfered. + */ +SANE_Int sanei_genesys_exposure_time2(Genesys_Device * dev, float ydpi, +                                      StepType step_type, int endpixel, int exposure_by_led) +{ +  int exposure_by_ccd = endpixel + 32; +    unsigned max_speed_motor_w = dev->motor.get_slope(step_type).max_speed_w; +    int exposure_by_motor = static_cast<int>((max_speed_motor_w * dev->motor.base_ydpi) / ydpi); + +  int exposure = exposure_by_ccd; + +  if (exposure < exposure_by_motor) +    exposure = exposure_by_motor; + +  if (exposure < exposure_by_led && dev->model->is_cis) +    exposure = exposure_by_led; + +    DBG(DBG_info, "%s: ydpi=%d, step=%d, endpixel=%d led=%d => exposure=%d\n", __func__, +        static_cast<int>(ydpi), static_cast<unsigned>(step_type), endpixel, +        exposure_by_led, exposure); +  return exposure; +} + + +/* Sends a block of shading information to the scanner. +   The data is placed at address 0x0000 for color mode, gray mode and +   unconditionally for the following CCD chips: HP2300, HP2400 and HP5345 +   In the other cases (lineart, halftone on ccd chips not mentioned) the +   addresses are 0x2a00 for dpihw==0, 0x5500 for dpihw==1 and 0xa800 for +   dpihw==2. //Note: why this? + +   The data needs to be of size "size", and in little endian byte order. + */ +static void genesys_send_offset_and_shading(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                            uint8_t* data, int size) +{ +    DBG_HELPER_ARGS(dbg, "(size = %d)", size); +  int dpihw; +  int start_address; + +  /* ASIC higher than gl843 doesn't have register 2A/2B, so we route to +   * a per ASIC shading data loading function if available. +   * It is also used for scanners using SHDAREA */ +    if (dev->cmd_set->has_send_shading_data()) { +        dev->cmd_set->send_shading_data(dev, sensor, data, size); +        return; +    } + +  /* gl646, gl84[123] case */ +  dpihw = dev->reg.get8(0x05) >> 6; + +  /* TODO invert the test so only the 2 models behaving like that are +   * tested instead of adding all the others */ +  /* many scanners send coefficient for lineart/gray like in color mode */ +  if ((dev->settings.scan_mode == ScanColorMode::LINEART || +       dev->settings.scan_mode == ScanColorMode::HALFTONE) +        && dev->model->sensor_id != SensorId::CCD_PLUSTEK_OPTICBOOK_3800 +        && dev->model->sensor_id != SensorId::CCD_KVSS080 +        && dev->model->sensor_id != SensorId::CCD_G4050 +        && dev->model->sensor_id != SensorId::CCD_HP_4850C +        && dev->model->sensor_id != SensorId::CCD_CANON_4400F +        && dev->model->sensor_id != SensorId::CCD_CANON_8400F +        && dev->model->sensor_id != SensorId::CCD_CANON_8600F +        && dev->model->sensor_id != SensorId::CCD_DSMOBILE600 +        && dev->model->sensor_id != SensorId::CCD_XP300 +        && dev->model->sensor_id != SensorId::CCD_DP665 +        && dev->model->sensor_id != SensorId::CCD_DP685 +        && dev->model->sensor_id != SensorId::CIS_CANON_LIDE_80 +        && dev->model->sensor_id != SensorId::CCD_ROADWARRIOR +        && dev->model->sensor_id != SensorId::CCD_HP2300 +        && dev->model->sensor_id != SensorId::CCD_HP2400 +        && dev->model->sensor_id != SensorId::CCD_HP3670 +        && dev->model->sensor_id != SensorId::CCD_5345)	/* lineart, halftone */ +    { +        if (dpihw == 0) {		/* 600 dpi */ +            start_address = 0x02a00; +        } else if (dpihw == 1) {	/* 1200 dpi */ +            start_address = 0x05500; +        } else if (dpihw == 2) {	/* 2400 dpi */ +            start_address = 0x0a800; +        } else {			/* reserved */ +            throw SaneException("unknown dpihw"); +        } +    } +    else { // color +        start_address = 0x00; +    } + +    dev->interface->write_buffer(0x3c, start_address, data, size); +} + +// ? +void sanei_genesys_init_shading_data(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                     int pixels_per_line) +{ +    DBG_HELPER_ARGS(dbg, "pixels_per_line: %d", pixels_per_line); + +    if (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) { +        return; +    } + +  int channels; +  int i; + +    if (dev->cmd_set->has_send_shading_data()) { +        return; +    } + +  DBG(DBG_proc, "%s (pixels_per_line = %d)\n", __func__, pixels_per_line); + +    // BUG: GRAY shouldn't probably be in the if condition below. Discovered when refactoring +    if (dev->settings.scan_mode == ScanColorMode::GRAY || +        dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS) +    { +        channels = 3; +    } else { +        channels = 1; +    } + +  // 16 bit black, 16 bit white +  std::vector<uint8_t> shading_data(pixels_per_line * 4 * channels, 0); + +  uint8_t* shading_data_ptr = shading_data.data(); + +  for (i = 0; i < pixels_per_line * channels; i++) +    { +      *shading_data_ptr++ = 0x00;	/* dark lo */ +      *shading_data_ptr++ = 0x00;	/* dark hi */ +      *shading_data_ptr++ = 0x00;	/* white lo */ +      *shading_data_ptr++ = 0x40;	/* white hi -> 0x4000 */ +    } + +    genesys_send_offset_and_shading(dev, sensor, shading_data.data(), +                                    pixels_per_line * 4 * channels); +} + + +// Find the position of the reference point: takes gray level 8 bits data and find +// first CCD usable pixel and top of scanning area +void sanei_genesys_search_reference_point(Genesys_Device* dev, Genesys_Sensor& sensor, +                                          const uint8_t* src_data, int start_pixel, int dpi, +                                          int width, int height) +{ +    DBG_HELPER(dbg); +  int x, y; +  int current, left, top = 0; +  int size, count; +  int level = 80;		/* edge threshold level */ + +    // sanity check +    if ((width < 3) || (height < 3)) { +        throw SaneException("invalid width or height"); +    } + +  /* transformed image data */ +  size = width * height; +  std::vector<uint8_t> image2(size, 0); +  std::vector<uint8_t> image(size, 0); + +  /* laplace filter to denoise picture */ +    std::memcpy(image2.data(), src_data, size); +    std::memcpy(image.data(), src_data, size);	// to initialize unprocessed part of the image buffer + +    for (y = 1; y < height - 1; y++) { +        for (x = 1; x < width - 1; x++) { +            image[y * width + x] = +                (image2[(y - 1) * width + x + 1] + 2 * image2[(y - 1) * width + x] + +                image2[(y - 1) * width + x - 1] + 2 * image2[y * width + x + 1] + +                4 * image2[y * width + x] + 2 * image2[y * width + x - 1] + +                image2[(y + 1) * width + x + 1] + 2 * image2[(y + 1) * width + x] + +                image2[(y + 1) * width + x - 1]) / 16; +        } +    } + +    image2 = image; +  if (DBG_LEVEL >= DBG_data) +    sanei_genesys_write_pnm_file("gl_laplace.pnm", image.data(), 8, 1, width, height); + +  /* apply X direction sobel filter +     -1  0  1 +     -2  0  2 +     -1  0  1 +     and finds threshold level +   */ +  level = 0; +    for (y = 2; y < height - 2; y++) { +        for (x = 2; x < width - 2; x++) { +            current = image2[(y - 1) * width + x + 1] - image2[(y - 1) * width + x - 1] + +                      2 * image2[y * width + x + 1] - 2 * image2[y * width + x - 1] + +                      image2[(y + 1) * width + x + 1] - image2[(y + 1) * width + x - 1]; +	if (current < 0) +	  current = -current; +	if (current > 255) +	  current = 255; +	image[y * width + x] = current; +	if (current > level) +	  level = current; +        } +    } +  if (DBG_LEVEL >= DBG_data) +    sanei_genesys_write_pnm_file("gl_xsobel.pnm", image.data(), 8, 1, width, height); + +  /* set up detection level */ +  level = level / 3; + +  /* find left black margin first +     todo: search top before left +     we average the result of N searches */ +  left = 0; +  count = 0; +  for (y = 2; y < 11; y++) +    { +      x = 8; +      while ((x < width / 2) && (image[y * width + x] < level)) +	{ +	  image[y * width + x] = 255; +	  x++; +	} +      count++; +      left += x; +    } +  if (DBG_LEVEL >= DBG_data) +    sanei_genesys_write_pnm_file("gl_detected-xsobel.pnm", image.data(), 8, 1, width, height); +  left = left / count; + +    // turn it in CCD pixel at full sensor optical resolution +    sensor.ccd_start_xoffset = start_pixel + (left * sensor.optical_res) / dpi; + +  /* find top edge by detecting black strip */ +  /* apply Y direction sobel filter +     -1 -2 -1 +     0  0  0 +     1  2  1 +   */ +  level = 0; +    for (y = 2; y < height - 2; y++) { +        for (x = 2; x < width - 2; x++) { +            current = -image2[(y - 1) * width + x + 1] - 2 * image2[(y - 1) * width + x] - +                      image2[(y - 1) * width + x - 1] + image2[(y + 1) * width + x + 1] + +                      2 * image2[(y + 1) * width + x] + image2[(y + 1) * width + x - 1]; +	if (current < 0) +	  current = -current; +	if (current > 255) +	  current = 255; +	image[y * width + x] = current; +	if (current > level) +	  level = current; +      } +    } +  if (DBG_LEVEL >= DBG_data) +    sanei_genesys_write_pnm_file("gl_ysobel.pnm", image.data(), 8, 1, width, height); + +  /* set up detection level */ +  level = level / 3; + +  /* search top of horizontal black stripe : TODO yet another flag */ +    if (dev->model->sensor_id == SensorId::CCD_5345 +        && dev->model->motor_id == MotorId::MD_5345) +    { +      top = 0; +      count = 0; +      for (x = width / 2; x < width - 1; x++) +	{ +	  y = 2; +	  while ((y < height) && (image[x + y * width] < level)) +	    { +	      image[y * width + x] = 255; +	      y++; +	    } +	  count++; +	  top += y; +	} +      if (DBG_LEVEL >= DBG_data) +        sanei_genesys_write_pnm_file("gl_detected-ysobel.pnm", image.data(), 8, 1, width, height); +      top = top / count; + +      /* bottom of black stripe is of fixed witdh, this hardcoded value +       * will be moved into device struct if more such values are needed */ +      top += 10; +        dev->model->y_offset_calib_white = (top * MM_PER_INCH) / dpi; +        DBG(DBG_info, "%s: black stripe y_offset = %f mm \n", __func__, +            dev->model->y_offset_calib_white.value()); +    } + +  /* find white corner in dark area : TODO yet another flag */ +    if ((dev->model->sensor_id == SensorId::CCD_HP2300 && dev->model->motor_id == MotorId::HP2300) || +        (dev->model->sensor_id == SensorId::CCD_HP2400 && dev->model->motor_id == MotorId::HP2400) || +        (dev->model->sensor_id == SensorId::CCD_HP3670 && dev->model->motor_id == MotorId::HP3670)) +    { +      top = 0; +      count = 0; +      for (x = 10; x < 60; x++) +	{ +	  y = 2; +	  while ((y < height) && (image[x + y * width] < level)) +	    y++; +	  top += y; +	  count++; +	} +      top = top / count; +        dev->model->y_offset_calib_white = (top * MM_PER_INCH) / dpi; +        DBG(DBG_info, "%s: white corner y_offset = %f mm\n", __func__, +            dev->model->y_offset_calib_white.value()); +    } + +    DBG(DBG_proc, "%s: ccd_start_xoffset = %d, left = %d, top = %d\n", __func__, +        sensor.ccd_start_xoffset, left, top); +} + +namespace gl843 { +    void gl843_park_xpa_lamp(Genesys_Device* dev); +    void gl843_set_xpa_motor_power(Genesys_Device* dev, Genesys_Register_Set& regs, bool set); +} // namespace gl843 + +namespace gl124 { +    void gl124_setup_scan_gpio(Genesys_Device* dev, int resolution); +} // namespace gl124 + +void scanner_clear_scan_and_feed_counts(Genesys_Device& dev) +{ +    switch (dev.model->asic_type) { +        case AsicType::GL843: { +            dev.interface->write_register(gl843::REG_0x0D, +                                          gl843::REG_0x0D_CLRLNCNT | gl843::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL845: +        case AsicType::GL846: { +            dev.interface->write_register(gl846::REG_0x0D, +                                          gl846::REG_0x0D_CLRLNCNT | gl846::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL847:{ +            dev.interface->write_register(gl847::REG_0x0D, +                                          gl847::REG_0x0D_CLRLNCNT | gl847::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL124:{ +            dev.interface->write_register(gl124::REG_0x0D, +                                          gl124::REG_0x0D_CLRLNCNT | gl124::REG_0x0D_CLRMCNT); +            break; +        } +        default: +            throw SaneException("Unsupported asic type"); +    } +} + +void scanner_clear_scan_and_feed_counts2(Genesys_Device& dev) +{ +    // FIXME: switch to scanner_clear_scan_and_feed_counts when updating tests +    switch (dev.model->asic_type) { +        case AsicType::GL843: { +            dev.interface->write_register(gl843::REG_0x0D, gl843::REG_0x0D_CLRLNCNT); +            dev.interface->write_register(gl843::REG_0x0D, gl843::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL845: +        case AsicType::GL846: { +            dev.interface->write_register(gl846::REG_0x0D, gl846::REG_0x0D_CLRLNCNT); +            dev.interface->write_register(gl846::REG_0x0D, gl846::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL847: { +            dev.interface->write_register(gl847::REG_0x0D, gl847::REG_0x0D_CLRLNCNT); +            dev.interface->write_register(gl847::REG_0x0D, gl847::REG_0x0D_CLRMCNT); +            break; +        } +        case AsicType::GL124: { +            dev.interface->write_register(gl124::REG_0x0D, gl124::REG_0x0D_CLRLNCNT); +            dev.interface->write_register(gl124::REG_0x0D, gl124::REG_0x0D_CLRMCNT); +            break; +        } +        default: +            throw SaneException("Unsupported asic type"); +    } +} + +bool scanner_is_motor_stopped(Genesys_Device& dev) +{ +    switch (dev.model->asic_type) { +        case AsicType::GL646: { +            auto status = scanner_read_status(dev); +            return !status.is_motor_enabled && status.is_feeding_finished; +        } +        case AsicType::GL841: { +            auto reg = dev.interface->read_register(gl841::REG_0x40); + +            return (!(reg & gl841::REG_0x40_DATAENB) && !(reg & gl841::REG_0x40_MOTMFLG)); +        } +        case AsicType::GL843: { +            auto status = scanner_read_status(dev); +            auto reg = dev.interface->read_register(gl843::REG_0x40); + +            return (!(reg & gl843::REG_0x40_DATAENB) && !(reg & gl843::REG_0x40_MOTMFLG) && +                    !status.is_motor_enabled); +        } +        case AsicType::GL845: +        case AsicType::GL846: { +            auto status = scanner_read_status(dev); +            auto reg = dev.interface->read_register(gl846::REG_0x40); + +            return (!(reg & gl846::REG_0x40_DATAENB) && !(reg & gl846::REG_0x40_MOTMFLG) && +                    !status.is_motor_enabled); +        } +        case AsicType::GL847: { +            auto status = scanner_read_status(dev); +            auto reg = dev.interface->read_register(gl847::REG_0x40); + +            return (!(reg & gl847::REG_0x40_DATAENB) && !(reg & gl847::REG_0x40_MOTMFLG) && +                    !status.is_motor_enabled); +        } +        case AsicType::GL124: { +            auto status = scanner_read_status(dev); +            auto reg = dev.interface->read_register(gl124::REG_0x100); + +            return (!(reg & gl124::REG_0x100_DATAENB) && !(reg & gl124::REG_0x100_MOTMFLG) && +                    !status.is_motor_enabled); +        } +        default: +            throw SaneException("Unsupported asic type"); +    } +} + +void scanner_stop_action(Genesys_Device& dev) +{ +    DBG_HELPER(dbg); + +    switch (dev.model->asic_type) { +        case AsicType::GL843: +        case AsicType::GL845: +        case AsicType::GL846: +        case AsicType::GL847: +        case AsicType::GL124: +            break; +        default: +            throw SaneException("Unsupported asic type"); +    } + +    if (dev.cmd_set->needs_update_home_sensor_gpio()) { +        dev.cmd_set->update_home_sensor_gpio(dev); +    } + +    if (scanner_is_motor_stopped(dev)) { +        DBG(DBG_info, "%s: already stopped\n", __func__); +        return; +    } + +    scanner_stop_action_no_move(dev, dev.reg); + +    if (is_testing_mode()) { +        return; +    } + +    for (unsigned i = 0; i < 10; ++i) { +        if (scanner_is_motor_stopped(dev)) { +            return; +        } + +        dev.interface->sleep_ms(100); +    } + +    throw SaneException(SANE_STATUS_IO_ERROR, "could not stop motor"); +} + +void scanner_stop_action_no_move(Genesys_Device& dev, genesys::Genesys_Register_Set& regs) +{ +    switch (dev.model->asic_type) { +        case AsicType::GL646: +        case AsicType::GL841: +        case AsicType::GL843: +        case AsicType::GL845: +        case AsicType::GL846: +        case AsicType::GL847: +        case AsicType::GL124: +            break; +        default: +            throw SaneException("Unsupported asic type"); +    } + +    regs_set_optical_off(dev.model->asic_type, regs); +    // same across all supported ASICs +    dev.interface->write_register(0x01, regs.get8(0x01)); + +    // looks like certain scanners lock up if we try to scan immediately after stopping previous +    // action. +    dev.interface->sleep_ms(100); +} + +void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, Direction direction) +{ +    DBG_HELPER_ARGS(dbg, "steps=%d direction=%d", steps, static_cast<unsigned>(direction)); + +    auto local_reg = dev.reg; + +    unsigned resolution = dev.model->get_resolution_settings(scan_method).get_min_resolution_y(); + +    const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 3, scan_method); + +    bool uses_secondary_head = (scan_method == ScanMethod::TRANSPARENCY || +                                scan_method == ScanMethod::TRANSPARENCY_INFRARED); +    bool uses_secondary_pos = uses_secondary_head && +                              dev.model->default_method == ScanMethod::FLATBED; + +    if (!dev.is_head_pos_known(ScanHeadId::PRIMARY)) { +        throw SaneException("Unknown head position"); +    } +    if (uses_secondary_pos && !dev.is_head_pos_known(ScanHeadId::SECONDARY)) { +        throw SaneException("Unknown head position"); +    } +    if (direction == Direction::BACKWARD && steps > dev.head_pos(ScanHeadId::PRIMARY)) { +        throw SaneException("Trying to feed behind the home position %d %d", +                            steps, dev.head_pos(ScanHeadId::PRIMARY)); +    } +    if (uses_secondary_pos && direction == Direction::BACKWARD && +        steps > dev.head_pos(ScanHeadId::SECONDARY)) +    { +        throw SaneException("Trying to feed behind the home position %d %d", +                            steps, dev.head_pos(ScanHeadId::SECONDARY)); +    } + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 0; +    session.params.starty = steps; +    session.params.pixels = 100; +    session.params.lines = 3; +    session.params.depth = 8; +    session.params.channels = 3; +    session.params.scan_method = scan_method; +    session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; +    if (dev.model->asic_type == AsicType::GL843) { +        session.params.color_filter = ColorFilter::RED; +    } else { +        session.params.color_filter = dev.settings.color_filter; +    } +    session.params.flags = ScanFlag::DISABLE_SHADING | +                           ScanFlag::DISABLE_GAMMA | +                           ScanFlag::FEEDING | +                           ScanFlag::IGNORE_LINE_DISTANCE; + +    if (dev.model->asic_type == AsicType::GL124) { +        session.params.flags |= ScanFlag::DISABLE_BUFFER_FULL_MOVE; +    } + +    if (direction == Direction::BACKWARD) { +        session.params.flags |= ScanFlag::REVERSE; +    } + +    compute_session(&dev, session, sensor); + +    dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session); + +    if (dev.model->asic_type != AsicType::GL843) { +        regs_set_exposure(dev.model->asic_type, local_reg, {0, 0, 0}); +    } +    scanner_clear_scan_and_feed_counts2(dev); + +    dev.interface->write_registers(local_reg); +    if (uses_secondary_head) { +        gl843::gl843_set_xpa_motor_power(&dev, local_reg, true); +    } + +    try { +        scanner_start_action(dev, true); +    } catch (...) { +        catch_all_exceptions(__func__, [&]() { +            gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); +        }); +        catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); }); +        // restore original registers +        catch_all_exceptions(__func__, [&]() { dev.interface->write_registers(dev.reg); }); +        throw; +    } + +    if (is_testing_mode()) { +        dev.interface->test_checkpoint("feed"); + +        dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, direction, steps); +        if (uses_secondary_pos) { +            dev.advance_head_pos_by_steps(ScanHeadId::SECONDARY, direction, steps); +        } + +        // FIXME: why don't we stop the scanner like on other ASICs +        if (dev.model->asic_type != AsicType::GL843) { +            scanner_stop_action(dev); +        } +        if (uses_secondary_head) { +            gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); +        } +        return; +    } + +    // wait until feed count reaches the required value +    // FIXME: should porbably wait for some timeout +    Status status; +    for (unsigned i = 0;; ++i) { +        status = scanner_read_status(dev); +        if (status.is_feeding_finished || ( +            direction == Direction::BACKWARD && status.is_at_home)) +        { +            break; +        } +        dev.interface->sleep_ms(10); +    } + +    // FIXME: why don't we stop the scanner like on other ASICs +    if (dev.model->asic_type != AsicType::GL843) { +        scanner_stop_action(dev); +    } +    if (uses_secondary_head) { +        gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); +    } + +    dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, direction, steps); +    if (uses_secondary_pos) { +        dev.advance_head_pos_by_steps(ScanHeadId::SECONDARY, direction, steps); +    } + +    // looks like certain scanners lock up if we scan immediately after feeding +    dev.interface->sleep_ms(100); +} + +void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home) +{ +    DBG_HELPER_ARGS(dbg, "wait_until_home = %d", wait_until_home); + +    switch (dev.model->asic_type) { +        case AsicType::GL843: +        case AsicType::GL845: +        case AsicType::GL846: +        case AsicType::GL847: +        case AsicType::GL124: +            break; +        default: +            throw SaneException("Unsupported asic type"); +    } + +    // FIXME: also check whether the scanner actually has a secondary head +    if (!dev.is_head_pos_known(ScanHeadId::SECONDARY) || +        dev.head_pos(ScanHeadId::SECONDARY) > 0 || +        dev.settings.scan_method == ScanMethod::TRANSPARENCY || +        dev.settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        scanner_move_back_home_ta(dev); +    } + +    if (dev.is_head_pos_known(ScanHeadId::PRIMARY) && +        dev.head_pos(ScanHeadId::PRIMARY) > 1000) +    { +        // leave 500 steps for regular slow back home +        scanner_move(dev, dev.model->default_method, dev.head_pos(ScanHeadId::PRIMARY) - 500, +                     Direction::BACKWARD); +    } + +    if (dev.cmd_set->needs_update_home_sensor_gpio()) { +        dev.cmd_set->update_home_sensor_gpio(dev); +    } + +    auto status = scanner_read_reliable_status(dev); + +    if (status.is_at_home) { +        dbg.log(DBG_info, "already at home"); +        dev.set_head_pos_zero(ScanHeadId::PRIMARY); +        return; +    } + +    if (dev.model->model_id == ModelId::CANON_LIDE_210) { +        // move the head back a little first +        if (dev.is_head_pos_known(ScanHeadId::PRIMARY) && +            dev.head_pos(ScanHeadId::PRIMARY) > 30) +        { +            scanner_move(dev, dev.model->default_method, 20, Direction::BACKWARD); +        } +    } + +    Genesys_Register_Set local_reg = dev.reg; +    unsigned resolution = sanei_genesys_get_lowest_ydpi(&dev); + +    const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 1, dev.model->default_method); + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 100; +    if (dev.model->asic_type == AsicType::GL843) { +        session.params.starty = 40000; +    } else { +        session.params.starty = 30000; +    } +    session.params.pixels = 100; +    session.params.lines = 100; +    session.params.depth = 8; +    session.params.channels = 1; +    session.params.scan_method = dev.settings.scan_method; +    if (dev.model->asic_type == AsicType::GL843) { +        session.params.scan_mode = ScanColorMode::LINEART; +        session.params.color_filter = dev.settings.color_filter; +    } else { +        session.params.scan_mode = ScanColorMode::GRAY; +        session.params.color_filter = ColorFilter::RED; +    } +    session.params.flags =  ScanFlag::DISABLE_SHADING | +                            ScanFlag::DISABLE_GAMMA | +                            ScanFlag::IGNORE_LINE_DISTANCE | +                            ScanFlag::REVERSE; +    if (dev.model->asic_type == AsicType::GL843) { +        session.params.flags |= ScanFlag::DISABLE_BUFFER_FULL_MOVE; +    } + +    compute_session(&dev, session, sensor); + +    dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session); + +    scanner_clear_scan_and_feed_counts(dev); + +    dev.interface->write_registers(local_reg); + +    if (dev.model->asic_type == AsicType::GL124) { +        gl124::gl124_setup_scan_gpio(&dev, resolution); +    } + +    try { +        scanner_start_action(dev, true); +    } catch (...) { +        catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); }); +        // restore original registers +        catch_all_exceptions(__func__, [&]() +        { +            dev.interface->write_registers(dev.reg); +        }); +        throw; +    } + +    if (dev.cmd_set->needs_update_home_sensor_gpio()) { +        dev.cmd_set->update_home_sensor_gpio(dev); +    } + +    if (is_testing_mode()) { +        dev.interface->test_checkpoint("move_back_home"); +        dev.set_head_pos_zero(ScanHeadId::PRIMARY); +        return; +    } + +    if (wait_until_home) { +        for (unsigned i = 0; i < 300; ++i) { +            auto status = scanner_read_status(dev); + +            if (status.is_at_home) { +                dbg.log(DBG_info, "reached home position"); +                if (dev.model->asic_type == AsicType::GL846 || +                    dev.model->asic_type == AsicType::GL847) +                { +                    scanner_stop_action(dev); +                } +                dev.set_head_pos_zero(ScanHeadId::PRIMARY); +                return; +            } + +            dev.interface->sleep_ms(100); +        } + +        // when we come here then the scanner needed too much time for this, so we better stop +        // the motor +        catch_all_exceptions(__func__, [&](){ scanner_stop_action(dev); }); +        dev.set_head_pos_unknown(); +        throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for scanhead to go home"); +    } +    dbg.log(DBG_info, "scanhead is still moving"); +} + +void scanner_move_back_home_ta(Genesys_Device& dev) +{ +    DBG_HELPER(dbg); + +    switch (dev.model->asic_type) { +        case AsicType::GL843: +            break; +        default: +            throw SaneException("Unsupported asic type"); +    } + +    Genesys_Register_Set local_reg = dev.reg; + +    auto scan_method = ScanMethod::TRANSPARENCY; +    unsigned resolution = dev.model->get_resolution_settings(scan_method).get_min_resolution_y(); + +    const auto& sensor = sanei_genesys_find_sensor(&dev, resolution, 1, scan_method); + +    if (dev.is_head_pos_known(ScanHeadId::SECONDARY) && +        dev.head_pos(ScanHeadId::SECONDARY) > 1000) +    { +        // leave 500 steps for regular slow back home +        scanner_move(dev, scan_method, dev.head_pos(ScanHeadId::SECONDARY) - 500, +                     Direction::BACKWARD); +    } + +    ScanSession session; +    session.params.xres = resolution; +    session.params.yres = resolution; +    session.params.startx = 100; +    session.params.starty = 30000; +    session.params.pixels = 100; +    session.params.lines = 100; +    session.params.depth = 8; +    session.params.channels = 1; +    session.params.scan_method = scan_method; +    session.params.scan_mode = ScanColorMode::GRAY; +    session.params.color_filter = ColorFilter::RED; +    session.params.flags =  ScanFlag::DISABLE_SHADING | +                            ScanFlag::DISABLE_GAMMA | +                            ScanFlag::IGNORE_LINE_DISTANCE | +                            ScanFlag::REVERSE; + +    compute_session(&dev, session, sensor); + +    dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session); + +    scanner_clear_scan_and_feed_counts(dev); + +    dev.interface->write_registers(local_reg); +    gl843::gl843_set_xpa_motor_power(&dev, local_reg, true); + +    try { +        scanner_start_action(dev, true); +    } catch (...) { +        catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); }); +        // restore original registers +        catch_all_exceptions(__func__, [&]() { dev.interface->write_registers(dev.reg); }); +        throw; +    } + +    if (is_testing_mode()) { +        dev.interface->test_checkpoint("move_back_home_ta"); + +        if (dev.is_head_pos_known(ScanHeadId::PRIMARY)) { +            if (dev.head_pos(ScanHeadId::PRIMARY) > dev.head_pos(ScanHeadId::SECONDARY)) { +                dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, Direction::BACKWARD, +                                              dev.head_pos(ScanHeadId::SECONDARY)); +            } else { +                dev.set_head_pos_zero(ScanHeadId::PRIMARY); +            } +            dev.set_head_pos_zero(ScanHeadId::SECONDARY); +        } + +        scanner_stop_action(dev); +        gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); +        return; +    } + +    for (unsigned i = 0; i < 1200; ++i) { + +        auto status = scanner_read_status(dev); + +        if (status.is_at_home) { +            dbg.log(DBG_info, "TA reached home position"); + +            if (dev.is_head_pos_known(ScanHeadId::PRIMARY)) { +                if (dev.head_pos(ScanHeadId::PRIMARY) > dev.head_pos(ScanHeadId::SECONDARY)) { +                    dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, Direction::BACKWARD, +                                                  dev.head_pos(ScanHeadId::SECONDARY)); +                } else { +                    dev.set_head_pos_zero(ScanHeadId::PRIMARY); +                } +                dev.set_head_pos_zero(ScanHeadId::SECONDARY); +            } + +            scanner_stop_action(dev); +            gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); +            return; +        } + +        dev.interface->sleep_ms(100); +    } + +    throw SaneException("Timeout waiting for XPA lamp to park"); +} + +void sanei_genesys_calculate_zmod(bool two_table, +                                  uint32_t exposure_time, +                                  const std::vector<uint16_t>& slope_table, +                                  unsigned acceleration_steps, +                                  unsigned move_steps, +                                  unsigned buffer_acceleration_steps, +                                  uint32_t* out_z1, uint32_t* out_z2) +{ +    DBG(DBG_info, "%s: two_table=%d\n", __func__, two_table); + +    // acceleration total time +    unsigned sum = std::accumulate(slope_table.begin(), slope_table.begin() + acceleration_steps, +                                   0, std::plus<unsigned>()); + +    /* Z1MOD: +        c = sum(slope_table; reg_stepno) +        d = reg_fwdstep * <cruising speed> +        Z1MOD = (c+d) % exposure_time +    */ +    *out_z1 = (sum + buffer_acceleration_steps * slope_table[acceleration_steps - 1]) % exposure_time; + +    /* Z2MOD: +        a = sum(slope_table; reg_stepno) +        b = move_steps or 1 if 2 tables +        Z1MOD = (a+b) % exposure_time +    */ +    if (!two_table) { +        sum = sum + (move_steps * slope_table[acceleration_steps - 1]); +    } else { +        sum = sum + slope_table[acceleration_steps - 1]; +    } +    *out_z2 = sum % exposure_time; +} + +static uint8_t genesys_adjust_gain(double* applied_multi, double multi, uint8_t gain) +{ +  double voltage, original_voltage; +  uint8_t new_gain = 0; + +  DBG(DBG_proc, "%s: multi=%f, gain=%d\n", __func__, multi, gain); + +  voltage = 0.5 + gain * 0.25; +  original_voltage = voltage; + +  voltage *= multi; + +    new_gain = static_cast<std::uint8_t>((voltage - 0.5) * 4); +  if (new_gain > 0x0e) +    new_gain = 0x0e; + +  voltage = 0.5 + (new_gain) * 0.25; + +  *applied_multi = voltage / original_voltage; + +  DBG(DBG_proc, "%s: orig voltage=%.2f, new voltage=%.2f, *applied_multi=%f, new_gain=%d\n", +      __func__, original_voltage, voltage, *applied_multi, new_gain); + +  return new_gain; +} + + +// todo: is return status necessary (unchecked?) +static void genesys_average_white(Genesys_Device* dev, Genesys_Sensor& sensor, int channels, +                                  int channel, uint8_t* data, int size, int *max_average) +{ + +    DBG_HELPER_ARGS(dbg, "channels=%d, channel=%d, size=%d", channels, channel, size); +  int gain_white_ref, sum, range; +  int average; +  int i; + +  range = size / 50; + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        gain_white_ref = sensor.fau_gain_white_ref * 256; +    } else { +        gain_white_ref = sensor.gain_white_ref * 256; +    } + +  if (range < 1) +    range = 1; + +  size = size / (2 * range * channels); + +  data += (channel * 2); + +  *max_average = 0; + +  while (size--) +    { +      sum = 0; +      for (i = 0; i < range; i++) +	{ +	  sum += (*data); +	  sum += *(data + 1) * 256; +	  data += (2 * channels);	/* byte based */ +	} + +      average = (sum / range); +      if (average > *max_average) +	*max_average = average; +    } + +  DBG(DBG_proc, "%s: max_average=%d, gain_white_ref = %d, finished\n", __func__, *max_average, +      gain_white_ref); + +  if (*max_average >= gain_white_ref) +        throw SaneException(SANE_STATUS_INVAL); +} + +/* todo: understand, values are too high */ +static int +genesys_average_black (Genesys_Device * dev, int channel, +		       uint8_t * data, int pixels) +{ +  int i; +  int sum; +  int pixel_step; + +  DBG(DBG_proc, "%s: channel=%d, pixels=%d\n", __func__, channel, pixels); + +  sum = 0; + +  if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS) +    { +      data += (channel * 2); +      pixel_step = 3 * 2; +    } +  else +    { +      pixel_step = 2; +    } + +  for (i = 0; i < pixels; i++) +    { +      sum += *data; +      sum += *(data + 1) * 256; + +      data += pixel_step; +    } + +  DBG(DBG_proc, "%s = %d\n", __func__, sum / pixels); + +    return sum / pixels; +} + + +// todo: check; it works but the lines 1, 2, and 3 are too dark even with the +// same offset and gain settings? +static void genesys_coarse_calibration(Genesys_Device* dev, Genesys_Sensor& sensor) +{ +    DBG_HELPER_ARGS(dbg, "scan_mode = %d", static_cast<unsigned>(dev->settings.scan_mode)); +  int black_pixels; +  int white_average; +  uint8_t offset[4] = { 0xa0, 0x00, 0xa0, 0x40 };	/* first value isn't used */ +  uint16_t white[12], dark[12]; +  int i, j; + +  black_pixels = sensor.black_pixels +    * dev->settings.xres / sensor.optical_res; + +    unsigned channels = dev->settings.get_channels(); + +  DBG(DBG_info, "channels %d y_size %f xres %d\n", channels, dev->model->y_size.value(), +      dev->settings.xres); +    unsigned size = static_cast<unsigned>(channels * 2 * dev->model->y_size * dev->settings.xres / +                                          MM_PER_INCH); +  /*       1        1               mm                      1/inch        inch/mm */ + +  std::vector<uint8_t> calibration_data(size); +  std::vector<uint8_t> all_data(size * 4, 1); + +    dev->cmd_set->set_fe(dev, sensor, AFE_INIT); + +  dev->frontend.set_gain(0, 2); +  dev->frontend.set_gain(1, 2); +  dev->frontend.set_gain(2, 2); // TODO: ?  was 2 +  dev->frontend.set_offset(0, offset[0]); +  dev->frontend.set_offset(1, offset[0]); +  dev->frontend.set_offset(2, offset[0]); + +  for (i = 0; i < 4; i++)	/* read 4 lines */ +    { +      if (i < 3)		/* first 3 lines */ +        { +          dev->frontend.set_offset(0, offset[i]); +          dev->frontend.set_offset(1, offset[i]); +          dev->frontend.set_offset(2, offset[i]); +        } + +      if (i == 1)		/* second line */ +	{ +	  double applied_multi; +	  double gain_white_ref; + +            if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +                dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +            { +                gain_white_ref = sensor.fau_gain_white_ref * 256; +            } else { +                gain_white_ref = sensor.gain_white_ref * 256; +            } + +            // white and black are defined downwards + +            uint8_t gain0 = genesys_adjust_gain(&applied_multi, +                                                gain_white_ref / (white[0] - dark[0]), +                                                dev->frontend.get_gain(0)); +            uint8_t gain1 = genesys_adjust_gain(&applied_multi, +                                                gain_white_ref / (white[1] - dark[1]), +                                                dev->frontend.get_gain(1)); +            uint8_t gain2 = genesys_adjust_gain(&applied_multi, +                                                gain_white_ref / (white[2] - dark[2]), +                                                dev->frontend.get_gain(2)); +            // FIXME: looks like overwritten data. Are the above calculations doing +            // anything at all? +            dev->frontend.set_gain(0, gain0); +            dev->frontend.set_gain(1, gain1); +            dev->frontend.set_gain(2, gain2); +            dev->frontend.set_gain(0, 2); +            dev->frontend.set_gain(1, 2); +            dev->frontend.set_gain(2, 2); + +            dev->interface->write_fe_register(0x28, dev->frontend.get_gain(0)); +            dev->interface->write_fe_register(0x29, dev->frontend.get_gain(1)); +            dev->interface->write_fe_register(0x2a, dev->frontend.get_gain(2)); +	} + +      if (i == 3)		/* last line */ +	{ +	  double x, y, rate; + +	  for (j = 0; j < 3; j++) +	    { + +                x = static_cast<double>(dark[(i - 2) * 3 + j] - +			  dark[(i - 1) * 3 + j]) * 254 / (offset[i - 1] / 2 - +							  offset[i - 2] / 2); +	      y = x - x * (offset[i - 1] / 2) / 254 - dark[(i - 1) * 3 + j]; +	      rate = (x - DARK_VALUE - y) * 254 / x + 0.5; + +                uint8_t curr_offset = static_cast<uint8_t>(rate); + +                if (curr_offset > 0x7f) { +                    curr_offset = 0x7f; +                } +                curr_offset <<= 1; +                dev->frontend.set_offset(j, curr_offset); +	    } +	} +        dev->interface->write_fe_register(0x20, dev->frontend.get_offset(0)); +        dev->interface->write_fe_register(0x21, dev->frontend.get_offset(1)); +        dev->interface->write_fe_register(0x22, dev->frontend.get_offset(2)); + +      DBG(DBG_info, +          "%s: doing scan: gain: %d/%d/%d, offset: %d/%d/%d\n", __func__, +          dev->frontend.get_gain(0), +          dev->frontend.get_gain(1), +          dev->frontend.get_gain(2), +          dev->frontend.get_offset(0), +          dev->frontend.get_offset(1), +          dev->frontend.get_offset(2)); + + +        dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, false); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("coarse_calibration"); +            dev->cmd_set->end_scan(dev, &dev->calib_reg, true); +            return; +        } + +      sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size); +      std::memcpy(all_data.data() + i * size, calibration_data.data(), size); +      if (i == 3)		/* last line */ +	{ +          std::vector<uint8_t> all_data_8(size * 4 / 2); +	  unsigned int count; + +        for (count = 0; count < static_cast<unsigned>(size * 4 / 2); count++) { +            all_data_8[count] = all_data[count * 2 + 1]; +        } +        sanei_genesys_write_pnm_file("gl_coarse.pnm", all_data_8.data(), 8, channels, size / 6, 4); +	} + +        dev->cmd_set->end_scan(dev, &dev->calib_reg, true); + +      if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS) +	{ +	  for (j = 0; j < 3; j++) +	    { +            genesys_average_white(dev, sensor, 3, j, calibration_data.data(), size, &white_average); +	      white[i * 3 + j] = white_average; +	      dark[i * 3 + j] = +        genesys_average_black (dev, j, calibration_data.data(), +				       black_pixels); +              DBG(DBG_info, "%s: white[%d]=%d, black[%d]=%d\n", __func__, +                  i * 3 + j, white[i * 3 + j], i * 3 + j, dark[i * 3 + j]); +	    } +	} +      else			/* one color-component modes */ +	{ +        genesys_average_white(dev, sensor, 1, 0, calibration_data.data(), size, &white_average); +	  white[i * 3 + 0] = white[i * 3 + 1] = white[i * 3 + 2] = +	    white_average; +	  dark[i * 3 + 0] = dark[i * 3 + 1] = dark[i * 3 + 2] = +        genesys_average_black (dev, 0, calibration_data.data(), black_pixels); +	} +    }				/* for (i = 0; i < 4; i++) */ + +  DBG(DBG_info, "%s: final: gain: %d/%d/%d, offset: %d/%d/%d\n", __func__, +      dev->frontend.get_gain(0), +      dev->frontend.get_gain(1), +      dev->frontend.get_gain(2), +      dev->frontend.get_offset(0), +      dev->frontend.get_offset(1), +      dev->frontend.get_offset(2)); +} + +/** + * scans a white area with motor and lamp off to get the per CCD pixel offset + * that will be used to compute shading coefficient + * @param dev scanner's device + */ +static void genesys_shading_calibration_impl(Genesys_Device* dev, const Genesys_Sensor& sensor, +                                             std::vector<std::uint16_t>& out_average_data, +                                             bool is_dark, const std::string& log_filename_prefix) +{ +    DBG_HELPER(dbg); + +    debug_dump(DBG_info, dev->calib_session); + +  size_t size; +  uint32_t pixels_per_line; +  uint8_t channels; + +  /* end pixel - start pixel */ +  pixels_per_line = dev->calib_pixels; +  channels = dev->calib_channels; + +  uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset; + +    // FIXME: we set this during both dark and white calibration. A cleaner approach should +    // probably be used +    dev->average_size = channels * out_pixels_per_line; + +    out_average_data.clear(); +    out_average_data.resize(dev->average_size); + +    if (is_dark && dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) { +        // FIXME: dark shading currently not supported on infrared transparency scans +        return; +    } + +    // FIXME: the current calculation is likely incorrect on non-GL843 implementations, +    // but this needs checking +    if (dev->calib_total_bytes_to_read > 0) { +        size = dev->calib_total_bytes_to_read; +    } else if (dev->model->asic_type == AsicType::GL843) { +        size = channels * 2 * pixels_per_line * dev->calib_lines; +    } else { +        size = channels * 2 * pixels_per_line * (dev->calib_lines + 1); +    } + +  std::vector<uint16_t> calibration_data(size / 2); + +    bool motor = true; +  if (dev->model->flags & GENESYS_FLAG_SHADING_NO_MOVE) +    { +        motor = false; +    } + +    // turn off motor and lamp power for flatbed scanners, but not for sheetfed scanners +    // because they have a calibration sheet with a sufficient black strip +    if (is_dark && !dev->model->is_sheetfed) { +        sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, false); +        sanei_genesys_set_motor_power(dev->calib_reg, motor); +    } else { +        sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, true); +        sanei_genesys_set_motor_power(dev->calib_reg, motor); +    } + +    dev->interface->write_registers(dev->calib_reg); + +    if (is_dark) { +        // wait some time to let lamp to get dark +        dev->interface->sleep_ms(200); +    } else if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION) { +        // make sure lamp is bright again +        // FIXME: what about scanners that take a long time to warm the lamp? +        dev->interface->sleep_ms(500); +    } + +    bool start_motor = !is_dark; +    dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, start_motor); + + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint(is_dark ? "dark_shading_calibration" +                                                : "white_shading_calibration"); +        dev->cmd_set->end_scan(dev, &dev->calib_reg, true); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, reinterpret_cast<std::uint8_t*>(calibration_data.data()), +                                         size); + +    dev->cmd_set->end_scan(dev, &dev->calib_reg, true); + +    if (dev->model->flags & GENESYS_FLAG_16BIT_DATA_INVERTED) { +        for (std::size_t i = 0; i < size / 2; ++i) { +            auto value = calibration_data[i]; +            value = ((value >> 8) & 0xff) | ((value << 8) & 0xff00); +            calibration_data[i] = value; +        } +    } + +    std::fill(out_average_data.begin(), +              out_average_data.begin() + dev->calib_pixels_offset * channels, 0); + +    compute_array_percentile_approx(out_average_data.data() + dev->calib_pixels_offset * channels, +                                    calibration_data.data(), +                                    dev->calib_lines, pixels_per_line * channels, +                                    0.5f); + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file16((log_filename_prefix + "_shading.pnm").c_str(), +                                       calibration_data.data(), +                                       channels, pixels_per_line, dev->calib_lines); +        sanei_genesys_write_pnm_file16((log_filename_prefix + "_average.pnm").c_str(), +                                       out_average_data.data(), +                                       channels, out_pixels_per_line, 1); +    } +} + + +static void genesys_dark_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +    genesys_shading_calibration_impl(dev, sensor, dev->dark_average_data, true, "gl_black_"); +} +/* + * this function builds dummy dark calibration data so that we can + * compute shading coefficient in a clean way + *  todo: current values are hardcoded, we have to find if they + * can be computed from previous calibration data (when doing offset + * calibration ?) + */ +static void genesys_dummy_dark_shading(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +  uint32_t pixels_per_line; +  uint8_t channels; +  uint32_t skip, xend; +  int dummy1, dummy2, dummy3;	/* dummy black average per channel */ + +  pixels_per_line = dev->calib_pixels; +  channels = dev->calib_channels; + +  uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset; + +    dev->average_size = channels * out_pixels_per_line; +  dev->dark_average_data.clear(); +  dev->dark_average_data.resize(dev->average_size, 0); + +  /* we average values on 'the left' where CCD pixels are under casing and +     give darkest values. We then use these as dummy dark calibration */ +  if (dev->settings.xres <= sensor.optical_res / 2) +    { +      skip = 4; +      xend = 36; +    } +  else +    { +      skip = 4; +      xend = 68; +    } +    if (dev->model->sensor_id==SensorId::CCD_G4050 || +        dev->model->sensor_id==SensorId::CCD_HP_4850C +     || dev->model->sensor_id==SensorId::CCD_CANON_4400F +     || dev->model->sensor_id==SensorId::CCD_CANON_8400F +     || dev->model->sensor_id==SensorId::CCD_KVSS080) +    { +      skip = 2; +      xend = sensor.black_pixels; +    } + +  /* average each channels on half left margin */ +  dummy1 = 0; +  dummy2 = 0; +  dummy3 = 0; + +    for (unsigned x = skip + 1; x <= xend; x++) { +        dummy1 += dev->white_average_data[channels * x]; +        if (channels > 1) { +            dummy2 += dev->white_average_data[channels * x + 1]; +            dummy3 += dev->white_average_data[channels * x + 2]; +        } +    } + +  dummy1 /= (xend - skip); +  if (channels > 1) +    { +      dummy2 /= (xend - skip); +      dummy3 /= (xend - skip); +    } +  DBG(DBG_proc, "%s: dummy1=%d, dummy2=%d, dummy3=%d \n", __func__, dummy1, dummy2, dummy3); + +  /* fill dark_average */ +    for (unsigned x = 0; x < out_pixels_per_line; x++) { +        dev->dark_average_data[channels * x] = dummy1; +        if (channels > 1) { +            dev->dark_average_data[channels * x + 1] = dummy2; +            dev->dark_average_data[channels * x + 2] = dummy3; +        } +    } +} + + +static void genesys_repark_sensor_before_shading(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +    if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK) { +        dev->cmd_set->move_back_home(dev, true); + +        if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +            dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +        { +            dev->cmd_set->move_to_ta(dev); +        } +    } +} + +static void genesys_repark_sensor_after_white_shading(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +    if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK) { +        dev->cmd_set->move_back_home(dev, true); +    } +} + +static void genesys_white_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +    genesys_shading_calibration_impl(dev, sensor, dev->white_average_data, false, "gl_white_"); +} + +// This calibration uses a scan over the calibration target, comprising a black and a white strip. +// (So the motor must be on.) +static void genesys_dark_white_shading_calibration(Genesys_Device* dev, +                                                   const Genesys_Sensor& sensor) +{ +    DBG_HELPER_ARGS(dbg, "lines = %zu", dev->calib_lines); +  size_t size; +  uint32_t pixels_per_line; +  uint8_t channels; +  unsigned int x; +  uint32_t dark, white, dark_sum, white_sum, dark_count, white_count, col, +    dif; + +  pixels_per_line = dev->calib_pixels; +  channels = dev->calib_channels; + +  uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset; + +    dev->average_size = channels * out_pixels_per_line; + +  dev->white_average_data.clear(); +  dev->white_average_data.resize(dev->average_size); + +  dev->dark_average_data.clear(); +  dev->dark_average_data.resize(dev->average_size); + +  if (dev->calib_total_bytes_to_read > 0) +    size = dev->calib_total_bytes_to_read; +  else +    size = channels * 2 * pixels_per_line * dev->calib_lines; + +  std::vector<uint8_t> calibration_data(size); + +    bool motor = true; +  if (dev->model->flags & GENESYS_FLAG_SHADING_NO_MOVE) +    { +        motor = false; +    } + +    // turn on motor and lamp power +    sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, true); +    sanei_genesys_set_motor_power(dev->calib_reg, motor); + +    dev->interface->write_registers(dev->calib_reg); + +    dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, false); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("dark_white_shading_calibration"); +        dev->cmd_set->end_scan(dev, &dev->calib_reg, true); +        return; +    } + +    sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size); + +    dev->cmd_set->end_scan(dev, &dev->calib_reg, true); + +  if (DBG_LEVEL >= DBG_data) +    { +      if (dev->model->is_cis) +        { +          sanei_genesys_write_pnm_file("gl_black_white_shading.pnm", calibration_data.data(), +                                       16, 1, pixels_per_line*channels, +                                       dev->calib_lines); +        } +      else +        { +          sanei_genesys_write_pnm_file("gl_black_white_shading.pnm", calibration_data.data(), +                                       16, channels, pixels_per_line, +                                       dev->calib_lines); +        } +    } + + +    std::fill(dev->dark_average_data.begin(), +              dev->dark_average_data.begin() + dev->calib_pixels_offset * channels, 0); +    std::fill(dev->white_average_data.begin(), +              dev->white_average_data.begin() + dev->calib_pixels_offset * channels, 0); + +    uint16_t* average_white = dev->white_average_data.data() + dev->calib_pixels_offset * channels; +    uint16_t* average_dark = dev->dark_average_data.data() + dev->calib_pixels_offset * channels; + +  for (x = 0; x < pixels_per_line * channels; x++) +    { +      dark = 0xffff; +      white = 0; + +            for (std::size_t y = 0; y < dev->calib_lines; y++) +	{ +	  col = calibration_data[(x + y * pixels_per_line * channels) * 2]; +	  col |= +	    calibration_data[(x + y * pixels_per_line * channels) * 2 + +			     1] << 8; + +	  if (col > white) +	    white = col; +	  if (col < dark) +	    dark = col; +	} + +      dif = white - dark; + +      dark = dark + dif / 8; +      white = white - dif / 8; + +      dark_count = 0; +      dark_sum = 0; + +      white_count = 0; +      white_sum = 0; + +            for (std::size_t y = 0; y < dev->calib_lines; y++) +	{ +	  col = calibration_data[(x + y * pixels_per_line * channels) * 2]; +	  col |= +	    calibration_data[(x + y * pixels_per_line * channels) * 2 + +			     1] << 8; + +	  if (col >= white) +	    { +	      white_sum += col; +	      white_count++; +	    } +	  if (col <= dark) +	    { +	      dark_sum += col; +	      dark_count++; +	    } + +	} + +      dark_sum /= dark_count; +      white_sum /= white_count; + +        *average_dark++ = dark_sum; +        *average_white++ = white_sum; +    } + +    if (DBG_LEVEL >= DBG_data) { +        sanei_genesys_write_pnm_file16("gl_white_average.pnm", dev->white_average_data.data(), +                                       channels, out_pixels_per_line, 1); +        sanei_genesys_write_pnm_file16("gl_dark_average.pnm", dev->dark_average_data.data(), +                                       channels, out_pixels_per_line, 1); +    } +} + +/* computes one coefficient given bright-dark value + * @param coeff factor giving 1.00 gain + * @param target desired target code + * @param value brght-dark value + * */ +static unsigned int +compute_coefficient (unsigned int coeff, unsigned int target, unsigned int value) +{ +  int result; + +  if (value > 0) +    { +      result = (coeff * target) / value; +      if (result >= 65535) +	{ +	  result = 65535; +	} +    } +  else +    { +      result = coeff; +    } +  return result; +} + +/** @brief compute shading coefficients for LiDE scanners + * The dark/white shading is actually performed _after_ reducing + * resolution via averaging. only dark/white shading data for what would be + * first pixel at full resolution is used. + * + * scanner raw input to output value calculation: + *   o=(i-off)*(gain/coeff) + * + * from datasheet: + *   off=dark_average + *   gain=coeff*bright_target/(bright_average-dark_average) + * works for dark_target==0 + * + * what we want is these: + *   bright_target=(bright_average-off)*(gain/coeff) + *   dark_target=(dark_average-off)*(gain/coeff) + * leading to + *  off = (dark_average*bright_target - bright_average*dark_target)/(bright_target - dark_target) + *  gain = (bright_target - dark_target)/(bright_average - dark_average)*coeff + * + * @param dev scanner's device + * @param shading_data memory area where to store the computed shading coefficients + * @param pixels_per_line number of pixels per line + * @param words_per_color memory words per color channel + * @param channels number of color channels (actually 1 or 3) + * @param o shading coefficients left offset + * @param coeff 4000h or 2000h depending on fast scan mode or not (GAIN4 bit) + * @param target_bright value of the white target code + * @param target_dark value of the black target code +*/ +static void +compute_averaged_planar (Genesys_Device * dev, const Genesys_Sensor& sensor, +			 uint8_t * shading_data, +			 unsigned int pixels_per_line, +			 unsigned int words_per_color, +			 unsigned int channels, +			 unsigned int o, +			 unsigned int coeff, +			 unsigned int target_bright, +			 unsigned int target_dark) +{ +  unsigned int x, i, j, br, dk, res, avgpixels, basepixels, val; +  unsigned int fill,factor; + +  DBG(DBG_info, "%s: pixels=%d, offset=%d\n", __func__, pixels_per_line, o); + +  /* initialize result */ +  memset (shading_data, 0xff, words_per_color * 3 * 2); + +  /* +     strangely i can write 0x20000 bytes beginning at 0x00000 without overwriting +     slope tables - which begin at address 0x10000(for 1200dpi hw mode): +     memory is organized in words(2 bytes) instead of single bytes. explains +     quite some things +   */ +/* +  another one: the dark/white shading is actually performed _after_ reducing +  resolution via averaging. only dark/white shading data for what would be +  first pixel at full resolution is used. + */ +/* +  scanner raw input to output value calculation: +    o=(i-off)*(gain/coeff) + +  from datasheet: +    off=dark_average +    gain=coeff*bright_target/(bright_average-dark_average) +  works for dark_target==0 + +  what we want is these: +    bright_target=(bright_average-off)*(gain/coeff) +    dark_target=(dark_average-off)*(gain/coeff) +  leading to +    off = (dark_average*bright_target - bright_average*dark_target)/(bright_target - dark_target) +    gain = (bright_target - dark_target)/(bright_average - dark_average)*coeff + */ +  res = dev->settings.xres; + +    if (sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres) > 1) +    { +        res *= 2; +    } + +  /* this should be evenly dividable */ +  basepixels = sensor.optical_res / res; + +  /* gl841 supports 1/1 1/2 1/3 1/4 1/5 1/6 1/8 1/10 1/12 1/15 averaging */ +  if (basepixels < 1) +    avgpixels = 1; +  else if (basepixels < 6) +    avgpixels = basepixels; +  else if (basepixels < 8) +    avgpixels = 6; +  else if (basepixels < 10) +    avgpixels = 8; +  else if (basepixels < 12) +    avgpixels = 10; +  else if (basepixels < 15) +    avgpixels = 12; +  else +    avgpixels = 15; + +  /* LiDE80 packs shading data */ +    if (dev->model->sensor_id != SensorId::CIS_CANON_LIDE_80) { +      factor=1; +      fill=avgpixels; +    } +  else +    { +      factor=avgpixels; +      fill=1; +    } + +  DBG(DBG_info, "%s: averaging over %d pixels\n", __func__, avgpixels); +  DBG(DBG_info, "%s: packing factor is %d\n", __func__, factor); +  DBG(DBG_info, "%s: fill length is %d\n", __func__, fill); + +  for (x = 0; x <= pixels_per_line - avgpixels; x += avgpixels) +    { +      if ((x + o) * 2 * 2 + 3 > words_per_color * 2) +	break; + +      for (j = 0; j < channels; j++) +	{ + +	  dk = 0; +	  br = 0; +	  for (i = 0; i < avgpixels; i++) +	    { +                // dark data +                dk += dev->dark_average_data[(x + i + pixels_per_line * j)]; +                // white data +                br += dev->white_average_data[(x + i + pixels_per_line * j)]; +	    } + +	  br /= avgpixels; +	  dk /= avgpixels; + +	  if (br * target_dark > dk * target_bright) +	    val = 0; +	  else if (dk * target_bright - br * target_dark > +		   65535 * (target_bright - target_dark)) +	    val = 65535; +	  else +            { +	      val = (dk * target_bright - br * target_dark) / (target_bright - target_dark); +            } + +          /*fill all pixels, even if only the last one is relevant*/ +	  for (i = 0; i < fill; i++) +	    { +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j] = val & 0xff; +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 1] = val >> 8; +	    } + +	  val = br - dk; + +	  if (65535 * val > (target_bright - target_dark) * coeff) +            { +	      val = (coeff * (target_bright - target_dark)) / val; +            } +	  else +            { +	      val = 65535; +            } + +          /*fill all pixels, even if only the last one is relevant*/ +	  for (i = 0; i < fill; i++) +	    { +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 2] = val & 0xff; +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 3] = val >> 8; +	    } +	} + +      /* fill remaining channels */ +      for (j = channels; j < 3; j++) +	{ +	  for (i = 0; i < fill; i++) +	    { +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j    ] = shading_data[(x/factor + o + i) * 2 * 2    ]; +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 1] = shading_data[(x/factor + o + i) * 2 * 2 + 1]; +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 2] = shading_data[(x/factor + o + i) * 2 * 2 + 2]; +	      shading_data[(x/factor + o + i) * 2 * 2 + words_per_color * 2 * j + 3] = shading_data[(x/factor + o + i) * 2 * 2 + 3]; +	    } +	} +    } +} + +static std::array<unsigned, 3> color_order_to_cmat(ColorOrder color_order) +{ +    switch (color_order) { +        case ColorOrder::RGB: return {0, 1, 2}; +        case ColorOrder::GBR: return {2, 0, 1}; +        default: +            throw std::logic_error("Unknown color order"); +    } +} + +/** + * Computes shading coefficient using formula in data sheet. 16bit data values + * manipulated here are little endian. For now we assume deletion scanning type + * and that there is always 3 channels. + * @param dev scanner's device + * @param shading_data memory area where to store the computed shading coefficients + * @param pixels_per_line number of pixels per line + * @param channels number of color channels (actually 1 or 3) + * @param cmat color transposition matrix + * @param offset shading coefficients left offset + * @param coeff 4000h or 2000h depending on fast scan mode or not + * @param target value of the target code + */ +static void compute_coefficients(Genesys_Device * dev, +		      uint8_t * shading_data, +		      unsigned int pixels_per_line, +		      unsigned int channels, +                                 ColorOrder color_order, +		      int offset, +		      unsigned int coeff, +		      unsigned int target) +{ +  uint8_t *ptr;			/* contain 16bit words in little endian */ +  unsigned int x, c; +  unsigned int val, br, dk; +  unsigned int start, end; + +  DBG(DBG_io, "%s: pixels_per_line=%d,  coeff=0x%04x\n", __func__, pixels_per_line, coeff); + +    auto cmat = color_order_to_cmat(color_order); + +  /* compute start & end values depending of the offset */ +  if (offset < 0) +   { +      start = -1 * offset; +      end = pixels_per_line; +   } +  else +   { +     start = 0; +     end = pixels_per_line - offset; +   } + +  for (c = 0; c < channels; c++) +    { +      for (x = start; x < end; x++) +	{ +	  /* TODO if channels=1 , use filter to know the base addr */ +	  ptr = shading_data + 4 * ((x + offset) * channels + cmat[c]); + +        // dark data +        dk = dev->dark_average_data[x * channels + c]; + +        // white data +        br = dev->white_average_data[x * channels + c]; + +	  /* compute coeff */ +	  val=compute_coefficient(coeff,target,br-dk); + +	  /* assign it */ +	  ptr[0] = dk & 255; +	  ptr[1] = dk / 256; +	  ptr[2] = val & 0xff; +	  ptr[3] = val / 256; + +	} +    } +} + +/** + * Computes shading coefficient using formula in data sheet. 16bit data values + * manipulated here are little endian. Data is in planar form, ie grouped by + * lines of the same color component. + * @param dev scanner's device + * @param shading_data memory area where to store the computed shading coefficients + * @param factor averaging factor when the calibration scan is done at a higher resolution + * than the final scan + * @param pixels_per_line number of pixels per line + * @param words_per_color total number of shading data words for one color element + * @param channels number of color channels (actually 1 or 3) + * @param cmat transcoding matrix for color channel order + * @param offset shading coefficients left offset + * @param coeff 4000h or 2000h depending on fast scan mode or not + * @param target white target value + */ +static void compute_planar_coefficients(Genesys_Device * dev, +			     uint8_t * shading_data, +			     unsigned int factor, +			     unsigned int pixels_per_line, +			     unsigned int words_per_color, +			     unsigned int channels, +                                        ColorOrder color_order, +			     unsigned int offset, +			     unsigned int coeff, +			     unsigned int target) +{ +  uint8_t *ptr;			/* contains 16bit words in little endian */ +  uint32_t x, c, i; +  uint32_t val, dk, br; + +    auto cmat = color_order_to_cmat(color_order); + +  DBG(DBG_io, "%s: factor=%d, pixels_per_line=%d, words=0x%X, coeff=0x%04x\n", __func__, factor, +      pixels_per_line, words_per_color, coeff); +  for (c = 0; c < channels; c++) +    { +      /* shading data is larger than pixels_per_line so offset can be neglected */ +      for (x = 0; x < pixels_per_line; x+=factor) +	{ +	  /* x2 because of 16 bit values, and x2 since one coeff for dark +	   * and another for white */ +	  ptr = shading_data + words_per_color * cmat[c] * 2 + (x + offset) * 4; + +	  dk = 0; +	  br = 0; + +	  /* average case */ +	  for(i=0;i<factor;i++) +	  { +                dk += dev->dark_average_data[((x+i) + pixels_per_line * c)]; +                br += dev->white_average_data[((x+i) + pixels_per_line * c)]; +	  } +	  dk /= factor; +	  br /= factor; + +	  val = compute_coefficient (coeff, target, br - dk); + +	  /* we duplicate the information to have calibration data at optical resolution */ +	  for (i = 0; i < factor; i++) +	    { +	      ptr[0 + 4 * i] = dk & 255; +	      ptr[1 + 4 * i] = dk / 256; +	      ptr[2 + 4 * i] = val & 0xff; +	      ptr[3 + 4 * i] = val / 256; +	    } +	} +    } +  /* in case of gray level scan, we duplicate shading information on all +   * three color channels */ +  if(channels==1) +  { +	  memcpy(shading_data+cmat[1]*2*words_per_color, +	         shading_data+cmat[0]*2*words_per_color, +		 words_per_color*2); +	  memcpy(shading_data+cmat[2]*2*words_per_color, +	         shading_data+cmat[0]*2*words_per_color, +		 words_per_color*2); +  } +} + +static void +compute_shifted_coefficients (Genesys_Device * dev, +                              const Genesys_Sensor& sensor, +			      uint8_t * shading_data, +			      unsigned int pixels_per_line, +			      unsigned int channels, +                              ColorOrder color_order, +			      int offset, +			      unsigned int coeff, +			      unsigned int target_dark, +			      unsigned int target_bright, +			      unsigned int patch_size)		/* contigous extent */ +{ +  unsigned int x, avgpixels, basepixels, i, j, val1, val2; +  unsigned int br_tmp [3], dk_tmp [3]; +  uint8_t *ptr = shading_data + offset * 3 * 4;                 /* contain 16bit words in little endian */ +  unsigned int patch_cnt = offset * 3;                          /* at start, offset of first patch */ + +    auto cmat = color_order_to_cmat(color_order); + +  x = dev->settings.xres; +  if (sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres) > 1) +    x *= 2;							/* scanner is using half-ccd mode */ +  basepixels = sensor.optical_res / x;			/*this should be evenly dividable */ + +      /* gl841 supports 1/1 1/2 1/3 1/4 1/5 1/6 1/8 1/10 1/12 1/15 averaging */ +      if (basepixels < 1) +        avgpixels = 1; +      else if (basepixels < 6) +        avgpixels = basepixels; +      else if (basepixels < 8) +        avgpixels = 6; +      else if (basepixels < 10) +        avgpixels = 8; +      else if (basepixels < 12) +        avgpixels = 10; +      else if (basepixels < 15) +        avgpixels = 12; +      else +        avgpixels = 15; +  DBG(DBG_info, "%s: pixels_per_line=%d,  coeff=0x%04x,  averaging over %d pixels\n", __func__, +      pixels_per_line, coeff, avgpixels); + +  for (x = 0; x <= pixels_per_line - avgpixels; x += avgpixels) { +    memset (&br_tmp, 0, sizeof(br_tmp)); +    memset (&dk_tmp, 0, sizeof(dk_tmp)); + +    for (i = 0; i < avgpixels; i++) { +      for (j = 0; j < channels; j++) { +                br_tmp[j] += dev->white_average_data[((x + i) * channels + j)]; +                dk_tmp[i] += dev->dark_average_data[((x + i) * channels + j)]; +      } +    } +    for (j = 0; j < channels; j++) { +      br_tmp[j] /= avgpixels; +      dk_tmp[j] /= avgpixels; + +      if (br_tmp[j] * target_dark > dk_tmp[j] * target_bright) +        val1 = 0; +      else if (dk_tmp[j] * target_bright - br_tmp[j] * target_dark > 65535 * (target_bright - target_dark)) +        val1 = 65535; +      else +        val1 = (dk_tmp[j] * target_bright - br_tmp[j] * target_dark) / (target_bright - target_dark); + +      val2 = br_tmp[j] - dk_tmp[j]; +      if (65535 * val2 > (target_bright - target_dark) * coeff) +        val2 = (coeff * (target_bright - target_dark)) / val2; +      else +        val2 = 65535; + +      br_tmp[j] = val1; +      dk_tmp[j] = val2; +    } +    for (i = 0; i < avgpixels; i++) { +      for (j = 0; j < channels; j++) { +        * ptr++ = br_tmp[ cmat[j] ] & 0xff; +        * ptr++ = br_tmp[ cmat[j] ] >> 8; +        * ptr++ = dk_tmp[ cmat[j] ] & 0xff; +        * ptr++ = dk_tmp[ cmat[j] ] >> 8; +        patch_cnt++; +        if (patch_cnt == patch_size) { +          patch_cnt = 0; +          val1 = cmat[2]; +          cmat[2] = cmat[1]; +          cmat[1] = cmat[0]; +          cmat[0] = val1; +        } +      } +    } +  } +} + +static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); + +    if (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) { +        return; +    } + +  uint32_t pixels_per_line; +  uint8_t channels; +  int o; +  unsigned int length;		/**> number of shading calibration data words */ +  unsigned int factor; +  unsigned int coeff, target_code, words_per_color = 0; + +  pixels_per_line = dev->calib_pixels + dev->calib_pixels_offset; +  channels = dev->calib_channels; + +  /* we always build data for three channels, even for gray +   * we make the shading data such that each color channel data line is contiguous +   * to the next one, which allow to write the 3 channels in 1 write +   * during genesys_send_shading_coefficient, some values are words, other bytes +   * hence the x2 factor */ +  switch (dev->reg.get8(0x05) >> 6) +    { +      /* 600 dpi */ +    case 0: +      words_per_color = 0x2a00; +      break; +      /* 1200 dpi */ +    case 1: +      words_per_color = 0x5500; +      break; +      /* 2400 dpi */ +    case 2: +      words_per_color = 0xa800; +      break; +      /* 4800 dpi */ +    case 3: +      words_per_color = 0x15000; +      break; +    } + +  /* special case, memory is aligned on 0x5400, this has yet to be explained */ +  /* could be 0xa800 because sensor is truly 2400 dpi, then halved because +   * we only set 1200 dpi */ +  if(dev->model->sensor_id==SensorId::CIS_CANON_LIDE_80) +    { +      words_per_color = 0x5400; +    } + +  length = words_per_color * 3 * 2; + +  /* allocate computed size */ +  // contains 16bit words in little endian +  std::vector<uint8_t> shading_data(length, 0); + +  /* TARGET/(Wn-Dn) = white gain -> ~1.xxx then it is multiplied by 0x2000 +     or 0x4000 to give an integer +     Wn = white average for column n +     Dn = dark average for column n +   */ +    if (get_registers_gain4_bit(dev->model->asic_type, dev->calib_reg)) { +        coeff = 0x4000; +    } else { +        coeff = 0x2000; +    } + +  /* compute avg factor */ +  if(dev->settings.xres>sensor.optical_res) +    { +      factor=1; +    } +  else +    { +      factor=sensor.optical_res/dev->settings.xres; +    } + +  /* for GL646, shading data is planar if REG_0x01_FASTMOD is set and +   * chunky if not. For now we rely on the fact that we know that +   * each sensor is used only in one mode. Currently only the CIS_XP200 +   * sets REG_0x01_FASTMOD. +   */ + +  /* TODO setup a struct in genesys_devices that +   * will handle these settings instead of having this switch growing up */ +  switch (dev->model->sensor_id) +    { +    case SensorId::CCD_XP300: +    case SensorId::CCD_ROADWARRIOR: +    case SensorId::CCD_DP665: +    case SensorId::CCD_DP685: +    case SensorId::CCD_DSMOBILE600: +      target_code = 0xdc00; +      o = 4; +      compute_planar_coefficients (dev, +                   shading_data.data(), +				   factor, +				   pixels_per_line, +				   words_per_color, +				   channels, +                   ColorOrder::RGB, +				   o, +				   coeff, +				   target_code); +      break; +    case SensorId::CIS_XP200: +      target_code = 0xdc00; +      o = 2; +      compute_planar_coefficients (dev, +                   shading_data.data(), +				   1, +				   pixels_per_line, +				   words_per_color, +				   channels, +                   ColorOrder::GBR, +				   o, +				   coeff, +				   target_code); +      break; +    case SensorId::CCD_HP2300: +      target_code = 0xdc00; +      o = 2; +      if(dev->settings.xres<=sensor.optical_res/2) +       { +          o = o - sensor.dummy_pixel / 2; +       } +      compute_coefficients (dev, +                shading_data.data(), +			    pixels_per_line, +			    3, +                            ColorOrder::RGB, +                            o, +                            coeff, +                            target_code); +      break; +    case SensorId::CCD_5345: +      target_code = 0xe000; +      o = 4; +      if(dev->settings.xres<=sensor.optical_res/2) +       { +          o = o - sensor.dummy_pixel; +       } +      compute_coefficients (dev, +                shading_data.data(), +			    pixels_per_line, +			    3, +                            ColorOrder::RGB, +                            o, +                            coeff, +                            target_code); +      break; +    case SensorId::CCD_HP3670: +    case SensorId::CCD_HP2400: +      target_code = 0xe000; +            // offset is dependent on ccd_pixels_per_system_pixel(), but we couldn't use this in +            // common code previously. +            // FIXME: use sensor.ccd_pixels_per_system_pixel() +      if(dev->settings.xres<=300) +        { +                o = -10; +        } +      else if(dev->settings.xres<=600) +        { +                o = -6; +        } +      else +        { +          o = +2; +        } +      compute_coefficients (dev, +                shading_data.data(), +			    pixels_per_line, +			    3, +                            ColorOrder::RGB, +                            o, +                            coeff, +                            target_code); +      break; +    case SensorId::CCD_KVSS080: +    case SensorId::CCD_PLUSTEK_OPTICBOOK_3800: +    case SensorId::CCD_G4050: +        case SensorId::CCD_HP_4850C: +    case SensorId::CCD_CANON_4400F: +    case SensorId::CCD_CANON_8400F: +    case SensorId::CCD_CANON_8600F: +    case SensorId::CCD_PLUSTEK_OPTICFILM_7200I: +    case SensorId::CCD_PLUSTEK_OPTICFILM_7300: +    case SensorId::CCD_PLUSTEK_OPTICFILM_7500I: +      target_code = 0xe000; +      o = 0; +      compute_coefficients (dev, +                shading_data.data(), +			    pixels_per_line, +			    3, +                            ColorOrder::RGB, +                            o, +                            coeff, +                            target_code); +      break; +    case SensorId::CIS_CANON_LIDE_700F: +    case SensorId::CIS_CANON_LIDE_100: +    case SensorId::CIS_CANON_LIDE_200: +    case SensorId::CIS_CANON_LIDE_110: +    case SensorId::CIS_CANON_LIDE_120: +    case SensorId::CIS_CANON_LIDE_210: +    case SensorId::CIS_CANON_LIDE_220: +        /* TODO store this in a data struct so we avoid +         * growing this switch */ +        switch(dev->model->sensor_id) +          { +          case SensorId::CIS_CANON_LIDE_110: +          case SensorId::CIS_CANON_LIDE_120: +          case SensorId::CIS_CANON_LIDE_210: +          case SensorId::CIS_CANON_LIDE_220: +          case SensorId::CIS_CANON_LIDE_700F: +                target_code = 0xc000; +            break; +          default: +            target_code = 0xdc00; +          } +        words_per_color=pixels_per_line*2; +        length = words_per_color * 3 * 2; +        shading_data.clear(); +        shading_data.resize(length, 0); +        compute_planar_coefficients (dev, +                                     shading_data.data(), +                                     1, +                                     pixels_per_line, +                                     words_per_color, +                                     channels, +                                     ColorOrder::RGB, +                                     0, +                                     coeff, +                                     target_code); +      break; +    case SensorId::CIS_CANON_LIDE_35: +      compute_averaged_planar (dev, sensor, +                               shading_data.data(), +                               pixels_per_line, +                               words_per_color, +                               channels, +                               4, +                               coeff, +                               0xe000, +                               0x0a00); +      break; +    case SensorId::CIS_CANON_LIDE_80: +      compute_averaged_planar (dev, sensor, +                               shading_data.data(), +                               pixels_per_line, +                               words_per_color, +                               channels, +                               0, +                               coeff, +			       0xe000, +                               0x0800); +      break; +    case SensorId::CCD_PLUSTEK_OPTICPRO_3600: +      compute_shifted_coefficients (dev, sensor, +                        shading_data.data(), +			            pixels_per_line, +			            channels, +                        ColorOrder::RGB, +			            12,         /* offset */ +			            coeff, + 			            0x0001,      /* target_dark */ +			            0xf900,      /* target_bright */ +			            256);        /* patch_size: contigous extent */ +      break; +    default: +        throw SaneException(SANE_STATUS_UNSUPPORTED, "sensor %d not supported", +                            static_cast<unsigned>(dev->model->sensor_id)); +      break; +    } + +    // do the actual write of shading calibration data to the scanner +    genesys_send_offset_and_shading(dev, sensor, shading_data.data(), length); +} + + +/** + * search calibration cache list for an entry matching required scan. + * If one is found, set device calibration with it + * @param dev scanner's device + * @return false if no matching cache entry has been + * found, true if one has been found and used. + */ +static bool +genesys_restore_calibration(Genesys_Device * dev, Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); + +    // if no cache or no function to evaluate cache entry ther can be no match/ +    if (dev->calibration_cache.empty()) { +        return false; +    } + +    auto session = dev->cmd_set->calculate_scan_session(dev, sensor, dev->settings); + +  /* we walk the link list of calibration cache in search for a +   * matching one */ +  for (auto& cache : dev->calibration_cache) +    { +        if (sanei_genesys_is_compatible_calibration(dev, session, &cache, false)) { +            dev->frontend = cache.frontend; +          /* we don't restore the gamma fields */ +          sensor.exposure = cache.sensor.exposure; + +          dev->average_size = cache.average_size; +          dev->calib_pixels = cache.calib_pixels; +          dev->calib_channels = cache.calib_channels; + +          dev->dark_average_data = cache.dark_average_data; +          dev->white_average_data = cache.white_average_data; + +            if (!dev->cmd_set->has_send_shading_data()) { +            genesys_send_shading_coefficient(dev, sensor); +          } + +          DBG(DBG_proc, "%s: restored\n", __func__); +          return true; +	} +    } +  DBG(DBG_proc, "%s: completed(nothing found)\n", __func__); +  return false; +} + + +static void genesys_save_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +#ifdef HAVE_SYS_TIME_H +  struct timeval time; +#endif + +    auto session = dev->cmd_set->calculate_scan_session(dev, sensor, dev->settings); + +  auto found_cache_it = dev->calibration_cache.end(); +  for (auto cache_it = dev->calibration_cache.begin(); cache_it != dev->calibration_cache.end(); +       cache_it++) +    { +        if (sanei_genesys_is_compatible_calibration(dev, session, &*cache_it, true)) { +            found_cache_it = cache_it; +            break; +        } +    } + +  /* if we found on overridable cache, we reuse it */ +  if (found_cache_it == dev->calibration_cache.end()) +    { +      /* create a new cache entry and insert it in the linked list */ +      dev->calibration_cache.push_back(Genesys_Calibration_Cache()); +      found_cache_it = std::prev(dev->calibration_cache.end()); +    } + +  found_cache_it->average_size = dev->average_size; + +  found_cache_it->dark_average_data = dev->dark_average_data; +  found_cache_it->white_average_data = dev->white_average_data; + +    found_cache_it->params = session.params; +  found_cache_it->frontend = dev->frontend; +  found_cache_it->sensor = sensor; + +  found_cache_it->calib_pixels = dev->calib_pixels; +  found_cache_it->calib_channels = dev->calib_channels; + +#ifdef HAVE_SYS_TIME_H +    gettimeofday(&time, nullptr); +  found_cache_it->last_calibration = time.tv_sec; +#endif +} + +/** + * does the calibration process for a flatbed scanner + * - offset calibration + * - gain calibration + * - shading calibration + * @param dev device to calibrate + */ +static void genesys_flatbed_calibration(Genesys_Device* dev, Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +  uint32_t pixels_per_line; + +    unsigned coarse_res = sensor.optical_res; +    if (dev->settings.yres <= sensor.optical_res / 2) { +        coarse_res /= 2; +    } + +    if (dev->model->model_id == ModelId::CANON_8400F) { +        coarse_res = 1600; +    } + +    if (dev->model->model_id == ModelId::CANON_4400F || +        dev->model->model_id == ModelId::CANON_8600F) +    { +        coarse_res = 1200; +    } + +  /* do offset calibration if needed */ +  if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION) +    { +        dev->interface->record_progress_message("offset_calibration"); +        dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg); + +      /* since all the registers are set up correctly, just use them */ +        dev->interface->record_progress_message("coarse_gain_calibration"); +        dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, coarse_res); +    } else { +    /* since we have 2 gain calibration proc, skip second if first one was +       used. */ +        dev->interface->record_progress_message("init_regs_for_coarse_calibration"); +        dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg); + +        dev->interface->record_progress_message("genesys_coarse_calibration"); +        genesys_coarse_calibration(dev, sensor); +    } + +  if (dev->model->is_cis) +    { +      /* the afe now sends valid data for doing led calibration */ +        dev->interface->record_progress_message("led_calibration"); +        switch (dev->model->asic_type) { +            case AsicType::GL124: +            case AsicType::GL845: +            case AsicType::GL846: +            case AsicType::GL847: { +                auto calib_exposure = dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg); +                for (auto& sensor_update : +                        sanei_genesys_find_sensors_all_for_write(dev, sensor.method)) { +                    sensor_update.get().exposure = calib_exposure; +                } +                sensor.exposure = calib_exposure; +                break; +            } +            default: { +                sensor.exposure = dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg); +            } +        } + + +      /* calibrate afe again to match new exposure */ +      if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION) { +            dev->interface->record_progress_message("offset_calibration"); +            dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg); + +            // since all the registers are set up correctly, just use them + +            dev->interface->record_progress_message("coarse_gain_calibration"); +            dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, coarse_res); +        } else { +            // since we have 2 gain calibration proc, skip second if first one was used +            dev->interface->record_progress_message("init_regs_for_coarse_calibration"); +            dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg); + +            dev->interface->record_progress_message("genesys_coarse_calibration"); +            genesys_coarse_calibration(dev, sensor); +        } +    } + +  /* we always use sensor pixel number when the ASIC can't handle multi-segments sensor */ +  if (!(dev->model->flags & GENESYS_FLAG_SIS_SENSOR)) +    { +        pixels_per_line = static_cast<std::uint32_t>((dev->model->x_size * dev->settings.xres) / +                                                     MM_PER_INCH); +    } +  else +    { +      pixels_per_line = sensor.sensor_pixels; +    } + +    // send default shading data +    dev->interface->record_progress_message("sanei_genesys_init_shading_data"); +    sanei_genesys_init_shading_data(dev, sensor, pixels_per_line); + +  if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +      dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +  { +        dev->cmd_set->move_to_ta(dev); +  } + +    // shading calibration +    if (dev->model->flags & GENESYS_FLAG_DARK_WHITE_CALIBRATION) { +        dev->interface->record_progress_message("init_regs_for_shading"); +        dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg); + +        dev->interface->record_progress_message("genesys_dark_white_shading_calibration"); +        genesys_dark_white_shading_calibration(dev, sensor); +    } else { +        DBG(DBG_proc, "%s : genesys_dark_shading_calibration dev->calib_reg ", __func__); +        debug_dump(DBG_proc, dev->calib_reg); + +        if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION) { +            dev->interface->record_progress_message("init_regs_for_shading"); +            dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg); + +            dev->interface->record_progress_message("genesys_dark_shading_calibration"); +            genesys_dark_shading_calibration(dev, sensor); +            genesys_repark_sensor_before_shading(dev); +        } + +        dev->interface->record_progress_message("init_regs_for_shading2"); +        dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg); + +        dev->interface->record_progress_message("genesys_white_shading_calibration"); +        genesys_white_shading_calibration(dev, sensor); +        genesys_repark_sensor_after_white_shading(dev); + +        if (!(dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)) { +            genesys_dummy_dark_shading(dev, sensor); +        } +    } + +    if (!dev->cmd_set->has_send_shading_data()) { +        dev->interface->record_progress_message("genesys_send_shading_coefficient"); +        genesys_send_shading_coefficient(dev, sensor); +    } +} + +/** + * Does the calibration process for a sheetfed scanner + * - offset calibration + * - gain calibration + * - shading calibration + * During calibration a predefined calibration sheet with specific black and white + * areas is used. + * @param dev device to calibrate + */ +static void genesys_sheetfed_calibration(Genesys_Device* dev, Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +    bool forward = true; + +    // first step, load document +    dev->cmd_set->load_document(dev); + +  /* led, offset and gain calibration are influenced by scan +   * settings. So we set it to sensor resolution */ +  dev->settings.xres = sensor.optical_res; +  /* XP200 needs to calibrate a full and half sensor's resolution */ +    if (dev->model->sensor_id == SensorId::CIS_XP200 && +        dev->settings.xres <= sensor.optical_res / 2) +    { +        dev->settings.xres /= 2; +    } + +  /* the afe needs to sends valid data even before calibration */ + +  /* go to a white area */ +    try { +        dev->cmd_set->search_strip(dev, sensor, forward, false); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); +        throw; +    } + +  if (dev->model->is_cis) +    { +        dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg); +    } + +  /* calibrate afe */ +  if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION) +    { +        dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg); + +      /* since all the registers are set up correctly, just use them */ + +        dev->cmd_set->coarse_gain_calibration(dev, sensor, dev->calib_reg, sensor.optical_res); +    } +  else +    /* since we have 2 gain calibration proc, skip second if first one was +       used. */ +    { +        dev->cmd_set->init_regs_for_coarse_calibration(dev, sensor, dev->calib_reg); + +        genesys_coarse_calibration(dev, sensor); +    } + +  /* search for a full width black strip and then do a 16 bit scan to +   * gather black shading data */ +  if (dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION) +    { +      /* seek black/white reverse/forward */ +        try { +            dev->cmd_set->search_strip(dev, sensor, forward, true); +        } catch (...) { +            catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); +            throw; +        } + +        dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg); + +        try { +            genesys_dark_shading_calibration(dev, sensor); +        } catch (...) { +            catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); +            throw; +        } +        forward = false; +    } + + +  /* go to a white area */ +    try { +        dev->cmd_set->search_strip(dev, sensor, forward, false); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); +        throw; +    } + +  genesys_repark_sensor_before_shading(dev); + +    dev->cmd_set->init_regs_for_shading(dev, sensor, dev->calib_reg); + +    try { +        genesys_white_shading_calibration(dev, sensor); +        genesys_repark_sensor_after_white_shading(dev); +    } catch (...) { +        catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); +        throw; +    } + +    // in case we haven't black shading data, build it from black pixels of white calibration +    // FIXME: shouldn't we use genesys_dummy_dark_shading() ? +    if (!(dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)) { +      dev->dark_average_data.clear(); +        dev->dark_average_data.resize(dev->average_size, 0x0f0f); +      /* XXX STEF XXX +       * with black point in white shading, build an average black +       * pixel and use it to fill the dark_average +       * dev->calib_pixels +       (sensor.sensor_pixels * dev->settings.xres) / sensor.optical_res, +       dev->calib_lines, +       */ +    } + +  /* send the shading coefficient when doing whole line shading +   * but not when using SHDAREA like GL124 */ +    if (!dev->cmd_set->has_send_shading_data()) { +        genesys_send_shading_coefficient(dev, sensor); +    } + +    // save the calibration data +    genesys_save_calibration(dev, sensor); + +    // and finally eject calibration sheet +    dev->cmd_set->eject_document(dev); + +    // restore settings +    dev->settings.xres = sensor.optical_res; +} + +/** + * does the calibration process for a device + * @param dev device to calibrate + */ +static void genesys_scanner_calibration(Genesys_Device* dev, Genesys_Sensor& sensor) +{ +    DBG_HELPER(dbg); +    if (!dev->model->is_sheetfed) { +        genesys_flatbed_calibration(dev, sensor); +        return; +    } +    genesys_sheetfed_calibration(dev, sensor); +} + + +/* ------------------------------------------------------------------------ */ +/*                  High level (exported) functions                         */ +/* ------------------------------------------------------------------------ */ + +/* + * wait lamp to be warm enough by scanning the same line until + * differences between two scans are below a threshold + */ +static void genesys_warmup_lamp(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); +    unsigned seconds = 0; +  int pixel; +  int channels, total_size; +  double first_average = 0; +  double second_average = 0; +  int difference = 255; +    int lines = 3; + +  const auto& sensor = sanei_genesys_find_sensor_any(dev); + +    dev->cmd_set->init_regs_for_warmup(dev, sensor, &dev->reg, &channels, &total_size); +  std::vector<uint8_t> first_line(total_size); +  std::vector<uint8_t> second_line(total_size); + +  do +    { +      DBG(DBG_info, "%s: one more loop\n", __func__); +        dev->cmd_set->begin_scan(dev, sensor, &dev->reg, false); + +        if (is_testing_mode()) { +            dev->interface->test_checkpoint("warmup_lamp"); +            dev->cmd_set->end_scan(dev, &dev->reg, true); +            return; +        } + +        wait_until_buffer_non_empty(dev); + +        try { +            sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size); +        } catch (...) { +            // FIXME: document why this retry is here +            sanei_genesys_read_data_from_scanner(dev, first_line.data(), total_size); +        } + +        dev->cmd_set->end_scan(dev, &dev->reg, true); + +        dev->interface->sleep_ms(1000); +      seconds++; + +        dev->cmd_set->begin_scan(dev, sensor, &dev->reg, false); + +        wait_until_buffer_non_empty(dev); + +        sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size); +        dev->cmd_set->end_scan(dev, &dev->reg, true); + +      /* compute difference between the two scans */ +      for (pixel = 0; pixel < total_size; pixel++) +	{ +            // 16 bit data +            if (dev->session.params.depth == 16) { +	      first_average += (first_line[pixel] + first_line[pixel + 1] * 256); +	      second_average += (second_line[pixel] + second_line[pixel + 1] * 256); +	      pixel++; +	    } +	  else +	    { +	      first_average += first_line[pixel]; +	      second_average += second_line[pixel]; +	    } +	} +        if (dev->session.params.depth == 16) { +	  first_average /= pixel; +	  second_average /= pixel; +            difference = static_cast<int>(std::fabs(first_average - second_average)); +	  DBG(DBG_info, "%s: average = %.2f, diff = %.3f\n", __func__, +	      100 * ((second_average) / (256 * 256)), +	      100 * (difference / second_average)); + +	  if (second_average > (100 * 256) +	      && (difference / second_average) < 0.002) +	    break; +	} +      else +	{ +	  first_average /= pixel; +	  second_average /= pixel; +	  if (DBG_LEVEL >= DBG_data) +	    { +              sanei_genesys_write_pnm_file("gl_warmup1.pnm", first_line.data(), 8, channels, +                                           total_size / (lines * channels), lines); +              sanei_genesys_write_pnm_file("gl_warmup2.pnm", second_line.data(), 8, channels, +                                           total_size / (lines * channels), lines); +	    } +	  DBG(DBG_info, "%s: average 1 = %.2f, average 2 = %.2f\n", __func__, first_average, +	      second_average); +          /* if delta below 15/255 ~= 5.8%, lamp is considred warm enough */ +	  if (fabs (first_average - second_average) < 15 +	      && second_average > 55) +	    break; +	} + +      /* sleep another second before next loop */ +        dev->interface->sleep_ms(1000); +        seconds++; +    } while (seconds < WARMUP_TIME); + +  if (seconds >= WARMUP_TIME) +    { +        throw SaneException(SANE_STATUS_IO_ERROR, +                            "warmup timed out after %d seconds. Lamp defective?", seconds); +    } +  else +    { +      DBG(DBG_info, "%s: warmup succeeded after %d seconds\n", __func__, seconds); +    } +} + + +// High-level start of scanning +static void genesys_start_scan(Genesys_Device* dev, bool lamp_off) +{ +    DBG_HELPER(dbg); +  unsigned int steps, expected; + +  /* since not all scanners are set ot wait for head to park +   * we check we are not still parking before starting a new scan */ +    if (dev->parking) { +        sanei_genesys_wait_for_home(dev); +    } + +    // disable power saving +    dev->cmd_set->save_power(dev, false); + +  /* wait for lamp warmup : until a warmup for TRANSPARENCY is designed, skip +   * it when scanning from XPA. */ +  if (!(dev->model->flags & GENESYS_FLAG_SKIP_WARMUP) +    && (dev->settings.scan_method == ScanMethod::FLATBED)) +    { +        genesys_warmup_lamp(dev); +    } + +  /* set top left x and y values by scanning the internals if flatbed scanners */ +    if (!dev->model->is_sheetfed) { +      /* do the geometry detection only once */ +      if ((dev->model->flags & GENESYS_FLAG_SEARCH_START) +      && (dev->model->y_offset_calib_white == 0)) +	{ +        dev->cmd_set->search_start_position (dev); + +            dev->parking = false; +            dev->cmd_set->move_back_home(dev, true); +	} +      else +	{ +	  /* Go home */ +	  /* TODO: check we can drop this since we cannot have the +	     scanner's head wandering here */ +            dev->parking = false; +            dev->cmd_set->move_back_home(dev, true); +	} +    } + +  /* move to calibration area for transparency adapter */ +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        dev->cmd_set->move_to_ta(dev); +    } + +  /* load document if needed (for sheetfed scanner for instance) */ +    if (dev->model->is_sheetfed) { +        dev->cmd_set->load_document(dev); +    } + +    auto& sensor = sanei_genesys_find_sensor_for_write(dev, dev->settings.xres, +                                                       dev->settings.get_channels(), +                                                       dev->settings.scan_method); + +    // send gamma tables. They have been set to device or user value +    // when setting option value */ +    dev->cmd_set->send_gamma_table(dev, sensor); + +  /* try to use cached calibration first */ +  if (!genesys_restore_calibration (dev, sensor)) +    { +       /* calibration : sheetfed scanners can't calibrate before each scan */ +       /* and also those who have the NO_CALIBRATION flag                  */ +        if (!(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION) && !dev->model->is_sheetfed) { +            genesys_scanner_calibration(dev, sensor); +          genesys_save_calibration (dev, sensor); +	} +      else +	{ +          DBG(DBG_warn, "%s: no calibration done\n", __func__); +	} +    } + +  /* build look up table for dynamic lineart */ +    if (dev->settings.scan_mode == ScanColorMode::LINEART) { +        sanei_genesys_load_lut(dev->lineart_lut, 8, 8, 50, 205, dev->settings.threshold_curve, +                               dev->settings.threshold-127); +    } + +    dev->cmd_set->wait_for_motor_stop(dev); + +    if (dev->cmd_set->needs_home_before_init_regs_for_scan(dev)) { +        dev->cmd_set->move_back_home(dev, true); +    } + +    if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || +        dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) +    { +        dev->cmd_set->move_to_ta(dev); +    } + +    dev->cmd_set->init_regs_for_scan(dev, sensor); + +  /* no lamp during scan */ +    if (lamp_off) { +        sanei_genesys_set_lamp_power(dev, sensor, dev->reg, false); +    } + +  /* GL124 is using SHDAREA, so we have to wait for scan to be set up before +   * sending shading data */ +    if (dev->cmd_set->has_send_shading_data() && +        !(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)) +    { +        genesys_send_shading_coefficient(dev, sensor); +    } + +    // now send registers for scan +    dev->interface->write_registers(dev->reg); + +    // start effective scan +    dev->cmd_set->begin_scan(dev, sensor, &dev->reg, true); + +    if (is_testing_mode()) { +        dev->interface->test_checkpoint("start_scan"); +        return; +    } + +  /*do we really need this? the valid data check should be sufficent -- pierre*/ +  /* waits for head to reach scanning position */ +  expected = dev->reg.get8(0x3d) * 65536 +           + dev->reg.get8(0x3e) * 256 +           + dev->reg.get8(0x3f); +  do +    { +        // wait some time between each test to avoid overloading USB and CPU +        dev->interface->sleep_ms(100); +        sanei_genesys_read_feed_steps (dev, &steps); +    } +  while (steps < expected); + +    wait_until_buffer_non_empty(dev); + +    // we wait for at least one word of valid scan data +    // this is also done in sanei_genesys_read_data_from_scanner -- pierre +    if (!dev->model->is_sheetfed) { +        do { +            dev->interface->sleep_ms(100); +            sanei_genesys_read_valid_words(dev, &steps); +        } +      while (steps < 1); +    } +} + +static void genesys_fill_read_buffer(Genesys_Device* dev) +{ +    DBG_HELPER(dbg); + +  /* for sheetfed scanner, we must check is document is shorter than +   * the requested scan */ +    if (dev->model->is_sheetfed) { +        dev->cmd_set->detect_document_end(dev); +    } + +    std::size_t size = dev->read_buffer.size() - dev->read_buffer.avail(); + +  /* due to sensors and motors, not all data can be directly used. It +   * may have to be read from another intermediate buffer and then processed. +   * There are currently 3 intermediate stages: +   * - handling of odd/even sensors +   * - handling of line interpolation for motors that can't have low +   *   enough dpi +   * - handling of multi-segments sensors +   * +   * This is also the place where full duplex data will be handled. +   */ +    dev->pipeline_buffer.get_data(size, dev->read_buffer.get_write_pos(size)); + +    dev->read_buffer.produce(size); +} + +/* this function does the effective data read in a manner that suits +   the scanner. It does data reordering and resizing if need. +   It also manages EOF and I/O errors, and line distance correction. +    Returns true on success, false on end-of-file. +*/ +static void genesys_read_ordered_data(Genesys_Device* dev, SANE_Byte* destination, size_t* len) +{ +    DBG_HELPER(dbg); +    size_t bytes = 0; +  uint8_t *work_buffer_src; +  Genesys_Buffer *src_buffer; + +    if (!dev->read_active) { +      *len = 0; +        throw SaneException("read is not active"); +    } + +    DBG(DBG_info, "%s: frontend requested %zu bytes\n", __func__, *len); +    DBG(DBG_info, "%s: bytes_to_read=%zu, total_bytes_read=%zu\n", __func__, +        dev->total_bytes_to_read, dev->total_bytes_read); + +  /* is there data left to scan */ +  if (dev->total_bytes_read >= dev->total_bytes_to_read) +    { +      /* issue park command immediatly in case scanner can handle it +       * so we save time */ +        if (!dev->model->is_sheetfed && !(dev->model->flags & GENESYS_FLAG_MUST_WAIT) && +            !dev->parking) +        { +            dev->cmd_set->move_back_home(dev, false); +            dev->parking = true; +        } +        throw SaneException(SANE_STATUS_EOF, "nothing more to scan: EOF"); +    } + +/* convert data */ +/* +  0. fill_read_buffer +-------------- read_buffer ---------------------- +  1a). (opt)uncis                    (assumes color components to be laid out +                                    planar) +  1b). (opt)reverse_RGB              (assumes pixels to be BGR or BBGGRR)) +-------------- lines_buffer ---------------------- +  2a). (opt)line_distance_correction (assumes RGB or RRGGBB) +  2b). (opt)unstagger                (assumes pixels to be depth*channels/8 +                                      bytes long, unshrinked) +------------- shrink_buffer --------------------- +  3. (opt)shrink_lines             (assumes component separation in pixels) +-------------- out_buffer ----------------------- +  4. memcpy to destination (for lineart with bit reversal) +*/ +/*FIXME: for lineart we need sub byte addressing in buffers, or conversion to +  bytes at 0. and back to bits at 4. +Problems with the first approach: +  - its not clear how to check if we need to output an incomplete byte +    because it is the last one. + */ +/*FIXME: add lineart support for gl646. in the meantime add logic to convert +  from gray to lineart at the end? would suffer the above problem, +  total_bytes_to_read and total_bytes_read help in that case. + */ + +    if (is_testing_mode()) { +        if (dev->total_bytes_read + *len > dev->total_bytes_to_read) { +            *len = dev->total_bytes_to_read - dev->total_bytes_read; +        } +        dev->total_bytes_read += *len; +    } else { +        genesys_fill_read_buffer(dev); + +        src_buffer = &(dev->read_buffer); + +        /* move data to destination */ +        bytes = std::min(src_buffer->avail(), *len); + +        work_buffer_src = src_buffer->get_read_pos(); + +        std::memcpy(destination, work_buffer_src, bytes); +        *len = bytes; + +        /* avoid signaling some extra data because we have treated a full block +        * on the last block */ +        if (dev->total_bytes_read + *len > dev->total_bytes_to_read) { +            *len = dev->total_bytes_to_read - dev->total_bytes_read; +        } + +        /* count bytes sent to frontend */ +        dev->total_bytes_read += *len; + +        src_buffer->consume(bytes); +    } + +  /* end scan if all needed data have been read */ +   if(dev->total_bytes_read >= dev->total_bytes_to_read) +    { +        dev->cmd_set->end_scan(dev, &dev->reg, true); +        if (dev->model->is_sheetfed) { +            dev->cmd_set->eject_document (dev); +        } +    } + +    DBG(DBG_proc, "%s: completed, %zu bytes read\n", __func__, bytes); +} + + + +/* ------------------------------------------------------------------------ */ +/*                  Start of higher level functions                         */ +/* ------------------------------------------------------------------------ */ + +static size_t +max_string_size (const SANE_String_Const strings[]) +{ +  size_t size, max_size = 0; +  SANE_Int i; + +  for (i = 0; strings[i]; ++i) +    { +      size = strlen (strings[i]) + 1; +      if (size > max_size) +	max_size = size; +    } +  return max_size; +} + +static std::size_t max_string_size(const std::vector<const char*>& strings) +{ +    std::size_t max_size = 0; +    for (const auto& s : strings) { +        if (!s) { +            continue; +        } +        max_size = std::max(max_size, std::strlen(s)); +    } +    return max_size; +} + +static unsigned pick_resolution(const std::vector<unsigned>& resolutions, unsigned resolution, +                                const char* direction) +{ +    DBG_HELPER(dbg); + +    if (resolutions.empty()) +        throw SaneException("Empty resolution list"); + +    unsigned best_res = resolutions.front(); +    unsigned min_diff = abs_diff(best_res, resolution); + +    for (auto it = std::next(resolutions.begin()); it != resolutions.end(); ++it) { +        unsigned curr_diff = abs_diff(*it, resolution); +        if (curr_diff < min_diff) { +            min_diff = curr_diff; +            best_res = *it; +        } +    } + +    if (best_res != resolution) { +        DBG(DBG_warn, "%s: using resolution %d that is nearest to %d for direction %s\n", +            __func__, best_res, resolution, direction); +    } +    return best_res; +} + +static void calc_parameters(Genesys_Scanner* s) +{ +    DBG_HELPER(dbg); +  double tl_x = 0, tl_y = 0, br_x = 0, br_y = 0; + +    tl_x = SANE_UNFIX(s->pos_top_left_x); +    tl_y = SANE_UNFIX(s->pos_top_left_y); +    br_x = SANE_UNFIX(s->pos_bottom_right_x); +    br_y = SANE_UNFIX(s->pos_bottom_right_y); + +    s->params.last_frame = true;	/* only single pass scanning supported */ + +    if (s->mode == SANE_VALUE_SCAN_MODE_GRAY || s->mode == SANE_VALUE_SCAN_MODE_LINEART) { +        s->params.format = SANE_FRAME_GRAY; +    } else { +        s->params.format = SANE_FRAME_RGB; +    } + +    if (s->mode == SANE_VALUE_SCAN_MODE_LINEART) { +        s->params.depth = 1; +    } else { +        s->params.depth = s->bit_depth; +    } + +    s->dev->settings.scan_method = s->scan_method; +    const auto& resolutions = s->dev->model->get_resolution_settings(s->dev->settings.scan_method); + +    s->dev->settings.depth = s->bit_depth; + +  /* interpolation */ +  s->dev->settings.disable_interpolation = s->disable_interpolation; + +  // FIXME: use correct sensor +  const auto& sensor = sanei_genesys_find_sensor_any(s->dev); + +    // hardware settings +    if (static_cast<unsigned>(s->resolution) > sensor.optical_res && +        s->dev->settings.disable_interpolation) +    { +        s->dev->settings.xres = sensor.optical_res; +    } else { +        s->dev->settings.xres = s->resolution; +    } +    s->dev->settings.yres = s->resolution; + +    s->dev->settings.xres = pick_resolution(resolutions.resolutions_x, s->dev->settings.xres, "X"); +    s->dev->settings.yres = pick_resolution(resolutions.resolutions_y, s->dev->settings.yres, "Y"); + +    s->params.lines = static_cast<unsigned>(((br_y - tl_y) * s->dev->settings.yres) / +                                            MM_PER_INCH); +    unsigned pixels_per_line = static_cast<unsigned>(((br_x - tl_x) * s->dev->settings.xres) / +                                                     MM_PER_INCH); + +  /* we need an even pixels number +   * TODO invert test logic or generalize behaviour across all ASICs */ +    if ((s->dev->model->flags & GENESYS_FLAG_SIS_SENSOR) || +        s->dev->model->asic_type == AsicType::GL847 || +        s->dev->model->asic_type == AsicType::GL124 || +        s->dev->model->asic_type == AsicType::GL845 || +        s->dev->model->asic_type == AsicType::GL846 || +        s->dev->model->asic_type == AsicType::GL843) +    { +        if (s->dev->settings.xres <= 1200) { +            pixels_per_line = (pixels_per_line / 4) * 4; +        } else if (s->dev->settings.xres < s->dev->settings.yres) { +            // BUG: this is an artifact of the fact that the resolution was twice as large than +            // the actual resolution when scanning above the supported scanner X resolution +            pixels_per_line = (pixels_per_line / 8) * 8; +        } else { +            pixels_per_line = (pixels_per_line / 16) * 16; +        } +    } + +  /* corner case for true lineart for sensor with several segments +   * or when xres is doubled to match yres */ +    if (s->dev->settings.xres >= 1200 && ( +                s->dev->model->asic_type == AsicType::GL124 || +                s->dev->model->asic_type == AsicType::GL847 || +                s->dev->session.params.xres < s->dev->session.params.yres)) +    { +        if (s->dev->settings.xres < s->dev->settings.yres) { +            // FIXME: this is an artifact of the fact that the resolution was twice as large than +            // the actual resolution when scanning above the supported scanner X resolution +            pixels_per_line = (pixels_per_line / 8) * 8; +        } else { +            pixels_per_line = (pixels_per_line / 16) * 16; +        } +    } + +    unsigned xres_factor = s->resolution / s->dev->settings.xres; + +    unsigned bytes_per_line = 0; + +  if (s->params.depth > 8) +    { +      s->params.depth = 16; +        bytes_per_line = 2 * pixels_per_line; +    } +  else if (s->params.depth == 1) +    { +        // round down pixel number. This will is lossy operation, at most 7 pixels will be lost +        pixels_per_line = (pixels_per_line / 8) * 8; +        bytes_per_line = pixels_per_line / 8; +    } else { +        bytes_per_line = pixels_per_line; +    } + +    if (s->params.format == SANE_FRAME_RGB) { +        bytes_per_line *= 3; +    } + +    s->dev->settings.scan_mode = option_string_to_scan_color_mode(s->mode); + +  s->dev->settings.lines = s->params.lines; +    s->dev->settings.pixels = pixels_per_line; +    s->dev->settings.requested_pixels = pixels_per_line * xres_factor; +    s->params.pixels_per_line = pixels_per_line * xres_factor; +    s->params.bytes_per_line = bytes_per_line * xres_factor; +  s->dev->settings.tl_x = tl_x; +  s->dev->settings.tl_y = tl_y; + +    // threshold setting +    s->dev->settings.threshold = static_cast<int>(2.55 * (SANE_UNFIX(s->threshold))); + +    // color filter +    if (s->color_filter == "Red") { +        s->dev->settings.color_filter = ColorFilter::RED; +    } else if (s->color_filter == "Green") { +        s->dev->settings.color_filter = ColorFilter::GREEN; +    } else if (s->color_filter == "Blue") { +        s->dev->settings.color_filter = ColorFilter::BLUE; +    } else { +        s->dev->settings.color_filter = ColorFilter::NONE; +    } + +    // true gray +    if (s->color_filter == "None") { +        s->dev->settings.true_gray = 1; +    } else { +        s->dev->settings.true_gray = 0; +    } + +    // threshold curve for dynamic rasterization +    s->dev->settings.threshold_curve = s->threshold_curve; + +  /* some digital processing requires the whole picture to be buffered */ +  /* no digital processing takes place when doing preview, or when bit depth is +   * higher than 8 bits */ +  if ((s->swdespeck || s->swcrop || s->swdeskew || s->swderotate ||(SANE_UNFIX(s->swskip)>0)) +    && (!s->preview) +    && (s->bit_depth <= 8)) +    { +        s->dev->buffer_image = true; +    } +  else +    { +        s->dev->buffer_image = false; +    } + +  /* brigthness and contrast only for for 8 bit scans */ +  if(s->bit_depth <= 8) +    { +      s->dev->settings.contrast = (s->contrast * 127) / 100; +      s->dev->settings.brightness = (s->brightness * 127) / 100; +    } +  else +    { +      s->dev->settings.contrast=0; +      s->dev->settings.brightness=0; +    } + +  /* cache expiration time */ +   s->dev->settings.expiration_time = s->expiration_time; +} + + +static void create_bpp_list (Genesys_Scanner * s, const std::vector<unsigned>& bpp) +{ +    s->bpp_list[0] = bpp.size(); +    std::reverse_copy(bpp.begin(), bpp.end(), s->bpp_list + 1); +} + +/** @brief this function initialize a gamma vector based on the ASIC: + * Set up a default gamma table vector based on device description + * gl646: 12 or 14 bits gamma table depending on GENESYS_FLAG_14BIT_GAMMA + * gl84x: 16 bits + * gl12x: 16 bits + * @param scanner pointer to scanner session to get options + * @param option option number of the gamma table to set + */ +static void +init_gamma_vector_option (Genesys_Scanner * scanner, int option) +{ +  /* the option is inactive until the custom gamma control +   * is enabled */ +  scanner->opt[option].type = SANE_TYPE_INT; +  scanner->opt[option].cap |= SANE_CAP_INACTIVE | SANE_CAP_ADVANCED; +  scanner->opt[option].unit = SANE_UNIT_NONE; +  scanner->opt[option].constraint_type = SANE_CONSTRAINT_RANGE; +    if (scanner->dev->model->asic_type == AsicType::GL646) { +      if ((scanner->dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) != 0) +	{ +	  scanner->opt[option].size = 16384 * sizeof (SANE_Word); +	  scanner->opt[option].constraint.range = &u14_range; +	} +      else +	{			/* 12 bits gamma tables */ +	  scanner->opt[option].size = 4096 * sizeof (SANE_Word); +	  scanner->opt[option].constraint.range = &u12_range; +	} +    } +  else +    {				/* other asics have 16 bits words gamma table */ +      scanner->opt[option].size = 256 * sizeof (SANE_Word); +      scanner->opt[option].constraint.range = &u16_range; +    } +} + +/** + * allocate a geometry range + * @param size maximum size of the range + * @return a pointer to a valid range or nullptr + */ +static SANE_Range create_range(float size) +{ +    SANE_Range range; +    range.min = SANE_FIX(0.0); +    range.max = SANE_FIX(size); +    range.quant = SANE_FIX(0.0); +    return range; +} + +/** @brief generate calibration cache file nam + * Generates the calibration cache file name to use. + * Tries to store the chache in $HOME/.sane or + * then fallbacks to $TMPDIR or TMP. The filename + * uses the model name if only one scanner is plugged + * else is uses the device name when several identical + * scanners are in use. + * @param currdev current scanner device + * @return an allocated string containing a file name + */ +static std::string calibration_filename(Genesys_Device *currdev) +{ +    std::string ret; +    ret.resize(PATH_MAX); + +  char filename[80]; +  unsigned int count; +  unsigned int i; + +  /* first compute the DIR where we can store cache: +   * 1 - home dir +   * 2 - $TMPDIR +   * 3 - $TMP +   * 4 - tmp dir +   * 5 - temp dir +   * 6 - then resort to current dir +   */ +    char* ptr = std::getenv("HOME"); +    if (ptr == nullptr) { +        ptr = std::getenv("USERPROFILE"); +    } +    if (ptr == nullptr) { +        ptr = std::getenv("TMPDIR"); +    } +    if (ptr == nullptr) { +        ptr = std::getenv("TMP"); +    } + +  /* now choose filename: +   * 1 - if only one scanner, name of the model +   * 2 - if several scanners of the same model, use device name, +   *     replacing special chars +   */ +  count=0; +  /* count models of the same names if several scanners attached */ +    if(s_devices->size() > 1) { +        for (const auto& dev : *s_devices) { +            if (dev.model->model_id == currdev->model->model_id) { +                count++; +            } +        } +    } +  if(count>1) +    { +        std::snprintf(filename, sizeof(filename), "%s.cal", currdev->file_name.c_str()); +      for(i=0;i<strlen(filename);i++) +        { +          if(filename[i]==':'||filename[i]==PATH_SEP) +            { +              filename[i]='_'; +            } +        } +    } +  else +    { +      snprintf(filename,sizeof(filename),"%s.cal",currdev->model->name); +    } + +  /* build final final name : store dir + filename */ +    if (ptr == nullptr) { +        int size = std::snprintf(&ret.front(), ret.size(), "%s", filename); +        ret.resize(size); +    } +  else +    { +        int size = 0; +#ifdef HAVE_MKDIR +        /* make sure .sane directory exists in existing store dir */ +        size = std::snprintf(&ret.front(), ret.size(), "%s%c.sane", ptr, PATH_SEP); +        ret.resize(size); +        mkdir(ret.c_str(), 0700); + +        ret.resize(PATH_MAX); +#endif +        size = std::snprintf(&ret.front(), ret.size(), "%s%c.sane%c%s", +                             ptr, PATH_SEP, PATH_SEP, filename); +        ret.resize(size); +    } + +    DBG(DBG_info, "%s: calibration filename >%s<\n", __func__, ret.c_str()); + +    return ret; +} + +static void set_resolution_option_values(Genesys_Scanner& s, bool reset_resolution_value) +{ +    auto resolutions = s.dev->model->get_resolutions(s.scan_method); + +    s.opt_resolution_values.resize(resolutions.size() + 1, 0); +    s.opt_resolution_values[0] = resolutions.size(); +    std::copy(resolutions.begin(), resolutions.end(), s.opt_resolution_values.begin() + 1); + +    s.opt[OPT_RESOLUTION].constraint.word_list = s.opt_resolution_values.data(); + +    if (reset_resolution_value) { +        s.resolution = *std::min_element(resolutions.begin(), resolutions.end()); +    } +} + +static void set_xy_range_option_values(Genesys_Scanner& s) +{ +    if (s.scan_method == ScanMethod::FLATBED) +    { +        s.opt_x_range = create_range(static_cast<float>(s.dev->model->x_size)); +        s.opt_y_range = create_range(static_cast<float>(s.dev->model->y_size)); +    } +  else +    { +        s.opt_x_range = create_range(static_cast<float>(s.dev->model->x_size_ta)); +        s.opt_y_range = create_range(static_cast<float>(s.dev->model->y_size_ta)); +    } + +    s.opt[OPT_TL_X].constraint.range = &s.opt_x_range; +    s.opt[OPT_TL_Y].constraint.range = &s.opt_y_range; +    s.opt[OPT_BR_X].constraint.range = &s.opt_x_range; +    s.opt[OPT_BR_Y].constraint.range = &s.opt_y_range; + +    s.pos_top_left_x = 0; +    s.pos_top_left_y = 0; +    s.pos_bottom_right_x = s.opt_x_range.max; +    s.pos_bottom_right_y = s.opt_y_range.max; +} + +static void init_options(Genesys_Scanner* s) +{ +    DBG_HELPER(dbg); +  SANE_Int option; +  Genesys_Model *model = s->dev->model; + +  memset (s->opt, 0, sizeof (s->opt)); + +  for (option = 0; option < NUM_OPTIONS; ++option) +    { +      s->opt[option].size = sizeof (SANE_Word); +      s->opt[option].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; +    } +  s->opt[OPT_NUM_OPTS].name = SANE_NAME_NUM_OPTIONS; +  s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS; +  s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS; +  s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT; +  s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT; + +  /* "Mode" group: */ +  s->opt[OPT_MODE_GROUP].name = "scanmode-group"; +  s->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode"); +  s->opt[OPT_MODE_GROUP].desc = ""; +  s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_MODE_GROUP].size = 0; +  s->opt[OPT_MODE_GROUP].cap = 0; +  s->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +  /* scan mode */ +  s->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE; +  s->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE; +  s->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE; +  s->opt[OPT_MODE].type = SANE_TYPE_STRING; +  s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST; +  s->opt[OPT_MODE].size = max_string_size (mode_list); +  s->opt[OPT_MODE].constraint.string_list = mode_list; +  s->mode = SANE_VALUE_SCAN_MODE_GRAY; + +  /* scan source */ +    s->opt_source_values.clear(); +    for (const auto& resolution_setting : model->resolutions) { +        for (auto method : resolution_setting.methods) { +            s->opt_source_values.push_back(scan_method_to_option_string(method)); +        } +    } +    s->opt_source_values.push_back(nullptr); + +  s->opt[OPT_SOURCE].name = SANE_NAME_SCAN_SOURCE; +  s->opt[OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE; +  s->opt[OPT_SOURCE].desc = SANE_DESC_SCAN_SOURCE; +  s->opt[OPT_SOURCE].type = SANE_TYPE_STRING; +  s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST; +    s->opt[OPT_SOURCE].size = max_string_size(s->opt_source_values); +    s->opt[OPT_SOURCE].constraint.string_list = s->opt_source_values.data(); +    if (s->opt_source_values.size() < 2) { +        throw SaneException("No scan methods specified for scanner"); +    } +    s->scan_method = model->default_method; + +  /* preview */ +  s->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW; +  s->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW; +  s->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW; +  s->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL; +  s->opt[OPT_PREVIEW].unit = SANE_UNIT_NONE; +  s->opt[OPT_PREVIEW].constraint_type = SANE_CONSTRAINT_NONE; +  s->preview = false; + +  /* bit depth */ +  s->opt[OPT_BIT_DEPTH].name = SANE_NAME_BIT_DEPTH; +  s->opt[OPT_BIT_DEPTH].title = SANE_TITLE_BIT_DEPTH; +  s->opt[OPT_BIT_DEPTH].desc = SANE_DESC_BIT_DEPTH; +  s->opt[OPT_BIT_DEPTH].type = SANE_TYPE_INT; +  s->opt[OPT_BIT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST; +  s->opt[OPT_BIT_DEPTH].size = sizeof (SANE_Word); +  s->opt[OPT_BIT_DEPTH].constraint.word_list = s->bpp_list; +  create_bpp_list (s, model->bpp_gray_values); +    s->bit_depth = model->bpp_gray_values[0]; + +    // resolution +  s->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION; +  s->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION; +  s->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION; +  s->opt[OPT_RESOLUTION].type = SANE_TYPE_INT; +  s->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI; +  s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST; +    set_resolution_option_values(*s, true); + +  /* "Geometry" group: */ +  s->opt[OPT_GEOMETRY_GROUP].name = SANE_NAME_GEOMETRY; +  s->opt[OPT_GEOMETRY_GROUP].title = SANE_I18N ("Geometry"); +  s->opt[OPT_GEOMETRY_GROUP].desc = ""; +  s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED; +  s->opt[OPT_GEOMETRY_GROUP].size = 0; +  s->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +    s->opt_x_range = create_range(static_cast<float>(model->x_size)); +    s->opt_y_range = create_range(static_cast<float>(model->y_size)); + +    // scan area +  s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X; +  s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X; +  s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X; +  s->opt[OPT_TL_X].type = SANE_TYPE_FIXED; +  s->opt[OPT_TL_X].unit = SANE_UNIT_MM; +  s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE; + +  s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y; +  s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y; +  s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y; +  s->opt[OPT_TL_Y].type = SANE_TYPE_FIXED; +  s->opt[OPT_TL_Y].unit = SANE_UNIT_MM; +  s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE; + +  s->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X; +  s->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X; +  s->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X; +  s->opt[OPT_BR_X].type = SANE_TYPE_FIXED; +  s->opt[OPT_BR_X].unit = SANE_UNIT_MM; +  s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE; + +  s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y; +  s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y; +  s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y; +  s->opt[OPT_BR_Y].type = SANE_TYPE_FIXED; +  s->opt[OPT_BR_Y].unit = SANE_UNIT_MM; +  s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE; + +    set_xy_range_option_values(*s); + +  /* "Enhancement" group: */ +  s->opt[OPT_ENHANCEMENT_GROUP].name = SANE_NAME_ENHANCEMENT; +  s->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N ("Enhancement"); +  s->opt[OPT_ENHANCEMENT_GROUP].desc = ""; +  s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_ENHANCEMENT_GROUP].cap = SANE_CAP_ADVANCED; +  s->opt[OPT_ENHANCEMENT_GROUP].size = 0; +  s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +  /* custom-gamma table */ +  s->opt[OPT_CUSTOM_GAMMA].name = SANE_NAME_CUSTOM_GAMMA; +  s->opt[OPT_CUSTOM_GAMMA].title = SANE_TITLE_CUSTOM_GAMMA; +  s->opt[OPT_CUSTOM_GAMMA].desc = SANE_DESC_CUSTOM_GAMMA; +  s->opt[OPT_CUSTOM_GAMMA].type = SANE_TYPE_BOOL; +  s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_ADVANCED; +  s->custom_gamma = false; + +  /* grayscale gamma vector */ +  s->opt[OPT_GAMMA_VECTOR].name = SANE_NAME_GAMMA_VECTOR; +  s->opt[OPT_GAMMA_VECTOR].title = SANE_TITLE_GAMMA_VECTOR; +  s->opt[OPT_GAMMA_VECTOR].desc = SANE_DESC_GAMMA_VECTOR; +  init_gamma_vector_option (s, OPT_GAMMA_VECTOR); + +  /* red gamma vector */ +  s->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R; +  s->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R; +  s->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R; +  init_gamma_vector_option (s, OPT_GAMMA_VECTOR_R); + +  /* green gamma vector */ +  s->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G; +  s->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G; +  s->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G; +  init_gamma_vector_option (s, OPT_GAMMA_VECTOR_G); + +  /* blue gamma vector */ +  s->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B; +  s->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B; +  s->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B; +  init_gamma_vector_option (s, OPT_GAMMA_VECTOR_B); + +  /* currently, there are only gamma table options in this group, +   * so if the scanner doesn't support gamma table, disable the +   * whole group */ +  if (!(model->flags & GENESYS_FLAG_CUSTOM_GAMMA)) +    { +      s->opt[OPT_ENHANCEMENT_GROUP].cap |= SANE_CAP_INACTIVE; +      s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE; +      DBG(DBG_info, "%s: custom gamma disabled\n", __func__); +    } + +  /* software base image enhancements, these are consuming as many +   * memory than used by the full scanned image and may fail at high +   * resolution +   */ +  /* software deskew */ +  s->opt[OPT_SWDESKEW].name = "swdeskew"; +  s->opt[OPT_SWDESKEW].title = "Software deskew"; +  s->opt[OPT_SWDESKEW].desc = "Request backend to rotate skewed pages digitally"; +  s->opt[OPT_SWDESKEW].type = SANE_TYPE_BOOL; +  s->opt[OPT_SWDESKEW].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; +  s->swdeskew = false; + +  /* software deskew */ +  s->opt[OPT_SWDESPECK].name = "swdespeck"; +  s->opt[OPT_SWDESPECK].title = "Software despeck"; +  s->opt[OPT_SWDESPECK].desc = "Request backend to remove lone dots digitally"; +  s->opt[OPT_SWDESPECK].type = SANE_TYPE_BOOL; +  s->opt[OPT_SWDESPECK].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; +  s->swdespeck = false; + +  /* software despeckle radius */ +  s->opt[OPT_DESPECK].name = "despeck"; +  s->opt[OPT_DESPECK].title = "Software despeckle diameter"; +  s->opt[OPT_DESPECK].desc = "Maximum diameter of lone dots to remove from scan"; +  s->opt[OPT_DESPECK].type = SANE_TYPE_INT; +  s->opt[OPT_DESPECK].unit = SANE_UNIT_NONE; +  s->opt[OPT_DESPECK].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_DESPECK].constraint.range = &swdespeck_range; +  s->opt[OPT_DESPECK].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED | SANE_CAP_INACTIVE; +  s->despeck = 1; + +  /* crop by software */ +  s->opt[OPT_SWCROP].name = "swcrop"; +  s->opt[OPT_SWCROP].title = SANE_I18N ("Software crop"); +  s->opt[OPT_SWCROP].desc = SANE_I18N ("Request backend to remove border from pages digitally"); +  s->opt[OPT_SWCROP].type = SANE_TYPE_BOOL; +  s->opt[OPT_SWCROP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; +  s->opt[OPT_SWCROP].unit = SANE_UNIT_NONE; +  s->swcrop = false; + +  /* Software blank page skip */ +  s->opt[OPT_SWSKIP].name = "swskip"; +  s->opt[OPT_SWSKIP].title = SANE_I18N ("Software blank skip percentage"); +  s->opt[OPT_SWSKIP].desc = SANE_I18N("Request driver to discard pages with low numbers of dark pixels"); +  s->opt[OPT_SWSKIP].type = SANE_TYPE_FIXED; +  s->opt[OPT_SWSKIP].unit = SANE_UNIT_PERCENT; +  s->opt[OPT_SWSKIP].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_SWSKIP].constraint.range = &(percentage_range); +  s->opt[OPT_SWSKIP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; +  s->swskip = 0;    // disable by default + +  /* Software Derotate */ +  s->opt[OPT_SWDEROTATE].name = "swderotate"; +  s->opt[OPT_SWDEROTATE].title = SANE_I18N ("Software derotate"); +  s->opt[OPT_SWDEROTATE].desc = SANE_I18N("Request driver to detect and correct 90 degree image rotation"); +  s->opt[OPT_SWDEROTATE].type = SANE_TYPE_BOOL; +  s->opt[OPT_SWDEROTATE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED; +  s->opt[OPT_SWDEROTATE].unit = SANE_UNIT_NONE; +  s->swderotate = false; + +  /* Software brightness */ +  s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS; +  s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS; +  s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS; +  s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT; +  s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE; +  s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_BRIGHTNESS].constraint.range = &(enhance_range); +  s->opt[OPT_BRIGHTNESS].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; +  s->brightness = 0;    // disable by default + +  /* Sowftware contrast */ +  s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST; +  s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST; +  s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST; +  s->opt[OPT_CONTRAST].type = SANE_TYPE_INT; +  s->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE; +  s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_CONTRAST].constraint.range = &(enhance_range); +  s->opt[OPT_CONTRAST].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; +  s->contrast = 0;  // disable by default + +  /* "Extras" group: */ +  s->opt[OPT_EXTRAS_GROUP].name = "extras-group"; +  s->opt[OPT_EXTRAS_GROUP].title = SANE_I18N ("Extras"); +  s->opt[OPT_EXTRAS_GROUP].desc = ""; +  s->opt[OPT_EXTRAS_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_EXTRAS_GROUP].cap = SANE_CAP_ADVANCED; +  s->opt[OPT_EXTRAS_GROUP].size = 0; +  s->opt[OPT_EXTRAS_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +  /* BW threshold */ +  s->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD; +  s->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD; +  s->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD; +  s->opt[OPT_THRESHOLD].type = SANE_TYPE_FIXED; +  s->opt[OPT_THRESHOLD].unit = SANE_UNIT_PERCENT; +  s->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_THRESHOLD].constraint.range = &percentage_range; +  s->threshold = SANE_FIX(50); + +  /* BW threshold curve */ +  s->opt[OPT_THRESHOLD_CURVE].name = "threshold-curve"; +  s->opt[OPT_THRESHOLD_CURVE].title = SANE_I18N ("Threshold curve"); +  s->opt[OPT_THRESHOLD_CURVE].desc = SANE_I18N ("Dynamic threshold curve, from light to dark, normally 50-65"); +  s->opt[OPT_THRESHOLD_CURVE].type = SANE_TYPE_INT; +  s->opt[OPT_THRESHOLD_CURVE].unit = SANE_UNIT_NONE; +  s->opt[OPT_THRESHOLD_CURVE].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_THRESHOLD_CURVE].constraint.range = &threshold_curve_range; +  s->threshold_curve = 50; + +  /* disable_interpolation */ +  s->opt[OPT_DISABLE_INTERPOLATION].name = "disable-interpolation"; +  s->opt[OPT_DISABLE_INTERPOLATION].title = +    SANE_I18N ("Disable interpolation"); +  s->opt[OPT_DISABLE_INTERPOLATION].desc = +    SANE_I18N +    ("When using high resolutions where the horizontal resolution is smaller " +     "than the vertical resolution this disables horizontal interpolation."); +  s->opt[OPT_DISABLE_INTERPOLATION].type = SANE_TYPE_BOOL; +  s->opt[OPT_DISABLE_INTERPOLATION].unit = SANE_UNIT_NONE; +  s->opt[OPT_DISABLE_INTERPOLATION].constraint_type = SANE_CONSTRAINT_NONE; +  s->disable_interpolation = false; + +  /* color filter */ +  s->opt[OPT_COLOR_FILTER].name = "color-filter"; +  s->opt[OPT_COLOR_FILTER].title = SANE_I18N ("Color filter"); +  s->opt[OPT_COLOR_FILTER].desc = +    SANE_I18N +    ("When using gray or lineart this option selects the used color."); +  s->opt[OPT_COLOR_FILTER].type = SANE_TYPE_STRING; +  s->opt[OPT_COLOR_FILTER].constraint_type = SANE_CONSTRAINT_STRING_LIST; +  /* true gray not yet supported for GL847 and GL124 scanners */ +    if (!model->is_cis || model->asic_type==AsicType::GL847 || model->asic_type==AsicType::GL124) { +      s->opt[OPT_COLOR_FILTER].size = max_string_size (color_filter_list); +      s->opt[OPT_COLOR_FILTER].constraint.string_list = color_filter_list; +      s->color_filter = s->opt[OPT_COLOR_FILTER].constraint.string_list[1]; +    } +  else +    { +      s->opt[OPT_COLOR_FILTER].size = max_string_size (cis_color_filter_list); +      s->opt[OPT_COLOR_FILTER].constraint.string_list = cis_color_filter_list; +      /* default to "None" ie true gray */ +      s->color_filter = s->opt[OPT_COLOR_FILTER].constraint.string_list[3]; +    } + +    // no support for color filter for cis+gl646 scanners +    if (model->asic_type == AsicType::GL646 && model->is_cis) { +      DISABLE (OPT_COLOR_FILTER); +    } + +  /* calibration store file name */ +  s->opt[OPT_CALIBRATION_FILE].name = "calibration-file"; +  s->opt[OPT_CALIBRATION_FILE].title = SANE_I18N ("Calibration file"); +  s->opt[OPT_CALIBRATION_FILE].desc = SANE_I18N ("Specify the calibration file to use"); +  s->opt[OPT_CALIBRATION_FILE].type = SANE_TYPE_STRING; +  s->opt[OPT_CALIBRATION_FILE].unit = SANE_UNIT_NONE; +  s->opt[OPT_CALIBRATION_FILE].size = PATH_MAX; +  s->opt[OPT_CALIBRATION_FILE].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED; +  s->opt[OPT_CALIBRATION_FILE].constraint_type = SANE_CONSTRAINT_NONE; +  s->calibration_file.clear(); +  /* disable option if ran as root */ +#ifdef HAVE_GETUID +  if(geteuid()==0) +    { +      DISABLE (OPT_CALIBRATION_FILE); +    } +#endif + +  /* expiration time for calibration cache entries */ +  s->opt[OPT_EXPIRATION_TIME].name = "expiration-time"; +  s->opt[OPT_EXPIRATION_TIME].title = SANE_I18N ("Calibration cache expiration time"); +  s->opt[OPT_EXPIRATION_TIME].desc = SANE_I18N ("Time (in minutes) before a cached calibration expires. " +     "A value of 0 means cache is not used. A negative value means cache never expires."); +  s->opt[OPT_EXPIRATION_TIME].type = SANE_TYPE_INT; +  s->opt[OPT_EXPIRATION_TIME].unit = SANE_UNIT_NONE; +  s->opt[OPT_EXPIRATION_TIME].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_EXPIRATION_TIME].constraint.range = &expiration_range; +  s->expiration_time = 60;  // 60 minutes by default + +  /* Powersave time (turn lamp off) */ +  s->opt[OPT_LAMP_OFF_TIME].name = "lamp-off-time"; +  s->opt[OPT_LAMP_OFF_TIME].title = SANE_I18N ("Lamp off time"); +  s->opt[OPT_LAMP_OFF_TIME].desc = +    SANE_I18N +    ("The lamp will be turned off after the given time (in minutes). " +     "A value of 0 means, that the lamp won't be turned off."); +  s->opt[OPT_LAMP_OFF_TIME].type = SANE_TYPE_INT; +  s->opt[OPT_LAMP_OFF_TIME].unit = SANE_UNIT_NONE; +  s->opt[OPT_LAMP_OFF_TIME].constraint_type = SANE_CONSTRAINT_RANGE; +  s->opt[OPT_LAMP_OFF_TIME].constraint.range = &time_range; +  s->lamp_off_time = 15;    // 15 minutes + +  /* turn lamp off during scan */ +  s->opt[OPT_LAMP_OFF].name = "lamp-off-scan"; +  s->opt[OPT_LAMP_OFF].title = SANE_I18N ("Lamp off during scan"); +  s->opt[OPT_LAMP_OFF].desc = SANE_I18N ("The lamp will be turned off during scan. "); +  s->opt[OPT_LAMP_OFF].type = SANE_TYPE_BOOL; +  s->opt[OPT_LAMP_OFF].unit = SANE_UNIT_NONE; +  s->opt[OPT_LAMP_OFF].constraint_type = SANE_CONSTRAINT_NONE; +  s->lamp_off = false; + +  s->opt[OPT_SENSOR_GROUP].name = SANE_NAME_SENSORS; +  s->opt[OPT_SENSOR_GROUP].title = SANE_TITLE_SENSORS; +  s->opt[OPT_SENSOR_GROUP].desc = SANE_DESC_SENSORS; +  s->opt[OPT_SENSOR_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_SENSOR_GROUP].cap = SANE_CAP_ADVANCED; +  s->opt[OPT_SENSOR_GROUP].size = 0; +  s->opt[OPT_SENSOR_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +  s->opt[OPT_SCAN_SW].name = SANE_NAME_SCAN; +  s->opt[OPT_SCAN_SW].title = SANE_TITLE_SCAN; +  s->opt[OPT_SCAN_SW].desc = SANE_DESC_SCAN; +  s->opt[OPT_SCAN_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_SCAN_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_SCAN_SW) +    s->opt[OPT_SCAN_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_SCAN_SW].cap = SANE_CAP_INACTIVE; + +  /* SANE_NAME_FILE is not for buttons */ +  s->opt[OPT_FILE_SW].name = "file"; +  s->opt[OPT_FILE_SW].title = SANE_I18N ("File button"); +  s->opt[OPT_FILE_SW].desc = SANE_I18N ("File button"); +  s->opt[OPT_FILE_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_FILE_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_FILE_SW) +    s->opt[OPT_FILE_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_FILE_SW].cap = SANE_CAP_INACTIVE; + +  s->opt[OPT_EMAIL_SW].name = SANE_NAME_EMAIL; +  s->opt[OPT_EMAIL_SW].title = SANE_TITLE_EMAIL; +  s->opt[OPT_EMAIL_SW].desc = SANE_DESC_EMAIL; +  s->opt[OPT_EMAIL_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_EMAIL_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_EMAIL_SW) +    s->opt[OPT_EMAIL_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_EMAIL_SW].cap = SANE_CAP_INACTIVE; + +  s->opt[OPT_COPY_SW].name = SANE_NAME_COPY; +  s->opt[OPT_COPY_SW].title = SANE_TITLE_COPY; +  s->opt[OPT_COPY_SW].desc = SANE_DESC_COPY; +  s->opt[OPT_COPY_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_COPY_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_COPY_SW) +    s->opt[OPT_COPY_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_COPY_SW].cap = SANE_CAP_INACTIVE; + +  s->opt[OPT_PAGE_LOADED_SW].name = SANE_NAME_PAGE_LOADED; +  s->opt[OPT_PAGE_LOADED_SW].title = SANE_TITLE_PAGE_LOADED; +  s->opt[OPT_PAGE_LOADED_SW].desc = SANE_DESC_PAGE_LOADED; +  s->opt[OPT_PAGE_LOADED_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_PAGE_LOADED_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_PAGE_LOADED_SW) +    s->opt[OPT_PAGE_LOADED_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_PAGE_LOADED_SW].cap = SANE_CAP_INACTIVE; + +  /* OCR button */ +  s->opt[OPT_OCR_SW].name = "ocr"; +  s->opt[OPT_OCR_SW].title = SANE_I18N ("OCR button"); +  s->opt[OPT_OCR_SW].desc = SANE_I18N ("OCR button"); +  s->opt[OPT_OCR_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_OCR_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_OCR_SW) +    s->opt[OPT_OCR_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_OCR_SW].cap = SANE_CAP_INACTIVE; + +  /* power button */ +  s->opt[OPT_POWER_SW].name = "power"; +  s->opt[OPT_POWER_SW].title = SANE_I18N ("Power button"); +  s->opt[OPT_POWER_SW].desc = SANE_I18N ("Power button"); +  s->opt[OPT_POWER_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_POWER_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_POWER_SW) +    s->opt[OPT_POWER_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_POWER_SW].cap = SANE_CAP_INACTIVE; + +  /* extra button */ +  s->opt[OPT_EXTRA_SW].name = "extra"; +  s->opt[OPT_EXTRA_SW].title = SANE_I18N ("Extra button"); +  s->opt[OPT_EXTRA_SW].desc = SANE_I18N ("Extra button"); +  s->opt[OPT_EXTRA_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_EXTRA_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_EXTRA_SW) +    s->opt[OPT_EXTRA_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_EXTRA_SW].cap = SANE_CAP_INACTIVE; + +  /* calibration needed */ +  s->opt[OPT_NEED_CALIBRATION_SW].name = "need-calibration"; +  s->opt[OPT_NEED_CALIBRATION_SW].title = SANE_I18N ("Needs calibration"); +  s->opt[OPT_NEED_CALIBRATION_SW].desc = SANE_I18N ("The scanner needs calibration for the current settings"); +  s->opt[OPT_NEED_CALIBRATION_SW].type = SANE_TYPE_BOOL; +  s->opt[OPT_NEED_CALIBRATION_SW].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_CALIBRATE) +    s->opt[OPT_NEED_CALIBRATION_SW].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED; +  else +    s->opt[OPT_NEED_CALIBRATION_SW].cap = SANE_CAP_INACTIVE; + +  /* button group */ +  s->opt[OPT_BUTTON_GROUP].name = "buttons"; +  s->opt[OPT_BUTTON_GROUP].title = SANE_I18N ("Buttons"); +  s->opt[OPT_BUTTON_GROUP].desc = ""; +  s->opt[OPT_BUTTON_GROUP].type = SANE_TYPE_GROUP; +  s->opt[OPT_BUTTON_GROUP].cap = SANE_CAP_ADVANCED; +  s->opt[OPT_BUTTON_GROUP].size = 0; +  s->opt[OPT_BUTTON_GROUP].constraint_type = SANE_CONSTRAINT_NONE; + +  /* calibrate button */ +  s->opt[OPT_CALIBRATE].name = "calibrate"; +  s->opt[OPT_CALIBRATE].title = SANE_I18N ("Calibrate"); +  s->opt[OPT_CALIBRATE].desc = +    SANE_I18N ("Start calibration using special sheet"); +  s->opt[OPT_CALIBRATE].type = SANE_TYPE_BUTTON; +  s->opt[OPT_CALIBRATE].unit = SANE_UNIT_NONE; +  if (model->buttons & GENESYS_HAS_CALIBRATE) +    s->opt[OPT_CALIBRATE].cap = +      SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED | +      SANE_CAP_AUTOMATIC; +  else +    s->opt[OPT_CALIBRATE].cap = SANE_CAP_INACTIVE; + +  /* clear calibration cache button */ +  s->opt[OPT_CLEAR_CALIBRATION].name = "clear-calibration"; +  s->opt[OPT_CLEAR_CALIBRATION].title = SANE_I18N ("Clear calibration"); +  s->opt[OPT_CLEAR_CALIBRATION].desc = SANE_I18N ("Clear calibration cache"); +  s->opt[OPT_CLEAR_CALIBRATION].type = SANE_TYPE_BUTTON; +  s->opt[OPT_CLEAR_CALIBRATION].unit = SANE_UNIT_NONE; +  s->opt[OPT_CLEAR_CALIBRATION].size = 0; +  s->opt[OPT_CLEAR_CALIBRATION].constraint_type = SANE_CONSTRAINT_NONE; +  s->opt[OPT_CLEAR_CALIBRATION].cap = +    SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED; + +  /* force calibration cache button */ +  s->opt[OPT_FORCE_CALIBRATION].name = "force-calibration"; +  s->opt[OPT_FORCE_CALIBRATION].title = SANE_I18N("Force calibration"); +  s->opt[OPT_FORCE_CALIBRATION].desc = SANE_I18N("Force calibration ignoring all and any calibration caches"); +  s->opt[OPT_FORCE_CALIBRATION].type = SANE_TYPE_BUTTON; +  s->opt[OPT_FORCE_CALIBRATION].unit = SANE_UNIT_NONE; +  s->opt[OPT_FORCE_CALIBRATION].size = 0; +  s->opt[OPT_FORCE_CALIBRATION].constraint_type = SANE_CONSTRAINT_NONE; +  s->opt[OPT_FORCE_CALIBRATION].cap = +    SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED; + +    // ignore offsets option +    s->opt[OPT_IGNORE_OFFSETS].name = "ignore-internal-offsets"; +    s->opt[OPT_IGNORE_OFFSETS].title = SANE_I18N("Ignore internal offsets"); +    s->opt[OPT_IGNORE_OFFSETS].desc = +        SANE_I18N("Acquires the image including the internal calibration areas of the scanner"); +    s->opt[OPT_IGNORE_OFFSETS].type = SANE_TYPE_BUTTON; +    s->opt[OPT_IGNORE_OFFSETS].unit = SANE_UNIT_NONE; +    s->opt[OPT_IGNORE_OFFSETS].size = 0; +    s->opt[OPT_IGNORE_OFFSETS].constraint_type = SANE_CONSTRAINT_NONE; +    s->opt[OPT_IGNORE_OFFSETS].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | +                                     SANE_CAP_ADVANCED; + +    calc_parameters(s); +} + +static bool present; + +// this function is passed to C API, it must not throw +static SANE_Status +check_present (SANE_String_Const devname) noexcept +{ +    DBG_HELPER_ARGS(dbg, "%s detected.", devname); +    present = true; +  return SANE_STATUS_GOOD; +} + +static Genesys_Device* attach_usb_device(const char* devname, +                                         std::uint16_t vendor_id, std::uint16_t product_id) +{ +    Genesys_USB_Device_Entry* found_usb_dev = nullptr; +    for (auto& usb_dev : *s_usb_devices) { +        if (usb_dev.vendor == vendor_id && +            usb_dev.product == product_id) +        { +            found_usb_dev = &usb_dev; +            break; +        } +    } + +    if (found_usb_dev == nullptr) { +        throw SaneException("vendor 0x%xd product 0x%xd is not supported by this backend", +                            vendor_id, product_id); +    } + +    s_devices->emplace_back(); +    Genesys_Device* dev = &s_devices->back(); +    dev->file_name = devname; + +    dev->model = &found_usb_dev->model; +    dev->vendorId = found_usb_dev->vendor; +    dev->productId = found_usb_dev->product; +    dev->usb_mode = 0; // i.e. unset +    dev->already_initialized = false; +    return dev; +} + +static Genesys_Device* attach_device_by_name(SANE_String_Const devname, bool may_wait) +{ +    DBG_HELPER_ARGS(dbg, " devname: %s, may_wait = %d", devname, may_wait); + +    if (!devname) { +        throw SaneException("devname must not be nullptr"); +    } + +    for (auto& dev : *s_devices) { +        if (dev.file_name == devname) { +            DBG(DBG_info, "%s: device `%s' was already in device list\n", __func__, devname); +            return &dev; +        } +    } + +  DBG(DBG_info, "%s: trying to open device `%s'\n", __func__, devname); + +    UsbDevice usb_dev; + +    usb_dev.open(devname); +    DBG(DBG_info, "%s: device `%s' successfully opened\n", __func__, devname); + +    int vendor, product; +    usb_dev.get_vendor_product(vendor, product); +    usb_dev.close(); + +  /* KV-SS080 is an auxiliary device which requires a master device to be here */ +  if(vendor == 0x04da && product == 0x100f) +    { +        present = false; +      sanei_usb_find_devices (vendor, 0x1006, check_present); +      sanei_usb_find_devices (vendor, 0x1007, check_present); +      sanei_usb_find_devices (vendor, 0x1010, check_present); +        if (present == false) { +            throw SaneException("master device not present"); +        } +    } + +    Genesys_Device* dev = attach_usb_device(devname, vendor, product); + +    DBG(DBG_info, "%s: found %s flatbed scanner %s at %s\n", __func__, dev->model->vendor, +        dev->model->model, dev->file_name.c_str()); + +    return dev; +} + +// this function is passed to C API and must not throw +static SANE_Status attach_one_device(SANE_String_Const devname) noexcept +{ +    DBG_HELPER(dbg); +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        attach_device_by_name(devname, false); +    }); +} + +/* configuration framework functions */ + +// this function is passed to C API, it must not throw +static SANE_Status +config_attach_genesys(SANEI_Config __sane_unused__ *config, const char *devname) noexcept +{ +  /* the devname has been processed and is ready to be used +   * directly. Since the backend is an USB only one, we can +   * call sanei_usb_attach_matching_devices straight */ +  sanei_usb_attach_matching_devices (devname, attach_one_device); + +  return SANE_STATUS_GOOD; +} + +/* probes for scanner to attach to the backend */ +static void probe_genesys_devices() +{ +    DBG_HELPER(dbg); +    if (is_testing_mode()) { +        attach_usb_device(get_testing_device_name().c_str(), +                          get_testing_vendor_id(), get_testing_product_id()); +        return; +    } + +  SANEI_Config config; + +    // set configuration options structure : no option for this backend +    config.descriptors = nullptr; +    config.values = nullptr; +  config.count = 0; + +    TIE(sanei_configure_attach(GENESYS_CONFIG_FILE, &config, config_attach_genesys)); + +    DBG(DBG_info, "%s: %zu devices currently attached\n", __func__, s_devices->size()); +} + +/** + * This should be changed if one of the substructures of +   Genesys_Calibration_Cache change, but it must be changed if there are +   changes that don't change size -- at least for now, as we store most +   of Genesys_Calibration_Cache as is. +*/ +static const char* CALIBRATION_IDENT = "sane_genesys"; +static const int CALIBRATION_VERSION = 21; + +bool read_calibration(std::istream& str, Genesys_Device::Calibration& calibration, +                      const std::string& path) +{ +    DBG_HELPER(dbg); + +    std::string ident; +    serialize(str, ident); + +    if (ident != CALIBRATION_IDENT) { +        DBG(DBG_info, "%s: Incorrect calibration file '%s' header\n", __func__, path.c_str()); +        return false; +    } + +    size_t version; +    serialize(str, version); + +    if (version != CALIBRATION_VERSION) { +        DBG(DBG_info, "%s: Incorrect calibration file '%s' version\n", __func__, path.c_str()); +        return false; +    } + +    calibration.clear(); +    serialize(str, calibration); +    return true; +} + +/** + * reads previously cached calibration data + * from file defined in dev->calib_file + */ +static bool sanei_genesys_read_calibration(Genesys_Device::Calibration& calibration, +                                           const std::string& path) +{ +    DBG_HELPER(dbg); + +    std::ifstream str; +    str.open(path); +    if (!str.is_open()) { +        DBG(DBG_info, "%s: Cannot open %s\n", __func__, path.c_str()); +        return false; +    } + +    return read_calibration(str, calibration, path); +} + +void write_calibration(std::ostream& str, Genesys_Device::Calibration& calibration) +{ +    std::string ident = CALIBRATION_IDENT; +    serialize(str, ident); +    size_t version = CALIBRATION_VERSION; +    serialize(str, version); +    serialize_newline(str); +    serialize(str, calibration); +} + +static void write_calibration(Genesys_Device::Calibration& calibration, const std::string& path) +{ +    DBG_HELPER(dbg); + +    std::ofstream str; +    str.open(path); +    if (!str.is_open()) { +        throw SaneException("Cannot open calibration for writing"); +    } +    write_calibration(str, calibration); +} + +/** @brief buffer scanned picture + * In order to allow digital processing, we must be able to put all the + * scanned picture in a buffer. + */ +static void genesys_buffer_image(Genesys_Scanner *s) +{ +    DBG_HELPER(dbg); +  size_t maximum;     /**> maximum bytes size of the scan */ +  size_t len;	      /**> length of scanned data read */ +  size_t total;	      /**> total of butes read */ +  size_t size;	      /**> size of image buffer */ +  size_t read_size;   /**> size of reads */ +  int lines;	      /** number of lines of the scan */ +  Genesys_Device *dev = s->dev; + +  /* compute maximum number of lines for the scan */ +  if (s->params.lines > 0) +    { +      lines = s->params.lines; +    } +  else +    { +        lines = static_cast<int>((dev->model->y_size * dev->settings.yres) / MM_PER_INCH); +    } +  DBG(DBG_info, "%s: buffering %d lines of %d bytes\n", __func__, lines, +      s->params.bytes_per_line); + +  /* maximum bytes to read */ +  maximum = s->params.bytes_per_line * lines; +    if (s->dev->settings.scan_mode == ScanColorMode::LINEART) { +      maximum *= 8; +    } + +  /* initial size of the read buffer */ +  size = +    ((2048 * 2048) / s->params.bytes_per_line) * s->params.bytes_per_line; + +  /* read size */ +  read_size = size / 2; + +  dev->img_buffer.resize(size); + +  /* loop reading data until we reach maximum or EOF */ +    total = 0; +    while (total < maximum) { +      len = size - maximum; +      if (len > read_size) +	{ +	  len = read_size; +	} + +        try { +            genesys_read_ordered_data(dev, dev->img_buffer.data() + total, &len); +        } catch (const SaneException& e) { +            if (e.status() == SANE_STATUS_EOF) { +                // ideally we shouldn't end up here, but because computations are duplicated and +                // slightly different everywhere in the genesys backend, we have no other choice +                break; +            } +            throw; +        } +      total += len; + +        // do we need to enlarge read buffer ? +        if (total + read_size > size) { +            size += read_size; +            dev->img_buffer.resize(size); +        } +    } + +  /* since digital processing is going to take place, +   * issue head parking command so that the head move while +   * computing so we can save time +   */ +    if (!dev->model->is_sheetfed && !dev->parking) { +        dev->cmd_set->move_back_home(dev, dev->model->flags & GENESYS_FLAG_MUST_WAIT); +      dev->parking = !(s->dev->model->flags & GENESYS_FLAG_MUST_WAIT); +    } + +  /* in case of dynamic lineart, we have buffered gray data which +   * must be converted to lineart first */ +    if (s->dev->settings.scan_mode == ScanColorMode::LINEART) { +      total/=8; +      std::vector<uint8_t> lineart(total); + +      genesys_gray_lineart (dev, +                            dev->img_buffer.data(), +                            lineart.data(), +                            dev->settings.pixels, +                            (total*8)/dev->settings.pixels, +                            dev->settings.threshold); +      dev->img_buffer = lineart; +    } + +  /* update counters */ +  dev->total_bytes_to_read = total; +  dev->total_bytes_read = 0; + +  /* update params */ +  s->params.lines = total / s->params.bytes_per_line; +  if (DBG_LEVEL >= DBG_io2) +    { +      sanei_genesys_write_pnm_file("gl_unprocessed.pnm", dev->img_buffer.data(), s->params.depth, +                                   s->params.format==SANE_FRAME_RGB ? 3 : 1, +                                   s->params.pixels_per_line, s->params.lines); +    } +} + +/* -------------------------- SANE API functions ------------------------- */ + +void sane_init_impl(SANE_Int * version_code, SANE_Auth_Callback authorize) +{ +  DBG_INIT (); +    DBG_HELPER_ARGS(dbg, "authorize %s null", authorize ? "!=" : "=="); +    DBG(DBG_init, "SANE Genesys backend from %s\n", PACKAGE_STRING); + +    if (!is_testing_mode()) { +#ifdef HAVE_LIBUSB +        DBG(DBG_init, "SANE Genesys backend built with libusb-1.0\n"); +#endif +#ifdef HAVE_LIBUSB_LEGACY +        DBG(DBG_init, "SANE Genesys backend built with libusb\n"); +#endif +    } + +    if (version_code) { +        *version_code = SANE_VERSION_CODE(SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, 0); +    } + +    if (!is_testing_mode()) { +        sanei_usb_init(); +    } + +  /* init sanei_magic */ +  sanei_magic_init(); + +  s_scanners.init(); +  s_devices.init(); +  s_sane_devices.init(); +    s_sane_devices_data.init(); +  s_sane_devices_ptrs.init(); +  genesys_init_sensor_tables(); +  genesys_init_frontend_tables(); +    genesys_init_gpo_tables(); +    genesys_init_motor_tables(); +    genesys_init_motor_profile_tables(); +    genesys_init_usb_device_tables(); + + +  DBG(DBG_info, "%s: %s endian machine\n", __func__, +#ifdef WORDS_BIGENDIAN +       "big" +#else +       "little" +#endif +    ); + +    // cold-plug case :detection of allready connected scanners +    probe_genesys_devices(); +} + + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_init(SANE_Int * version_code, SANE_Auth_Callback authorize) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_init_impl(version_code, authorize); +    }); +} + +void +sane_exit_impl(void) +{ +    DBG_HELPER(dbg); + +    if (!is_testing_mode()) { +        sanei_usb_exit(); +    } + +  run_functions_at_backend_exit(); +} + +SANE_GENESYS_API_LINKAGE +void sane_exit() +{ +    catch_all_exceptions(__func__, [](){ sane_exit_impl(); }); +} + +void sane_get_devices_impl(const SANE_Device *** device_list, SANE_Bool local_only) +{ +    DBG_HELPER_ARGS(dbg, "local_only = %s", local_only ? "true" : "false"); + +    if (!is_testing_mode()) { +        // hot-plug case : detection of newly connected scanners */ +        sanei_usb_scan_devices(); +    } +    probe_genesys_devices(); + +    s_sane_devices->clear(); +    s_sane_devices_data->clear(); +    s_sane_devices_ptrs->clear(); +    s_sane_devices->reserve(s_devices->size()); +    s_sane_devices_data->reserve(s_devices->size()); +    s_sane_devices_ptrs->reserve(s_devices->size() + 1); + +    for (auto dev_it = s_devices->begin(); dev_it != s_devices->end();) { + +        if (is_testing_mode()) { +            present = true; +        } else { +            present = false; +            sanei_usb_find_devices(dev_it->vendorId, dev_it->productId, check_present); +        } + +        if (present) { +            s_sane_devices->emplace_back(); +            s_sane_devices_data->emplace_back(); +            auto& sane_device = s_sane_devices->back(); +            auto& sane_device_data = s_sane_devices_data->back(); +            sane_device_data.name = dev_it->file_name; +            sane_device.name = sane_device_data.name.c_str(); +            sane_device.vendor = dev_it->model->vendor; +            sane_device.model = dev_it->model->model; +            sane_device.type = "flatbed scanner"; +            s_sane_devices_ptrs->push_back(&sane_device); +            dev_it++; +        } else { +            dev_it = s_devices->erase(dev_it); +        } +    } +    s_sane_devices_ptrs->push_back(nullptr); + +    *const_cast<SANE_Device***>(device_list) = s_sane_devices_ptrs->data(); +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_get_devices(const SANE_Device *** device_list, SANE_Bool local_only) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_get_devices_impl(device_list, local_only); +    }); +} + +static void sane_open_impl(SANE_String_Const devicename, SANE_Handle * handle) +{ +    DBG_HELPER_ARGS(dbg, "devicename = %s", devicename); +    Genesys_Device* dev = nullptr; + +  /* devicename="" or devicename="genesys" are default values that use +   * first available device +   */ +    if (devicename[0] && strcmp ("genesys", devicename) != 0) { +      /* search for the given devicename in the device list */ +        for (auto& d : *s_devices) { +            if (d.file_name == devicename) { +                dev = &d; +                break; +            } +        } + +        if (dev) { +            DBG(DBG_info, "%s: found `%s' in devlist\n", __func__, dev->model->name); +        } else if (is_testing_mode()) { +            DBG(DBG_info, "%s: couldn't find `%s' in devlist, not attaching", __func__, devicename); +        } else { +            DBG(DBG_info, "%s: couldn't find `%s' in devlist, trying attach\n", __func__, +                devicename); +            dbg.status("attach_device_by_name"); +            dev = attach_device_by_name(devicename, true); +            dbg.clear(); +        } +    } else { +        // empty devicename or "genesys" -> use first device +        if (!s_devices->empty()) { +            dev = &s_devices->front(); +            DBG(DBG_info, "%s: empty devicename, trying `%s'\n", __func__, dev->file_name.c_str()); +        } +    } + +    if (!dev) { +        throw SaneException("could not find the device to open: %s", devicename); +    } + +  if (dev->model->flags & GENESYS_FLAG_UNTESTED) +    { +      DBG(DBG_error0, "WARNING: Your scanner is not fully supported or at least \n"); +      DBG(DBG_error0, "         had only limited testing. Please be careful and \n"); +      DBG(DBG_error0, "         report any failure/success to \n"); +      DBG(DBG_error0, "         sane-devel@alioth-lists.debian.net. Please provide as many\n"); +      DBG(DBG_error0, "         details as possible, e.g. the exact name of your\n"); +      DBG(DBG_error0, "         scanner and what does (not) work.\n"); +    } + +    dbg.vstatus("open device '%s'", dev->file_name.c_str()); + +    if (is_testing_mode()) { +        auto interface = std::unique_ptr<TestScannerInterface>{new TestScannerInterface{dev}}; +        interface->set_checkpoint_callback(get_testing_checkpoint_callback()); +        dev->interface = std::move(interface); +    } else { +        dev->interface = std::unique_ptr<ScannerInterfaceUsb>{new ScannerInterfaceUsb{dev}}; +    } +    dev->interface->get_usb_device().open(dev->file_name.c_str()); +    dbg.clear(); + +  s_scanners->push_back(Genesys_Scanner()); +  auto* s = &s_scanners->back(); + +  s->dev = dev; +    s->scanning = false; +    s->dev->parking = false; +    s->dev->read_active = false; +  s->dev->force_calibration = 0; +  s->dev->line_count = 0; + +  *handle = s; + +    if (!dev->already_initialized) { +        sanei_genesys_init_structs (dev); +    } + +    init_options(s); + +    sanei_genesys_init_cmd_set(s->dev); + +    // FIXME: we create sensor tables for the sensor, this should happen when we know which sensor +    // we will select +    dev->cmd_set->init(dev); + +    // some hardware capabilities are detected through sensors +    s->dev->cmd_set->update_hardware_sensors (s); + +  /* here is the place to fetch a stored calibration cache */ +  if (s->dev->force_calibration == 0) +    { +        auto path = calibration_filename(s->dev); +        s->calibration_file = path; +        s->dev->calib_file = path; +      DBG(DBG_info, "%s: Calibration filename set to:\n", __func__); +      DBG(DBG_info, "%s: >%s<\n", __func__, s->dev->calib_file.c_str()); + +        catch_all_exceptions(__func__, [&]() +        { +            sanei_genesys_read_calibration(s->dev->calibration_cache, s->dev->calib_file); +        }); +    } +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_open(SANE_String_Const devicename, SANE_Handle* handle) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_open_impl(devicename, handle); +    }); +} + +void +sane_close_impl(SANE_Handle handle) +{ +    DBG_HELPER(dbg); + +  /* remove handle from list of open handles: */ +  auto it = s_scanners->end(); +  for (auto it2 = s_scanners->begin(); it2 != s_scanners->end(); it2++) +    { +      if (&*it2 == handle) { +          it = it2; +          break; +        } +    } +  if (it == s_scanners->end()) +    { +      DBG(DBG_error, "%s: invalid handle %p\n", __func__, handle); +      return;			/* oops, not a handle we know about */ +    } + +  Genesys_Scanner* s = &*it; + +  /* eject document for sheetfed scanners */ +    if (s->dev->model->is_sheetfed) { +        catch_all_exceptions(__func__, [&](){ s->dev->cmd_set->eject_document(s->dev); }); +    } +  else +    { +      /* in case scanner is parking, wait for the head +       * to reach home position */ +        if (s->dev->parking) { +            sanei_genesys_wait_for_home(s->dev); +        } +    } + +    // enable power saving before leaving +    s->dev->cmd_set->save_power(s->dev, true); + +    // here is the place to store calibration cache +    if (s->dev->force_calibration == 0 && !is_testing_mode()) { +        catch_all_exceptions(__func__, [&](){ write_calibration(s->dev->calibration_cache, +                                                                s->dev->calib_file); }); +    } + +    s->dev->already_initialized = false; + +  s->dev->clear(); + +    // LAMP OFF : same register across all the ASICs */ +    s->dev->interface->write_register(0x03, 0x00); + +    catch_all_exceptions(__func__, [&](){ s->dev->interface->get_usb_device().clear_halt(); }); + +    // we need this to avoid these ASIC getting stuck in bulk writes +    catch_all_exceptions(__func__, [&](){ s->dev->interface->get_usb_device().reset(); }); + +    // not freeing s->dev because it's in the dev list +    catch_all_exceptions(__func__, [&](){ s->dev->interface->get_usb_device().close(); }); + +  s_scanners->erase(it); +} + +SANE_GENESYS_API_LINKAGE +void sane_close(SANE_Handle handle) +{ +    catch_all_exceptions(__func__, [=]() +    { +        sane_close_impl(handle); +    }); +} + +const SANE_Option_Descriptor * +sane_get_option_descriptor_impl(SANE_Handle handle, SANE_Int option) +{ +    DBG_HELPER(dbg); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +    if (static_cast<unsigned>(option) >= NUM_OPTIONS) { +        return nullptr; +    } + +  DBG(DBG_io2, "%s: option = %s (%d)\n", __func__, s->opt[option].name, option); +  return s->opt + option; +} + + +SANE_GENESYS_API_LINKAGE +const SANE_Option_Descriptor* sane_get_option_descriptor(SANE_Handle handle, SANE_Int option) +{ +    const SANE_Option_Descriptor* ret = nullptr; +    catch_all_exceptions(__func__, [&]() +    { +        ret = sane_get_option_descriptor_impl(handle, option); +    }); +    return ret; +} + +static void print_option(DebugMessageHelper& dbg, const Genesys_Scanner& s, int option, void* val) +{ +    switch (s.opt[option].type) { +        case SANE_TYPE_INT: { +            dbg.vlog(DBG_proc, "value: %d", *reinterpret_cast<SANE_Word*>(val)); +            return; +        } +        case SANE_TYPE_BOOL: { +            dbg.vlog(DBG_proc, "value: %s", *reinterpret_cast<SANE_Bool*>(val) ? "true" : "false"); +            return; +        } +        case SANE_TYPE_FIXED: { +            dbg.vlog(DBG_proc, "value: %f", SANE_UNFIX(*reinterpret_cast<SANE_Word*>(val))); +            return; +        } +        case SANE_TYPE_STRING: { +            dbg.vlog(DBG_proc, "value: %s", reinterpret_cast<char*>(val)); +            return; +        } +        default: break; +    } +    dbg.log(DBG_proc, "value: (non-printable)"); +} + +static void get_option_value(Genesys_Scanner* s, int option, void* val) +{ +    DBG_HELPER_ARGS(dbg, "option: %s (%d)", s->opt[option].name, option); +  unsigned int i; +    SANE_Word* table = nullptr; +  std::vector<uint16_t> gamma_table; +  unsigned option_size = 0; + +    const Genesys_Sensor* sensor = nullptr; +    if (sanei_genesys_has_sensor(s->dev, s->dev->settings.xres, s->dev->settings.get_channels(), +                                 s->dev->settings.scan_method)) +    { +        sensor = &sanei_genesys_find_sensor(s->dev, s->dev->settings.xres, +                                            s->dev->settings.get_channels(), +                                            s->dev->settings.scan_method); +    } + +  switch (option) +    { +      /* geometry */ +    case OPT_TL_X: +        *reinterpret_cast<SANE_Word*>(val) = s->pos_top_left_x; +        break; +    case OPT_TL_Y: +        *reinterpret_cast<SANE_Word*>(val) = s->pos_top_left_y; +        break; +    case OPT_BR_X: +        *reinterpret_cast<SANE_Word*>(val) = s->pos_bottom_right_x; +        break; +    case OPT_BR_Y: +        *reinterpret_cast<SANE_Word*>(val) = s->pos_bottom_right_y; +        break; +      /* word options: */ +    case OPT_NUM_OPTS: +        *reinterpret_cast<SANE_Word*>(val) = NUM_OPTIONS; +        break; +    case OPT_RESOLUTION: +        *reinterpret_cast<SANE_Word*>(val) = s->resolution; +        break; +    case OPT_BIT_DEPTH: +        *reinterpret_cast<SANE_Word*>(val) = s->bit_depth; +        break; +    case OPT_PREVIEW: +        *reinterpret_cast<SANE_Word*>(val) = s->preview; +        break; +    case OPT_THRESHOLD: +        *reinterpret_cast<SANE_Word*>(val) = s->threshold; +        break; +    case OPT_THRESHOLD_CURVE: +        *reinterpret_cast<SANE_Word*>(val) = s->threshold_curve; +        break; +    case OPT_DISABLE_INTERPOLATION: +        *reinterpret_cast<SANE_Word*>(val) = s->disable_interpolation; +        break; +    case OPT_LAMP_OFF: +        *reinterpret_cast<SANE_Word*>(val) = s->lamp_off; +        break; +    case OPT_LAMP_OFF_TIME: +        *reinterpret_cast<SANE_Word*>(val) = s->lamp_off_time; +        break; +    case OPT_SWDESKEW: +        *reinterpret_cast<SANE_Word*>(val) = s->swdeskew; +        break; +    case OPT_SWCROP: +        *reinterpret_cast<SANE_Word*>(val) = s->swcrop; +        break; +    case OPT_SWDESPECK: +        *reinterpret_cast<SANE_Word*>(val) = s->swdespeck; +        break; +    case OPT_SWDEROTATE: +        *reinterpret_cast<SANE_Word*>(val) = s->swderotate; +        break; +    case OPT_SWSKIP: +        *reinterpret_cast<SANE_Word*>(val) = s->swskip; +        break; +    case OPT_DESPECK: +        *reinterpret_cast<SANE_Word*>(val) = s->despeck; +        break; +    case OPT_CONTRAST: +        *reinterpret_cast<SANE_Word*>(val) = s->contrast; +        break; +    case OPT_BRIGHTNESS: +        *reinterpret_cast<SANE_Word*>(val) = s->brightness; +        break; +    case OPT_EXPIRATION_TIME: +        *reinterpret_cast<SANE_Word*>(val) = s->expiration_time; +        break; +    case OPT_CUSTOM_GAMMA: +        *reinterpret_cast<SANE_Word*>(val) = s->custom_gamma; +        break; + +      /* string options: */ +    case OPT_MODE: +        std::strcpy(reinterpret_cast<char*>(val), s->mode.c_str()); +        break; +    case OPT_COLOR_FILTER: +        std::strcpy(reinterpret_cast<char*>(val), s->color_filter.c_str()); +        break; +    case OPT_CALIBRATION_FILE: +        std::strcpy(reinterpret_cast<char*>(val), s->calibration_file.c_str()); +        break; +    case OPT_SOURCE: +        std::strcpy(reinterpret_cast<char*>(val), scan_method_to_option_string(s->scan_method)); +        break; + +      /* word array options */ +    case OPT_GAMMA_VECTOR: +        if (!sensor) +            throw SaneException("Unsupported scanner mode selected"); + +        table = reinterpret_cast<SANE_Word*>(val); +        if (s->color_filter == "Red") { +            gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_RED); +        } else if (s->color_filter == "Blue") { +            gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_BLUE); +        } else { +            gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_GREEN); +        } +        option_size = s->opt[option].size / sizeof (SANE_Word); +        if (gamma_table.size() != option_size) { +            throw std::runtime_error("The size of the gamma tables does not match"); +        } +        for (i = 0; i < option_size; i++) { +            table[i] = gamma_table[i]; +        } +        break; +    case OPT_GAMMA_VECTOR_R: +        if (!sensor) +            throw SaneException("Unsupported scanner mode selected"); + +        table = reinterpret_cast<SANE_Word*>(val); +        gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_RED); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        if (gamma_table.size() != option_size) { +            throw std::runtime_error("The size of the gamma tables does not match"); +        } +        for (i = 0; i < option_size; i++) { +            table[i] = gamma_table[i]; +	} +      break; +    case OPT_GAMMA_VECTOR_G: +        if (!sensor) +            throw SaneException("Unsupported scanner mode selected"); + +        table = reinterpret_cast<SANE_Word*>(val); +        gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_GREEN); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        if (gamma_table.size() != option_size) { +            throw std::runtime_error("The size of the gamma tables does not match"); +        } +        for (i = 0; i < option_size; i++) { +            table[i] = gamma_table[i]; +        } +      break; +    case OPT_GAMMA_VECTOR_B: +        if (!sensor) +            throw SaneException("Unsupported scanner mode selected"); + +        table = reinterpret_cast<SANE_Word*>(val); +        gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_BLUE); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        if (gamma_table.size() != option_size) { +            throw std::runtime_error("The size of the gamma tables does not match"); +        } +        for (i = 0; i < option_size; i++) { +            table[i] = gamma_table[i]; +        } +      break; +      /* sensors */ +    case OPT_SCAN_SW: +    case OPT_FILE_SW: +    case OPT_EMAIL_SW: +    case OPT_COPY_SW: +    case OPT_PAGE_LOADED_SW: +    case OPT_OCR_SW: +    case OPT_POWER_SW: +    case OPT_EXTRA_SW: +        s->dev->cmd_set->update_hardware_sensors(s); +        *reinterpret_cast<SANE_Bool*>(val) = s->buttons[genesys_option_to_button(option)].read(); +        break; + +        case OPT_NEED_CALIBRATION_SW: { +            if (!sensor) { +                throw SaneException("Unsupported scanner mode selected"); +            } + +            // scanner needs calibration for current mode unless a matching calibration cache is +            // found + +            bool result = true; + +            auto session = s->dev->cmd_set->calculate_scan_session(s->dev, *sensor, +                                                                   s->dev->settings); + +            for (auto& cache : s->dev->calibration_cache) { +                if (sanei_genesys_is_compatible_calibration(s->dev, session, &cache, false)) { +                    *reinterpret_cast<SANE_Bool*>(val) = SANE_FALSE; +                } +            } +            *reinterpret_cast<SANE_Bool*>(val) = result; +            break; +        } +    default: +      DBG(DBG_warn, "%s: can't get unknown option %d\n", __func__, option); +    } +    print_option(dbg, *s, option, val); +} + +/** @brief set calibration file value + * Set calibration file value. Load new cache values from file if it exists, + * else creates the file*/ +static void set_calibration_value(Genesys_Scanner* s, const char* val) +{ +    DBG_HELPER(dbg); + +    std::string new_calib_path = val; +    Genesys_Device::Calibration new_calibration; + +    bool is_calib_success = false; +    catch_all_exceptions(__func__, [&]() +    { +        is_calib_success = sanei_genesys_read_calibration(new_calibration, new_calib_path); +    }); + +    if (!is_calib_success) { +        return; +    } + +    s->dev->calibration_cache = std::move(new_calibration); +    s->dev->calib_file = new_calib_path; +    s->calibration_file = new_calib_path; +    DBG(DBG_info, "%s: Calibration filename set to '%s':\n", __func__, new_calib_path.c_str()); +} + +/* sets an option , called by sane_control_option */ +static void set_option_value(Genesys_Scanner* s, int option, void *val, SANE_Int* myinfo) +{ +    DBG_HELPER_ARGS(dbg, "option: %s (%d)", s->opt[option].name, option); +    print_option(dbg, *s, option, val); + +  SANE_Word *table; +  unsigned int i; +  unsigned option_size = 0; + +  switch (option) +    { +    case OPT_TL_X: +        s->pos_top_left_x = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_TL_Y: +        s->pos_top_left_y = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_BR_X: +        s->pos_bottom_right_x = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_BR_Y: +        s->pos_bottom_right_y = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_RESOLUTION: +        s->resolution = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_THRESHOLD: +        s->threshold = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_THRESHOLD_CURVE: +        s->threshold_curve = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_SWCROP: +        s->swcrop = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_SWDESKEW: +        s->swdeskew = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_DESPECK: +        s->despeck = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_SWDEROTATE: +        s->swderotate = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_SWSKIP: +        s->swskip = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_DISABLE_INTERPOLATION: +        s->disable_interpolation = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_LAMP_OFF: +        s->lamp_off = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_PREVIEW: +        s->preview = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_BRIGHTNESS: +        s->brightness = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_CONTRAST: +        s->contrast = *reinterpret_cast<SANE_Word*>(val); +        calc_parameters(s); +        *myinfo |= SANE_INFO_RELOAD_PARAMS; +        break; +    case OPT_SWDESPECK: +        s->swdespeck = *reinterpret_cast<SANE_Word*>(val); +        if (s->swdespeck) { +            ENABLE(OPT_DESPECK); +        } else { +            DISABLE(OPT_DESPECK); +        } +        calc_parameters(s); +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +      break; +    /* software enhancement functions only apply to 8 or 1 bits data */ +    case OPT_BIT_DEPTH: +        s->bit_depth = *reinterpret_cast<SANE_Word*>(val); +        if(s->bit_depth>8) +        { +          DISABLE(OPT_SWDESKEW); +          DISABLE(OPT_SWDESPECK); +          DISABLE(OPT_SWCROP); +          DISABLE(OPT_DESPECK); +          DISABLE(OPT_SWDEROTATE); +          DISABLE(OPT_SWSKIP); +          DISABLE(OPT_CONTRAST); +          DISABLE(OPT_BRIGHTNESS); +        } +      else +        { +          ENABLE(OPT_SWDESKEW); +          ENABLE(OPT_SWDESPECK); +          ENABLE(OPT_SWCROP); +          ENABLE(OPT_DESPECK); +          ENABLE(OPT_SWDEROTATE); +          ENABLE(OPT_SWSKIP); +          ENABLE(OPT_CONTRAST); +          ENABLE(OPT_BRIGHTNESS); +        } +        calc_parameters(s); +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +      break; +    case OPT_SOURCE: { +        auto scan_method = option_string_to_scan_method(reinterpret_cast<const char*>(val)); +        if (s->scan_method != scan_method) { +            s->scan_method = scan_method; + +            set_xy_range_option_values(*s); +            set_resolution_option_values(*s, false); + +            *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +        } +        break; +    } +    case OPT_MODE: +      s->mode = reinterpret_cast<const char*>(val); + +      if (s->mode == SANE_VALUE_SCAN_MODE_LINEART) +	{ +	  ENABLE (OPT_THRESHOLD); +	  ENABLE (OPT_THRESHOLD_CURVE); +	  DISABLE (OPT_BIT_DEPTH); +                if (s->dev->model->asic_type != AsicType::GL646 || !s->dev->model->is_cis) { +                    ENABLE(OPT_COLOR_FILTER); +                } +	} +      else +	{ +	  DISABLE (OPT_THRESHOLD); +	  DISABLE (OPT_THRESHOLD_CURVE); +          if (s->mode == SANE_VALUE_SCAN_MODE_GRAY) +	    { +                    if (s->dev->model->asic_type != AsicType::GL646 || !s->dev->model->is_cis) { +                        ENABLE(OPT_COLOR_FILTER); +                    } +	      create_bpp_list (s, s->dev->model->bpp_gray_values); +                    s->bit_depth = s->dev->model->bpp_gray_values[0]; +	    } +	  else +	    { +	      DISABLE (OPT_COLOR_FILTER); +	      create_bpp_list (s, s->dev->model->bpp_color_values); +                    s->bit_depth = s->dev->model->bpp_color_values[0]; +	    } +	} +        calc_parameters(s); + +      /* if custom gamma, toggle gamma table options according to the mode */ +      if (s->custom_gamma) +	{ +          if (s->mode == SANE_VALUE_SCAN_MODE_COLOR) +	    { +	      DISABLE (OPT_GAMMA_VECTOR); +	      ENABLE (OPT_GAMMA_VECTOR_R); +	      ENABLE (OPT_GAMMA_VECTOR_G); +	      ENABLE (OPT_GAMMA_VECTOR_B); +	    } +	  else +	    { +	      ENABLE (OPT_GAMMA_VECTOR); +	      DISABLE (OPT_GAMMA_VECTOR_R); +	      DISABLE (OPT_GAMMA_VECTOR_G); +	      DISABLE (OPT_GAMMA_VECTOR_B); +	    } +	} + +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +      break; +    case OPT_COLOR_FILTER: +      s->color_filter = reinterpret_cast<const char*>(val); +        calc_parameters(s); +      break; +    case OPT_CALIBRATION_FILE: +            if (s->dev->force_calibration == 0) { +                set_calibration_value(s, reinterpret_cast<const char*>(val)); +            } +            break; +    case OPT_LAMP_OFF_TIME: +        if (*reinterpret_cast<SANE_Word*>(val) != s->lamp_off_time) { +            s->lamp_off_time = *reinterpret_cast<SANE_Word*>(val); +                s->dev->cmd_set->set_powersaving(s->dev, s->lamp_off_time); +        } +        break; +    case OPT_EXPIRATION_TIME: +        if (*reinterpret_cast<SANE_Word*>(val) != s->expiration_time) { +            s->expiration_time = *reinterpret_cast<SANE_Word*>(val); +            // BUG: this is most likely not intended behavior, found out during refactor +                s->dev->cmd_set->set_powersaving(s->dev, s->expiration_time); +	} +        break; + +    case OPT_CUSTOM_GAMMA: +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +        s->custom_gamma = *reinterpret_cast<SANE_Bool*>(val); + +        if (s->custom_gamma) { +          if (s->mode == SANE_VALUE_SCAN_MODE_COLOR) +	    { +	      DISABLE (OPT_GAMMA_VECTOR); +	      ENABLE (OPT_GAMMA_VECTOR_R); +	      ENABLE (OPT_GAMMA_VECTOR_G); +	      ENABLE (OPT_GAMMA_VECTOR_B); +	    } +	  else +	    { +	      ENABLE (OPT_GAMMA_VECTOR); +	      DISABLE (OPT_GAMMA_VECTOR_R); +	      DISABLE (OPT_GAMMA_VECTOR_G); +	      DISABLE (OPT_GAMMA_VECTOR_B); +	    } +	} +      else +	{ +	  DISABLE (OPT_GAMMA_VECTOR); +	  DISABLE (OPT_GAMMA_VECTOR_R); +	  DISABLE (OPT_GAMMA_VECTOR_G); +	  DISABLE (OPT_GAMMA_VECTOR_B); +            for (auto& table : s->dev->gamma_override_tables) { +                table.clear(); +            } +	} +      break; + +    case OPT_GAMMA_VECTOR: +        table = reinterpret_cast<SANE_Word*>(val); +        option_size = s->opt[option].size / sizeof (SANE_Word); + +        s->dev->gamma_override_tables[GENESYS_RED].resize(option_size); +        s->dev->gamma_override_tables[GENESYS_GREEN].resize(option_size); +        s->dev->gamma_override_tables[GENESYS_BLUE].resize(option_size); +        for (i = 0; i < option_size; i++) { +            s->dev->gamma_override_tables[GENESYS_RED][i] = table[i]; +            s->dev->gamma_override_tables[GENESYS_GREEN][i] = table[i]; +            s->dev->gamma_override_tables[GENESYS_BLUE][i] = table[i]; +        } +      break; +    case OPT_GAMMA_VECTOR_R: +        table = reinterpret_cast<SANE_Word*>(val); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        s->dev->gamma_override_tables[GENESYS_RED].resize(option_size); +        for (i = 0; i < option_size; i++) { +            s->dev->gamma_override_tables[GENESYS_RED][i] = table[i]; +        } +      break; +    case OPT_GAMMA_VECTOR_G: +        table = reinterpret_cast<SANE_Word*>(val); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        s->dev->gamma_override_tables[GENESYS_GREEN].resize(option_size); +        for (i = 0; i < option_size; i++) { +            s->dev->gamma_override_tables[GENESYS_GREEN][i] = table[i]; +        } +      break; +    case OPT_GAMMA_VECTOR_B: +        table = reinterpret_cast<SANE_Word*>(val); +        option_size = s->opt[option].size / sizeof (SANE_Word); +        s->dev->gamma_override_tables[GENESYS_BLUE].resize(option_size); +        for (i = 0; i < option_size; i++) { +            s->dev->gamma_override_tables[GENESYS_BLUE][i] = table[i]; +        } +      break; +        case OPT_CALIBRATE: { +            auto& sensor = sanei_genesys_find_sensor_for_write(s->dev, s->dev->settings.xres, +                                                               s->dev->settings.get_channels(), +                                                               s->dev->settings.scan_method); +            catch_all_exceptions(__func__, [&]() +            { +                s->dev->cmd_set->save_power(s->dev, false); +                genesys_scanner_calibration(s->dev, sensor); +            }); +            catch_all_exceptions(__func__, [&]() +            { +                s->dev->cmd_set->save_power(s->dev, true); +            }); +            *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +            break; +        } +    case OPT_CLEAR_CALIBRATION: +      s->dev->calibration_cache.clear(); + +      /* remove file */ +      unlink(s->dev->calib_file.c_str()); +      /* signals that sensors will have to be read again */ +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +      break; +    case OPT_FORCE_CALIBRATION: +      s->dev->force_calibration = 1; +      s->dev->calibration_cache.clear(); +      s->dev->calib_file.clear(); + +      /* signals that sensors will have to be read again */ +      *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; +      break; + +        case OPT_IGNORE_OFFSETS: { +            s->dev->ignore_offsets = true; +            break; +        } +    default: +      DBG(DBG_warn, "%s: can't set unknown option %d\n", __func__, option); +    } +} + + +/* sets and gets scanner option values */ +void sane_control_option_impl(SANE_Handle handle, SANE_Int option, +                              SANE_Action action, void *val, SANE_Int * info) +{ +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); +    auto action_str = (action == SANE_ACTION_GET_VALUE) ? "get" : +                      (action == SANE_ACTION_SET_VALUE) ? "set" : +                      (action == SANE_ACTION_SET_AUTO) ? "set_auto" : "unknown"; +    DBG_HELPER_ARGS(dbg, "action = %s, option = %s (%d)", action_str, +                    s->opt[option].name, option); + +  SANE_Word cap; +  SANE_Int myinfo = 0; + +    if (info) { +        *info = 0; +    } + +    if (s->scanning) { +        throw SaneException(SANE_STATUS_DEVICE_BUSY, +                            "don't call this function while scanning (option = %s (%d))", +                            s->opt[option].name, option); +    } +    if (option >= NUM_OPTIONS || option < 0) { +        throw SaneException("option %d >= NUM_OPTIONS || option < 0", option); +    } + +  cap = s->opt[option].cap; + +    if (!SANE_OPTION_IS_ACTIVE (cap)) { +        throw SaneException("option %d is inactive", option); +    } + +    switch (action) { +        case SANE_ACTION_GET_VALUE: +            get_option_value(s, option, val); +            break; + +        case SANE_ACTION_SET_VALUE: +            if (!SANE_OPTION_IS_SETTABLE (cap)) { +                throw SaneException("option %d is not settable", option); +            } + +            TIE(sanei_constrain_value(s->opt + option, val, &myinfo)); + +            set_option_value(s, option, val, &myinfo); +            break; + +        case SANE_ACTION_SET_AUTO: +            throw SaneException("SANE_ACTION_SET_AUTO unsupported since no option " +                                "has SANE_CAP_AUTOMATIC"); +        default: +            throw SaneException("unknown action %d for option %d", action, option); +    } + +  if (info) +    *info = myinfo; +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_control_option(SANE_Handle handle, SANE_Int option, +                                           SANE_Action action, void *val, SANE_Int * info) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_control_option_impl(handle, option, action, val, info); +    }); +} + +void sane_get_parameters_impl(SANE_Handle handle, SANE_Parameters* params) +{ +    DBG_HELPER(dbg); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +  /* don't recompute parameters once data reading is active, ie during scan */ +    if (!s->dev->read_active) { +        calc_parameters(s); +    } +  if (params) +    { +      *params = s->params; + +      /* in the case of a sheetfed scanner, when full height is specified +       * we override the computed line number with -1 to signal that we +       * don't know the real document height. +       * We don't do that doing buffering image for digital processing +       */ +        if (s->dev->model->is_sheetfed && !s->dev->buffer_image && +            s->pos_bottom_right_y == s->opt[OPT_BR_Y].constraint.range->max) +        { +	  params->lines = -1; +	} +    } +    debug_dump(DBG_proc, *params); +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_get_parameters(SANE_Handle handle, SANE_Parameters* params) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_get_parameters_impl(handle, params); +    }); +} + +void sane_start_impl(SANE_Handle handle) +{ +    DBG_HELPER(dbg); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +    if (s->pos_top_left_x >= s->pos_bottom_right_x) { +        throw SaneException("top left x >= bottom right x"); +    } +    if (s->pos_top_left_y >= s->pos_bottom_right_y) { +        throw SaneException("top left y >= bottom right y"); +    } + +  /* First make sure we have a current parameter set.  Some of the +     parameters will be overwritten below, but that's OK.  */ + +    calc_parameters(s); +    genesys_start_scan(s->dev, s->lamp_off); + +    s->scanning = true; + +  /* allocate intermediate buffer when doing dynamic lineart */ +    if (s->dev->settings.scan_mode == ScanColorMode::LINEART) { +        s->dev->binarize_buffer.clear(); +        s->dev->binarize_buffer.alloc(s->dev->settings.pixels); +        s->dev->local_buffer.clear(); +        s->dev->local_buffer.alloc(s->dev->binarize_buffer.size() * 8); +    } + +  /* if one of the software enhancement option is selected, +   * we do the scan internally, process picture then put it an internal +   * buffer. Since cropping may change scan parameters, we recompute them +   * at the end */ +  if (s->dev->buffer_image) +    { +        genesys_buffer_image(s); + +      /* check if we need to skip this page, sheetfed scanners +       * can go to next doc while flatbed ones can't */ +        if (s->swskip > 0 && IS_ACTIVE(OPT_SWSKIP)) { +            auto status = sanei_magic_isBlank(&s->params, +                                              s->dev->img_buffer.data(), +                                              SANE_UNFIX(s->swskip)); + +            if (status == SANE_STATUS_NO_DOCS && s->dev->model->is_sheetfed) { +                DBG(DBG_info, "%s: blank page, recurse\n", __func__); +                sane_start(handle); +                return; +            } + +            if (status != SANE_STATUS_GOOD) { +                throw SaneException(status); +            } +        } + +        if (s->swdeskew) { +            const auto& sensor = sanei_genesys_find_sensor(s->dev, s->dev->settings.xres, +                                                           s->dev->settings.get_channels(), +                                                           s->dev->settings.scan_method); +            catch_all_exceptions(__func__, [&](){ genesys_deskew(s, sensor); }); +        } + +        if (s->swdespeck) { +            catch_all_exceptions(__func__, [&](){ genesys_despeck(s); }); +        } + +        if(s->swcrop) { +            catch_all_exceptions(__func__, [&](){ genesys_crop(s); }); +        } + +        if(s->swderotate) { +            catch_all_exceptions(__func__, [&](){ genesys_derotate(s); }); +        } +    } +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_start(SANE_Handle handle) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_start_impl(handle); +    }); +} + +void sane_read_impl(SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int* len) +{ +    DBG_HELPER(dbg); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); +  Genesys_Device *dev; +  size_t local_len; + +    if (!s) { +        throw SaneException("handle is nullptr"); +    } + +  dev=s->dev; +    if (!dev) { +        throw SaneException("dev is nullptr"); +    } + +    if (!buf) { +        throw SaneException("buf is nullptr"); +    } + +    if (!len) { +        throw SaneException("len is nullptr"); +    } + +  *len = 0; + +    if (!s->scanning) { +        throw SaneException(SANE_STATUS_CANCELLED, +                            "scan was cancelled, is over or has not been initiated yet"); +    } + +  DBG(DBG_proc, "%s: start, %d maximum bytes required\n", __func__, max_len); +    DBG(DBG_io2, "%s: bytes_to_read=%zu, total_bytes_read=%zu\n", __func__, +        dev->total_bytes_to_read, dev->total_bytes_read); + +  if(dev->total_bytes_read>=dev->total_bytes_to_read) +    { +      DBG(DBG_proc, "%s: nothing more to scan: EOF\n", __func__); + +      /* issue park command immediatly in case scanner can handle it +       * so we save time */ +        if (!dev->model->is_sheetfed && !(dev->model->flags & GENESYS_FLAG_MUST_WAIT) && +            !dev->parking) +        { +            dev->cmd_set->move_back_home(dev, false); +            dev->parking = true; +        } +        throw SaneException(SANE_STATUS_EOF); +    } + +  local_len = max_len; + +  /* in case of image processing, all data has been stored in +   * buffer_image. So read data from it if it exists, else from scanner */ +  if(!dev->buffer_image) +    { +      /* dynamic lineart is another kind of digital processing that needs +       * another layer of buffering on top of genesys_read_ordered_data */ +        if (dev->settings.scan_mode == ScanColorMode::LINEART) { +          /* if buffer is empty, fill it with genesys_read_ordered_data */ +          if(dev->binarize_buffer.avail() == 0) +            { +              /* store gray data */ +              local_len=dev->local_buffer.size(); +              dev->local_buffer.reset(); +                genesys_read_ordered_data(dev, dev->local_buffer.get_write_pos(local_len), +                                          &local_len); +              dev->local_buffer.produce(local_len); + +                dev->binarize_buffer.reset(); +                if (!is_testing_mode()) { +                    genesys_gray_lineart(dev, dev->local_buffer.get_read_pos(), +                                         dev->binarize_buffer.get_write_pos(local_len / 8), +                                         dev->settings.pixels, +                                         local_len / dev->settings.pixels, +                                         dev->settings.threshold); +                } +                dev->binarize_buffer.produce(local_len / 8); +            } + +          /* return data from lineart buffer if any, up to the available amount */ +          local_len = max_len; +          if (static_cast<std::size_t>(max_len) > dev->binarize_buffer.avail()) +            { +              local_len=dev->binarize_buffer.avail(); +            } +          if(local_len) +            { +              memcpy(buf, dev->binarize_buffer.get_read_pos(), local_len); +              dev->binarize_buffer.consume(local_len); +            } +        } +      else +        { +            // most usual case, direct read of data from scanner */ +            genesys_read_ordered_data(dev, buf, &local_len); +        } +    } +  else /* read data from buffer */ +    { +      if(dev->total_bytes_read+local_len>dev->total_bytes_to_read) +        { +          local_len=dev->total_bytes_to_read-dev->total_bytes_read; +        } +      memcpy(buf, dev->img_buffer.data() + dev->total_bytes_read, local_len); +      dev->total_bytes_read+=local_len; +    } + +  *len = local_len; +    if (local_len > static_cast<std::size_t>(max_len)) { +      fprintf (stderr, "[genesys] sane_read: returning incorrect length!!\n"); +    } +  DBG(DBG_proc, "%s: %d bytes returned\n", __func__, *len); +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_read(SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int* len) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_read_impl(handle, buf, max_len, len); +    }); +} + +void sane_cancel_impl(SANE_Handle handle) +{ +    DBG_HELPER(dbg); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +    s->scanning = false; +    s->dev->read_active = false; +  s->dev->img_buffer.clear(); + +  /* no need to end scan if we are parking the head */ +    if (!s->dev->parking) { +        s->dev->cmd_set->end_scan(s->dev, &s->dev->reg, true); +    } + +  /* park head if flatbed scanner */ +    if (!s->dev->model->is_sheetfed) { +        if (!s->dev->parking) { +            s->dev->cmd_set->move_back_home (s->dev, s->dev->model->flags & +                                                    GENESYS_FLAG_MUST_WAIT); + +          s->dev->parking = !(s->dev->model->flags & GENESYS_FLAG_MUST_WAIT); +        } +    } +  else +    {				/* in case of sheetfed scanners, we have to eject the document if still present */ +        s->dev->cmd_set->eject_document(s->dev); +    } + +  /* enable power saving mode unless we are parking .... */ +    if (!s->dev->parking) { +        s->dev->cmd_set->save_power(s->dev, true); +    } + +  return; +} + +SANE_GENESYS_API_LINKAGE +void sane_cancel(SANE_Handle handle) +{ +    catch_all_exceptions(__func__, [=]() { sane_cancel_impl(handle); }); +} + +void sane_set_io_mode_impl(SANE_Handle handle, SANE_Bool non_blocking) +{ +    DBG_HELPER_ARGS(dbg, "handle = %p, non_blocking = %s", handle, +                    non_blocking == SANE_TRUE ? "true" : "false"); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +    if (!s->scanning) { +        throw SaneException("not scanning"); +    } +    if (non_blocking) { +        throw SaneException(SANE_STATUS_UNSUPPORTED); +    } +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_set_io_mode(SANE_Handle handle, SANE_Bool non_blocking) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_set_io_mode_impl(handle, non_blocking); +    }); +} + +void sane_get_select_fd_impl(SANE_Handle handle, SANE_Int* fd) +{ +    DBG_HELPER_ARGS(dbg, "handle = %p, fd = %p", handle, reinterpret_cast<void*>(fd)); +    Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + +    if (!s->scanning) { +        throw SaneException("not scanning"); +    } +    throw SaneException(SANE_STATUS_UNSUPPORTED); +} + +SANE_GENESYS_API_LINKAGE +SANE_Status sane_get_select_fd(SANE_Handle handle, SANE_Int* fd) +{ +    return wrap_exceptions_to_status_code(__func__, [=]() +    { +        sane_get_select_fd_impl(handle, fd); +    }); +} + +GenesysButtonName genesys_option_to_button(int option) +{ +    switch (option) { +    case OPT_SCAN_SW: return BUTTON_SCAN_SW; +    case OPT_FILE_SW: return BUTTON_FILE_SW; +    case OPT_EMAIL_SW: return BUTTON_EMAIL_SW; +    case OPT_COPY_SW: return BUTTON_COPY_SW; +    case OPT_PAGE_LOADED_SW: return BUTTON_PAGE_LOADED_SW; +    case OPT_OCR_SW: return BUTTON_OCR_SW; +    case OPT_POWER_SW: return BUTTON_POWER_SW; +    case OPT_EXTRA_SW: return BUTTON_EXTRA_SW; +    default: throw std::runtime_error("Unknown option to convert to button index"); +    } +} + +} // namespace genesys | 
