diff --git a/backend/genesys.cc b/backend/genesys.cc index f94c42973..f36dd3242 100644 --- a/backend/genesys.cc +++ b/backend/genesys.cc @@ -1022,6 +1022,11 @@ void sanei_genesys_init_shading_data(Genesys_Device* dev, const Genesys_Sensor& int pixels_per_line) { DBG_HELPER(dbg); + + if (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) { + return; + } + int channels; int i; @@ -2400,6 +2405,11 @@ compute_shifted_coefficients (Genesys_Device * dev, static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_Sensor& sensor) { DBG_HELPER(dbg); + + if (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) { + return; + } + uint32_t pixels_per_line; uint8_t channels; int o; diff --git a/backend/genesys_gl843.cc b/backend/genesys_gl843.cc index 7f0cfc24c..15b643438 100644 --- a/backend/genesys_gl843.cc +++ b/backend/genesys_gl843.cc @@ -1058,7 +1058,8 @@ static void gl843_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sens r = sanei_genesys_get_address (reg, REG01); r->value &= ~REG01_SCAN; if ((session.params.flags & SCAN_FLAG_DISABLE_SHADING) || - (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)) + (dev->model->flags & GENESYS_FLAG_NO_CALIBRATION || + (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE))) { r->value &= ~REG01_DVDSET; } diff --git a/backend/genesys_image_pipeline.cc b/backend/genesys_image_pipeline.cc index 721aeabd6..666cf8cd9 100644 --- a/backend/genesys_image_pipeline.cc +++ b/backend/genesys_image_pipeline.cc @@ -653,6 +653,55 @@ bool ImagePipelineNodeExtract::get_next_row_data(std::uint8_t* out_data) return got_data; } +ImagePipelineNodeCalibrate::ImagePipelineNodeCalibrate(ImagePipelineNode& source, + const std::vector& bottom, + const std::vector& top) : + source_{source} +{ + auto size = std::min(bottom.size(), top.size()); + offset_.reserve(size); + multiplier_.reserve(size); + + for (std::size_t i = 0; i < size; ++i) { + offset_.push_back(bottom[i] / 65535.0f); + multiplier_.push_back(65535.0f / (top[i] - bottom[i])); + } +} + +bool ImagePipelineNodeCalibrate::get_next_row_data(std::uint8_t* out_data) +{ + bool ret = source_.get_next_row_data(out_data); + + auto format = get_format(); + auto depth = get_pixel_format_depth(format); + std::size_t max_value = 1; + switch (depth) { + case 8: max_value = 255; break; + case 16: max_value = 65535; break; + default: + throw SaneException("Unsupported depth for calibration %d", depth); + } + unsigned channels = get_pixel_channels(format); + + std::size_t max_calib_i = offset_.size(); + std::size_t curr_calib_i = 0; + + for (std::size_t x = 0, width = get_width(); x < width && curr_calib_i < max_calib_i; ++x) { + for (unsigned ch = 0; ch < channels && curr_calib_i < max_calib_i; ++ch) { + std::int32_t value = get_raw_channel_from_row(out_data, x, ch, format); + + float value_f = static_cast(value) / max_value; + value_f = (value_f - offset_[curr_calib_i]) * multiplier_[curr_calib_i]; + value_f = std::round(value_f * max_value); + value = clamp(static_cast(value_f), 0, max_value); + set_raw_channel_to_row(out_data, x, ch, value, format); + + curr_calib_i++; + } + } + return ret; +} + ImagePipelineNodeDebug::ImagePipelineNodeDebug(ImagePipelineNode& source, const std::string& path) : source_(source), diff --git a/backend/genesys_image_pipeline.h b/backend/genesys_image_pipeline.h index 27a52bd53..bb138b83c 100644 --- a/backend/genesys_image_pipeline.h +++ b/backend/genesys_image_pipeline.h @@ -474,6 +474,29 @@ private: std::vector cached_line_; }; +// A pipeline node that mimics the calibration behavior on Genesys chips +class ImagePipelineNodeCalibrate : public ImagePipelineNode +{ +public: + + ImagePipelineNodeCalibrate(ImagePipelineNode& source, const std::vector& bottom, + const std::vector& top); + + std::size_t get_width() const override { return source_.get_width(); } + std::size_t get_height() const override { return source_.get_height(); } + PixelFormat get_format() const override { return source_.get_format(); } + + bool eof() const override { return source_.eof(); } + + bool get_next_row_data(std::uint8_t* out_data) override; + +private: + ImagePipelineNode& source_; + + std::vector offset_; + std::vector multiplier_; +}; + class ImagePipelineNodeDebug : public ImagePipelineNode { public: diff --git a/backend/genesys_low.cc b/backend/genesys_low.cc index d548cde5f..313d18b60 100644 --- a/backend/genesys_low.cc +++ b/backend/genesys_low.cc @@ -1746,6 +1746,19 @@ void build_image_pipeline(Genesys_Device* dev, const ScanSession& session) "_2_after_stagger.pnm"); } + if ((dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) && + !(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION)) + { + dev->pipeline.push_node(dev->dark_average_data, + dev->white_average_data); + + if (DBG_LEVEL >= DBG_io2) { + dev->pipeline.push_node("gl_pipeline_" + + std::to_string(s_pipeline_index) + + "_3_after_calibrate.pnm"); + } + } + if (session.output_pixels != session.params.get_requested_pixels()) { dev->pipeline.push_node(session.params.get_requested_pixels()); } diff --git a/backend/genesys_low.h b/backend/genesys_low.h index 37de81888..9073b2bf5 100644 --- a/backend/genesys_low.h +++ b/backend/genesys_low.h @@ -136,6 +136,8 @@ #define GENESYS_FLAG_FULL_HWDPI_MODE (1 << 19) /**< scanner always use maximum hw dpi to setup the sensor */ // scanner has infrared transparency scanning capability #define GENESYS_FLAG_HAS_UTA_INFRARED (1 << 20) +// scanner calibration is handled on the host side +#define GENESYS_FLAG_CALIBRATION_HOST_SIDE (1 << 21) #define GENESYS_HAS_NO_BUTTONS 0 /**< scanner has no supported button */ #define GENESYS_HAS_SCAN_SW (1 << 0) /**< scanner has SCAN button */ diff --git a/testsuite/backend/genesys/tests_image_pipeline.cc b/testsuite/backend/genesys/tests_image_pipeline.cc index dd4924d75..8253e0f85 100644 --- a/testsuite/backend/genesys/tests_image_pipeline.cc +++ b/testsuite/backend/genesys/tests_image_pipeline.cc @@ -424,6 +424,78 @@ void test_node_pixel_shift_lines() ASSERT_EQ(out_data, expected_data); } +void test_node_calibrate_8bit() +{ + using Data = std::vector; + + Data in_data = { + 0x20, 0x38, 0x38 + }; + + std::vector bottom = { + 0x1000, 0x2000, 0x3000 + }; + + std::vector top = { + 0x3000, 0x4000, 0x5000 + }; + + ImagePipelineStack stack; + stack.push_first_node(1, 1, PixelFormat::RGB888, + std::move(in_data)); + stack.push_node(bottom, top); + + ASSERT_EQ(stack.get_output_width(), 1u); + ASSERT_EQ(stack.get_output_height(), 1u); + ASSERT_EQ(stack.get_output_row_bytes(), 3u); + ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888); + + auto out_data = stack.get_all_data(); + + Data expected_data = { + // note that we don't handle rounding properly in the implementation + 0x80, 0xc1, 0x41 + }; + + ASSERT_EQ(out_data, expected_data); +} + +void test_node_calibrate_16bit() +{ + using Data = std::vector; + + Data in_data = { + 0x00, 0x20, 0x00, 0x38, 0x00, 0x38 + }; + + std::vector bottom = { + 0x1000, 0x2000, 0x3000 + }; + + std::vector top = { + 0x3000, 0x4000, 0x5000 + }; + + ImagePipelineStack stack; + stack.push_first_node(1, 1, PixelFormat::RGB161616, + std::move(in_data)); + stack.push_node(bottom, top); + + ASSERT_EQ(stack.get_output_width(), 1u); + ASSERT_EQ(stack.get_output_height(), 1u); + ASSERT_EQ(stack.get_output_row_bytes(), 6u); + ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB161616); + + auto out_data = stack.get_all_data(); + + Data expected_data = { + // note that we don't handle rounding properly in the implementation + 0x00, 0x80, 0xff, 0xbf, 0x00, 0x40 + }; + + ASSERT_EQ(out_data, expected_data); +} + void test_image_pipeline() { test_image_buffer_genesys_usb(); @@ -438,4 +510,6 @@ void test_image_pipeline() test_node_split_mono_lines(); test_node_component_shift_lines(); test_node_pixel_shift_lines(); + test_node_calibrate_8bit(); + test_node_calibrate_16bit(); }