diff options
Diffstat (limited to 'backend/genesys/genesys.cpp')
-rw-r--r-- | backend/genesys/genesys.cpp | 4079 |
1 files changed, 2185 insertions, 1894 deletions
diff --git a/backend/genesys/genesys.cpp b/backend/genesys/genesys.cpp index 7c25168..9d80cfa 100644 --- a/backend/genesys/genesys.cpp +++ b/backend/genesys/genesys.cpp @@ -61,9 +61,9 @@ #define DEBUG_NOT_STATIC #include "genesys.h" -#include "conv.h" #include "gl124_registers.h" #include "gl841_registers.h" +#include "gl842_registers.h" #include "gl843_registers.h" #include "gl846_registers.h" #include "gl847_registers.h" @@ -73,7 +73,6 @@ #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> @@ -111,8 +110,8 @@ 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, + // SANE_TITLE_HALFTONE, not used + // SANE_VALUE_SCAN_MODE_LINEART, not used nullptr }; @@ -131,12 +130,6 @@ static SANE_String_Const cis_color_filter_list[] = { nullptr }; -static SANE_Range swdespeck_range = { - 1, - 9, - 1 -}; - static SANE_Range time_range = { 0, /* minimum */ 60, /* maximum */ @@ -162,15 +155,9 @@ static const SANE_Range u16_range = { }; 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 */ + float_to_fixed(0), // minimum + float_to_fixed(100), // maximum + float_to_fixed(1) // quantization }; /** @@ -191,7 +178,7 @@ static const SANE_Range expiration_range = { 1 /* quantization */ }; -const Genesys_Sensor& sanei_genesys_find_sensor_any(Genesys_Device* dev) +const Genesys_Sensor& sanei_genesys_find_sensor_any(const Genesys_Device* dev) { DBG_HELPER(dbg); for (const auto& sensor : *s_sensors) { @@ -202,7 +189,7 @@ const Genesys_Sensor& sanei_genesys_find_sensor_any(Genesys_Device* dev) throw std::runtime_error("Given device does not have sensor defined"); } -Genesys_Sensor* find_sensor_impl(Genesys_Device* dev, unsigned dpi, unsigned channels, +Genesys_Sensor* find_sensor_impl(const Genesys_Device* dev, unsigned dpi, unsigned channels, ScanMethod scan_method) { DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels, @@ -217,7 +204,7 @@ Genesys_Sensor* find_sensor_impl(Genesys_Device* dev, unsigned dpi, unsigned cha return nullptr; } -bool sanei_genesys_has_sensor(Genesys_Device* dev, unsigned dpi, unsigned channels, +bool sanei_genesys_has_sensor(const Genesys_Device* dev, unsigned dpi, unsigned channels, ScanMethod scan_method) { DBG_HELPER_ARGS(dbg, "dpi: %d, channels: %d, scan_method: %d", dpi, channels, @@ -225,8 +212,8 @@ bool sanei_genesys_has_sensor(Genesys_Device* dev, unsigned dpi, unsigned channe 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) +const Genesys_Sensor& sanei_genesys_find_sensor(const 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)); @@ -250,12 +237,14 @@ Genesys_Sensor& sanei_genesys_find_sensor_for_write(Genesys_Device* dev, unsigne std::vector<std::reference_wrapper<const Genesys_Sensor>> - sanei_genesys_find_sensors_all(Genesys_Device* dev, ScanMethod scan_method) + sanei_genesys_find_sensors_all(const 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); + for (auto& sensor : *s_sensors) { + if (dev->model->sensor_id == sensor.sensor_id && sensor.method == scan_method) { + ret.push_back(sensor); + } } return ret; } @@ -308,6 +297,24 @@ void sanei_genesys_init_structs (Genesys_Device * dev) } } + if (dev->model->asic_type == AsicType::GL845 || + dev->model->asic_type == AsicType::GL846 || + dev->model->asic_type == AsicType::GL847 || + dev->model->asic_type == AsicType::GL124) + { + bool memory_layout_found = false; + for (const auto& memory_layout : *s_memory_layout) { + if (memory_layout.models.matches(dev->model->model_id)) { + dev->memory_layout = memory_layout; + memory_layout_found = true; + break; + } + } + if (!memory_layout_found) { + throw SaneException("Could not find memory layout"); + } + } + 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), @@ -316,33 +323,6 @@ void sanei_genesys_init_structs (Genesys_Device * dev) } } -/* 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 @@ -382,7 +362,7 @@ void sanei_genesys_create_default_gamma_table(Genesys_Device* dev, int size = 0; int max = 0; if (dev->model->asic_type == AsicType::GL646) { - if (dev->model->flags & GENESYS_FLAG_14BIT_GAMMA) { + if (has_flag(dev->model->flags, ModelFlag::GAMMA_14BIT)) { size = 16384; } else { size = 4096; @@ -411,34 +391,30 @@ void sanei_genesys_create_default_gamma_table(Genesys_Device* dev, 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) +SANE_Int sanei_genesys_exposure_time2(Genesys_Device * dev, const MotorProfile& profile, float ydpi, + 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; + unsigned max_speed_motor_w = profile.slope.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_motor) { + exposure = exposure_by_motor; + } - if (exposure < exposure_by_led && dev->model->is_cis) - exposure = exposure_by_led; + 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; + 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. */ @@ -446,7 +422,6 @@ static void genesys_send_offset_and_shading(Genesys_Device* dev, const Genesys_S 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 @@ -457,84 +432,30 @@ static void genesys_send_offset_and_shading(Genesys_Device* dev, const Genesys_S 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; - } + 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; - } + unsigned channels = dev->settings.get_channels(); // 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++) - { + for (unsigned 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 */ @@ -545,184 +466,6 @@ void sanei_genesys_init_shading_data(Genesys_Device* dev, const Genesys_Sensor& 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 @@ -730,6 +473,16 @@ namespace gl124 { void scanner_clear_scan_and_feed_counts(Genesys_Device& dev) { switch (dev.model->asic_type) { + case AsicType::GL841: { + dev.interface->write_register(gl841::REG_0x0D, + gl841::REG_0x0D_CLRLNCNT); + break; + } + case AsicType::GL842: { + dev.interface->write_register(gl842::REG_0x0D, + gl842::REG_0x0D_CLRLNCNT); + break; + } case AsicType::GL843: { dev.interface->write_register(gl843::REG_0x0D, gl843::REG_0x0D_CLRLNCNT | gl843::REG_0x0D_CLRMCNT); @@ -756,34 +509,107 @@ void scanner_clear_scan_and_feed_counts(Genesys_Device& dev) } } -void scanner_clear_scan_and_feed_counts2(Genesys_Device& dev) +void scanner_send_slope_table(Genesys_Device* dev, const Genesys_Sensor& sensor, unsigned table_nr, + const std::vector<uint16_t>& slope_table) { - // 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); + DBG_HELPER_ARGS(dbg, "table_nr = %d, steps = %zu", table_nr, slope_table.size()); + + unsigned max_table_nr = 0; + switch (dev->model->asic_type) { + case AsicType::GL646: { + max_table_nr = 2; break; } + case AsicType::GL841: + case AsicType::GL842: + case AsicType::GL843: 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); + case AsicType::GL846: + case AsicType::GL847: + case AsicType::GL124: { + max_table_nr = 4; 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); + default: + throw SaneException("Unsupported ASIC type"); + } + + if (table_nr > max_table_nr) { + throw SaneException("invalid table number %d", table_nr); + } + + std::vector<uint8_t> table; + table.reserve(slope_table.size() * 2); + for (std::size_t i = 0; i < slope_table.size(); i++) { + table.push_back(slope_table[i] & 0xff); + table.push_back(slope_table[i] >> 8); + } + if (dev->model->asic_type == AsicType::GL841 || + dev->model->model_id == ModelId::CANON_LIDE_90) + { + // BUG: do this on all gl842 scanners + auto max_table_size = get_slope_table_max_size(dev->model->asic_type); + table.reserve(max_table_size * 2); + while (table.size() < max_table_size * 2) { + table.push_back(slope_table.back() & 0xff); + table.push_back(slope_table.back() >> 8); + } + } + + if (dev->interface->is_mock()) { + dev->interface->record_slope_table(table_nr, slope_table); + } + + switch (dev->model->asic_type) { + case AsicType::GL646: { + unsigned dpihw = dev->reg.find_reg(0x05).value >> 6; + unsigned start_address = 0; + if (dpihw == 0) { // 600 dpi + start_address = 0x08000; + } else if (dpihw == 1) { // 1200 dpi + start_address = 0x10000; + } else if (dpihw == 2) { // 2400 dpi + start_address = 0x1f800; + } else { + throw SaneException("Unexpected dpihw"); + } + dev->interface->write_buffer(0x3c, start_address + table_nr * 0x100, table.data(), + table.size()); break; } + case AsicType::GL841: + case AsicType::GL842: { + unsigned start_address = 0; + switch (sensor.register_dpihw) { + case 600: start_address = 0x08000; break; + case 1200: start_address = 0x10000; break; + case 2400: start_address = 0x20000; break; + default: throw SaneException("Unexpected dpihw"); + } + dev->interface->write_buffer(0x3c, start_address + table_nr * 0x200, table.data(), + table.size()); + break; + } + case AsicType::GL843: { + // slope table addresses are fixed : 0x40000, 0x48000, 0x50000, 0x58000, 0x60000 + // XXX STEF XXX USB 1.1 ? sanei_genesys_write_0x8c (dev, 0x0f, 0x14); + dev->interface->write_gamma(0x28, 0x40000 + 0x8000 * table_nr, table.data(), + table.size()); + break; + } + case AsicType::GL845: + case AsicType::GL846: + case AsicType::GL847: 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); + // slope table addresses are fixed + dev->interface->write_ahb(0x10000000 + 0x4000 * table_nr, table.size(), + table.data()); break; } default: - throw SaneException("Unsupported asic type"); + throw SaneException("Unsupported ASIC type"); } + } bool scanner_is_motor_stopped(Genesys_Device& dev) @@ -794,9 +620,18 @@ bool scanner_is_motor_stopped(Genesys_Device& dev) return !status.is_motor_enabled && status.is_feeding_finished; } case AsicType::GL841: { + auto status = scanner_read_status(dev); auto reg = dev.interface->read_register(gl841::REG_0x40); - return (!(reg & gl841::REG_0x40_DATAENB) && !(reg & gl841::REG_0x40_MOTMFLG)); + return (!(reg & gl841::REG_0x40_DATAENB) && !(reg & gl841::REG_0x40_MOTMFLG) && + !status.is_motor_enabled); + } + case AsicType::GL842: { + auto status = scanner_read_status(dev); + auto reg = dev.interface->read_register(gl842::REG_0x40); + + return (!(reg & gl842::REG_0x40_DATAENB) && !(reg & gl842::REG_0x40_MOTMFLG) && + !status.is_motor_enabled); } case AsicType::GL843: { auto status = scanner_read_status(dev); @@ -832,11 +667,31 @@ bool scanner_is_motor_stopped(Genesys_Device& dev) } } +void scanner_setup_sensor(Genesys_Device& dev, const Genesys_Sensor& sensor, + Genesys_Register_Set& regs) +{ + DBG_HELPER(dbg); + + for (const auto& custom_reg : sensor.custom_regs) { + regs.set8(custom_reg.address, custom_reg.value); + } + + if (dev.model->asic_type != AsicType::GL841 && + dev.model->asic_type != AsicType::GL843) + { + regs_set_exposure(dev.model->asic_type, regs, sensor.exposure); + } + + dev.segment_order = sensor.segment_order; +} + void scanner_stop_action(Genesys_Device& dev) { DBG_HELPER(dbg); switch (dev.model->asic_type) { + case AsicType::GL841: + case AsicType::GL842: case AsicType::GL843: case AsicType::GL845: case AsicType::GL846: @@ -847,9 +702,7 @@ void scanner_stop_action(Genesys_Device& dev) throw SaneException("Unsupported asic type"); } - if (dev.cmd_set->needs_update_home_sensor_gpio()) { - dev.cmd_set->update_home_sensor_gpio(dev); - } + dev.cmd_set->update_home_sensor_gpio(dev); if (scanner_is_motor_stopped(dev)) { DBG(DBG_info, "%s: already stopped\n", __func__); @@ -878,6 +731,7 @@ void scanner_stop_action_no_move(Genesys_Device& dev, genesys::Genesys_Register_ switch (dev.model->asic_type) { case AsicType::GL646: case AsicType::GL841: + case AsicType::GL842: case AsicType::GL843: case AsicType::GL845: case AsicType::GL846: @@ -908,7 +762,9 @@ void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, D 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); + scan_method == ScanMethod::TRANSPARENCY_INFRARED) && + (!has_flag(dev.model->flags, ModelFlag::UTA_NO_SECONDARY_MOTOR)); + bool uses_secondary_pos = uses_secondary_head && dev.model->default_method == ScanMethod::FLATBED; @@ -934,21 +790,19 @@ void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, D session.params.yres = resolution; session.params.startx = 0; session.params.starty = steps; - session.params.pixels = 100; + session.params.pixels = 50; session.params.lines = 3; session.params.depth = 8; - session.params.channels = 3; + session.params.channels = 1; 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.scan_mode = ScanColorMode::GRAY; + session.params.color_filter = ColorFilter::GREEN; + session.params.flags = ScanFlag::DISABLE_SHADING | ScanFlag::DISABLE_GAMMA | ScanFlag::FEEDING | - ScanFlag::IGNORE_LINE_DISTANCE; + ScanFlag::IGNORE_STAGGER_OFFSET | + ScanFlag::IGNORE_COLOR_OFFSET; if (dev.model->asic_type == AsicType::GL124) { session.params.flags |= ScanFlag::DISABLE_BUFFER_FULL_MOVE; @@ -963,20 +817,21 @@ void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, D 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}); + regs_set_exposure(dev.model->asic_type, local_reg, + sanei_genesys_fixup_exposure({0, 0, 0})); } - scanner_clear_scan_and_feed_counts2(dev); + scanner_clear_scan_and_feed_counts(dev); dev.interface->write_registers(local_reg); if (uses_secondary_head) { - gl843::gl843_set_xpa_motor_power(&dev, local_reg, true); + dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY_AND_SECONDARY); } try { scanner_start_action(dev, true); } catch (...) { catch_all_exceptions(__func__, [&]() { - gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); + dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY); }); catch_all_exceptions(__func__, [&]() { scanner_stop_action(dev); }); // restore original registers @@ -992,17 +847,18 @@ void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, D 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); - } + scanner_stop_action(dev); if (uses_secondary_head) { - gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); + dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY); } return; } // wait until feed count reaches the required value + if (dev.model->model_id == ModelId::CANON_LIDE_700F) { + dev.cmd_set->update_home_sensor_gpio(dev); + } + // FIXME: should porbably wait for some timeout Status status; for (unsigned i = 0;; ++i) { @@ -1015,12 +871,9 @@ void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, D 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); - } + scanner_stop_action(dev); if (uses_secondary_head) { - gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); + dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY); } dev.advance_head_pos_by_steps(ScanHeadId::PRIMARY, direction, steps); @@ -1032,11 +885,22 @@ void scanner_move(Genesys_Device& dev, ScanMethod scan_method, unsigned steps, D dev.interface->sleep_ms(100); } +void scanner_move_to_ta(Genesys_Device& dev) +{ + DBG_HELPER(dbg); + + unsigned feed = static_cast<unsigned>((dev.model->y_offset_sensor_to_ta * dev.motor.base_ydpi) / + MM_PER_INCH); + scanner_move(dev, dev.model->default_method, feed, Direction::FORWARD); +} + 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::GL841: + case AsicType::GL842: case AsicType::GL843: case AsicType::GL845: case AsicType::GL846: @@ -1047,11 +911,17 @@ void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home) throw SaneException("Unsupported asic type"); } + if (dev.model->is_sheetfed) { + dbg.vlog(DBG_proc, "sheetfed scanner, skipping going back home"); + return; + } + // FIXME: also check whether the scanner actually has a secondary head - if (!dev.is_head_pos_known(ScanHeadId::SECONDARY) || + 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) + dev.settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) && + (!has_flag(dev.model->flags, ModelFlag::UTA_NO_SECONDARY_MOTOR))) { scanner_move_back_home_ta(dev); } @@ -1064,9 +934,7 @@ void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home) Direction::BACKWARD); } - if (dev.cmd_set->needs_update_home_sensor_gpio()) { - dev.cmd_set->update_home_sensor_gpio(dev); - } + dev.cmd_set->update_home_sensor_gpio(dev); auto status = scanner_read_reliable_status(dev); @@ -1076,15 +944,6 @@ void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home) 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); @@ -1093,28 +952,22 @@ void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home) 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.startx = 0; + session.params.starty = 40000; + session.params.pixels = 50; + session.params.lines = 3; 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.scan_mode = ScanColorMode::GRAY; + session.params.color_filter = ColorFilter::GREEN; + session.params.flags = ScanFlag::DISABLE_SHADING | ScanFlag::DISABLE_GAMMA | - ScanFlag::IGNORE_LINE_DISTANCE | + ScanFlag::IGNORE_STAGGER_OFFSET | + ScanFlag::IGNORE_COLOR_OFFSET | ScanFlag::REVERSE; + if (dev.model->asic_type == AsicType::GL843) { session.params.flags |= ScanFlag::DISABLE_BUFFER_FULL_MOVE; } @@ -1143,9 +996,7 @@ void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home) throw; } - if (dev.cmd_set->needs_update_home_sensor_gpio()) { - dev.cmd_set->update_home_sensor_gpio(dev); - } + dev.cmd_set->update_home_sensor_gpio(dev); if (is_testing_mode()) { dev.interface->test_checkpoint("move_back_home"); @@ -1174,18 +1025,49 @@ void scanner_move_back_home(Genesys_Device& dev, bool wait_until_home) // 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(); + dev.set_head_pos_unknown(ScanHeadId::PRIMARY | ScanHeadId::SECONDARY); throw SaneException(SANE_STATUS_IO_ERROR, "timeout while waiting for scanhead to go home"); } dbg.log(DBG_info, "scanhead is still moving"); } +namespace { + bool should_use_secondary_motor_mode(Genesys_Device& dev) + { + bool should_use = !dev.is_head_pos_known(ScanHeadId::SECONDARY) || + !dev.is_head_pos_known(ScanHeadId::PRIMARY) || + dev.head_pos(ScanHeadId::SECONDARY) > dev.head_pos(ScanHeadId::PRIMARY); + bool supports = dev.model->model_id == ModelId::CANON_8600F; + return should_use && supports; + } + + void handle_motor_position_after_move_back_home_ta(Genesys_Device& dev, MotorMode motor_mode) + { + if (motor_mode == MotorMode::SECONDARY) { + dev.set_head_pos_zero(ScanHeadId::SECONDARY); + return; + } + + 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); + } + } +} // namespace + void scanner_move_back_home_ta(Genesys_Device& dev) { DBG_HELPER(dbg); switch (dev.model->asic_type) { + case AsicType::GL842: case AsicType::GL843: + case AsicType::GL845: break; default: throw SaneException("Unsupported asic type"); @@ -1199,7 +1081,9 @@ void scanner_move_back_home_ta(Genesys_Device& dev) 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) + dev.is_head_pos_known(ScanHeadId::PRIMARY) && + dev.head_pos(ScanHeadId::SECONDARY) > 1000 && + dev.head_pos(ScanHeadId::SECONDARY) <= dev.head_pos(ScanHeadId::PRIMARY)) { // leave 500 steps for regular slow back home scanner_move(dev, scan_method, dev.head_pos(ScanHeadId::SECONDARY) - 500, @@ -1209,18 +1093,20 @@ void scanner_move_back_home_ta(Genesys_Device& dev) 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.startx = 0; + session.params.starty = 40000; + session.params.pixels = 50; + session.params.lines = 3; 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.color_filter = ColorFilter::GREEN; + session.params.flags = ScanFlag::DISABLE_SHADING | ScanFlag::DISABLE_GAMMA | - ScanFlag::IGNORE_LINE_DISTANCE | + ScanFlag::IGNORE_STAGGER_OFFSET | + ScanFlag::IGNORE_COLOR_OFFSET | ScanFlag::REVERSE; compute_session(&dev, session, sensor); @@ -1230,7 +1116,11 @@ void scanner_move_back_home_ta(Genesys_Device& dev) scanner_clear_scan_and_feed_counts(dev); dev.interface->write_registers(local_reg); - gl843::gl843_set_xpa_motor_power(&dev, local_reg, true); + + auto motor_mode = should_use_secondary_motor_mode(dev) ? MotorMode::SECONDARY + : MotorMode::PRIMARY_AND_SECONDARY; + + dev.cmd_set->set_motor_mode(dev, local_reg, motor_mode); try { scanner_start_action(dev, true); @@ -1244,18 +1134,10 @@ void scanner_move_back_home_ta(Genesys_Device& dev) 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); - } + handle_motor_position_after_move_back_home_ta(dev, motor_mode); scanner_stop_action(dev); - gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); + dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY); return; } @@ -1266,18 +1148,10 @@ void scanner_move_back_home_ta(Genesys_Device& 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); - } + handle_motor_position_after_move_back_home_ta(dev, motor_mode); scanner_stop_action(dev); - gl843::gl843_set_xpa_motor_power(&dev, local_reg, false); + dev.cmd_set->set_motor_mode(dev, local_reg, MotorMode::PRIMARY); return; } @@ -1287,325 +1161,1148 @@ void scanner_move_back_home_ta(Genesys_Device& dev) 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) +void scanner_search_strip(Genesys_Device& dev, bool forward, bool black) { - DBG(DBG_info, "%s: two_table=%d\n", __func__, two_table); + DBG_HELPER_ARGS(dbg, "%s %s", black ? "black" : "white", forward ? "forward" : "reverse"); - // acceleration total time - unsigned sum = std::accumulate(slope_table.begin(), slope_table.begin() + acceleration_steps, - 0, std::plus<unsigned>()); + if (dev.model->asic_type == AsicType::GL841 && !black && forward) { + dev.frontend.set_gain(0, 0xff); + dev.frontend.set_gain(1, 0xff); + dev.frontend.set_gain(2, 0xff); + } - /* 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; + // set up for a gray scan at lowest dpi + const auto& resolution_settings = dev.model->get_resolution_settings(dev.settings.scan_method); + unsigned dpi = resolution_settings.get_min_resolution_x(); + unsigned channels = 1; - /* 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]); + auto& sensor = sanei_genesys_find_sensor(&dev, dpi, channels, dev.settings.scan_method); + dev.cmd_set->set_fe(&dev, sensor, AFE_SET); + scanner_stop_action(dev); + + + // shading calibration is done with dev.motor.base_ydpi + unsigned lines = static_cast<unsigned>(dev.model->y_size_calib_mm * dpi / MM_PER_INCH); + if (dev.model->asic_type == AsicType::GL841) { + lines = 10; // TODO: use dev.model->search_lines + lines = static_cast<unsigned>((lines * dpi) / MM_PER_INCH); + } + + unsigned pixels = dev.model->x_size_calib_mm * dpi / MM_PER_INCH; + + dev.set_head_pos_zero(ScanHeadId::PRIMARY); + + unsigned length = 20; + if (dev.model->asic_type == AsicType::GL841) { + // 20 cm max length for calibration sheet + length = static_cast<unsigned>(((200 * dpi) / MM_PER_INCH) / lines); + } + + auto local_reg = dev.reg; + + ScanSession session; + session.params.xres = dpi; + session.params.yres = dpi; + session.params.startx = 0; + session.params.starty = 0; + session.params.pixels = pixels; + session.params.lines = lines; + session.params.depth = 8; + session.params.channels = channels; + session.params.scan_method = dev.settings.scan_method; + session.params.scan_mode = ScanColorMode::GRAY; + session.params.color_filter = ColorFilter::RED; + session.params.flags = ScanFlag::DISABLE_SHADING | + ScanFlag::DISABLE_GAMMA; + if (dev.model->asic_type != AsicType::GL841 && !forward) { + session.params.flags |= ScanFlag::REVERSE; + } + compute_session(&dev, session, sensor); + + dev.cmd_set->init_regs_for_scan_session(&dev, sensor, &local_reg, session); + + dev.interface->write_registers(local_reg); + + dev.cmd_set->begin_scan(&dev, sensor, &local_reg, true); + + if (is_testing_mode()) { + dev.interface->test_checkpoint("search_strip"); + scanner_stop_action(dev); + return; + } + + wait_until_buffer_non_empty(&dev); + + // now we're on target, we can read data + auto image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes); + + scanner_stop_action(dev); + + unsigned pass = 0; + if (dbg_log_image_data()) { + char title[80]; + std::sprintf(title, "gl_search_strip_%s_%s%02d.tiff", + black ? "black" : "white", forward ? "fwd" : "bwd", pass); + write_tiff_file(title, image); + } + + // loop until strip is found or maximum pass number done + bool found = false; + while (pass < length && !found) { + dev.interface->write_registers(local_reg); + + // now start scan + dev.cmd_set->begin_scan(&dev, sensor, &local_reg, true); + + wait_until_buffer_non_empty(&dev); + + // now we're on target, we can read data + image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes); + + scanner_stop_action(dev); + + if (dbg_log_image_data()) { + char title[80]; + std::sprintf(title, "gl_search_strip_%s_%s%02d.tiff", + black ? "black" : "white", + forward ? "fwd" : "bwd", static_cast<int>(pass)); + write_tiff_file(title, image); + } + + unsigned white_level = 90; + unsigned black_level = 60; + + std::size_t count = 0; + // Search data to find black strip + // When searching forward, we only need one line of the searched color since we + // will scan forward. But when doing backward search, we need all the area of the ame color + if (forward) { + + for (std::size_t y = 0; y < image.get_height() && !found; y++) { + count = 0; + + // count of white/black pixels depending on the color searched + for (std::size_t x = 0; x < image.get_width(); x++) { + + // when searching for black, detect white pixels + if (black && image.get_raw_channel(x, y, 0) > white_level) { + count++; + } + + // when searching for white, detect black pixels + if (!black && image.get_raw_channel(x, y, 0) < black_level) { + count++; + } + } + + // at end of line, if count >= 3%, line is not fully of the desired color + // so we must go to next line of the buffer */ + // count*100/pixels < 3 + + auto found_percentage = (count * 100 / image.get_width()); + if (found_percentage < 3) { + found = 1; + DBG(DBG_data, "%s: strip found forward during pass %d at line %zu\n", __func__, + pass, y); + } else { + DBG(DBG_data, "%s: pixels=%zu, count=%zu (%zu%%)\n", __func__, + image.get_width(), count, found_percentage); + } + } + } else { + /* since calibration scans are done forward, we need the whole area + to be of the required color when searching backward + */ + count = 0; + for (std::size_t y = 0; y < image.get_height(); y++) { + // count of white/black pixels depending on the color searched + for (std::size_t x = 0; x < image.get_width(); x++) { + // when searching for black, detect white pixels + if (black && image.get_raw_channel(x, y, 0) > white_level) { + count++; + } + // when searching for white, detect black pixels + if (!black && image.get_raw_channel(x, y, 0) < black_level) { + count++; + } + } + } + + // at end of area, if count >= 3%, area is not fully of the desired color + // so we must go to next buffer + auto found_percentage = count * 100 / (image.get_width() * image.get_height()); + if (found_percentage < 3) { + found = 1; + DBG(DBG_data, "%s: strip found backward during pass %d \n", __func__, pass); + } else { + DBG(DBG_data, "%s: pixels=%zu, count=%zu (%zu%%)\n", __func__, image.get_width(), + count, found_percentage); + } + } + pass++; + } + + if (found) { + DBG(DBG_info, "%s: %s strip found\n", __func__, black ? "black" : "white"); } else { - sum = sum + slope_table[acceleration_steps - 1]; + throw SaneException(SANE_STATUS_UNSUPPORTED, "%s strip not found", + black ? "black" : "white"); } - *out_z2 = sum % exposure_time; } -static uint8_t genesys_adjust_gain(double* applied_multi, double multi, uint8_t gain) +static int dark_average_channel(const Image& image, unsigned black, unsigned channel) +{ + auto channels = get_pixel_channels(image.get_format()); + + unsigned avg[3]; + + // computes average values on black margin + for (unsigned ch = 0; ch < channels; ch++) { + avg[ch] = 0; + unsigned count = 0; + // FIXME: start with the second line because the black pixels often have noise on the first + // line; the cause is probably incorrectly cleaned up previous scan + for (std::size_t y = 1; y < image.get_height(); y++) { + for (unsigned j = 0; j < black; j++) { + avg[ch] += image.get_raw_channel(j, y, ch); + count++; + } + } + if (count > 0) { + avg[ch] /= count; + } + DBG(DBG_info, "%s: avg[%d] = %d\n", __func__, ch, avg[ch]); + } + DBG(DBG_info, "%s: average = %d\n", __func__, avg[channel]); + return avg[channel]; +} + +bool should_calibrate_only_active_area(const Genesys_Device& dev, + const Genesys_Settings& settings) { - double voltage, original_voltage; - uint8_t new_gain = 0; + if (settings.scan_method == ScanMethod::TRANSPARENCY || + settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) + { + if (dev.model->model_id == ModelId::CANON_4400F && settings.xres >= 4800) { + return true; + } + if (dev.model->model_id == ModelId::CANON_8600F && settings.xres == 4800) { + return true; + } + } + return false; +} + +void scanner_offset_calibration(Genesys_Device& dev, const Genesys_Sensor& sensor, + Genesys_Register_Set& regs) +{ + DBG_HELPER(dbg); + + if (dev.model->asic_type == AsicType::GL842 && + dev.frontend.layout.type != FrontendType::WOLFSON) + { + return; + } + + if (dev.model->asic_type == AsicType::GL843 && + dev.frontend.layout.type != FrontendType::WOLFSON) + { + return; + } + + if (dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846) + { + // no gain nor offset for AKM AFE + std::uint8_t reg04 = dev.interface->read_register(gl846::REG_0x04); + if ((reg04 & gl846::REG_0x04_FESET) == 0x02) { + return; + } + } + if (dev.model->asic_type == AsicType::GL847) { + // no gain nor offset for AKM AFE + std::uint8_t reg04 = dev.interface->read_register(gl847::REG_0x04); + if ((reg04 & gl847::REG_0x04_FESET) == 0x02) { + return; + } + } - DBG(DBG_proc, "%s: multi=%f, gain=%d\n", __func__, multi, gain); + if (dev.model->asic_type == AsicType::GL124) { + std::uint8_t reg0a = dev.interface->read_register(gl124::REG_0x0A); + if (((reg0a & gl124::REG_0x0A_SIFSEL) >> gl124::REG_0x0AS_SIFSEL) == 3) { + return; + } + } - voltage = 0.5 + gain * 0.25; - original_voltage = voltage; + unsigned target_pixels = dev.model->x_size_calib_mm * sensor.full_resolution / MM_PER_INCH; + unsigned start_pixel = 0; + unsigned black_pixels = (sensor.black_pixels * sensor.full_resolution) / sensor.full_resolution; - voltage *= multi; + unsigned channels = 3; + unsigned lines = 1; + unsigned resolution = sensor.full_resolution; - new_gain = static_cast<std::uint8_t>((voltage - 0.5) * 4); - if (new_gain > 0x0e) - new_gain = 0x0e; + const Genesys_Sensor* calib_sensor = &sensor; + if (dev.model->asic_type == AsicType::GL843) { + lines = 8; - voltage = 0.5 + (new_gain) * 0.25; + // compute divider factor to compute final pixels number + const auto& dpihw_sensor = sanei_genesys_find_sensor(&dev, dev.settings.xres, channels, + dev.settings.scan_method); + resolution = dpihw_sensor.shading_resolution; + unsigned factor = sensor.full_resolution / resolution; - *applied_multi = voltage / original_voltage; + calib_sensor = &sanei_genesys_find_sensor(&dev, resolution, channels, + dev.settings.scan_method); - 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); + target_pixels = dev.model->x_size_calib_mm * resolution / MM_PER_INCH; + black_pixels = calib_sensor->black_pixels / factor; - return new_gain; -} + if (should_calibrate_only_active_area(dev, dev.settings)) { + float offset = dev.model->x_offset_ta; + start_pixel = static_cast<int>((offset * calib_sensor->get_optical_resolution()) / MM_PER_INCH); + float size = dev.model->x_size_ta; + target_pixels = static_cast<int>((size * calib_sensor->get_optical_resolution()) / MM_PER_INCH); + } -// 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) -{ + if (dev.model->model_id == ModelId::CANON_4400F && + dev.settings.scan_method == ScanMethod::FLATBED) + { + return; + } + } - DBG_HELPER_ARGS(dbg, "channels=%d, channel=%d, size=%d", channels, channel, size); - int gain_white_ref, sum, range; - int average; - int i; + if (dev.model->model_id == ModelId::CANON_5600F) { + // FIXME: use same approach as for GL843 scanners + lines = 8; + } - range = size / 50; + if (dev.model->asic_type == AsicType::GL847) { + calib_sensor = &sanei_genesys_find_sensor(&dev, resolution, channels, + dev.settings.scan_method); + } - if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || - dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) + ScanFlag flags = ScanFlag::DISABLE_SHADING | + ScanFlag::DISABLE_GAMMA | + ScanFlag::SINGLE_LINE | + ScanFlag::IGNORE_STAGGER_OFFSET | + ScanFlag::IGNORE_COLOR_OFFSET; + + if (dev.settings.scan_method == ScanMethod::TRANSPARENCY || + dev.settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) { - gain_white_ref = sensor.fau_gain_white_ref * 256; + flags |= ScanFlag::USE_XPA; + } + + ScanSession session; + session.params.xres = resolution; + session.params.yres = resolution; + session.params.startx = start_pixel; + session.params.starty = 0; + session.params.pixels = target_pixels; + session.params.lines = lines; + session.params.depth = 8; + session.params.channels = channels; + session.params.scan_method = dev.settings.scan_method; + session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; + session.params.color_filter = dev.model->asic_type == AsicType::GL843 ? ColorFilter::RED + : dev.settings.color_filter; + session.params.flags = flags; + compute_session(&dev, session, *calib_sensor); + + dev.cmd_set->init_regs_for_scan_session(&dev, *calib_sensor, ®s, session); + + unsigned output_pixels = session.output_pixels; + + sanei_genesys_set_motor_power(regs, false); + + int top[3], bottom[3]; + int topavg[3], bottomavg[3], avg[3]; + + // init gain and offset + for (unsigned ch = 0; ch < 3; ch++) + { + bottom[ch] = 10; + dev.frontend.set_offset(ch, bottom[ch]); + dev.frontend.set_gain(ch, 0); + } + dev.cmd_set->set_fe(&dev, *calib_sensor, AFE_SET); + + // scan with bottom AFE settings + dev.interface->write_registers(regs); + DBG(DBG_info, "%s: starting first line reading\n", __func__); + + dev.cmd_set->begin_scan(&dev, *calib_sensor, ®s, true); + + if (is_testing_mode()) { + dev.interface->test_checkpoint("offset_calibration"); + if (dev.model->asic_type == AsicType::GL842 || + dev.model->asic_type == AsicType::GL843) + { + scanner_stop_action_no_move(dev, regs); + } + return; + } + + Image first_line; + if (dev.model->asic_type == AsicType::GL842 || + dev.model->asic_type == AsicType::GL843) + { + first_line = read_unshuffled_image_from_scanner(&dev, session, + session.output_total_bytes_raw); + scanner_stop_action_no_move(dev, regs); } else { - gain_white_ref = sensor.gain_white_ref * 256; + first_line = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes); + + if (dev.model->model_id == ModelId::CANON_5600F) { + scanner_stop_action_no_move(dev, regs); + } + } + + if (dbg_log_image_data()) { + char fn[40]; + std::snprintf(fn, 40, "gl843_bottom_offset_%03d_%03d_%03d.tiff", + bottom[0], bottom[1], bottom[2]); + write_tiff_file(fn, first_line); } - if (range < 1) - range = 1; + for (unsigned ch = 0; ch < 3; ch++) { + bottomavg[ch] = dark_average_channel(first_line, black_pixels, ch); + DBG(DBG_info, "%s: bottom avg %d=%d\n", __func__, ch, bottomavg[ch]); + } - size = size / (2 * range * channels); + // now top value + for (unsigned ch = 0; ch < 3; ch++) { + top[ch] = 255; + dev.frontend.set_offset(ch, top[ch]); + } + dev.cmd_set->set_fe(&dev, *calib_sensor, AFE_SET); - data += (channel * 2); + // scan with top AFE values + dev.interface->write_registers(regs); + DBG(DBG_info, "%s: starting second line reading\n", __func__); - *max_average = 0; + dev.cmd_set->begin_scan(&dev, *calib_sensor, ®s, true); - while (size--) + Image second_line; + if (dev.model->asic_type == AsicType::GL842 || + dev.model->asic_type == AsicType::GL843) { - sum = 0; - for (i = 0; i < range; i++) - { - sum += (*data); - sum += *(data + 1) * 256; - data += (2 * channels); /* byte based */ - } + second_line = read_unshuffled_image_from_scanner(&dev, session, + session.output_total_bytes_raw); + scanner_stop_action_no_move(dev, regs); + } else { + second_line = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes); - average = (sum / range); - if (average > *max_average) - *max_average = average; + if (dev.model->model_id == ModelId::CANON_5600F) { + scanner_stop_action_no_move(dev, regs); + } } - DBG(DBG_proc, "%s: max_average=%d, gain_white_ref = %d, finished\n", __func__, *max_average, - gain_white_ref); + for (unsigned ch = 0; ch < 3; ch++){ + topavg[ch] = dark_average_channel(second_line, black_pixels, ch); + DBG(DBG_info, "%s: top avg %d=%d\n", __func__, ch, topavg[ch]); + } + + unsigned pass = 0; + + std::vector<std::uint8_t> debug_image; + std::size_t debug_image_lines = 0; + std::string debug_image_info; - if (*max_average >= gain_white_ref) - throw SaneException(SANE_STATUS_INVAL); + // loop until acceptable level + while ((pass < 32) && ((top[0] - bottom[0] > 1) || + (top[1] - bottom[1] > 1) || + (top[2] - bottom[2] > 1))) + { + pass++; + + for (unsigned ch = 0; ch < 3; ch++) { + if (top[ch] - bottom[ch] > 1) { + dev.frontend.set_offset(ch, (top[ch] + bottom[ch]) / 2); + } + } + dev.cmd_set->set_fe(&dev, *calib_sensor, AFE_SET); + + // scan with no move + dev.interface->write_registers(regs); + DBG(DBG_info, "%s: starting second line reading\n", __func__); + dev.cmd_set->begin_scan(&dev, *calib_sensor, ®s, true); + + if (dev.model->asic_type == AsicType::GL842 || + dev.model->asic_type == AsicType::GL843) + { + second_line = read_unshuffled_image_from_scanner(&dev, session, + session.output_total_bytes_raw); + scanner_stop_action_no_move(dev, regs); + } else { + second_line = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes); + + if (dev.model->model_id == ModelId::CANON_5600F) { + scanner_stop_action_no_move(dev, regs); + } + } + + if (dbg_log_image_data()) { + char title[100]; + std::snprintf(title, 100, "lines: %d pixels_per_line: %d offsets[0..2]: %d %d %d\n", + lines, output_pixels, + dev.frontend.get_offset(0), + dev.frontend.get_offset(1), + dev.frontend.get_offset(2)); + debug_image_info += title; + std::copy(second_line.get_row_ptr(0), + second_line.get_row_ptr(0) + second_line.get_row_bytes() * second_line.get_height(), + std::back_inserter(debug_image)); + debug_image_lines += lines; + } + + for (unsigned ch = 0; ch < 3; ch++) { + avg[ch] = dark_average_channel(second_line, black_pixels, ch); + DBG(DBG_info, "%s: avg[%d]=%d offset=%d\n", __func__, ch, avg[ch], + dev.frontend.get_offset(ch)); + } + + // compute new boundaries + for (unsigned ch = 0; ch < 3; ch++) { + if (topavg[ch] >= avg[ch]) { + topavg[ch] = avg[ch]; + top[ch] = dev.frontend.get_offset(ch); + } else { + bottomavg[ch] = avg[ch]; + bottom[ch] = dev.frontend.get_offset(ch); + } + } + } + + if (dbg_log_image_data()) { + sanei_genesys_write_file("gl_offset_all_desc.txt", + reinterpret_cast<const std::uint8_t*>(debug_image_info.data()), + debug_image_info.size()); + write_tiff_file("gl_offset_all.tiff", debug_image.data(), session.params.depth, channels, + output_pixels, debug_image_lines); + } + + DBG(DBG_info, "%s: offset=(%d,%d,%d)\n", __func__, + dev.frontend.get_offset(0), + dev.frontend.get_offset(1), + dev.frontend.get_offset(2)); } -/* todo: understand, values are too high */ -static int -genesys_average_black (Genesys_Device * dev, int channel, - uint8_t * data, int pixels) +/* With offset and coarse calibration we only want to get our input range into + a reasonable shape. the fine calibration of the upper and lower bounds will + be done with shading. +*/ +void scanner_coarse_gain_calibration(Genesys_Device& dev, const Genesys_Sensor& sensor, + Genesys_Register_Set& regs, unsigned dpi) { - int i; - int sum; - int pixel_step; + DBG_HELPER_ARGS(dbg, "dpi = %d", dpi); - DBG(DBG_proc, "%s: channel=%d, pixels=%d\n", __func__, channel, pixels); + if (dev.model->asic_type == AsicType::GL842 && + dev.frontend.layout.type != FrontendType::WOLFSON) + { + return; + } - sum = 0; + if (dev.model->asic_type == AsicType::GL843 && + dev.frontend.layout.type != FrontendType::WOLFSON) + { + return; + } - if (dev->settings.scan_mode == ScanColorMode::COLOR_SINGLE_PASS) + if (dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846) { - data += (channel * 2); - pixel_step = 3 * 2; + // no gain nor offset for AKM AFE + std::uint8_t reg04 = dev.interface->read_register(gl846::REG_0x04); + if ((reg04 & gl846::REG_0x04_FESET) == 0x02) { + return; + } } - else + + if (dev.model->asic_type == AsicType::GL847) { + // no gain nor offset for AKM AFE + std::uint8_t reg04 = dev.interface->read_register(gl847::REG_0x04); + if ((reg04 & gl847::REG_0x04_FESET) == 0x02) { + return; + } + } + + if (dev.model->asic_type == AsicType::GL124) { + // no gain nor offset for TI AFE + std::uint8_t reg0a = dev.interface->read_register(gl124::REG_0x0A); + if (((reg0a & gl124::REG_0x0A_SIFSEL) >> gl124::REG_0x0AS_SIFSEL) == 3) { + return; + } + } + + if (dev.model->asic_type == AsicType::GL841) { + // feed to white strip if needed + if (dev.model->y_offset_calib_white > 0) { + unsigned move = static_cast<unsigned>( + (dev.model->y_offset_calib_white * (dev.motor.base_ydpi)) / MM_PER_INCH); + scanner_move(dev, dev.model->default_method, move, Direction::FORWARD); + } + } + + // coarse gain calibration is always done in color mode + unsigned channels = 3; + + unsigned resolution = sensor.full_resolution; + if (dev.model->asic_type == AsicType::GL841) { + const auto& dpihw_sensor = sanei_genesys_find_sensor(&dev, dev.settings.xres, channels, + dev.settings.scan_method); + resolution = dpihw_sensor.shading_resolution; + } + + if (dev.model->asic_type == AsicType::GL842 || + dev.model->asic_type == AsicType::GL843) { - pixel_step = 2; + const auto& dpihw_sensor = sanei_genesys_find_sensor(&dev, dpi, channels, + dev.settings.scan_method); + resolution = dpihw_sensor.shading_resolution; } - for (i = 0; i < pixels; i++) + float coeff = 1; + + // Follow CKSEL + if (dev.model->sensor_id == SensorId::CCD_KVSS080 || + dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846 || + dev.model->asic_type == AsicType::GL847 || + dev.model->asic_type == AsicType::GL124) { - sum += *data; - sum += *(data + 1) * 256; + if (dev.settings.xres < sensor.full_resolution) { + coeff = 0.9f; + } + } - data += pixel_step; + unsigned lines = 10; + if (dev.model->asic_type == AsicType::GL841) { + lines = 1; } - DBG(DBG_proc, "%s = %d\n", __func__, sum / pixels); + const Genesys_Sensor* calib_sensor = &sensor; + if (dev.model->asic_type == AsicType::GL841 || + dev.model->asic_type == AsicType::GL842 || + dev.model->asic_type == AsicType::GL843 || + dev.model->asic_type == AsicType::GL847) + { + calib_sensor = &sanei_genesys_find_sensor(&dev, resolution, channels, + dev.settings.scan_method); + } - return sum / pixels; + ScanFlag flags = ScanFlag::DISABLE_SHADING | + ScanFlag::DISABLE_GAMMA | + ScanFlag::SINGLE_LINE | + ScanFlag::IGNORE_STAGGER_OFFSET | + ScanFlag::IGNORE_COLOR_OFFSET; + + if (dev.settings.scan_method == ScanMethod::TRANSPARENCY || + dev.settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) + { + flags |= ScanFlag::USE_XPA; + } + + ScanSession session; + session.params.xres = resolution; + session.params.yres = dev.model->asic_type == AsicType::GL841 ? dev.settings.yres : resolution; + session.params.startx = 0; + session.params.starty = 0; + session.params.pixels = dev.model->x_size_calib_mm * resolution / MM_PER_INCH; + session.params.lines = lines; + session.params.depth = dev.model->asic_type == AsicType::GL841 ? 16 : 8; + session.params.channels = channels; + session.params.scan_method = dev.settings.scan_method; + session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; + session.params.color_filter = dev.settings.color_filter; + session.params.flags = flags; + compute_session(&dev, session, *calib_sensor); + + std::size_t pixels = session.output_pixels; + + try { + dev.cmd_set->init_regs_for_scan_session(&dev, *calib_sensor, ®s, session); + } catch (...) { + if (dev.model->asic_type != AsicType::GL841) { + catch_all_exceptions(__func__, [&](){ sanei_genesys_set_motor_power(regs, false); }); + } + throw; + } + + if (dev.model->asic_type != AsicType::GL841) { + sanei_genesys_set_motor_power(regs, false); + } + + dev.interface->write_registers(regs); + + if (dev.model->asic_type != AsicType::GL841) { + dev.cmd_set->set_fe(&dev, *calib_sensor, AFE_SET); + } + dev.cmd_set->begin_scan(&dev, *calib_sensor, ®s, true); + + if (is_testing_mode()) { + dev.interface->test_checkpoint("coarse_gain_calibration"); + scanner_stop_action(dev); + dev.cmd_set->move_back_home(&dev, true); + return; + } + + Image image; + if (dev.model->asic_type == AsicType::GL842 || + dev.model->asic_type == AsicType::GL843) + { + image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes_raw); + } else if (dev.model->asic_type == AsicType::GL124) { + // BUG: we probably want to read whole image, not just first line + image = read_unshuffled_image_from_scanner(&dev, session, session.output_line_bytes); + } else { + image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes); + } + + if (dev.model->asic_type == AsicType::GL842 || + dev.model->asic_type == AsicType::GL843) + { + scanner_stop_action_no_move(dev, regs); + } + + if (dbg_log_image_data()) { + write_tiff_file("gl_coarse_gain.tiff", image); + } + + for (unsigned ch = 0; ch < channels; ch++) { + float curr_output = 0; + float target_value = 0; + + if (dev.model->asic_type == AsicType::GL842 || + dev.model->asic_type == AsicType::GL843) + { + std::vector<uint16_t> values; + // FIXME: start from the second line because the first line often has artifacts. Probably + // caused by unclean cleanup of previous scan + for (std::size_t x = pixels / 4; x < (pixels * 3 / 4); x++) { + values.push_back(image.get_raw_channel(x, 1, ch)); + } + + // pick target value at 95th percentile of all values. There may be a lot of black values + // in transparency scans for example + std::sort(values.begin(), values.end()); + curr_output = static_cast<float>(values[unsigned((values.size() - 1) * 0.95)]); + target_value = calib_sensor->gain_white_ref * coeff; + + } else if (dev.model->asic_type == AsicType::GL841) { + // FIXME: use the GL843 approach + unsigned max = 0; + for (std::size_t x = 0; x < image.get_width(); x++) { + auto value = image.get_raw_channel(x, 0, ch); + if (value > max) { + max = value; + } + } + + curr_output = max; + target_value = 65535.0f; + } else { + // FIXME: use the GL843 approach + auto width = image.get_width(); + + std::uint64_t total = 0; + for (std::size_t x = width / 4; x < (width * 3 / 4); x++) { + total += image.get_raw_channel(x, 0, ch); + } + + curr_output = total / (width / 2); + target_value = calib_sensor->gain_white_ref * coeff; + } + + std::uint8_t out_gain = compute_frontend_gain(curr_output, target_value, + dev.frontend.layout.type); + dev.frontend.set_gain(ch, out_gain); + + DBG(DBG_proc, "%s: channel %d, curr=%f, target=%f, out_gain:%d\n", __func__, ch, + curr_output, target_value, out_gain); + + if (dev.model->asic_type == AsicType::GL841 && + target_value / curr_output > 30) + { + DBG(DBG_error0, "****************************************\n"); + DBG(DBG_error0, "* *\n"); + DBG(DBG_error0, "* Extremely low Brightness detected. *\n"); + DBG(DBG_error0, "* Check the scanning head is *\n"); + DBG(DBG_error0, "* unlocked and moving. *\n"); + DBG(DBG_error0, "* *\n"); + DBG(DBG_error0, "****************************************\n"); + throw SaneException(SANE_STATUS_JAMMED, "scanning head is locked"); + } + + dbg.vlog(DBG_info, "gain=(%d, %d, %d)", dev.frontend.get_gain(0), dev.frontend.get_gain(1), + dev.frontend.get_gain(2)); + } + + if (dev.model->is_cis) { + std::uint8_t min_gain = std::min({dev.frontend.get_gain(0), + dev.frontend.get_gain(1), + dev.frontend.get_gain(2)}); + + dev.frontend.set_gain(0, min_gain); + dev.frontend.set_gain(1, min_gain); + dev.frontend.set_gain(2, min_gain); + } + + dbg.vlog(DBG_info, "final gain=(%d, %d, %d)", dev.frontend.get_gain(0), + dev.frontend.get_gain(1), dev.frontend.get_gain(2)); + + scanner_stop_action(dev); + + dev.cmd_set->move_back_home(&dev, true); } +namespace gl124 { + void move_to_calibration_area(Genesys_Device* dev, const Genesys_Sensor& sensor, + Genesys_Register_Set& regs); +} // namespace gl124 -// 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) +SensorExposure scanner_led_calibration(Genesys_Device& dev, const Genesys_Sensor& sensor, + Genesys_Register_Set& regs) { - 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; + DBG_HELPER(dbg); - black_pixels = sensor.black_pixels - * dev->settings.xres / sensor.optical_res; + float move = 0; - unsigned channels = dev->settings.get_channels(); + if (dev.model->asic_type == AsicType::GL841) { + if (dev.model->y_offset_calib_white > 0) { + move = (dev.model->y_offset_calib_white * (dev.motor.base_ydpi)) / MM_PER_INCH; + scanner_move(dev, dev.model->default_method, static_cast<unsigned>(move), + Direction::FORWARD); + } + } else if (dev.model->asic_type == AsicType::GL842 || + dev.model->asic_type == AsicType::GL843) + { + // do nothing + } else if (dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846 || + dev.model->asic_type == AsicType::GL847) + { + move = dev.model->y_offset_calib_white; + move = static_cast<float>((move * (dev.motor.base_ydpi / 4)) / MM_PER_INCH); + if (move > 20) { + scanner_move(dev, dev.model->default_method, static_cast<unsigned>(move), + Direction::FORWARD); + } + } else if (dev.model->asic_type == AsicType::GL124) { + gl124::move_to_calibration_area(&dev, sensor, regs); + } - 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); + unsigned channels = 3; + unsigned resolution = sensor.shading_resolution; + const auto& calib_sensor = sanei_genesys_find_sensor(&dev, resolution, channels, + dev.settings.scan_method); + + if (dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846 || + dev.model->asic_type == AsicType::GL847 || + dev.model->asic_type == AsicType::GL124) + { + regs = dev.reg; // FIXME: apply this to all ASICs + } + + unsigned yres = resolution; + if (dev.model->asic_type == AsicType::GL841) { + yres = dev.settings.yres; // FIXME: remove this + } + + ScanSession session; + session.params.xres = resolution; + session.params.yres = yres; + session.params.startx = 0; + session.params.starty = 0; + session.params.pixels = dev.model->x_size_calib_mm * resolution / MM_PER_INCH; + session.params.lines = 1; + session.params.depth = 16; + session.params.channels = channels; + session.params.scan_method = dev.settings.scan_method; + session.params.scan_mode = ScanColorMode::COLOR_SINGLE_PASS; + session.params.color_filter = dev.settings.color_filter; + session.params.flags = ScanFlag::DISABLE_SHADING | + ScanFlag::DISABLE_GAMMA | + ScanFlag::SINGLE_LINE | + ScanFlag::IGNORE_STAGGER_OFFSET | + ScanFlag::IGNORE_COLOR_OFFSET; + compute_session(&dev, session, calib_sensor); + + dev.cmd_set->init_regs_for_scan_session(&dev, calib_sensor, ®s, session); + + if (dev.model->asic_type == AsicType::GL841) { + dev.interface->write_registers(regs); // FIXME: remove this + } + + std::uint16_t exp[3]; - dev->cmd_set->set_fe(dev, sensor, AFE_INIT); + if (dev.model->asic_type == AsicType::GL841) { + exp[0] = sensor.exposure.red; + exp[1] = sensor.exposure.green; + exp[2] = sensor.exposure.blue; + } else { + exp[0] = calib_sensor.exposure.red; + exp[1] = calib_sensor.exposure.green; + exp[2] = calib_sensor.exposure.blue; + } + + std::uint16_t target = sensor.gain_white_ref * 256; - 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]); + std::uint16_t min_exposure = 500; // only gl841 + std::uint16_t max_exposure = ((exp[0] + exp[1] + exp[2]) / 3) * 2; // only gl841 - for (i = 0; i < 4; i++) /* read 4 lines */ + std::uint16_t top[3] = {}; + std::uint16_t bottom[3] = {}; + + if (dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846) { - 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]); + bottom[0] = 29000; + bottom[1] = 29000; + bottom[2] = 29000; + + top[0] = 41000; + top[1] = 51000; + top[2] = 51000; + } else if (dev.model->asic_type == AsicType::GL847) { + bottom[0] = 28000; + bottom[1] = 28000; + bottom[2] = 28000; + + top[0] = 32000; + top[1] = 32000; + top[2] = 32000; + } + + if (dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846 || + dev.model->asic_type == AsicType::GL847 || + dev.model->asic_type == AsicType::GL124) + { + sanei_genesys_set_motor_power(regs, false); + } + + bool acceptable = false; + for (unsigned i_test = 0; i_test < 100 && !acceptable; ++i_test) { + regs_set_exposure(dev.model->asic_type, regs, { exp[0], exp[1], exp[2] }); + + if (dev.model->asic_type == AsicType::GL841) { + // FIXME: remove + dev.interface->write_register(0x10, (exp[0] >> 8) & 0xff); + dev.interface->write_register(0x11, exp[0] & 0xff); + dev.interface->write_register(0x12, (exp[1] >> 8) & 0xff); + dev.interface->write_register(0x13, exp[1] & 0xff); + dev.interface->write_register(0x14, (exp[2] >> 8) & 0xff); + dev.interface->write_register(0x15, exp[2] & 0xff); } - if (i == 1) /* second line */ - { - double applied_multi; - double gain_white_ref; + dev.interface->write_registers(regs); - if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || - dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) - { - gain_white_ref = sensor.fau_gain_white_ref * 256; + dbg.log(DBG_info, "starting line reading"); + dev.cmd_set->begin_scan(&dev, calib_sensor, ®s, true); + + if (is_testing_mode()) { + dev.interface->test_checkpoint("led_calibration"); + if (dev.model->asic_type == AsicType::GL841) { + scanner_stop_action(dev); + dev.cmd_set->move_back_home(&dev, true); + return { exp[0], exp[1], exp[2] }; + } else if (dev.model->asic_type == AsicType::GL124) { + scanner_stop_action(dev); + return calib_sensor.exposure; } else { - gain_white_ref = sensor.gain_white_ref * 256; + scanner_stop_action(dev); + dev.cmd_set->move_back_home(&dev, true); + return calib_sensor.exposure; } + } - // 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)); - } + auto image = read_unshuffled_image_from_scanner(&dev, session, session.output_line_bytes); - if (i == 3) /* last line */ - { - double x, y, rate; + scanner_stop_action(dev); - for (j = 0; j < 3; j++) - { + if (dbg_log_image_data()) { + char fn[30]; + std::snprintf(fn, 30, "gl_led_%02d.tiff", i_test); + write_tiff_file(fn, image); + } - 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; + int avg[3]; + for (unsigned ch = 0; ch < channels; ch++) { + avg[ch] = 0; + for (std::size_t x = 0; x < image.get_width(); x++) { + avg[ch] += image.get_raw_channel(x, 0, ch); + } + avg[ch] /= image.get_width(); + } - uint8_t curr_offset = static_cast<uint8_t>(rate); + dbg.vlog(DBG_info, "average: %d, %d, %d", avg[0], avg[1], avg[2]); - 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)); + acceptable = true; - 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)); + if (dev.model->asic_type == AsicType::GL841) { + if (avg[0] < avg[1] * 0.95 || avg[1] < avg[0] * 0.95 || + avg[0] < avg[2] * 0.95 || avg[2] < avg[0] * 0.95 || + avg[1] < avg[2] * 0.95 || avg[2] < avg[1] * 0.95) + { + acceptable = false; + } + // led exposure is not acceptable if white level is too low. + // ~80 hardcoded value for white level + if (avg[0] < 20000 || avg[1] < 20000 || avg[2] < 20000) { + acceptable = false; + } - dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, false); + // for scanners using target value + if (target > 0) { + acceptable = true; + for (unsigned i = 0; i < 3; i++) { + // we accept +- 2% delta from target + if (std::abs(avg[i] - target) > target / 50) { + exp[i] = (exp[i] * target) / avg[i]; + acceptable = false; + } + } + } else { + if (!acceptable) { + unsigned avga = (avg[0] + avg[1] + avg[2]) / 3; + exp[0] = (exp[0] * avga) / avg[0]; + exp[1] = (exp[1] * avga) / avg[1]; + exp[2] = (exp[2] * avga) / avg[2]; + /* Keep the resulting exposures below this value. Too long exposure drives + the ccd into saturation. We may fix this by relying on the fact that + we get a striped scan without shading, by means of statistical calculation + */ + unsigned avge = (exp[0] + exp[1] + exp[2]) / 3; + + if (avge > max_exposure) { + exp[0] = (exp[0] * max_exposure) / avge; + exp[1] = (exp[1] * max_exposure) / avge; + exp[2] = (exp[2] * max_exposure) / avge; + } + if (avge < min_exposure) { + exp[0] = (exp[0] * min_exposure) / avge; + exp[1] = (exp[1] * min_exposure) / avge; + exp[2] = (exp[2] * min_exposure) / avge; + } - if (is_testing_mode()) { - dev->interface->test_checkpoint("coarse_calibration"); - dev->cmd_set->end_scan(dev, &dev->calib_reg, true); - return; + } + } + } else if (dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846) + { + for (unsigned i = 0; i < 3; i++) { + if (avg[i] < bottom[i]) { + if (avg[i] != 0) { + exp[i] = (exp[i] * bottom[i]) / avg[i]; + } else { + exp[i] *= 10; + } + acceptable = false; + } + if (avg[i] > top[i]) { + if (avg[i] != 0) { + exp[i] = (exp[i] * top[i]) / avg[i]; + } else { + exp[i] *= 10; + } + acceptable = false; + } + } + } else if (dev.model->asic_type == AsicType::GL847) { + for (unsigned i = 0; i < 3; i++) { + if (avg[i] < bottom[i] || avg[i] > top[i]) { + auto target = (bottom[i] + top[i]) / 2; + if (avg[i] != 0) { + exp[i] = (exp[i] * target) / avg[i]; + } else { + exp[i] *= 10; + } + + acceptable = false; + } + } + } else if (dev.model->asic_type == AsicType::GL124) { + for (unsigned i = 0; i < 3; i++) { + // we accept +- 2% delta from target + if (std::abs(avg[i] - target) > target / 50) { + float prev_weight = 0.5; + if (avg[i] != 0) { + exp[i] = exp[i] * prev_weight + ((exp[i] * target) / avg[i]) * (1 - prev_weight); + } else { + exp[i] = exp[i] * prev_weight + (exp[i] * 10) * (1 - prev_weight); + } + acceptable = false; + } + } } + } - 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; + if (dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846 || + dev.model->asic_type == AsicType::GL847 || + dev.model->asic_type == AsicType::GL124) + { + // set these values as final ones for scan + regs_set_exposure(dev.model->asic_type, dev.reg, { exp[0], exp[1], exp[2] }); + } - for (count = 0; count < static_cast<unsigned>(size * 4 / 2); count++) { - all_data_8[count] = all_data[count * 2 + 1]; + if (dev.model->asic_type == AsicType::GL841 || + dev.model->asic_type == AsicType::GL842 || + dev.model->asic_type == AsicType::GL843) + { + dev.cmd_set->move_back_home(&dev, true); + } + + if (dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846 || + dev.model->asic_type == AsicType::GL847) + { + if (move > 20) { + dev.cmd_set->move_back_home(&dev, true); } - 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); + dbg.vlog(DBG_info,"acceptable exposure: %d, %d, %d\n", exp[0], exp[1], exp[2]); - 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)); + return { exp[0], exp[1], exp[2] }; +} + +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) +{ + // 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; } /** @@ -1614,22 +2311,41 @@ static void genesys_coarse_calibration(Genesys_Device* dev, Genesys_Sensor& sens * @param dev scanner's device */ static void genesys_shading_calibration_impl(Genesys_Device* dev, const Genesys_Sensor& sensor, + Genesys_Register_Set& local_reg, std::vector<std::uint16_t>& out_average_data, bool is_dark, const std::string& log_filename_prefix) { DBG_HELPER(dbg); + if (dev->model->asic_type == AsicType::GL646) { + dev->cmd_set->init_regs_for_shading(dev, sensor, local_reg); + local_reg = dev->reg; + } else { + local_reg = dev->reg; + dev->cmd_set->init_regs_for_shading(dev, sensor, local_reg); + dev->interface->write_registers(local_reg); + } + 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; + if (dev->model->asic_type == AsicType::GL842 || + dev->model->asic_type == AsicType::GL843 || + dev->model->model_id == ModelId::CANON_5600F) + { + pixels_per_line = dev->calib_session.output_pixels; + } else { + // BUG: this selects incorrect pixel number + pixels_per_line = dev->calib_session.params.pixels; + } + unsigned channels = dev->calib_session.params.channels; - uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset; + // BUG: we are using wrong pixel number here + unsigned start_offset = + dev->calib_session.params.startx * sensor.full_resolution / dev->calib_session.params.xres; + unsigned out_pixels_per_line = pixels_per_line + start_offset; // FIXME: we set this during both dark and white calibration. A cleaner approach should // probably be used @@ -1644,61 +2360,55 @@ static void genesys_shading_calibration_impl(Genesys_Device* dev, const Genesys_ } // 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; + // but this needs checking. Note the extra line when computing size. + if (dev->model->asic_type == AsicType::GL842 || + dev->model->asic_type == AsicType::GL843 || + dev->model->model_id == ModelId::CANON_5600F) + { + size = dev->calib_session.output_total_bytes_raw; } else { - size = channels * 2 * pixels_per_line * (dev->calib_lines + 1); + size = channels * 2 * pixels_per_line * (dev->calib_session.params.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); + sanei_genesys_set_lamp_power(dev, sensor, local_reg, false); } else { - sanei_genesys_set_lamp_power(dev, sensor, dev->calib_reg, true); - sanei_genesys_set_motor_power(dev->calib_reg, motor); + sanei_genesys_set_lamp_power(dev, sensor, local_reg, true); } + sanei_genesys_set_motor_power(local_reg, true); - dev->interface->write_registers(dev->calib_reg); + dev->interface->write_registers(local_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) { + } else if (has_flag(dev->model->flags, ModelFlag::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); + dev->cmd_set->begin_scan(dev, sensor, &local_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); + dev->cmd_set->end_scan(dev, &local_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); + dev->cmd_set->end_scan(dev, &local_reg, true); - if (dev->model->flags & GENESYS_FLAG_16BIT_DATA_INVERTED) { + if (has_flag(dev->model->flags, ModelFlag::SWAP_16BIT_DATA)) { for (std::size_t i = 0; i < size / 2; ++i) { auto value = calibration_data[i]; value = ((value >> 8) & 0xff) | ((value << 8) & 0xff00); @@ -1706,30 +2416,29 @@ static void genesys_shading_calibration_impl(Genesys_Device* dev, const Genesys_ } } + if (has_flag(dev->model->flags, ModelFlag::INVERT_PIXEL_DATA)) { + for (std::size_t i = 0; i < size / 2; ++i) { + calibration_data[i] = 0xffff - calibration_data[i]; + } + } + std::fill(out_average_data.begin(), - out_average_data.begin() + dev->calib_pixels_offset * channels, 0); + out_average_data.begin() + start_offset * channels, 0); - compute_array_percentile_approx(out_average_data.data() + dev->calib_pixels_offset * channels, + compute_array_percentile_approx(out_average_data.data() + + start_offset * channels, calibration_data.data(), - dev->calib_lines, pixels_per_line * channels, + dev->calib_session.params.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); + if (dbg_log_image_data()) { + write_tiff_file(log_filename_prefix + "_shading.tiff", calibration_data.data(), 16, + channels, pixels_per_line, dev->calib_session.params.lines); + write_tiff_file(log_filename_prefix + "_average.tiff", out_average_data.data(), 16, + 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 @@ -1737,18 +2446,28 @@ static void genesys_dark_shading_calibration(Genesys_Device* dev, const Genesys_ * can be computed from previous calibration data (when doing offset * calibration ?) */ -static void genesys_dummy_dark_shading(Genesys_Device* dev, const Genesys_Sensor& sensor) +static void genesys_dark_shading_by_dummy_pixel(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; + if (dev->model->asic_type == AsicType::GL842 || + dev->model->asic_type == AsicType::GL843) + { + pixels_per_line = dev->calib_session.output_pixels; + } else { + pixels_per_line = dev->calib_session.params.pixels; + } + + unsigned channels = dev->calib_session.params.channels; + + // BUG: we are using wrong pixel number here + unsigned start_offset = + dev->calib_session.params.startx * sensor.full_resolution / dev->calib_session.params.xres; - uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset; + unsigned out_pixels_per_line = pixels_per_line + start_offset; dev->average_size = channels * out_pixels_per_line; dev->dark_average_data.clear(); @@ -1756,8 +2475,7 @@ static void genesys_dummy_dark_shading(Genesys_Device* dev, const Genesys_Sensor /* 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) - { + if (dev->settings.xres <= sensor.full_resolution / 2) { skip = 4; xend = 36; } @@ -1807,17 +2525,22 @@ static void genesys_dummy_dark_shading(Genesys_Device* dev, const Genesys_Sensor } } +static void genesys_dark_shading_by_constant(Genesys_Device& dev) +{ + dev.dark_average_data.clear(); + dev.dark_average_data.resize(dev.average_size, 0x0101); +} static void genesys_repark_sensor_before_shading(Genesys_Device* dev) { DBG_HELPER(dbg); - if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK) { + if (has_flag(dev->model->flags, ModelFlag::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); + scanner_move_to_ta(*dev); } } } @@ -1825,34 +2548,153 @@ static void genesys_repark_sensor_before_shading(Genesys_Device* dev) static void genesys_repark_sensor_after_white_shading(Genesys_Device* dev) { DBG_HELPER(dbg); - if (dev->model->flags & GENESYS_FLAG_SHADING_REPARK) { + if (has_flag(dev->model->flags, ModelFlag::SHADING_REPARK)) { dev->cmd_set->move_back_home(dev, true); } } -static void genesys_white_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor) +static void genesys_host_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); - genesys_shading_calibration_impl(dev, sensor, dev->white_average_data, false, "gl_white_"); + + if (is_dark && dev.settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) { + // FIXME: dark shading currently not supported on infrared transparency scans + return; + } + + auto local_reg = dev.reg; + dev.cmd_set->init_regs_for_shading(&dev, sensor, local_reg); + + auto& session = dev.calib_session; + debug_dump(DBG_info, session); + + // 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, local_reg, false); + } else { + sanei_genesys_set_lamp_power(&dev, sensor, local_reg, true); + } + sanei_genesys_set_motor_power(local_reg, true); + + dev.interface->write_registers(local_reg); + + if (is_dark) { + // wait some time to let lamp to get dark + dev.interface->sleep_ms(200); + } else if (has_flag(dev.model->flags, ModelFlag::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, &local_reg, start_motor); + + if (is_testing_mode()) { + dev.interface->test_checkpoint(is_dark ? "host_dark_shading_calibration" + : "host_white_shading_calibration"); + dev.cmd_set->end_scan(&dev, &local_reg, true); + return; + } + + Image image = read_unshuffled_image_from_scanner(&dev, session, session.output_total_bytes_raw); + scanner_stop_action(dev); + + auto start_offset = session.params.startx; + auto out_pixels_per_line = start_offset + session.output_pixels; + + // FIXME: we set this during both dark and white calibration. A cleaner approach should + // probably be used + dev.average_size = session.params.channels * out_pixels_per_line; + + out_average_data.clear(); + out_average_data.resize(dev.average_size); + + std::fill(out_average_data.begin(), + out_average_data.begin() + start_offset * session.params.channels, 0); + + compute_array_percentile_approx(out_average_data.data() + + start_offset * session.params.channels, + reinterpret_cast<std::uint16_t*>(image.get_row_ptr(0)), + session.params.lines, + session.output_pixels * session.params.channels, + 0.5f); + + if (dbg_log_image_data()) { + write_tiff_file(log_filename_prefix + "_host_shading.tiff", image); + write_tiff_file(log_filename_prefix + "_host_average.tiff", out_average_data.data(), 16, + session.params.channels, out_pixels_per_line, 1); + } +} + +static void genesys_dark_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, + Genesys_Register_Set& local_reg) +{ + DBG_HELPER(dbg); + if (has_flag(dev->model->flags, ModelFlag::HOST_SIDE_CALIBRATION_COMPLETE_SCAN)) { + genesys_host_shading_calibration_impl(*dev, sensor, dev->dark_average_data, true, + "gl_black"); + } else { + genesys_shading_calibration_impl(dev, sensor, local_reg, dev->dark_average_data, true, + "gl_black"); + } +} + +static void genesys_white_shading_calibration(Genesys_Device* dev, const Genesys_Sensor& sensor, + Genesys_Register_Set& local_reg) +{ + DBG_HELPER(dbg); + if (has_flag(dev->model->flags, ModelFlag::HOST_SIDE_CALIBRATION_COMPLETE_SCAN)) { + genesys_host_shading_calibration_impl(*dev, sensor, dev->white_average_data, false, + "gl_white"); + } else { + genesys_shading_calibration_impl(dev, sensor, local_reg, 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) + const Genesys_Sensor& sensor, + Genesys_Register_Set& local_reg) { - DBG_HELPER_ARGS(dbg, "lines = %zu", dev->calib_lines); + DBG_HELPER(dbg); + + if (dev->model->asic_type == AsicType::GL646) { + dev->cmd_set->init_regs_for_shading(dev, sensor, local_reg); + local_reg = dev->reg; + } else { + local_reg = dev->reg; + dev->cmd_set->init_regs_for_shading(dev, sensor, local_reg); + dev->interface->write_registers(local_reg); + } + 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; + if (dev->model->asic_type == AsicType::GL842 || + dev->model->asic_type == AsicType::GL843) + { + pixels_per_line = dev->calib_session.output_pixels; + } else { + pixels_per_line = dev->calib_session.params.pixels; + } - uint32_t out_pixels_per_line = pixels_per_line + dev->calib_pixels_offset; + unsigned channels = dev->calib_session.params.channels; + + // BUG: we are using wrong pixel number here + unsigned start_offset = + dev->calib_session.params.startx * sensor.full_resolution / dev->calib_session.params.xres; + + unsigned out_pixels_per_line = pixels_per_line + start_offset; dev->average_size = channels * out_pixels_per_line; @@ -1862,68 +2704,65 @@ static void genesys_dark_white_shading_calibration(Genesys_Device* dev, 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) + if (dev->model->asic_type == AsicType::GL842 || + dev->model->asic_type == AsicType::GL843) { - motor = false; + size = dev->calib_session.output_total_bytes_raw; + } else { + // FIXME: on GL841 this is different than dev->calib_session.output_total_bytes_raw, + // needs checking + size = channels * 2 * pixels_per_line * dev->calib_session.params.lines; } + std::vector<uint8_t> calibration_data(size); + // 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); + sanei_genesys_set_lamp_power(dev, sensor, local_reg, true); + sanei_genesys_set_motor_power(local_reg, true); - dev->interface->write_registers(dev->calib_reg); + dev->interface->write_registers(local_reg); - dev->cmd_set->begin_scan(dev, sensor, &dev->calib_reg, false); + dev->cmd_set->begin_scan(dev, sensor, &local_reg, false); if (is_testing_mode()) { dev->interface->test_checkpoint("dark_white_shading_calibration"); - dev->cmd_set->end_scan(dev, &dev->calib_reg, true); + dev->cmd_set->end_scan(dev, &local_reg, true); return; } sanei_genesys_read_data_from_scanner(dev, calibration_data.data(), size); - dev->cmd_set->end_scan(dev, &dev->calib_reg, true); + dev->cmd_set->end_scan(dev, &local_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); + if (dbg_log_image_data()) { + if (dev->model->is_cis) { + write_tiff_file("gl_black_white_shading.tiff", calibration_data.data(), + 16, 1, pixels_per_line*channels, + dev->calib_session.params.lines); + } else { + write_tiff_file("gl_black_white_shading.tiff", calibration_data.data(), + 16, channels, pixels_per_line, + dev->calib_session.params.lines); } } std::fill(dev->dark_average_data.begin(), - dev->dark_average_data.begin() + dev->calib_pixels_offset * channels, 0); + dev->dark_average_data.begin() + start_offset * channels, 0); std::fill(dev->white_average_data.begin(), - dev->white_average_data.begin() + dev->calib_pixels_offset * channels, 0); + dev->white_average_data.begin() + start_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; + uint16_t* average_white = dev->white_average_data.data() + + start_offset * channels; + uint16_t* average_dark = dev->dark_average_data.data() + + start_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++) + for (std::size_t y = 0; y < dev->calib_session.params.lines; y++) { col = calibration_data[(x + y * pixels_per_line * channels) * 2]; col |= @@ -1947,7 +2786,7 @@ static void genesys_dark_white_shading_calibration(Genesys_Device* dev, white_count = 0; white_sum = 0; - for (std::size_t y = 0; y < dev->calib_lines; y++) + for (std::size_t y = 0; y < dev->calib_session.params.lines; y++) { col = calibration_data[(x + y * pixels_per_line * channels) * 2]; col |= @@ -1974,11 +2813,11 @@ static void genesys_dark_white_shading_calibration(Genesys_Device* dev, *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); + if (dbg_log_image_data()) { + write_tiff_file("gl_white_average.tiff", dev->white_average_data.data(), 16, channels, + out_pixels_per_line, 1); + write_tiff_file("gl_dark_average.tiff", dev->dark_average_data.data(), 16, channels, + out_pixels_per_line, 1); } } @@ -2085,13 +2924,12 @@ compute_averaged_planar (Genesys_Device * dev, const Genesys_Sensor& sensor, */ res = dev->settings.xres; - if (sensor.get_ccd_size_divisor_for_dpi(dev->settings.xres) > 1) - { + if (sensor.full_resolution > sensor.get_optical_resolution()) { res *= 2; } - /* this should be evenly dividable */ - basepixels = sensor.optical_res / res; + // this should be evenly dividable + basepixels = sensor.full_resolution / 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) @@ -2376,9 +3214,10 @@ compute_shifted_coefficients (Genesys_Device * dev, 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 */ + if (sensor.full_resolution > sensor.get_optical_resolution()) { + x *= 2; // scanner is using half-ccd mode + } + basepixels = sensor.full_resolution / 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) @@ -2451,19 +3290,30 @@ static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_ { DBG_HELPER(dbg); - if (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) { + if (sensor.use_host_side_calib) { 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; + + // BUG: we are using wrong pixel number here + unsigned start_offset = + dev->calib_session.params.startx * sensor.full_resolution / dev->calib_session.params.xres; + + if (dev->model->asic_type == AsicType::GL842 || + dev->model->asic_type == AsicType::GL843) + { + pixels_per_line = dev->calib_session.output_pixels + start_offset; + } else { + pixels_per_line = dev->calib_session.params.pixels + start_offset; + } + + unsigned channels = dev->calib_session.params.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 @@ -2504,25 +3354,27 @@ static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_ // contains 16bit words in little endian std::vector<uint8_t> shading_data(length, 0); + if (!dev->calib_session.computed) { + genesys_send_offset_and_shading(dev, sensor, shading_data.data(), length); + return; + } + /* 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)) { + if (get_registers_gain4_bit(dev->model->asic_type, dev->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; + if (dev->settings.xres > sensor.full_resolution) { + factor = 1; + } else { + factor = sensor.full_resolution / dev->settings.xres; } /* for GL646, shading data is planar if REG_0x01_FASTMOD is set and @@ -2536,6 +3388,7 @@ static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_ switch (dev->model->sensor_id) { case SensorId::CCD_XP300: + case SensorId::CCD_DOCKETPORT_487: case SensorId::CCD_ROADWARRIOR: case SensorId::CCD_DP665: case SensorId::CCD_DP685: @@ -2570,10 +3423,9 @@ static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_ case SensorId::CCD_HP2300: target_code = 0xdc00; o = 2; - if(dev->settings.xres<=sensor.optical_res/2) - { - o = o - sensor.dummy_pixel / 2; - } + if (dev->settings.xres <= sensor.full_resolution / 2) { + o = o - sensor.dummy_pixel / 2; + } compute_coefficients (dev, shading_data.data(), pixels_per_line, @@ -2586,7 +3438,7 @@ static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_ case SensorId::CCD_5345: target_code = 0xe000; o = 4; - if(dev->settings.xres<=sensor.optical_res/2) + if(dev->settings.xres<=sensor.full_resolution/2) { o = o - sensor.dummy_pixel; } @@ -2633,9 +3485,12 @@ static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_ case SensorId::CCD_CANON_4400F: case SensorId::CCD_CANON_8400F: case SensorId::CCD_CANON_8600F: + case SensorId::CCD_PLUSTEK_OPTICFILM_7200: case SensorId::CCD_PLUSTEK_OPTICFILM_7200I: case SensorId::CCD_PLUSTEK_OPTICFILM_7300: + case SensorId::CCD_PLUSTEK_OPTICFILM_7400: case SensorId::CCD_PLUSTEK_OPTICFILM_7500I: + case SensorId::CCD_PLUSTEK_OPTICFILM_8200I: target_code = 0xe000; o = 0; compute_coefficients (dev, @@ -2654,6 +3509,7 @@ static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_ case SensorId::CIS_CANON_LIDE_120: case SensorId::CIS_CANON_LIDE_210: case SensorId::CIS_CANON_LIDE_220: + case SensorId::CCD_CANON_5600F: /* TODO store this in a data struct so we avoid * growing this switch */ switch(dev->model->sensor_id) @@ -2684,6 +3540,8 @@ static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_ target_code); break; case SensorId::CIS_CANON_LIDE_35: + case SensorId::CIS_CANON_LIDE_60: + case SensorId::CIS_CANON_LIDE_90: compute_averaged_planar (dev, sensor, shading_data.data(), pixels_per_line, @@ -2756,9 +3614,8 @@ genesys_restore_calibration(Genesys_Device * dev, Genesys_Sensor& sensor) /* we don't restore the gamma fields */ sensor.exposure = cache.sensor.exposure; + dev->calib_session = cache.session; 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; @@ -2812,8 +3669,7 @@ static void genesys_save_calibration(Genesys_Device* dev, const Genesys_Sensor& 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; + found_cache_it->session = dev->calib_session; #ifdef HAVE_SYS_TIME_H gettimeofday(&time, nullptr); @@ -2821,20 +3677,13 @@ static void genesys_save_calibration(Genesys_Device* dev, const Genesys_Sensor& #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; + uint32_t pixels_per_line; - unsigned coarse_res = sensor.optical_res; - if (dev->settings.yres <= sensor.optical_res / 2) { + unsigned coarse_res = sensor.full_resolution; + if (dev->settings.yres <= sensor.full_resolution / 2) { coarse_res /= 2; } @@ -2848,35 +3697,29 @@ static void genesys_flatbed_calibration(Genesys_Device* dev, Genesys_Sensor& sen coarse_res = 1200; } - /* do offset calibration if needed */ - if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION) - { + auto local_reg = dev->initial_regs; + + if (!has_flag(dev->model->flags, ModelFlag::DISABLE_ADC_CALIBRATION)) { + // do ADC calibration first. dev->interface->record_progress_message("offset_calibration"); - dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg); + dev->cmd_set->offset_calibration(dev, sensor, local_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); + dev->cmd_set->coarse_gain_calibration(dev, sensor, local_reg, coarse_res); } - if (dev->model->is_cis) + if (dev->model->is_cis && + !has_flag(dev->model->flags, ModelFlag::DISABLE_EXPOSURE_CALIBRATION)) { - /* the afe now sends valid data for doing led calibration */ + // ADC now sends correct data, we can configure the exposure for the LEDs dev->interface->record_progress_message("led_calibration"); switch (dev->model->asic_type) { case AsicType::GL124: + case AsicType::GL841: case AsicType::GL845: case AsicType::GL846: case AsicType::GL847: { - auto calib_exposure = dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg); + auto calib_exposure = dev->cmd_set->led_calibration(dev, sensor, local_reg); for (auto& sensor_update : sanei_genesys_find_sensors_all_for_write(dev, sensor.method)) { sensor_update.get().exposure = calib_exposure; @@ -2885,80 +3728,66 @@ static void genesys_flatbed_calibration(Genesys_Device* dev, Genesys_Sensor& sen break; } default: { - sensor.exposure = dev->cmd_set->led_calibration(dev, sensor, dev->calib_reg); + sensor.exposure = dev->cmd_set->led_calibration(dev, sensor, local_reg); } } - - /* calibrate afe again to match new exposure */ - if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION) { + if (!has_flag(dev->model->flags, ModelFlag::DISABLE_ADC_CALIBRATION)) { + // recalibrate ADC again for the new LED exposure 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->cmd_set->offset_calibration(dev, sensor, local_reg); 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); + dev->cmd_set->coarse_gain_calibration(dev, sensor, local_reg, coarse_res); } } /* we always use sensor pixel number when the ASIC can't handle multi-segments sensor */ - if (!(dev->model->flags & GENESYS_FLAG_SIS_SENSOR)) - { + if (!has_flag(dev->model->flags, ModelFlag::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; + } else { + pixels_per_line = static_cast<std::uint32_t>((dev->model->x_size_calib_mm * dev->settings.xres) + / MM_PER_INCH); } // 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); - } + if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || + dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) + { + scanner_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); + if (!has_flag(dev->model->flags, ModelFlag::DISABLE_SHADING_CALIBRATION)) { + if (has_flag(dev->model->flags, ModelFlag::DARK_WHITE_CALIBRATION)) { + dev->interface->record_progress_message("genesys_dark_white_shading_calibration"); + genesys_dark_white_shading_calibration(dev, sensor, local_reg); + } else { + DBG(DBG_proc, "%s : genesys_dark_shading_calibration local_reg ", __func__); + debug_dump(DBG_proc, local_reg); - dev->interface->record_progress_message("genesys_dark_shading_calibration"); - genesys_dark_shading_calibration(dev, sensor); - genesys_repark_sensor_before_shading(dev); - } + if (has_flag(dev->model->flags, ModelFlag::DARK_CALIBRATION)) { + dev->interface->record_progress_message("genesys_dark_shading_calibration"); + genesys_dark_shading_calibration(dev, sensor, local_reg); + 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, local_reg); - dev->interface->record_progress_message("genesys_white_shading_calibration"); - genesys_white_shading_calibration(dev, sensor); - genesys_repark_sensor_after_white_shading(dev); + genesys_repark_sensor_after_white_shading(dev); - if (!(dev->model->flags & GENESYS_FLAG_DARK_CALIBRATION)) { - genesys_dummy_dark_shading(dev, sensor); + if (!has_flag(dev->model->flags, ModelFlag::DARK_CALIBRATION)) { + if (has_flag(dev->model->flags, ModelFlag::USE_CONSTANT_FOR_DARK_CALIBRATION)) { + genesys_dark_shading_by_constant(*dev); + } else { + genesys_dark_shading_by_dummy_pixel(dev, sensor); + } + } } } @@ -2982,68 +3811,62 @@ static void genesys_sheetfed_calibration(Genesys_Device* dev, Genesys_Sensor& se DBG_HELPER(dbg); bool forward = true; + auto local_reg = dev->initial_regs; + // 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; - } + unsigned coarse_res = sensor.full_resolution; /* 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); + scanner_search_strip(*dev, 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); + if (!has_flag(dev->model->flags, ModelFlag::DISABLE_ADC_CALIBRATION)) { + // do ADC calibration first. + dev->interface->record_progress_message("offset_calibration"); + dev->cmd_set->offset_calibration(dev, sensor, local_reg); + + dev->interface->record_progress_message("coarse_gain_calibration"); + dev->cmd_set->coarse_gain_calibration(dev, sensor, local_reg, coarse_res); } - /* calibrate afe */ - if (dev->model->flags & GENESYS_FLAG_OFFSET_CALIBRATION) + if (dev->model->is_cis && + !has_flag(dev->model->flags, ModelFlag::DISABLE_EXPOSURE_CALIBRATION)) { - dev->cmd_set->offset_calibration(dev, sensor, dev->calib_reg); - - /* since all the registers are set up correctly, just use them */ + // ADC now sends correct data, we can configure the exposure for the LEDs + dev->interface->record_progress_message("led_calibration"); + dev->cmd_set->led_calibration(dev, sensor, local_reg); - 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); + if (!has_flag(dev->model->flags, ModelFlag::DISABLE_ADC_CALIBRATION)) { + // recalibrate ADC again for the new LED exposure + dev->interface->record_progress_message("offset_calibration"); + dev->cmd_set->offset_calibration(dev, sensor, local_reg); - genesys_coarse_calibration(dev, sensor); + dev->interface->record_progress_message("coarse_gain_calibration"); + dev->cmd_set->coarse_gain_calibration(dev, sensor, local_reg, coarse_res); + } } /* 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 */ + if (has_flag(dev->model->flags, ModelFlag::DARK_CALIBRATION)) { + // seek black/white reverse/forward try { - dev->cmd_set->search_strip(dev, sensor, forward, true); + scanner_search_strip(*dev, 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); + genesys_dark_shading_calibration(dev, sensor, local_reg); } catch (...) { catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); throw; @@ -3054,7 +3877,7 @@ static void genesys_sheetfed_calibration(Genesys_Device* dev, Genesys_Sensor& se /* go to a white area */ try { - dev->cmd_set->search_strip(dev, sensor, forward, false); + scanner_search_strip(*dev, forward, false); } catch (...) { catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); throw; @@ -3062,10 +3885,8 @@ static void genesys_sheetfed_calibration(Genesys_Device* dev, Genesys_Sensor& se 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_white_shading_calibration(dev, sensor, local_reg); genesys_repark_sensor_after_white_shading(dev); } catch (...) { catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); @@ -3073,17 +3894,9 @@ static void genesys_sheetfed_calibration(Genesys_Device* dev, Genesys_Sensor& se } // 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, - */ + // FIXME: shouldn't we use genesys_dark_shading_by_dummy_pixel() ? + if (!has_flag(dev->model->flags, ModelFlag::DARK_CALIBRATION)) { + genesys_dark_shading_by_constant(*dev); } /* send the shading coefficient when doing whole line shading @@ -3099,7 +3912,7 @@ static void genesys_sheetfed_calibration(Genesys_Device* dev, Genesys_Sensor& se dev->cmd_set->eject_document(dev); // restore settings - dev->settings.xres = sensor.optical_res; + dev->settings.xres = sensor.full_resolution; } /** @@ -3129,22 +3942,23 @@ 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); + dev->cmd_set->init_regs_for_warmup(dev, sensor, &dev->reg); + dev->interface->write_registers(dev->reg); + + auto total_pixels = dev->session.output_pixels; + auto total_size = dev->session.output_line_bytes; + auto channels = dev->session.params.channels; + auto lines = dev->session.output_line_count; + 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__); + do { + first_line = second_line; + dev->cmd_set->begin_scan(dev, sensor, &dev->reg, false); if (is_testing_mode()) { @@ -3155,72 +3969,44 @@ static void genesys_warmup_lamp(Genesys_Device* dev) 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); - } - + sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size); dev->cmd_set->end_scan(dev, &dev->reg, true); - dev->interface->sleep_ms(1000); - seconds++; + // compute difference between the two scans + double first_average = 0; + double second_average = 0; + for (unsigned 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]; + } + } - dev->cmd_set->begin_scan(dev, sensor, &dev->reg, false); + first_average /= total_pixels; + second_average /= total_pixels; - wait_until_buffer_non_empty(dev); + if (dbg_log_image_data()) { + write_tiff_file("gl_warmup1.tiff", first_line.data(), dev->session.params.depth, + channels, total_size / (lines * channels), lines); + write_tiff_file("gl_warmup2.tiff", second_line.data(), dev->session.params.depth, + channels, total_size / (lines * channels), lines); + } - sanei_genesys_read_data_from_scanner(dev, second_line.data(), total_size); - dev->cmd_set->end_scan(dev, &dev->reg, true); + DBG(DBG_info, "%s: average 1 = %.2f, average 2 = %.2f\n", __func__, first_average, + second_average); - /* 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; - } + float average_difference = std::fabs(first_average - second_average) / second_average; + if (second_average > 0 && average_difference < 0.005) + { + dbg.vlog(DBG_info, "difference: %f, exiting", average_difference); + break; + } - /* sleep another second before next loop */ dev->interface->sleep_ms(1000); seconds++; } while (seconds < WARMUP_TIME); @@ -3236,6 +4022,37 @@ static void genesys_warmup_lamp(Genesys_Device* dev) } } +static void init_regs_for_scan(Genesys_Device& dev, const Genesys_Sensor& sensor, + Genesys_Register_Set& regs) +{ + DBG_HELPER(dbg); + debug_dump(DBG_info, dev.settings); + + auto session = dev.cmd_set->calculate_scan_session(&dev, sensor, dev.settings); + + if (dev.model->asic_type == AsicType::GL124 || + dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846 || + dev.model->asic_type == AsicType::GL847) + { + /* Fast move to scan area: + + We don't move fast the whole distance since it would involve computing + acceleration/deceleration distance for scan resolution. So leave a remainder for it so + scan makes the final move tuning + */ + + if (dev.settings.get_channels() * dev.settings.yres >= 600 && session.params.starty > 700) { + scanner_move(dev, dev.model->default_method, + static_cast<unsigned>(session.params.starty - 500), + Direction::FORWARD); + session.params.starty = 500; + } + compute_session(&dev, session, sensor); + } + + dev.cmd_set->init_regs_for_scan_session(&dev, sensor, ®s, session); +} // High-level start of scanning static void genesys_start_scan(Genesys_Device* dev, bool lamp_off) @@ -3243,6 +4060,7 @@ 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) { @@ -3254,38 +4072,30 @@ static void genesys_start_scan(Genesys_Device* dev, bool lamp_off) /* 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)) + if (has_flag(dev->model->flags, ModelFlag::WARMUP) && + (dev->settings.scan_method != ScanMethod::TRANSPARENCY_INFRARED)) { + if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || + dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) + { + scanner_move_to_ta(*dev); + } + 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); - } + // 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); + scanner_move_to_ta(*dev); } /* load document if needed (for sheetfed scanner for instance) */ @@ -3304,22 +4114,18 @@ static void genesys_start_scan(Genesys_Device* dev, bool lamp_off) /* 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) { + // calibration : sheetfed scanners can't calibrate before each scan. + // also don't run calibration for those scanners where all passes are disabled + bool shading_disabled = + has_flag(dev->model->flags, ModelFlag::DISABLE_ADC_CALIBRATION) && + has_flag(dev->model->flags, ModelFlag::DISABLE_EXPOSURE_CALIBRATION) && + has_flag(dev->model->flags, ModelFlag::DISABLE_SHADING_CALIBRATION); + if (!shading_disabled && !dev->model->is_sheetfed) { genesys_scanner_calibration(dev, sensor); - genesys_save_calibration (dev, sensor); - } - else - { + 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); @@ -3331,10 +4137,10 @@ static void genesys_start_scan(Genesys_Device* dev, bool lamp_off) if (dev->settings.scan_method == ScanMethod::TRANSPARENCY || dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) { - dev->cmd_set->move_to_ta(dev); + scanner_move_to_ta(*dev); } - dev->cmd_set->init_regs_for_scan(dev, sensor); + init_regs_for_scan(*dev, sensor, dev->reg); /* no lamp during scan */ if (lamp_off) { @@ -3344,7 +4150,7 @@ static void genesys_start_scan(Genesys_Device* dev, bool lamp_off) /* 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)) + !has_flag(dev->model->flags, ModelFlag::DISABLE_SHADING_CALIBRATION)) { genesys_send_shading_coefficient(dev, sensor); } @@ -3386,33 +4192,6 @@ static void genesys_start_scan(Genesys_Device* dev, bool lamp_off) } } -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. @@ -3422,8 +4201,6 @@ static void genesys_read_ordered_data(Genesys_Device* dev, SANE_Byte* destinatio { DBG_HELPER(dbg); size_t bytes = 0; - uint8_t *work_buffer_src; - Genesys_Buffer *src_buffer; if (!dev->read_active) { *len = 0; @@ -3439,7 +4216,7 @@ static void genesys_read_ordered_data(Genesys_Device* dev, SANE_Byte* destinatio { /* 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) && + if (!dev->model->is_sheetfed && !has_flag(dev->model->flags, ModelFlag::MUST_WAIT) && !dev->parking) { dev->cmd_set->move_back_home(dev, false); @@ -3448,61 +4225,22 @@ static void genesys_read_ordered_data(Genesys_Device* dev, SANE_Byte* destinatio 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; + if (dev->model->is_sheetfed) { + dev->cmd_set->detect_document_end(dev); + } - /* 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->pipeline_buffer.get_data(*len, destination); dev->total_bytes_read += *len; - - src_buffer->consume(bytes); } /* end scan if all needed data have been read */ @@ -3576,181 +4314,113 @@ static unsigned pick_resolution(const std::vector<unsigned>& resolutions, unsign return best_res; } -static void calc_parameters(Genesys_Scanner* s) +static Genesys_Settings calculate_scan_settings(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); + const auto* dev = s->dev; + Genesys_Settings settings; + settings.scan_method = s->scan_method; + settings.scan_mode = option_string_to_scan_color_mode(s->mode); - s->params.last_frame = true; /* only single pass scanning supported */ + settings.depth = s->bit_depth; - 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 (settings.depth > 8) { + settings.depth = 16; + } else if (settings.depth < 8) { + settings.depth = 1; } - 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; + const auto& resolutions = dev->model->get_resolution_settings(settings.scan_method); - /* interpolation */ - s->dev->settings.disable_interpolation = s->disable_interpolation; + settings.xres = pick_resolution(resolutions.resolutions_x, s->resolution, "X"); + settings.yres = pick_resolution(resolutions.resolutions_y, s->resolution, "Y"); - // 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; + settings.tl_x = fixed_to_float(s->pos_top_left_x); + settings.tl_y = fixed_to_float(s->pos_top_left_y); + float br_x = fixed_to_float(s->pos_bottom_right_x); + float br_y = fixed_to_float(s->pos_bottom_right_y); - 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) / + settings.lines = static_cast<unsigned>(((br_y - settings.tl_y) * 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; - } + unsigned pixels_per_line = static_cast<unsigned>(((br_x - settings.tl_x) * settings.xres) / + MM_PER_INCH); - s->dev->settings.scan_mode = option_string_to_scan_color_mode(s->mode); + const auto& sensor = sanei_genesys_find_sensor(dev, settings.xres, settings.get_channels(), + settings.scan_method); - 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; + pixels_per_line = session_adjust_output_pixels(pixels_per_line, *dev, sensor, + settings.xres, settings.yres, true); - // threshold setting - s->dev->settings.threshold = static_cast<int>(2.55 * (SANE_UNFIX(s->threshold))); + unsigned xres_factor = s->resolution / settings.xres; + settings.pixels = pixels_per_line; + settings.requested_pixels = pixels_per_line * xres_factor; - // color filter if (s->color_filter == "Red") { - s->dev->settings.color_filter = ColorFilter::RED; + settings.color_filter = ColorFilter::RED; } else if (s->color_filter == "Green") { - s->dev->settings.color_filter = ColorFilter::GREEN; + settings.color_filter = ColorFilter::GREEN; } else if (s->color_filter == "Blue") { - s->dev->settings.color_filter = ColorFilter::BLUE; + settings.color_filter = ColorFilter::BLUE; } else { - s->dev->settings.color_filter = ColorFilter::NONE; + settings.color_filter = ColorFilter::NONE; } - // true gray if (s->color_filter == "None") { - s->dev->settings.true_gray = 1; + settings.true_gray = 1; } else { - s->dev->settings.true_gray = 0; + 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) { + settings.contrast = (s->contrast * 127) / 100; + settings.brightness = (s->brightness * 127) / 100; + } else { + settings.contrast = 0; + settings.brightness = 0; } - /* 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; + settings.expiration_time = s->expiration_time; + + return settings; +} + +static SANE_Parameters calculate_scan_parameters(const Genesys_Device& dev, + const Genesys_Settings& settings) +{ + DBG_HELPER(dbg); + + auto sensor = sanei_genesys_find_sensor(&dev, settings.xres, settings.get_channels(), + settings.scan_method); + auto session = dev.cmd_set->calculate_scan_session(&dev, sensor, settings); + auto pipeline = build_image_pipeline(dev, session, 0, false); + + SANE_Parameters params; + if (settings.scan_mode == ScanColorMode::GRAY) { + params.format = SANE_FRAME_GRAY; + } else { + params.format = SANE_FRAME_RGB; } + // only single-pass scanning supported + params.last_frame = true; + params.depth = settings.depth; + params.lines = pipeline.get_output_height(); + params.pixels_per_line = pipeline.get_output_width(); + params.bytes_per_line = pipeline.get_output_row_bytes(); - /* cache expiration time */ - s->dev->settings.expiration_time = s->expiration_time; + return params; } +static void calc_parameters(Genesys_Scanner* s) +{ + DBG_HELPER(dbg); + + s->dev->settings = calculate_scan_settings(s); + s->params = calculate_scan_parameters(*s->dev, s->dev->settings); +} static void create_bpp_list (Genesys_Scanner * s, const std::vector<unsigned>& bpp) { @@ -3760,7 +4430,7 @@ static void create_bpp_list (Genesys_Scanner * s, const std::vector<unsigned>& b /** @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 + * gl646: 12 or 14 bits gamma table depending on ModelFlag::GAMMA_14BIT * gl84x: 16 bits * gl12x: 16 bits * @param scanner pointer to scanner session to get options @@ -3776,8 +4446,7 @@ init_gamma_vector_option (Genesys_Scanner * scanner, int option) 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) - { + if (has_flag(scanner->dev->model->flags, ModelFlag::GAMMA_14BIT)) { scanner->opt[option].size = 16384 * sizeof (SANE_Word); scanner->opt[option].constraint.range = &u14_range; } @@ -3802,9 +4471,9 @@ init_gamma_vector_option (Genesys_Scanner * scanner, int option) 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); + range.min = float_to_fixed(0.0); + range.max = float_to_fixed(size); + range.quant = float_to_fixed(0.0); return range; } @@ -3855,7 +4524,7 @@ static std::string calibration_filename(Genesys_Device *currdev) /* 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) { + if (dev.vendorId == currdev->vendorId && dev.productId == currdev->productId) { count++; } } @@ -3921,13 +4590,13 @@ 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)); + s.opt_x_range = create_range(s.dev->model->x_size); + s.opt_y_range = create_range(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_x_range = create_range(s.dev->model->x_size_ta); + s.opt_y_range = create_range(s.dev->model->y_size_ta); } s.opt[OPT_TL_X].constraint.range = &s.opt_x_range; @@ -3945,7 +4614,7 @@ static void init_options(Genesys_Scanner* s) { DBG_HELPER(dbg); SANE_Int option; - Genesys_Model *model = s->dev->model; + const Genesys_Model* model = s->dev->model; memset (s->opt, 0, sizeof (s->opt)); @@ -4038,8 +4707,8 @@ static void init_options(Genesys_Scanner* s) 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)); + s->opt_x_range = create_range(model->x_size); + s->opt_y_range = create_range(model->y_size); // scan area s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X; @@ -4116,8 +4785,7 @@ static void init_options(Genesys_Scanner* s) /* 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)) - { + if (!has_flag(model->flags, ModelFlag::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__); @@ -4127,61 +4795,6 @@ static void init_options(Genesys_Scanner* s) * 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; @@ -4214,39 +4827,6 @@ static void init_options(Genesys_Scanner* s) 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"); @@ -4508,36 +5088,39 @@ check_present (SANE_String_Const devname) noexcept return SANE_STATUS_GOOD; } -static Genesys_Device* attach_usb_device(const char* devname, - std::uint16_t vendor_id, std::uint16_t product_id) +const UsbDeviceEntry& get_matching_usb_dev(std::uint16_t vendor_id, std::uint16_t product_id, + std::uint16_t bcd_device) { - 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 (usb_dev.matches(vendor_id, product_id, bcd_device)) { + return usb_dev; } } - if (found_usb_dev == nullptr) { - throw SaneException("vendor 0x%xd product 0x%xd is not supported by this backend", - vendor_id, product_id); - } + throw SaneException("vendor 0x%x product 0x%x (bcdDevice 0x%x) " + "is not supported by this backend", + vendor_id, product_id, bcd_device); +} + +static Genesys_Device* attach_usb_device(const char* devname, + std::uint16_t vendor_id, std::uint16_t product_id, + std::uint16_t bcd_device) +{ + const auto& usb_dev = get_matching_usb_dev(vendor_id, product_id, bcd_device); 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->vendorId = vendor_id; + dev->productId = product_id; + dev->model = &usb_dev.model(); dev->usb_mode = 0; // i.e. unset dev->already_initialized = false; return dev; } +static bool s_attach_device_by_name_evaluate_bcd_device = false; + 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); @@ -4560,26 +5143,31 @@ static Genesys_Device* attach_device_by_name(SANE_String_Const devname, bool may 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); + auto vendor_id = usb_dev.get_vendor_id(); + auto product_id = usb_dev.get_product_id(); + auto bcd_device = UsbDeviceEntry::BCD_DEVICE_NOT_SET; + if (s_attach_device_by_name_evaluate_bcd_device) { + // when the device is already known before scanning, we don't want to call get_bcd_device() + // when iterating devices, as that will interfere with record/replay during testing. + bcd_device = usb_dev.get_bcd_device(); + } usb_dev.close(); /* KV-SS080 is an auxiliary device which requires a master device to be here */ - if(vendor == 0x04da && product == 0x100f) - { + if (vendor_id == 0x04da && product_id == 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); + sanei_usb_find_devices(vendor_id, 0x1006, check_present); + sanei_usb_find_devices(vendor_id, 0x1007, check_present); + sanei_usb_find_devices(vendor_id, 0x1010, check_present); if (present == false) { throw SaneException("master device not present"); } } - Genesys_Device* dev = attach_usb_device(devname, vendor, product); + Genesys_Device* dev = attach_usb_device(devname, vendor_id, product_id, bcd_device); - DBG(DBG_info, "%s: found %s flatbed scanner %s at %s\n", __func__, dev->model->vendor, - dev->model->model, dev->file_name.c_str()); + DBG(DBG_info, "%s: found %u flatbed scanner %u at %s\n", __func__, vendor_id, product_id, + dev->file_name.c_str()); return dev; } @@ -4614,7 +5202,8 @@ 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()); + get_testing_vendor_id(), get_testing_product_id(), + get_testing_bcd_device()); return; } @@ -4625,7 +5214,12 @@ static void probe_genesys_devices() config.values = nullptr; config.count = 0; - TIE(sanei_configure_attach(GENESYS_CONFIG_FILE, &config, config_attach_genesys)); + auto status = sanei_configure_attach(GENESYS_CONFIG_FILE, &config, config_attach_genesys); + if (status == SANE_STATUS_ACCESS_DENIED) { + dbg.vlog(DBG_error0, "Critical error: Couldn't access configuration file '%s'", + GENESYS_CONFIG_FILE); + } + TIE(status); DBG(DBG_info, "%s: %zu devices currently attached\n", __func__, s_devices->size()); } @@ -4637,7 +5231,7 @@ static void probe_genesys_devices() of Genesys_Calibration_Cache as is. */ static const char* CALIBRATION_IDENT = "sane_genesys"; -static const int CALIBRATION_VERSION = 21; +static const int CALIBRATION_VERSION = 31; bool read_calibration(std::istream& str, Genesys_Device::Calibration& calibration, const std::string& path) @@ -4706,114 +5300,6 @@ static void write_calibration(Genesys_Device::Calibration& calibration, const st 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) @@ -4839,9 +5325,6 @@ void sane_init_impl(SANE_Int * version_code, SANE_Auth_Callback authorize) sanei_usb_init(); } - /* init sanei_magic */ - sanei_magic_init(); - s_scanners.init(); s_devices.init(); s_sane_devices.init(); @@ -4850,8 +5333,8 @@ void sane_init_impl(SANE_Int * version_code, SANE_Auth_Callback authorize) genesys_init_sensor_tables(); genesys_init_frontend_tables(); genesys_init_gpo_tables(); + genesys_init_memory_layout_tables(); genesys_init_motor_tables(); - genesys_init_motor_profile_tables(); genesys_init_usb_device_tables(); @@ -4864,6 +5347,7 @@ void sane_init_impl(SANE_Int * version_code, SANE_Auth_Callback authorize) ); // cold-plug case :detection of allready connected scanners + s_attach_device_by_name_evaluate_bcd_device = false; probe_genesys_devices(); } @@ -4903,6 +5387,7 @@ void sane_get_devices_impl(const SANE_Device *** device_list, SANE_Bool local_on // hot-plug case : detection of newly connected scanners */ sanei_usb_scan_devices(); } + s_attach_device_by_name_evaluate_bcd_device = true; probe_genesys_devices(); s_sane_devices->clear(); @@ -4969,7 +5454,7 @@ static void sane_open_impl(SANE_String_Const devicename, SANE_Handle * handle) } if (dev) { - DBG(DBG_info, "%s: found `%s' in devlist\n", __func__, dev->model->name); + DBG(DBG_info, "%s: found `%s' in devlist\n", __func__, dev->file_name.c_str()); } else if (is_testing_mode()) { DBG(DBG_info, "%s: couldn't find `%s' in devlist, not attaching", __func__, devicename); } else { @@ -4991,37 +5476,53 @@ static void sane_open_impl(SANE_String_Const devicename, SANE_Handle * handle) 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"); - } + if (is_testing_mode()) { + // during testing we need to initialize dev->model before test scanner interface is created + // as that it needs to know what type of chip it needs to mimic. + auto vendor_id = get_testing_vendor_id(); + auto product_id = get_testing_product_id(); + auto bcd_device = get_testing_bcd_device(); - dbg.vstatus("open device '%s'", dev->file_name.c_str()); + dev->model = &get_matching_usb_dev(vendor_id, product_id, bcd_device).model(); - if (is_testing_mode()) { - auto interface = std::unique_ptr<TestScannerInterface>{new TestScannerInterface{dev}}; + auto interface = std::unique_ptr<TestScannerInterface>{ + new TestScannerInterface{dev, vendor_id, product_id, bcd_device}}; interface->set_checkpoint_callback(get_testing_checkpoint_callback()); dev->interface = std::move(interface); + + dev->interface->get_usb_device().open(dev->file_name.c_str()); } else { dev->interface = std::unique_ptr<ScannerInterfaceUsb>{new ScannerInterfaceUsb{dev}}; + + dbg.vstatus("open device '%s'", dev->file_name.c_str()); + dev->interface->get_usb_device().open(dev->file_name.c_str()); + dbg.clear(); + + auto bcd_device = dev->interface->get_usb_device().get_bcd_device(); + + dev->model = &get_matching_usb_dev(dev->vendorId, dev->productId, bcd_device).model(); + } + + dbg.vlog(DBG_info, "Opened device %s", dev->model->name); + + if (has_flag(dev->model->flags, ModelFlag::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"); } - 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->dev = dev; s->scanning = false; - s->dev->parking = false; - s->dev->read_active = false; - s->dev->force_calibration = 0; - s->dev->line_count = 0; + dev->parking = false; + dev->read_active = false; + dev->force_calibration = 0; + dev->line_count = 0; *handle = s; @@ -5029,31 +5530,18 @@ static void sane_open_impl(SANE_String_Const devicename, SANE_Handle * handle) sanei_genesys_init_structs (dev); } + dev->cmd_set = create_cmd_set(dev->model->asic_type); + init_options(s); - sanei_genesys_init_cmd_set(s->dev); + DBG_INIT(); // 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); - }); - } + dev->cmd_set->update_hardware_sensors (s); } SANE_GENESYS_API_LINKAGE @@ -5085,46 +5573,42 @@ sane_close_impl(SANE_Handle handle) return; /* oops, not a handle we know about */ } - Genesys_Scanner* s = &*it; + auto* dev = it->dev; - /* 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); + // eject document for sheetfed scanners + if (dev->model->is_sheetfed) { + catch_all_exceptions(__func__, [&](){ dev->cmd_set->eject_document(dev); }); + } else { + // in case scanner is parking, wait for the head to reach home position + if (dev->parking) { + sanei_genesys_wait_for_home(dev); } } // enable power saving before leaving - s->dev->cmd_set->save_power(s->dev, true); + dev->cmd_set->save_power(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); }); + if (dev->force_calibration == 0 && !is_testing_mode()) { + catch_all_exceptions(__func__, [&](){ write_calibration(dev->calibration_cache, + dev->calib_file); }); } - s->dev->already_initialized = false; - - s->dev->clear(); + dev->already_initialized = false; + dev->clear(); // LAMP OFF : same register across all the ASICs */ - s->dev->interface->write_register(0x03, 0x00); + dev->interface->write_register(0x03, 0x00); - catch_all_exceptions(__func__, [&](){ s->dev->interface->get_usb_device().clear_halt(); }); + catch_all_exceptions(__func__, [&](){ 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(); }); + catch_all_exceptions(__func__, [&](){ 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(); }); + // not freeing dev because it's in the dev list + catch_all_exceptions(__func__, [&](){ dev->interface->get_usb_device().close(); }); - s_scanners->erase(it); + s_scanners->erase(it); } SANE_GENESYS_API_LINKAGE @@ -5174,7 +5658,7 @@ static void print_option(DebugMessageHelper& dbg, const Genesys_Scanner& s, int return; } case SANE_TYPE_FIXED: { - dbg.vlog(DBG_proc, "value: %f", SANE_UNFIX(*reinterpret_cast<SANE_Word*>(val))); + dbg.vlog(DBG_proc, "value: %f", fixed_to_float(*reinterpret_cast<SANE_Word*>(val))); return; } case SANE_TYPE_STRING: { @@ -5189,18 +5673,19 @@ static void print_option(DebugMessageHelper& dbg, const Genesys_Scanner& s, int static void get_option_value(Genesys_Scanner* s, int option, void* val) { DBG_HELPER_ARGS(dbg, "option: %s (%d)", s->opt[option].name, option); + auto* dev = s->dev; 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)) + if (sanei_genesys_has_sensor(dev, dev->settings.xres, dev->settings.get_channels(), + 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); + sensor = &sanei_genesys_find_sensor(dev, dev->settings.xres, + dev->settings.get_channels(), + dev->settings.scan_method); } switch (option) @@ -5231,39 +5716,12 @@ static void get_option_value(Genesys_Scanner* s, int option, void* val) 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; @@ -5297,13 +5755,13 @@ static void get_option_value(Genesys_Scanner* s, int option, void* val) 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); - } + if (s->color_filter == "Red") { + gamma_table = get_gamma_table(dev, *sensor, GENESYS_RED); + } else if (s->color_filter == "Blue") { + gamma_table = get_gamma_table(dev, *sensor, GENESYS_BLUE); + } else { + gamma_table = get_gamma_table(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"); @@ -5317,7 +5775,7 @@ static void get_option_value(Genesys_Scanner* s, int option, void* val) throw SaneException("Unsupported scanner mode selected"); table = reinterpret_cast<SANE_Word*>(val); - gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_RED); + gamma_table = get_gamma_table(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"); @@ -5331,7 +5789,7 @@ static void get_option_value(Genesys_Scanner* s, int option, void* val) throw SaneException("Unsupported scanner mode selected"); table = reinterpret_cast<SANE_Word*>(val); - gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_GREEN); + gamma_table = get_gamma_table(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"); @@ -5345,7 +5803,7 @@ static void get_option_value(Genesys_Scanner* s, int option, void* val) throw SaneException("Unsupported scanner mode selected"); table = reinterpret_cast<SANE_Word*>(val); - gamma_table = get_gamma_table(s->dev, *sensor, GENESYS_BLUE); + gamma_table = get_gamma_table(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"); @@ -5377,11 +5835,10 @@ static void get_option_value(Genesys_Scanner* s, int option, void* val) bool result = true; - auto session = s->dev->cmd_set->calculate_scan_session(s->dev, *sensor, - s->dev->settings); + auto session = dev->cmd_set->calculate_scan_session(dev, *sensor, dev->settings); - for (auto& cache : s->dev->calibration_cache) { - if (sanei_genesys_is_compatible_calibration(s->dev, session, &cache, false)) { + for (auto& cache : dev->calibration_cache) { + if (sanei_genesys_is_compatible_calibration(dev, session, &cache, false)) { *reinterpret_cast<SANE_Bool*>(val) = SANE_FALSE; } } @@ -5400,6 +5857,7 @@ static void get_option_value(Genesys_Scanner* s, int option, void* val) static void set_calibration_value(Genesys_Scanner* s, const char* val) { DBG_HELPER(dbg); + auto dev = s->dev; std::string new_calib_path = val; Genesys_Device::Calibration new_calibration; @@ -5414,8 +5872,8 @@ static void set_calibration_value(Genesys_Scanner* s, const char* val) return; } - s->dev->calibration_cache = std::move(new_calibration); - s->dev->calib_file = new_calib_path; + dev->calibration_cache = std::move(new_calibration); + 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()); } @@ -5426,12 +5884,13 @@ static void set_option_value(Genesys_Scanner* s, int option, void *val, SANE_Int DBG_HELPER_ARGS(dbg, "option: %s (%d)", s->opt[option].name, option); print_option(dbg, *s, option, val); + auto* dev = s->dev; + SANE_Word *table; unsigned int i; unsigned option_size = 0; - switch (option) - { + switch (option) { case OPT_TL_X: s->pos_top_left_x = *reinterpret_cast<SANE_Word*>(val); calc_parameters(s); @@ -5457,46 +5916,6 @@ static void set_option_value(Genesys_Scanner* s, int option, void *val, SANE_Int 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); @@ -5517,38 +5936,14 @@ static void set_option_value(Genesys_Scanner* s, int option, void *val, SANE_Int 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); + } else { ENABLE(OPT_CONTRAST); ENABLE(OPT_BRIGHTNESS); } @@ -5567,38 +5962,22 @@ static void set_option_value(Genesys_Scanner* s, int option, void *val, SANE_Int } break; } - case OPT_MODE: - s->mode = reinterpret_cast<const char*>(val); + 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) { + if (s->mode == SANE_VALUE_SCAN_MODE_GRAY) { + if (dev->model->asic_type != AsicType::GL646 || !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); + create_bpp_list(s, dev->model->bpp_gray_values); + s->bit_depth = dev->model->bpp_gray_values[0]; + } else { + DISABLE(OPT_COLOR_FILTER); + create_bpp_list(s, dev->model->bpp_color_values); + s->bit_depth = dev->model->bpp_color_values[0]; + } + + calc_parameters(s); /* if custom gamma, toggle gamma table options according to the mode */ if (s->custom_gamma) @@ -5621,30 +6000,31 @@ static void set_option_value(Genesys_Scanner* s, int option, void *val, SANE_Int *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) { + case OPT_CALIBRATION_FILE: { + if (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: + case OPT_LAMP_OFF_TIME: { + if (*reinterpret_cast<SANE_Word*>(val) != s->lamp_off_time) { + s->lamp_off_time = *reinterpret_cast<SANE_Word*>(val); + dev->cmd_set->set_powersaving(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); + } + break; + } + case OPT_CUSTOM_GAMMA: { *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; s->custom_gamma = *reinterpret_cast<SANE_Bool*>(val); @@ -5670,88 +6050,96 @@ static void set_option_value(Genesys_Scanner* s, int option, void *val, SANE_Int 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(); + for (auto& table : 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); + break; + } - 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]; + case OPT_GAMMA_VECTOR: { + table = reinterpret_cast<SANE_Word*>(val); + option_size = s->opt[option].size / sizeof (SANE_Word); + + dev->gamma_override_tables[GENESYS_RED].resize(option_size); + dev->gamma_override_tables[GENESYS_GREEN].resize(option_size); + dev->gamma_override_tables[GENESYS_BLUE].resize(option_size); + for (i = 0; i < option_size; i++) { + dev->gamma_override_tables[GENESYS_RED][i] = table[i]; + dev->gamma_override_tables[GENESYS_GREEN][i] = table[i]; + dev->gamma_override_tables[GENESYS_BLUE][i] = table[i]; + } + break; } - 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]; + case OPT_GAMMA_VECTOR_R: { + table = reinterpret_cast<SANE_Word*>(val); + option_size = s->opt[option].size / sizeof (SANE_Word); + dev->gamma_override_tables[GENESYS_RED].resize(option_size); + for (i = 0; i < option_size; i++) { + dev->gamma_override_tables[GENESYS_RED][i] = table[i]; + } + break; } - 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]; + case OPT_GAMMA_VECTOR_G: { + table = reinterpret_cast<SANE_Word*>(val); + option_size = s->opt[option].size / sizeof (SANE_Word); + dev->gamma_override_tables[GENESYS_GREEN].resize(option_size); + for (i = 0; i < option_size; i++) { + dev->gamma_override_tables[GENESYS_GREEN][i] = table[i]; + } + break; } - 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]; + case OPT_GAMMA_VECTOR_B: { + table = reinterpret_cast<SANE_Word*>(val); + option_size = s->opt[option].size / sizeof (SANE_Word); + dev->gamma_override_tables[GENESYS_BLUE].resize(option_size); + for (i = 0; i < option_size; i++) { + dev->gamma_override_tables[GENESYS_BLUE][i] = table[i]; + } + break; } - 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); + auto& sensor = sanei_genesys_find_sensor_for_write(dev, dev->settings.xres, + dev->settings.get_channels(), + dev->settings.scan_method); catch_all_exceptions(__func__, [&]() { - s->dev->cmd_set->save_power(s->dev, false); - genesys_scanner_calibration(s->dev, sensor); + dev->cmd_set->save_power(dev, false); + genesys_scanner_calibration(dev, sensor); }); catch_all_exceptions(__func__, [&]() { - s->dev->cmd_set->save_power(s->dev, true); + dev->cmd_set->save_power(dev, true); }); *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; break; } - case OPT_CLEAR_CALIBRATION: - s->dev->calibration_cache.clear(); + case OPT_CLEAR_CALIBRATION: { + 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(); + // remove file + unlink(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: { + dev->force_calibration = 1; + dev->calibration_cache.clear(); + dev->calib_file.clear(); - /* signals that sensors will have to be read again */ - *myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS; - break; + // 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; + dev->ignore_offsets = true; break; } - default: - DBG(DBG_warn, "%s: can't set unknown option %d\n", __func__, option); + default: { + DBG(DBG_warn, "%s: can't set unknown option %d\n", __func__, option); + } } } @@ -5829,13 +6217,13 @@ void sane_get_parameters_impl(SANE_Handle handle, SANE_Parameters* params) { DBG_HELPER(dbg); Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + auto* dev = s->dev; /* don't recompute parameters once data reading is active, ie during scan */ - if (!s->dev->read_active) { + if (!dev->read_active) { calc_parameters(s); } - if (params) - { + if (params) { *params = s->params; /* in the case of a sheetfed scanner, when full height is specified @@ -5843,11 +6231,11 @@ void sane_get_parameters_impl(SANE_Handle handle, SANE_Parameters* params) * 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 && + if (dev->model->is_sheetfed && s->pos_bottom_right_y == s->opt[OPT_BR_Y].constraint.range->max) { - params->lines = -1; - } + params->lines = -1; + } } debug_dump(DBG_proc, *params); } @@ -5865,6 +6253,7 @@ void sane_start_impl(SANE_Handle handle) { DBG_HELPER(dbg); Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + auto* dev = s->dev; if (s->pos_top_left_x >= s->pos_bottom_right_x) { throw SaneException("top left x >= bottom right x"); @@ -5873,67 +6262,27 @@ void sane_start_impl(SANE_Handle handle) 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; + // fetch stored calibration + if (dev->force_calibration == 0) { + auto path = calibration_filename(dev); + s->calibration_file = path; + dev->calib_file = path; + DBG(DBG_info, "%s: Calibration filename set to:\n", __func__); + DBG(DBG_info, "%s: >%s<\n", __func__, dev->calib_file.c_str()); - /* 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); + catch_all_exceptions(__func__, [&]() + { + sanei_genesys_read_calibration(dev->calibration_cache, dev->calib_file); + }); } - /* 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; - } + // First make sure we have a current parameter set. Some of the + // parameters will be overwritten below, but that's OK. - 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); }); - } + calc_parameters(s); + genesys_start_scan(dev, s->lamp_off); - if(s->swderotate) { - catch_all_exceptions(__func__, [&](){ genesys_derotate(s); }); - } - } + s->scanning = true; } SANE_GENESYS_API_LINKAGE @@ -5945,18 +6294,18 @@ SANE_Status sane_start(SANE_Handle handle) }); } -void sane_read_impl(SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int* len) +// returns SANE_STATUS_GOOD if there are more data, SANE_STATUS_EOF otherwise +SANE_Status 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; + auto* dev = s->dev; if (!dev) { throw SaneException("dev is nullptr"); } @@ -5986,86 +6335,33 @@ void sane_read_impl(SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_ /* 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) && + if (!dev->model->is_sheetfed && !has_flag(dev->model->flags, ModelFlag::MUST_WAIT) && !dev->parking) { dev->cmd_set->move_back_home(dev, false); dev->parking = true; } - throw SaneException(SANE_STATUS_EOF); + return 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; - } + genesys_read_ordered_data(dev, buf, &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.log(DBG_error, "error: returning incorrect length"); } DBG(DBG_proc, "%s: %d bytes returned\n", __func__, *len); + return SANE_STATUS_GOOD; } 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__, [=]() + return wrap_exceptions_to_status_code_return(__func__, [=]() { - sane_read_impl(handle, buf, max_len, len); + return sane_read_impl(handle, buf, max_len, len); }); } @@ -6073,36 +6369,31 @@ void sane_cancel_impl(SANE_Handle handle) { DBG_HELPER(dbg); Genesys_Scanner* s = reinterpret_cast<Genesys_Scanner*>(handle); + auto* dev = s->dev; s->scanning = false; - s->dev->read_active = false; - s->dev->img_buffer.clear(); + dev->read_active = false; - /* 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); + // no need to end scan if we are parking the head + if (!dev->parking) { + dev->cmd_set->end_scan(dev, &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); + // park head if flatbed scanner + if (!dev->model->is_sheetfed) { + if (!dev->parking) { + dev->cmd_set->move_back_home(dev, has_flag(dev->model->flags, ModelFlag::MUST_WAIT)); + dev->parking = !has_flag(dev->model->flags, ModelFlag::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); + } else { + // in case of sheetfed scanners, we have to eject the document if still present + dev->cmd_set->eject_document(dev); } - /* enable power saving mode unless we are parking .... */ - if (!s->dev->parking) { - s->dev->cmd_set->save_power(s->dev, true); + // enable power saving mode unless we are parking .... + if (!dev->parking) { + dev->cmd_set->save_power(dev, true); } - - return; } SANE_GENESYS_API_LINKAGE |