diff options
author | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2020-08-24 18:45:34 +0200 |
---|---|---|
committer | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2020-08-24 18:45:34 +0200 |
commit | 2b3e02411ecc09e7d41741b5587655c9b2f955b7 (patch) | |
tree | d839746371ecb8ed64ac81d2e37c11fcd25a00ac /backend/genesys/low.cpp | |
parent | 787fb1d54ec9ee5fb941ae897fb201feb9cb2fd1 (diff) | |
parent | ad38bc6ecb80ddeb562841b33258dd53659b1da6 (diff) |
Update upstream source from tag 'upstream/1.0.31'
Update to upstream version '1.0.31'
with Debian dir aa7a39fe56343f5e164eec83783f4c923a394865
Diffstat (limited to 'backend/genesys/low.cpp')
-rw-r--r-- | backend/genesys/low.cpp | 1234 |
1 files changed, 607 insertions, 627 deletions
diff --git a/backend/genesys/low.cpp b/backend/genesys/low.cpp index 7937fcc..05ef46b 100644 --- a/backend/genesys/low.cpp +++ b/backend/genesys/low.cpp @@ -51,12 +51,23 @@ #include "gl124_registers.h" #include "gl646_registers.h" #include "gl841_registers.h" +#include "gl842_registers.h" #include "gl843_registers.h" #include "gl846_registers.h" #include "gl847_registers.h" #include "gl646_registers.h" +#include "gl124.h" +#include "gl646.h" +#include "gl841.h" +#include "gl842.h" +#include "gl843.h" +#include "gl846.h" +#include "gl847.h" +#include "gl646.h" + #include <cstdio> +#include <chrono> #include <cmath> #include <vector> @@ -66,29 +77,17 @@ namespace genesys { -/** - * setup the hardware dependent functions - */ - -namespace gl124 { std::unique_ptr<CommandSet> create_gl124_cmd_set(); } -namespace gl646 { std::unique_ptr<CommandSet> create_gl646_cmd_set(); } -namespace gl841 { std::unique_ptr<CommandSet> create_gl841_cmd_set(); } -namespace gl843 { std::unique_ptr<CommandSet> create_gl843_cmd_set(); } -namespace gl846 { std::unique_ptr<CommandSet> create_gl846_cmd_set(); } -namespace gl847 { std::unique_ptr<CommandSet> create_gl847_cmd_set(); } - -void sanei_genesys_init_cmd_set(Genesys_Device* dev) +std::unique_ptr<CommandSet> create_cmd_set(AsicType asic_type) { - DBG_INIT (); - DBG_HELPER(dbg); - switch (dev->model->asic_type) { - case AsicType::GL646: dev->cmd_set = gl646::create_gl646_cmd_set(); break; - case AsicType::GL841: dev->cmd_set = gl841::create_gl841_cmd_set(); break; - case AsicType::GL843: dev->cmd_set = gl843::create_gl843_cmd_set(); break; + switch (asic_type) { + case AsicType::GL646: return std::unique_ptr<CommandSet>(new gl646::CommandSetGl646{}); + case AsicType::GL841: return std::unique_ptr<CommandSet>(new gl841::CommandSetGl841{}); + case AsicType::GL842: return std::unique_ptr<CommandSet>(new gl842::CommandSetGl842{}); + case AsicType::GL843: return std::unique_ptr<CommandSet>(new gl843::CommandSetGl843{}); case AsicType::GL845: // since only a few reg bits differs we handle both together - case AsicType::GL846: dev->cmd_set = gl846::create_gl846_cmd_set(); break; - case AsicType::GL847: dev->cmd_set = gl847::create_gl847_cmd_set(); break; - case AsicType::GL124: dev->cmd_set = gl124::create_gl124_cmd_set(); break; + case AsicType::GL846: return std::unique_ptr<CommandSet>(new gl846::CommandSetGl846{}); + case AsicType::GL847: return std::unique_ptr<CommandSet>(new gl847::CommandSetGl847{}); + case AsicType::GL124: return std::unique_ptr<CommandSet>(new gl124::CommandSetGl124{}); default: throw SaneException(SANE_STATUS_INVAL, "unknown ASIC type"); } } @@ -108,116 +107,6 @@ void sanei_genesys_write_file(const char* filename, const std::uint8_t* data, st std::fclose(out); } -// Write data to a pnm file (e.g. calibration). For debugging only -// data is RGB or grey, with little endian byte order -void sanei_genesys_write_pnm_file(const char* filename, const std::uint8_t* data, int depth, - int channels, int pixels_per_line, int lines) -{ - DBG_HELPER_ARGS(dbg, "depth=%d, channels=%d, ppl=%d, lines=%d", depth, channels, - pixels_per_line, lines); - int count; - - std::FILE* out = std::fopen(filename, "w"); - if (!out) - { - throw SaneException("could not open %s for writing: %s\n", filename, strerror(errno)); - } - if(depth==1) - { - fprintf (out, "P4\n%d\n%d\n", pixels_per_line, lines); - } - else - { - std::fprintf(out, "P%c\n%d\n%d\n%d\n", channels == 1 ? '5' : '6', pixels_per_line, lines, - static_cast<int>(std::pow(static_cast<double>(2), - static_cast<double>(depth - 1)))); - } - if (channels == 3) - { - for (count = 0; count < (pixels_per_line * lines * 3); count++) - { - if (depth == 16) - fputc (*(data + 1), out); - fputc (*(data++), out); - if (depth == 16) - data++; - } - } - else - { - if (depth==1) - { - pixels_per_line/=8; - } - for (count = 0; count < (pixels_per_line * lines); count++) - { - switch (depth) - { - case 8: - fputc (*(data + count), out); - break; - case 16: - fputc (*(data + 1), out); - fputc (*(data), out); - data += 2; - break; - default: - fputc(data[count], out); - break; - } - } - } - std::fclose(out); -} - -void sanei_genesys_write_pnm_file16(const char* filename, const uint16_t* data, unsigned channels, - unsigned pixels_per_line, unsigned lines) -{ - DBG_HELPER_ARGS(dbg, "channels=%d, ppl=%d, lines=%d", channels, - pixels_per_line, lines); - - std::FILE* out = std::fopen(filename, "w"); - if (!out) { - throw SaneException("could not open %s for writing: %s\n", filename, strerror(errno)); - } - std::fprintf(out, "P%c\n%d\n%d\n%d\n", channels == 1 ? '5' : '6', - pixels_per_line, lines, 256 * 256 - 1); - - for (unsigned count = 0; count < (pixels_per_line * lines * channels); count++) { - fputc(*data >> 8, out); - fputc(*data & 0xff, out); - data++; - } - std::fclose(out); -} - -bool is_supported_write_pnm_file_image_format(PixelFormat format) -{ - switch (format) { - case PixelFormat::I1: - case PixelFormat::RGB111: - case PixelFormat::I8: - case PixelFormat::RGB888: - case PixelFormat::I16: - case PixelFormat::RGB161616: - return true; - default: - return false; - } -} - -void sanei_genesys_write_pnm_file(const char* filename, const Image& image) -{ - if (!is_supported_write_pnm_file_image_format(image.get_format())) { - throw SaneException("Unsupported format %d", static_cast<unsigned>(image.get_format())); - } - - sanei_genesys_write_pnm_file(filename, image.get_row_ptr(0), - get_pixel_format_depth(image.get_format()), - get_pixel_channels(image.get_format()), - image.get_width(), image.get_height()); -} - /* ------------------------------------------------------------------------ */ /* Read and write RAM, registers and AFE */ /* ------------------------------------------------------------------------ */ @@ -276,6 +165,7 @@ Status scanner_read_status(Genesys_Device& dev) case AsicType::GL124: address = 0x101; break; case AsicType::GL646: case AsicType::GL841: + case AsicType::GL842: case AsicType::GL843: case AsicType::GL845: case AsicType::GL846: @@ -336,28 +226,23 @@ void debug_print_status(DebugMessageHelper& dbg, Status val) dbg.vlog(DBG_info, "status=%s\n", str.str().c_str()); } -#if 0 -/* returns pixels per line from register set */ -/*candidate for moving into chip specific files?*/ -static int -genesys_pixels_per_line (Genesys_Register_Set * reg) +void scanner_register_rw_clear_bits(Genesys_Device& dev, std::uint16_t address, std::uint8_t mask) { - int pixels_per_line; - - pixels_per_line = reg->get8(0x32) * 256 + reg->get8(0x33); - pixels_per_line -= (reg->get8(0x30) * 256 + reg->get8(0x31)); + scanner_register_rw_bits(dev, address, 0x00, mask); +} - return pixels_per_line; +void scanner_register_rw_set_bits(Genesys_Device& dev, std::uint16_t address, std::uint8_t mask) +{ + scanner_register_rw_bits(dev, address, mask, mask); } -/* returns dpiset from register set */ -/*candidate for moving into chip specific files?*/ -static int -genesys_dpiset (Genesys_Register_Set * reg) +void scanner_register_rw_bits(Genesys_Device& dev, std::uint16_t address, + std::uint8_t value, std::uint8_t mask) { - return reg->get8(0x2c) * 256 + reg->get8(0x2d); + auto reg_value = dev.interface->read_register(address); + reg_value = (reg_value & ~mask) | (value & mask); + dev.interface->write_register(address, reg_value); } -#endif /** read the number of valid words in scanner's RAM * ie registers 42-43-44 @@ -481,7 +366,7 @@ void wait_until_has_valid_words(Genesys_Device* dev) unsigned words = 0; unsigned sleep_time_ms = 10; - for (unsigned wait_ms = 0; wait_ms < 50000; wait_ms += sleep_time_ms) { + for (unsigned wait_ms = 0; wait_ms < 70000; wait_ms += sleep_time_ms) { sanei_genesys_read_valid_words(dev, &words); if (words != 0) break; @@ -516,7 +401,7 @@ Image read_unshuffled_image_from_scanner(Genesys_Device* dev, const ScanSession& dev->model->line_mode_color_order); auto width = get_pixels_from_row_bytes(format, session.output_line_bytes_raw); - auto height = session.output_line_count * (dev->model->is_cis ? session.params.channels : 1); + auto height = session.optical_line_count; Image image(width, height, format); @@ -525,7 +410,7 @@ Image read_unshuffled_image_from_scanner(Genesys_Device* dev, const ScanSession& throw SaneException("Trying to read too much data %zu (max %zu)", total_bytes, max_bytes); } if (total_bytes != max_bytes) { - DBG(DBG_info, "WARNING %s: trying to read not enough data (%zu, full fill %zu\n", __func__, + DBG(DBG_info, "WARNING %s: trying to read not enough data (%zu, full fill %zu)\n", __func__, total_bytes, max_bytes); } @@ -534,26 +419,138 @@ Image read_unshuffled_image_from_scanner(Genesys_Device* dev, const ScanSession& ImagePipelineStack pipeline; pipeline.push_first_node<ImagePipelineNodeImageSource>(image); - if ((dev->model->flags & GENESYS_FLAG_16BIT_DATA_INVERTED) && session.params.depth == 16) { - dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>(); + if (session.segment_count > 1) { + auto output_width = session.output_segment_pixel_group_count * session.segment_count; + pipeline.push_node<ImagePipelineNodeDesegment>(output_width, dev->segment_order, + session.conseq_pixel_dist, + 1, 1); } + if (session.params.depth == 16) { + unsigned num_swaps = 0; + if (has_flag(dev->model->flags, ModelFlag::SWAP_16BIT_DATA)) { + num_swaps++; + } #ifdef WORDS_BIGENDIAN - if (depth == 16) { - dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>(); + num_swaps++; +#endif + if (num_swaps % 2 != 0) { + dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>(); + } } + + if (has_flag(dev->model->flags, ModelFlag::INVERT_PIXEL_DATA)) { + pipeline.push_node<ImagePipelineNodeInvert>(); + } + + if (dev->model->is_cis && session.params.channels == 3) { + pipeline.push_node<ImagePipelineNodeMergeMonoLines>(dev->model->line_mode_color_order); + } + + if (pipeline.get_output_format() == PixelFormat::BGR888) { + pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB888); + } + + if (pipeline.get_output_format() == PixelFormat::BGR161616) { + pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB161616); + } + + return pipeline.get_image(); +} + + +Image read_shuffled_image_from_scanner(Genesys_Device* dev, const ScanSession& session) +{ + DBG_HELPER(dbg); + + std::size_t total_bytes = 0; + std::size_t pixels_per_line = 0; + + if (dev->model->asic_type == AsicType::GL842 || + dev->model->asic_type == AsicType::GL843 || + dev->model->model_id == ModelId::CANON_5600F) + { + pixels_per_line = session.output_pixels; + } else { + // BUG: this selects incorrect pixel number + pixels_per_line = session.params.pixels; + } + + // FIXME: the current calculation is likely incorrect on non-GL843 implementations, + // 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) + { + total_bytes = session.output_total_bytes_raw; + } else { + total_bytes = session.params.channels * 2 * pixels_per_line * (session.params.lines + 1); + } + + auto format = create_pixel_format(session.params.depth, + dev->model->is_cis ? 1 : session.params.channels, + dev->model->line_mode_color_order); + + // auto width = get_pixels_from_row_bytes(format, session.output_line_bytes_raw); + auto width = pixels_per_line; + auto height = session.params.lines + 1; // BUG: incorrect + if (dev->model->asic_type == AsicType::GL842 || + dev->model->asic_type == AsicType::GL843 || + dev->model->model_id == ModelId::CANON_5600F) + { + height = session.optical_line_count; + } + + Image image(width, height, format); + + auto max_bytes = image.get_row_bytes() * height; + if (total_bytes > max_bytes) { + throw SaneException("Trying to read too much data %zu (max %zu)", total_bytes, max_bytes); + } + if (total_bytes != max_bytes) { + DBG(DBG_info, "WARNING %s: trying to read not enough data (%zu, full fill %zu)\n", __func__, + total_bytes, max_bytes); + } + + sanei_genesys_read_data_from_scanner(dev, image.get_row_ptr(0), total_bytes); + + ImagePipelineStack pipeline; + pipeline.push_first_node<ImagePipelineNodeImageSource>(image); + + if (session.segment_count > 1) { + auto output_width = session.output_segment_pixel_group_count * session.segment_count; + pipeline.push_node<ImagePipelineNodeDesegment>(output_width, dev->segment_order, + session.conseq_pixel_dist, + 1, 1); + } + + if (session.params.depth == 16) { + unsigned num_swaps = 0; + if (has_flag(dev->model->flags, ModelFlag::SWAP_16BIT_DATA)) { + num_swaps++; + } +#ifdef WORDS_BIGENDIAN + num_swaps++; #endif + if (num_swaps % 2 != 0) { + dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>(); + } + } + + if (has_flag(dev->model->flags, ModelFlag::INVERT_PIXEL_DATA)) { + pipeline.push_node<ImagePipelineNodeInvert>(); + } if (dev->model->is_cis && session.params.channels == 3) { - dev->pipeline.push_node<ImagePipelineNodeMergeMonoLines>(dev->model->line_mode_color_order); + pipeline.push_node<ImagePipelineNodeMergeMonoLines>(dev->model->line_mode_color_order); } - if (dev->pipeline.get_output_format() == PixelFormat::BGR888) { - dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB888); + if (pipeline.get_output_format() == PixelFormat::BGR888) { + pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB888); } - if (dev->pipeline.get_output_format() == PixelFormat::BGR161616) { - dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB161616); + if (pipeline.get_output_format() == PixelFormat::BGR161616) { + pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB161616); } return pipeline.get_image(); @@ -600,34 +597,27 @@ void sanei_genesys_set_lamp_power(Genesys_Device* dev, const Genesys_Sensor& sen if (dev->model->asic_type == AsicType::GL843) { regs_set_exposure(dev->model->asic_type, regs, sensor.exposure); + } - // we don't actually turn on lamp on infrared scan - if ((dev->model->model_id == ModelId::CANON_8400F || - dev->model->model_id == ModelId::CANON_8600F || - dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || - dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) && - dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) - { - regs.find_reg(0x03).value &= ~REG_0x03_LAMPPWR; - } + // we don't actually turn on lamp on infrared scan + if ((dev->model->model_id == ModelId::CANON_8400F || + dev->model->model_id == ModelId::CANON_8600F || + dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || + dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I || + dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_8200I) && + dev->settings.scan_method == ScanMethod::TRANSPARENCY_INFRARED) + { + regs.find_reg(0x03).value &= ~REG_0x03_LAMPPWR; } } else { regs.find_reg(0x03).value &= ~REG_0x03_LAMPPWR; if (dev->model->asic_type == AsicType::GL841) { - regs_set_exposure(dev->model->asic_type, regs, {0x0101, 0x0101, 0x0101}); + regs_set_exposure(dev->model->asic_type, regs, sanei_genesys_fixup_exposure({0, 0, 0})); regs.set8(0x19, 0xff); } - - if (dev->model->asic_type == AsicType::GL843) { - if (dev->model->model_id == ModelId::PANASONIC_KV_SS080 || - dev->model->model_id == ModelId::HP_SCANJET_4850C || - dev->model->model_id == ModelId::HP_SCANJET_G4010 || - dev->model->model_id == ModelId::HP_SCANJET_G4050) - { - // BUG: datasheet says we shouldn't set exposure to zero - regs_set_exposure(dev->model->asic_type, regs, {0, 0, 0}); - } + if (dev->model->model_id == ModelId::CANON_5600F) { + regs_set_exposure(dev->model->asic_type, regs, sanei_genesys_fixup_exposure({0, 0, 0})); } } regs.state.is_lamp_on = set; @@ -786,218 +776,144 @@ void sanei_genesys_send_gamma_table(Genesys_Device* dev, const Genesys_Sensor& s } } -static unsigned align_int_up(unsigned num, unsigned alignment) -{ - unsigned mask = alignment - 1; - if (num & mask) - num = (num & ~mask) + alignment; - return num; -} - -void compute_session_buffer_sizes(AsicType asic, ScanSession& s) +void compute_session_pixel_offsets(const Genesys_Device* dev, ScanSession& s, + const Genesys_Sensor& sensor) { - size_t line_bytes = s.output_line_bytes; - size_t line_bytes_stagger = s.output_line_bytes; - - if (asic != AsicType::GL646) { - // BUG: this is historical artifact and should be removed. Note that buffer sizes affect - // how often we request the scanner for data and thus change the USB traffic. - line_bytes_stagger = - multiply_by_depth_ceil(s.optical_pixels, s.params.depth) * s.params.channels; - } - - struct BufferConfig { - size_t* result_size = nullptr; - size_t lines = 0; - size_t lines_mult = 0; - size_t max_size = 0; // does not apply if 0 - size_t stagger_lines = 0; - - BufferConfig() = default; - BufferConfig(std::size_t* rs, std::size_t l, std::size_t lm, std::size_t ms, - std::size_t sl) : - result_size{rs}, - lines{l}, - lines_mult{lm}, - max_size{ms}, - stagger_lines{sl} - {} - }; + if (dev->model->asic_type == AsicType::GL646) { + s.pixel_startx += s.output_startx * sensor.full_resolution / s.params.xres; + s.pixel_endx = s.pixel_startx + s.optical_pixels * s.full_resolution / s.optical_resolution; - std::array<BufferConfig, 4> configs; - if (asic == AsicType::GL124 || asic == AsicType::GL843) { - configs = { { - { &s.buffer_size_read, 32, 1, 0, s.max_color_shift_lines + s.num_staggered_lines }, - { &s.buffer_size_lines, 32, 1, 0, s.max_color_shift_lines + s.num_staggered_lines }, - { &s.buffer_size_shrink, 16, 1, 0, 0 }, - { &s.buffer_size_out, 8, 1, 0, 0 }, - } }; - } else if (asic == AsicType::GL841) { - size_t max_buf = sanei_genesys_get_bulk_max_size(asic); - configs = { { - { &s.buffer_size_read, 8, 2, max_buf, s.max_color_shift_lines + s.num_staggered_lines }, - { &s.buffer_size_lines, 8, 2, max_buf, s.max_color_shift_lines + s.num_staggered_lines }, - { &s.buffer_size_shrink, 8, 1, max_buf, 0 }, - { &s.buffer_size_out, 8, 1, 0, 0 }, - } }; - } else { - configs = { { - { &s.buffer_size_read, 16, 1, 0, s.max_color_shift_lines + s.num_staggered_lines }, - { &s.buffer_size_lines, 16, 1, 0, s.max_color_shift_lines + s.num_staggered_lines }, - { &s.buffer_size_shrink, 8, 1, 0, 0 }, - { &s.buffer_size_out, 8, 1, 0, 0 }, - } }; - } - - for (BufferConfig& config : configs) { - size_t buf_size = line_bytes * config.lines; - if (config.max_size > 0 && buf_size > config.max_size) { - buf_size = (config.max_size / line_bytes) * line_bytes; + } else if (dev->model->asic_type == AsicType::GL841 || + dev->model->asic_type == AsicType::GL842 || + dev->model->asic_type == AsicType::GL843 || + dev->model->asic_type == AsicType::GL845 || + dev->model->asic_type == AsicType::GL846 || + dev->model->asic_type == AsicType::GL847) + { + unsigned startx_xres = s.optical_resolution; + if (dev->model->model_id == ModelId::CANON_5600F || + dev->model->model_id == ModelId::CANON_LIDE_90) + { + if (s.output_resolution == 1200) { + startx_xres /= 2; + } + if (s.output_resolution >= 2400) { + startx_xres /= 4; + } } - buf_size *= config.lines_mult; - buf_size += line_bytes_stagger * config.stagger_lines; - *config.result_size = buf_size; - } -} - -void compute_session_pipeline(const Genesys_Device* dev, ScanSession& s) -{ - auto channels = s.params.channels; - auto depth = s.params.depth; + s.pixel_startx = (s.output_startx * startx_xres) / s.params.xres; + s.pixel_endx = s.pixel_startx + s.optical_pixels_raw; - s.pipeline_needs_reorder = true; - if (channels != 3 && depth != 16) { - s.pipeline_needs_reorder = false; - } -#ifndef WORDS_BIGENDIAN - if (channels != 3 && depth == 16) { - s.pipeline_needs_reorder = false; - } - if (channels == 3 && depth == 16 && !dev->model->is_cis && - dev->model->line_mode_color_order == ColorOrder::RGB) + } else if (dev->model->asic_type == AsicType::GL124) { - s.pipeline_needs_reorder = false; + s.pixel_startx = s.output_startx * sensor.full_resolution / s.params.xres; + s.pixel_endx = s.pixel_startx + s.optical_pixels_raw; } -#endif - if (channels == 3 && depth == 8 && !dev->model->is_cis && - dev->model->line_mode_color_order == ColorOrder::RGB) + + // align pixels to correct boundary for unstaggering + unsigned needed_x_alignment = std::max(s.stagger_x.size(), s.stagger_y.size()); + unsigned aligned_pixel_startx = align_multiple_floor(s.pixel_startx, needed_x_alignment); + s.pixel_endx -= s.pixel_startx - aligned_pixel_startx; + s.pixel_startx = aligned_pixel_startx; + + s.pixel_startx = sensor.pixel_count_ratio.apply(s.pixel_startx); + s.pixel_endx = sensor.pixel_count_ratio.apply(s.pixel_endx); + + if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200 || + dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || + dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || + dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) { - s.pipeline_needs_reorder = false; + s.pixel_startx = align_multiple_floor(s.pixel_startx, sensor.pixel_count_ratio.divisor()); + s.pixel_endx = align_multiple_floor(s.pixel_endx, sensor.pixel_count_ratio.divisor()); } - s.pipeline_needs_ccd = s.max_color_shift_lines + s.num_staggered_lines > 0; - s.pipeline_needs_shrink = dev->settings.requested_pixels != s.output_pixels; } -void compute_session_pixel_offsets(const Genesys_Device* dev, ScanSession& s, - const Genesys_Sensor& sensor) +unsigned session_adjust_output_pixels(unsigned output_pixels, + const Genesys_Device& dev, const Genesys_Sensor& sensor, + unsigned output_xresolution, unsigned output_yresolution, + bool adjust_output_pixels) { - unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel(); - - if (dev->model->asic_type == AsicType::GL646) { - - // startx cannot be below dummy pixel value - s.pixel_startx = sensor.dummy_pixel; - if (has_flag(s.params.flags, ScanFlag::USE_XCORRECTION) && sensor.ccd_start_xoffset > 0) { - s.pixel_startx = sensor.ccd_start_xoffset; - } - s.pixel_startx += s.params.startx; - - if (sensor.stagger_config.stagger_at_resolution(s.params.xres, s.params.yres) > 0) { - s.pixel_startx |= 1; - } - - s.pixel_endx = s.pixel_startx + s.optical_pixels; - - s.pixel_startx /= sensor.ccd_pixels_per_system_pixel() * s.ccd_size_divisor; - s.pixel_endx /= sensor.ccd_pixels_per_system_pixel() * s.ccd_size_divisor; - - } else if (dev->model->asic_type == AsicType::GL841) { - s.pixel_startx = ((sensor.ccd_start_xoffset + s.params.startx) * s.optical_resolution) - / sensor.optical_res; + bool adjust_optical_pixels = !adjust_output_pixels; + if (dev.model->model_id == ModelId::CANON_5600F) { + adjust_optical_pixels = true; + adjust_output_pixels = true; + } + if (adjust_optical_pixels) { + auto optical_resolution = sensor.get_optical_resolution(); - s.pixel_startx += sensor.dummy_pixel + 1; + // FIXME: better way would be to compute and return the required multiplier + unsigned optical_pixels = (output_pixels * optical_resolution) / output_xresolution; - if (s.num_staggered_lines > 0 && (s.pixel_startx & 1) == 0) { - s.pixel_startx++; + if (dev.model->asic_type == AsicType::GL841 || + dev.model->asic_type == AsicType::GL842) + { + optical_pixels = align_multiple_ceil(optical_pixels, 2); } - /* In case of SHDAREA, we need to align start on pixel average factor, startx is - different than 0 only when calling for function to setup for scan, where shading data - needs to be align. - - NOTE: we can check the value of the register here, because we don't set this bit - anywhere except in initialization. - */ - const uint8_t REG_0x01_SHDAREA = 0x02; - if ((dev->reg.find_reg(0x01).value & REG_0x01_SHDAREA) != 0) { - unsigned average_factor = s.optical_resolution / s.params.xres; - s.pixel_startx = align_multiple_floor(s.pixel_startx, average_factor); + if (dev.model->asic_type == AsicType::GL646 && output_xresolution == 400) { + optical_pixels = align_multiple_floor(optical_pixels, 6); } - s.pixel_endx = s.pixel_startx + s.optical_pixels; - - } else if (dev->model->asic_type == AsicType::GL843) { - - s.pixel_startx = (s.params.startx + sensor.dummy_pixel) / ccd_pixels_per_system_pixel; - s.pixel_endx = s.pixel_startx + s.optical_pixels / ccd_pixels_per_system_pixel; - - s.pixel_startx /= s.hwdpi_divisor; - s.pixel_endx /= s.hwdpi_divisor; - - // in case of stagger we have to start at an odd coordinate - bool stagger_starts_even = dev->model->model_id == ModelId::CANON_8400F; - if (s.num_staggered_lines > 0) { - if (!stagger_starts_even && (s.pixel_startx & 1) == 0) { - s.pixel_startx++; - s.pixel_endx++; - } else if (stagger_starts_even && (s.pixel_startx & 1) != 0) { - s.pixel_startx++; - s.pixel_endx++; + if (dev.model->asic_type == AsicType::GL843) { + // ensure the number of optical pixels is divisible by 2. + // In quarter-CCD mode optical_pixels is 4x larger than the actual physical number + optical_pixels = align_multiple_ceil(optical_pixels, + 2 * sensor.full_resolution / optical_resolution); + if (dev.model->model_id == ModelId::PLUSTEK_OPTICFILM_7200 || + dev.model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || + dev.model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || + dev.model->model_id == ModelId::PLUSTEK_OPTICFILM_7400 || + dev.model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I || + dev.model->model_id == ModelId::PLUSTEK_OPTICFILM_8200I) + { + optical_pixels = align_multiple_ceil(optical_pixels, 16); } } + output_pixels = (optical_pixels * output_xresolution) / optical_resolution; + } - } else if (dev->model->asic_type == AsicType::GL845 || - dev->model->asic_type == AsicType::GL846 || - dev->model->asic_type == AsicType::GL847) - { - s.pixel_startx = s.params.startx; - - if (s.num_staggered_lines > 0) { - s.pixel_startx |= 1; - } - - s.pixel_startx += sensor.ccd_start_xoffset * ccd_pixels_per_system_pixel; - s.pixel_endx = s.pixel_startx + s.optical_pixels_raw; - - s.pixel_startx /= s.hwdpi_divisor * s.segment_count * ccd_pixels_per_system_pixel; - s.pixel_endx /= s.hwdpi_divisor * s.segment_count * ccd_pixels_per_system_pixel; - - } else if (dev->model->asic_type == AsicType::GL124) { - s.pixel_startx = s.params.startx; + if (adjust_output_pixels) { + // TODO: the following may no longer be needed but were applied historically. - if (s.num_staggered_lines > 0) { - s.pixel_startx |= 1; + // we need an even pixels number + // TODO invert test logic or generalize behaviour across all ASICs + if (has_flag(dev.model->flags, ModelFlag::SIS_SENSOR) || + dev.model->asic_type == AsicType::GL847 || + dev.model->asic_type == AsicType::GL124 || + dev.model->asic_type == AsicType::GL845 || + dev.model->asic_type == AsicType::GL846 || + dev.model->asic_type == AsicType::GL843) + { + if (output_xresolution <= 1200) { + output_pixels = align_multiple_floor(output_pixels, 4); + } else if (output_xresolution < output_yresolution) { + // 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 + output_pixels = align_multiple_floor(output_pixels, 8); + } else { + output_pixels = align_multiple_floor(output_pixels, 16); + } } - s.pixel_startx /= ccd_pixels_per_system_pixel; - // FIXME: should we add sensor.dummy_pxel to pixel_startx at this point? - s.pixel_endx = s.pixel_startx + s.optical_pixels / ccd_pixels_per_system_pixel; - - s.pixel_startx /= s.hwdpi_divisor * s.segment_count; - s.pixel_endx /= s.hwdpi_divisor * s.segment_count; - - std::uint32_t segcnt = (sensor.custom_regs.get_value(gl124::REG_SEGCNT) << 16) + - (sensor.custom_regs.get_value(gl124::REG_SEGCNT + 1) << 8) + - sensor.custom_regs.get_value(gl124::REG_SEGCNT + 2); - if (s.pixel_endx == segcnt) { - s.pixel_endx = 0; + // corner case for true lineart for sensor with several segments or when xres is doubled + // to match yres */ + if (output_xresolution >= 1200 && ( + dev.model->asic_type == AsicType::GL124 || + dev.model->asic_type == AsicType::GL847 || + dev.session.params.xres < dev.session.params.yres)) + { + if (output_xresolution < output_yresolution) { + // 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 + output_pixels = align_multiple_floor(output_pixels, 8); + } else { + output_pixels = align_multiple_floor(output_pixels, 16); + } } } - s.pixel_count_multiplier = sensor.pixel_count_multiplier; - - s.pixel_startx *= sensor.pixel_count_multiplier; - s.pixel_endx *= sensor.pixel_count_multiplier; + return output_pixels; } void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Sensor& sensor) @@ -1011,68 +927,36 @@ void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Se throw SaneException("Unsupported depth setting %d", s.params.depth); } - unsigned ccd_pixels_per_system_pixel = sensor.ccd_pixels_per_system_pixel(); - // compute optical and output resolutions - - if (dev->model->asic_type == AsicType::GL843) { - // FIXME: this may be incorrect, but need more scanners to test - s.hwdpi_divisor = sensor.get_hwdpi_divisor_for_dpi(s.params.xres); - } else { - s.hwdpi_divisor = sensor.get_hwdpi_divisor_for_dpi(s.params.xres * ccd_pixels_per_system_pixel); - } - - s.ccd_size_divisor = sensor.get_ccd_size_divisor_for_dpi(s.params.xres); - - if (dev->model->asic_type == AsicType::GL646) { - s.optical_resolution = sensor.optical_res; - } else { - s.optical_resolution = sensor.optical_res / s.ccd_size_divisor; - } + s.full_resolution = sensor.full_resolution; + s.optical_resolution = sensor.get_optical_resolution(); s.output_resolution = s.params.xres; + s.pixel_count_ratio = sensor.pixel_count_ratio; + if (s.output_resolution > s.optical_resolution) { throw std::runtime_error("output resolution higher than optical resolution"); } - // compute the number of optical pixels that will be acquired by the chip - s.optical_pixels = (s.params.pixels * s.optical_resolution) / s.output_resolution; - if (s.optical_pixels * s.output_resolution < s.params.pixels * s.optical_resolution) { - s.optical_pixels++; - } - - if (dev->model->asic_type == AsicType::GL841) { - if (s.optical_pixels & 1) - s.optical_pixels++; - } - - if (dev->model->asic_type == AsicType::GL646 && s.params.xres == 400) { - s.optical_pixels = (s.optical_pixels / 6) * 6; - } + s.output_pixels = session_adjust_output_pixels(s.params.pixels, *dev, sensor, + s.params.xres, s.params.yres, false); - if (dev->model->asic_type == AsicType::GL843) { - // ensure the number of optical pixels is divisible by 2. - // In quarter-CCD mode optical_pixels is 4x larger than the actual physical number - s.optical_pixels = align_int_up(s.optical_pixels, 2 * s.ccd_size_divisor); + // Compute the number of optical pixels that will be acquired by the chip. + // The necessary alignment requirements have already been computed by + // get_session_output_pixels_multiplier + s.optical_pixels = (s.output_pixels * s.optical_resolution) / s.output_resolution; - if (dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7200I || - dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7300 || - dev->model->model_id == ModelId::PLUSTEK_OPTICFILM_7500I) - { - s.optical_pixels = align_int_up(s.optical_pixels, 16); - } - } + if (static_cast<int>(s.params.startx) + sensor.output_pixel_offset < 0) + throw SaneException("Invalid sensor.output_pixel_offset"); + s.output_startx = static_cast<unsigned>( + static_cast<int>(s.params.startx) + sensor.output_pixel_offset); - // after all adjustments on the optical pixels have been made, compute the number of pixels - // to retrieve from the chip - s.output_pixels = (s.optical_pixels * s.output_resolution) / s.optical_resolution; + s.stagger_x = sensor.stagger_x; + s.stagger_y = sensor.stagger_y; - // Note: staggering is not applied for calibration. Staggering starts at 2400 dpi s.num_staggered_lines = 0; - if (!has_flag(s.params.flags, ScanFlag::IGNORE_LINE_DISTANCE)) - { - s.num_staggered_lines = sensor.stagger_config.stagger_at_resolution(s.params.xres, - s.params.yres); + if (!has_flag(s.params.flags, ScanFlag::IGNORE_STAGGER_OFFSET)) { + s.num_staggered_lines = s.stagger_y.max_shift() * s.params.yres / s.params.xres; } s.color_shift_lines_r = dev->model->ld_shift_r; @@ -1091,12 +975,14 @@ void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Se s.color_shift_lines_b = (s.color_shift_lines_b * s.params.yres) / dev->motor.base_ydpi; s.max_color_shift_lines = 0; - if (s.params.channels > 1 && !has_flag(s.params.flags, ScanFlag::IGNORE_LINE_DISTANCE)) { + if (s.params.channels > 1 && !has_flag(s.params.flags, ScanFlag::IGNORE_COLOR_OFFSET)) { s.max_color_shift_lines = std::max(s.color_shift_lines_r, std::max(s.color_shift_lines_g, s.color_shift_lines_b)); } s.output_line_count = s.params.lines + s.max_color_shift_lines + s.num_staggered_lines; + s.optical_line_count = dev->model->is_cis ? s.output_line_count * s.params.channels + : s.output_line_count; s.output_channel_bytes = multiply_by_depth_ceil(s.output_pixels, s.params.depth); s.output_line_bytes = s.output_channel_bytes * s.params.channels; @@ -1107,29 +993,62 @@ void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Se s.output_line_bytes_raw = s.output_line_bytes; s.conseq_pixel_dist = 0; - if (dev->model->asic_type == AsicType::GL845 || - dev->model->asic_type == AsicType::GL846 || - dev->model->asic_type == AsicType::GL847) + // FIXME: Use ModelFlag::SIS_SENSOR + if ((dev->model->asic_type == AsicType::GL845 || + dev->model->asic_type == AsicType::GL846 || + dev->model->asic_type == AsicType::GL847) && + dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_7400 && + dev->model->model_id != ModelId::PLUSTEK_OPTICFILM_8200I) { if (s.segment_count > 1) { s.conseq_pixel_dist = sensor.segment_size; - // in case of multi-segments sensor, we have to add the width of the sensor crossed by - // the scan area - unsigned extra_segment_scan_area = align_multiple_ceil(s.conseq_pixel_dist, 2); - extra_segment_scan_area *= s.segment_count - 1; - extra_segment_scan_area *= s.hwdpi_divisor * s.segment_count; - extra_segment_scan_area *= ccd_pixels_per_system_pixel; + // in case of multi-segments sensor, we have expand the scan area to sensor boundary + if (dev->model->model_id == ModelId::CANON_5600F) { + unsigned startx_xres = s.optical_resolution; + if (dev->model->model_id == ModelId::CANON_5600F) { + if (s.output_resolution == 1200) { + startx_xres /= 2; + } + if (s.output_resolution >= 2400) { + startx_xres /= 4; + } + } + unsigned optical_startx = s.output_startx * startx_xres / s.params.xres; + unsigned optical_endx = optical_startx + s.optical_pixels; - s.optical_pixels_raw += extra_segment_scan_area; + unsigned multi_segment_size_output = s.segment_count * s.conseq_pixel_dist; + unsigned multi_segment_size_optical = + (multi_segment_size_output * s.optical_resolution) / s.output_resolution; + + optical_endx = align_multiple_ceil(optical_endx, multi_segment_size_optical); + s.optical_pixels_raw = optical_endx - optical_startx; + s.optical_pixels_raw = align_multiple_floor(s.optical_pixels_raw, + 4 * s.optical_resolution / s.output_resolution); + } else { + // BUG: the following code will likely scan too much. Use the CANON_5600F approach + unsigned extra_segment_scan_area = align_multiple_ceil(s.conseq_pixel_dist, 2); + extra_segment_scan_area *= s.segment_count - 1; + extra_segment_scan_area = s.pixel_count_ratio.apply_inverse(extra_segment_scan_area); + + s.optical_pixels_raw += extra_segment_scan_area; + } } - s.output_line_bytes_raw = multiply_by_depth_ceil( - (s.optical_pixels_raw * s.output_resolution) / sensor.optical_res / s.segment_count, - s.params.depth); + if (dev->model->model_id == ModelId::CANON_5600F) { + auto output_pixels_raw = (s.optical_pixels_raw * s.output_resolution) / s.optical_resolution; + auto output_channel_bytes_raw = multiply_by_depth_ceil(output_pixels_raw, s.params.depth); + s.output_line_bytes_raw = output_channel_bytes_raw * s.params.channels; + } else { + s.output_line_bytes_raw = multiply_by_depth_ceil( + (s.optical_pixels_raw * s.output_resolution) / sensor.full_resolution / s.segment_count, + s.params.depth); + } } - if (dev->model->asic_type == AsicType::GL841) { + if (dev->model->asic_type == AsicType::GL841 || + dev->model->asic_type == AsicType::GL842) + { if (dev->model->is_cis) { s.output_line_bytes_raw = s.output_channel_bytes; } @@ -1139,27 +1058,43 @@ void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Se if (dev->model->is_cis) { s.output_line_bytes_raw = s.output_channel_bytes; } - s.conseq_pixel_dist = s.output_pixels / s.ccd_size_divisor / s.segment_count; + s.conseq_pixel_dist = s.output_pixels / (s.full_resolution / s.optical_resolution) / s.segment_count; } - if (dev->model->asic_type == AsicType::GL843) { - s.conseq_pixel_dist = s.output_pixels / s.segment_count; + if (dev->model->asic_type == AsicType::GL842 || + dev->model->asic_type == AsicType::GL843) + { + if (dev->model->is_cis) { + if (s.segment_count > 1) { + s.conseq_pixel_dist = sensor.segment_size; + } + } else { + s.conseq_pixel_dist = s.output_pixels / s.segment_count; + } } s.output_segment_pixel_group_count = 0; if (dev->model->asic_type == AsicType::GL124 || + dev->model->asic_type == AsicType::GL842 || dev->model->asic_type == AsicType::GL843) { - s.output_segment_pixel_group_count = multiply_by_depth_ceil( - s.output_pixels / s.ccd_size_divisor / s.segment_count, s.params.depth); + s.output_segment_pixel_group_count = s.output_pixels / + (s.full_resolution / s.optical_resolution * s.segment_count); } + + if (dev->model->model_id == ModelId::CANON_LIDE_90) { + s.output_segment_pixel_group_count = s.output_pixels / s.segment_count; + } + if (dev->model->asic_type == AsicType::GL845 || dev->model->asic_type == AsicType::GL846 || dev->model->asic_type == AsicType::GL847) { - s.output_segment_pixel_group_count = multiply_by_depth_ceil( - s.optical_pixels / (s.hwdpi_divisor * s.segment_count * ccd_pixels_per_system_pixel), - s.params.depth); + if (dev->model->model_id == ModelId::CANON_5600F) { + s.output_segment_pixel_group_count = s.output_pixels / s.segment_count; + } else { + s.output_segment_pixel_group_count = s.pixel_count_ratio.apply(s.optical_pixels); + } } s.output_line_bytes_requested = multiply_by_depth_ceil( @@ -1167,11 +1102,16 @@ void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Se s.output_total_bytes_raw = s.output_line_bytes_raw * s.output_line_count; s.output_total_bytes = s.output_line_bytes * s.output_line_count; + if (dev->model->model_id == ModelId::CANON_LIDE_90) { + s.output_total_bytes_raw *= s.params.channels; + s.output_total_bytes *= s.params.channels; + } - compute_session_buffer_sizes(dev->model->asic_type, s); - compute_session_pipeline(dev, s); + s.buffer_size_read = s.output_line_bytes_raw * 64; compute_session_pixel_offsets(dev, s, sensor); + s.shading_pixel_offset = sensor.shading_pixel_offset; + if (dev->model->asic_type == AsicType::GL124 || dev->model->asic_type == AsicType::GL845 || dev->model->asic_type == AsicType::GL846) @@ -1179,7 +1119,10 @@ void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Se s.enable_ledadd = (s.params.channels == 1 && dev->model->is_cis && dev->settings.true_gray); } + s.use_host_side_calib = sensor.use_host_side_calib; + if (dev->model->asic_type == AsicType::GL841 || + dev->model->asic_type == AsicType::GL842 || dev->model->asic_type == AsicType::GL843) { // no 16 bit gamma for this ASIC @@ -1194,177 +1137,166 @@ void compute_session(const Genesys_Device* dev, ScanSession& s, const Genesys_Se debug_dump(DBG_info, s); } -static std::size_t get_usb_buffer_read_size(AsicType asic, const ScanSession& session) +ImagePipelineStack build_image_pipeline(const Genesys_Device& dev, const ScanSession& session, + unsigned pipeline_index, bool log_image_data) { - switch (asic) { - case AsicType::GL646: - // buffer not used on this chip set - return 1; - - case AsicType::GL124: - // BUG: we shouldn't multiply by channels here nor divide by ccd_size_divisor - return session.output_line_bytes_raw / session.ccd_size_divisor * session.params.channels; - - case AsicType::GL845: - case AsicType::GL846: - case AsicType::GL847: - // BUG: we shouldn't multiply by channels here - return session.output_line_bytes_raw * session.params.channels; - - case AsicType::GL843: - return session.output_line_bytes_raw * 2; - - default: - throw SaneException("Unknown asic type"); - } -} - -static FakeBufferModel get_fake_usb_buffer_model(const ScanSession& session) -{ - FakeBufferModel model; - model.push_step(session.buffer_size_read, 1); - - if (session.pipeline_needs_reorder) { - model.push_step(session.buffer_size_lines, session.output_line_bytes); - } - if (session.pipeline_needs_ccd) { - model.push_step(session.buffer_size_shrink, session.output_line_bytes); - } - if (session.pipeline_needs_shrink) { - model.push_step(session.buffer_size_out, session.output_line_bytes); - } - - return model; -} - -void build_image_pipeline(Genesys_Device* dev, const ScanSession& session) -{ - static unsigned s_pipeline_index = 0; - - s_pipeline_index++; - auto format = create_pixel_format(session.params.depth, - dev->model->is_cis ? 1 : session.params.channels, - dev->model->line_mode_color_order); + dev.model->is_cis ? 1 : session.params.channels, + dev.model->line_mode_color_order); auto depth = get_pixel_format_depth(format); auto width = get_pixels_from_row_bytes(format, session.output_line_bytes_raw); - auto read_data_from_usb = [dev](std::size_t size, std::uint8_t* data) + auto read_data_from_usb = [&dev](std::size_t size, std::uint8_t* data) { - dev->interface->bulk_read_data(0x45, data, size); + DBG(DBG_info, "read_data_from_usb: reading %zu bytes\n", size); + auto begin = std::chrono::high_resolution_clock::now(); + dev.interface->bulk_read_data(0x45, data, size); + auto end = std::chrono::high_resolution_clock::now(); + float us = std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count(); + float speed = size / us; // bytes/us == MB/s + DBG(DBG_info, "read_data_from_usb: reading %zu bytes finished %f MB/s\n", size, speed); return true; }; - auto lines = session.output_line_count * (dev->model->is_cis ? session.params.channels : 1); + auto debug_prefix = "gl_pipeline_" + std::to_string(pipeline_index); - dev->pipeline.clear(); + ImagePipelineStack pipeline; - // FIXME: here we are complicating things for the time being to preserve the existing behaviour - // This allows to be sure that the changes to the image pipeline have not introduced - // regressions. + auto lines = session.optical_line_count; + auto buffer_size = session.buffer_size_read; - if (session.segment_count > 1) { - // BUG: we're reading one line too much - dev->pipeline.push_first_node<ImagePipelineNodeBufferedCallableSource>( - width, lines + 1, format, - get_usb_buffer_read_size(dev->model->asic_type, session), read_data_from_usb); + // At least GL841 requires reads to be aligned to 2 bytes and will fail on some devices on + // certain circumstances. + buffer_size = align_multiple_ceil(buffer_size, 2); + + auto& src_node = pipeline.push_first_node<ImagePipelineNodeBufferedCallableSource>( + width, lines, format, buffer_size, read_data_from_usb); + src_node.set_last_read_multiple(2); + if (log_image_data) { + pipeline.push_node<ImagePipelineNodeDebug>(debug_prefix + "_0_from_usb.tiff"); + } + + if (session.segment_count > 1) { auto output_width = session.output_segment_pixel_group_count * session.segment_count; - dev->pipeline.push_node<ImagePipelineNodeDesegment>(output_width, dev->segment_order, + pipeline.push_node<ImagePipelineNodeDesegment>(output_width, dev.segment_order, session.conseq_pixel_dist, 1, 1); - } else { - auto read_bytes_left_after_deseg = session.output_line_bytes * session.output_line_count; - if (dev->model->asic_type == AsicType::GL646) { - read_bytes_left_after_deseg *= dev->model->is_cis ? session.params.channels : 1; - } - dev->pipeline.push_first_node<ImagePipelineNodeBufferedGenesysUsb>( - width, lines, format, read_bytes_left_after_deseg, - get_fake_usb_buffer_model(session), read_data_from_usb); + if (log_image_data) { + pipeline.push_node<ImagePipelineNodeDebug>(debug_prefix + "_1_after_desegment.tiff"); + } } - if (DBG_LEVEL >= DBG_io2) { - dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" + - std::to_string(s_pipeline_index) + - "_0_before_swap.pnm"); - } + if (depth == 16) { + unsigned num_swaps = 0; + if (has_flag(dev.model->flags, ModelFlag::SWAP_16BIT_DATA)) { + num_swaps++; + } +#ifdef WORDS_BIGENDIAN + num_swaps++; +#endif + if (num_swaps % 2 != 0) { + pipeline.push_node<ImagePipelineNodeSwap16BitEndian>(); - if ((dev->model->flags & GENESYS_FLAG_16BIT_DATA_INVERTED) && depth == 16) { - dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>(); + if (log_image_data) { + pipeline.push_node<ImagePipelineNodeDebug>(debug_prefix + "_2_after_swap.tiff"); + } + } } -#ifdef WORDS_BIGENDIAN - if (depth == 16) { - dev->pipeline.push_node<ImagePipelineNodeSwap16BitEndian>(); + if (has_flag(dev.model->flags, ModelFlag::INVERT_PIXEL_DATA)) { + pipeline.push_node<ImagePipelineNodeInvert>(); + + if (log_image_data) { + pipeline.push_node<ImagePipelineNodeDebug>(debug_prefix + "_3_after_invert.tiff"); + } } -#endif - if (DBG_LEVEL >= DBG_io2) { - dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" + - std::to_string(s_pipeline_index) + - "_1_after_swap.pnm"); + if (dev.model->is_cis && session.params.channels == 3) { + pipeline.push_node<ImagePipelineNodeMergeMonoLines>(dev.model->line_mode_color_order); + + if (log_image_data) { + pipeline.push_node<ImagePipelineNodeDebug>(debug_prefix + "_4_after_merge_mono.tiff"); + } } - if (dev->model->is_cis && session.params.channels == 3) { - dev->pipeline.push_node<ImagePipelineNodeMergeMonoLines>(dev->model->line_mode_color_order); + if (pipeline.get_output_format() == PixelFormat::BGR888) { + pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB888); } - if (dev->pipeline.get_output_format() == PixelFormat::BGR888) { - dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB888); + if (pipeline.get_output_format() == PixelFormat::BGR161616) { + pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB161616); } - if (dev->pipeline.get_output_format() == PixelFormat::BGR161616) { - dev->pipeline.push_node<ImagePipelineNodeFormatConvert>(PixelFormat::RGB161616); + if (log_image_data) { + pipeline.push_node<ImagePipelineNodeDebug>(debug_prefix + "_5_after_format.tiff"); } if (session.max_color_shift_lines > 0 && session.params.channels == 3) { - dev->pipeline.push_node<ImagePipelineNodeComponentShiftLines>( + pipeline.push_node<ImagePipelineNodeComponentShiftLines>( session.color_shift_lines_r, session.color_shift_lines_g, session.color_shift_lines_b); + + if (log_image_data) { + pipeline.push_node<ImagePipelineNodeDebug>(debug_prefix + "_6_after_color_unshift.tiff"); + } } - if (DBG_LEVEL >= DBG_io2) { - dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" + - std::to_string(s_pipeline_index) + - "_2_after_shift.pnm"); + if (!session.stagger_x.empty()) { + // FIXME: the image will be scaled to requested pixel count without regard to the reduction + // of image size in this step. + pipeline.push_node<ImagePipelineNodePixelShiftColumns>(session.stagger_x.shifts()); + + if (log_image_data) { + pipeline.push_node<ImagePipelineNodeDebug>(debug_prefix + "_7_after_x_unstagger.tiff"); + } } if (session.num_staggered_lines > 0) { - std::vector<std::size_t> shifts{0, session.num_staggered_lines}; - dev->pipeline.push_node<ImagePipelineNodePixelShiftLines>(shifts); - } + pipeline.push_node<ImagePipelineNodePixelShiftLines>(session.stagger_y.shifts()); - if (DBG_LEVEL >= DBG_io2) { - dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" + - std::to_string(s_pipeline_index) + - "_3_after_stagger.pnm"); + if (log_image_data) { + pipeline.push_node<ImagePipelineNodeDebug>(debug_prefix + "_8_after_y_unstagger.tiff"); + } } - if ((dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) && - !(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)) + if (session.use_host_side_calib && + !has_flag(dev.model->flags, ModelFlag::DISABLE_SHADING_CALIBRATION) && + !has_flag(session.params.flags, ScanFlag::DISABLE_SHADING)) { - dev->pipeline.push_node<ImagePipelineNodeCalibrate>(dev->dark_average_data, - dev->white_average_data); + unsigned offset_pixels = session.params.startx + dev.calib_session.shading_pixel_offset; + unsigned offset_bytes = offset_pixels * dev.calib_session.params.channels; + pipeline.push_node<ImagePipelineNodeCalibrate>(dev.dark_average_data, + dev.white_average_data, offset_bytes); - if (DBG_LEVEL >= DBG_io2) { - dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" + - std::to_string(s_pipeline_index) + - "_4_after_calibrate.pnm"); + if (log_image_data) { + pipeline.push_node<ImagePipelineNodeDebug>(debug_prefix + "_9_after_calibrate.tiff"); } } - if (session.output_pixels != session.params.get_requested_pixels()) { - dev->pipeline.push_node<ImagePipelineNodeScaleRows>(session.params.get_requested_pixels()); + if (pipeline.get_output_width() != session.params.get_requested_pixels()) { + pipeline.push_node<ImagePipelineNodeScaleRows>(session.params.get_requested_pixels()); } - auto read_from_pipeline = [dev](std::size_t size, std::uint8_t* out_data) + return pipeline; +} + +void setup_image_pipeline(Genesys_Device& dev, const ScanSession& session) +{ + static unsigned s_pipeline_index = 0; + + s_pipeline_index++; + + dev.pipeline = build_image_pipeline(dev, session, s_pipeline_index, dbg_log_image_data()); + + auto read_from_pipeline = [&dev](std::size_t size, std::uint8_t* out_data) { - (void) size; // will be always equal to dev->pipeline.get_output_row_bytes() - return dev->pipeline.get_next_row_data(out_data); + (void) size; // will be always equal to dev.pipeline.get_output_row_bytes() + return dev.pipeline.get_next_row_data(out_data); }; - dev->pipeline_buffer = ImageBuffer{dev->pipeline.get_output_row_bytes(), + dev.pipeline_buffer = ImageBuffer{dev.pipeline.get_output_row_bytes(), read_from_pipeline}; } @@ -1394,6 +1326,32 @@ std::uint8_t compute_frontend_gain_wolfson(float value, float target_value) return clamp(code, 0, 255); } +std::uint8_t compute_frontend_gain_lide_80(float value, float target_value) +{ + int code = static_cast<int>((target_value / value) * 12); + return clamp(code, 0, 255); +} + +std::uint8_t compute_frontend_gain_wolfson_gl841(float value, float target_value) +{ + // this code path is similar to what generic wolfson code path uses and uses similar constants, + // but is likely incorrect. + float inv_gain = target_value / value; + inv_gain *= 0.69f; + int code = static_cast<int>(283 - 208 / inv_gain); + return clamp(code, 0, 255); +} + +std::uint8_t compute_frontend_gain_wolfson_gl846_gl847_gl124(float value, float target_value) +{ + // this code path is similar to what generic wolfson code path uses and uses similar constants, + // but is likely incorrect. + float inv_gain = target_value / value; + int code = static_cast<int>(283 - 208 / inv_gain); + return clamp(code, 0, 255); +} + + std::uint8_t compute_frontend_gain_analog_devices(float value, float target_value) { /* The flow of data through the frontend ADC is as follows (see e.g. AD9826 datasheet) @@ -1418,13 +1376,22 @@ std::uint8_t compute_frontend_gain_analog_devices(float value, float target_valu std::uint8_t compute_frontend_gain(float value, float target_value, FrontendType frontend_type) { - if (frontend_type == FrontendType::WOLFSON) { - return compute_frontend_gain_wolfson(value, target_value); - } - if (frontend_type == FrontendType::ANALOG_DEVICES) { - return compute_frontend_gain_analog_devices(value, target_value); + switch (frontend_type) { + case FrontendType::WOLFSON: + return compute_frontend_gain_wolfson(value, target_value); + case FrontendType::ANALOG_DEVICES: + return compute_frontend_gain_analog_devices(value, target_value); + case FrontendType::CANON_LIDE_80: + return compute_frontend_gain_lide_80(value, target_value); + case FrontendType::WOLFSON_GL841: + return compute_frontend_gain_wolfson_gl841(value, target_value); + case FrontendType::WOLFSON_GL846: + case FrontendType::ANALOG_DEVICES_GL847: + case FrontendType::WOLFSON_GL124: + return compute_frontend_gain_wolfson_gl846_gl847_gl124(value, target_value); + default: + throw SaneException("Unknown frontend to compute gain for"); } - throw SaneException("Unknown frontend to compute gain for"); } /** @brief initialize device @@ -1436,7 +1403,7 @@ std::uint8_t compute_frontend_gain(float value, float target_value, * @param dev device to initialize * @param max_regs umber of maximum used registers */ -void sanei_genesys_asic_init(Genesys_Device* dev, bool /*max_regs*/) +void sanei_genesys_asic_init(Genesys_Device* dev) { DBG_HELPER(dbg); @@ -1486,8 +1453,7 @@ void sanei_genesys_asic_init(Genesys_Device* dev, bool /*max_regs*/) dev->settings.color_filter = ColorFilter::RED; - /* duplicate initial values into calibration registers */ - dev->calib_reg = dev->reg; + dev->initial_regs = dev->reg; const auto& sensor = sanei_genesys_find_sensor_any(dev); @@ -1497,8 +1463,15 @@ void sanei_genesys_asic_init(Genesys_Device* dev, bool /*max_regs*/) dev->already_initialized = true; // Move to home if needed + if (dev->model->model_id == ModelId::CANON_8600F) { + if (!dev->cmd_set->is_head_home(*dev, ScanHeadId::SECONDARY)) { + dev->set_head_pos_unknown(ScanHeadId::SECONDARY); + } + if (!dev->cmd_set->is_head_home(*dev, ScanHeadId::PRIMARY)) { + dev->set_head_pos_unknown(ScanHeadId::SECONDARY); + } + } dev->cmd_set->move_back_home(dev, true); - dev->set_head_pos_zero(ScanHeadId::PRIMARY); // Set powersaving (default = 15 minutes) dev->cmd_set->set_powersaving(dev, 15); @@ -1510,6 +1483,7 @@ void scanner_start_action(Genesys_Device& dev, bool start_motor) switch (dev.model->asic_type) { case AsicType::GL646: case AsicType::GL841: + case AsicType::GL842: case AsicType::GL843: case AsicType::GL845: case AsicType::GL846: @@ -1527,8 +1501,7 @@ void scanner_start_action(Genesys_Device& dev, bool start_motor) } } -void sanei_genesys_set_dpihw(Genesys_Register_Set& regs, const Genesys_Sensor& sensor, - unsigned dpihw) +void sanei_genesys_set_dpihw(Genesys_Register_Set& regs, unsigned dpihw) { // same across GL646, GL841, GL843, GL846, GL847, GL124 const uint8_t REG_0x05_DPIHW_MASK = 0xc0; @@ -1537,10 +1510,6 @@ void sanei_genesys_set_dpihw(Genesys_Register_Set& regs, const Genesys_Sensor& s const uint8_t REG_0x05_DPIHW_2400 = 0x80; const uint8_t REG_0x05_DPIHW_4800 = 0xc0; - if (sensor.register_dpihw_override != 0) { - dpihw = sensor.register_dpihw_override; - } - uint8_t dpihw_setting; switch (dpihw) { case 600: @@ -1583,6 +1552,12 @@ void regs_set_exposure(AsicType asic_type, Genesys_Register_Set& regs, regs.set16(gl841::REG_EXPB, exposure.blue); break; } + case AsicType::GL842: { + regs.set16(gl842::REG_EXPR, exposure.red); + regs.set16(gl842::REG_EXPG, exposure.green); + regs.set16(gl842::REG_EXPB, exposure.blue); + break; + } case AsicType::GL843: { regs.set16(gl843::REG_EXPR, exposure.red); regs.set16(gl843::REG_EXPG, exposure.green); @@ -1619,6 +1594,10 @@ void regs_set_optical_off(AsicType asic_type, Genesys_Register_Set& regs) regs.find_reg(gl841::REG_0x01).value &= ~gl841::REG_0x01_SCAN; break; } + case AsicType::GL842: { + regs.find_reg(gl842::REG_0x01).value &= ~gl842::REG_0x01_SCAN; + break; + } case AsicType::GL843: { regs.find_reg(gl843::REG_0x01).value &= ~gl843::REG_0x01_SCAN; break; @@ -1648,6 +1627,8 @@ bool get_registers_gain4_bit(AsicType asic_type, const Genesys_Register_Set& reg return static_cast<bool>(regs.get8(gl646::REG_0x06) & gl646::REG_0x06_GAIN4); case AsicType::GL841: return static_cast<bool>(regs.get8(gl841::REG_0x06) & gl841::REG_0x06_GAIN4); + case AsicType::GL842: + return static_cast<bool>(regs.get8(gl842::REG_0x06) & gl842::REG_0x06_GAIN4); case AsicType::GL843: return static_cast<bool>(regs.get8(gl843::REG_0x06) & gl843::REG_0x06_GAIN4); case AsicType::GL845: @@ -1706,79 +1687,78 @@ void sanei_genesys_wait_for_home(Genesys_Device* dev) } } -/** @brief motor profile - * search for the database of motor profiles and get the best one. Each - * profile is at full step and at a reference exposure. Use first entry - * by default. - * @param motors motor profile database - * @param motor_type motor id - * @param exposure exposure time - * @return a pointer to a Motor_Profile struct - */ -const Motor_Profile& sanei_genesys_get_motor_profile(const std::vector<Motor_Profile>& motors, - MotorId motor_id, int exposure) +const MotorProfile* get_motor_profile_ptr(const std::vector<MotorProfile>& profiles, + unsigned exposure, + const ScanSession& session) { - int idx; + int best_i = -1; + + for (unsigned i = 0; i < profiles.size(); ++i) { + const auto& profile = profiles[i]; - idx=-1; - for (std::size_t i = 0; i < motors.size(); ++i) { - // exact match - if (motors[i].motor_id == motor_id && motors[i].exposure==exposure) { - return motors[i]; + if (!profile.resolutions.matches(session.params.yres)) { + continue; + } + if (!profile.scan_methods.matches(session.params.scan_method)) { + continue; } - // closest match - if (motors[i].motor_id == motor_id) { - /* if profile exposure is higher than the required one, - * the entry is a candidate for the closest match */ - if (motors[i].exposure == 0 || motors[i].exposure >= exposure) - { - if(idx<0) - { - /* no match found yet */ - idx=i; - } - else - { - /* test for better match */ - if(motors[i].exposure<motors[idx].exposure) - { - idx=i; - } + if (profile.max_exposure == exposure) { + return &profile; + } + + if (profile.max_exposure == 0 || profile.max_exposure >= exposure) { + if (best_i < 0) { + // no match found yet + best_i = i; + } else { + // test for better match + if (profiles[i].max_exposure < profiles[best_i].max_exposure) { + best_i = i; } } } } - /* default fallback */ - if(idx<0) - { - DBG (DBG_warn,"%s: using default motor profile\n",__func__); - idx=0; + if (best_i < 0) { + return nullptr; + } + + return &profiles[best_i]; +} + +const MotorProfile& get_motor_profile(const std::vector<MotorProfile>& profiles, + unsigned exposure, + const ScanSession& session) +{ + const auto* profile = get_motor_profile_ptr(profiles, exposure, session); + if (profile == nullptr) { + throw SaneException("Motor slope is not configured"); } - return motors[idx]; + return *profile; } -MotorSlopeTable sanei_genesys_slope_table(AsicType asic_type, int dpi, int exposure, int base_dpi, - unsigned step_multiplier, - const Motor_Profile& motor_profile) +MotorSlopeTable create_slope_table(AsicType asic_type, const Genesys_Motor& motor, unsigned ydpi, + unsigned exposure, unsigned step_multiplier, + const MotorProfile& motor_profile) { - unsigned target_speed_w = ((exposure * dpi) / base_dpi); + unsigned target_speed_w = ((exposure * ydpi) / motor.base_ydpi); - auto table = create_slope_table(motor_profile.slope, target_speed_w, motor_profile.step_type, - step_multiplier, 2 * step_multiplier, - get_slope_table_max_size(asic_type)); + auto table = create_slope_table_for_speed(motor_profile.slope, target_speed_w, + motor_profile.step_type, + step_multiplier, 2 * step_multiplier, + get_slope_table_max_size(asic_type)); return table; } MotorSlopeTable create_slope_table_fastest(AsicType asic_type, unsigned step_multiplier, - const Motor_Profile& motor_profile) + const MotorProfile& motor_profile) { - return create_slope_table(motor_profile.slope, motor_profile.slope.max_speed_w, - motor_profile.step_type, - step_multiplier, 2 * step_multiplier, - get_slope_table_max_size(asic_type)); + return create_slope_table_for_speed(motor_profile.slope, motor_profile.slope.max_speed_w, + motor_profile.step_type, + step_multiplier, 2 * step_multiplier, + get_slope_table_max_size(asic_type)); } /** @brief returns the lowest possible ydpi for the device |