kopia lustrzana https://gitlab.com/sane-project/backends
genesys: Implement image pipeline node to shift columns
rodzic
1bae94fd1e
commit
95d7196fca
|
@ -582,6 +582,63 @@ bool ImagePipelineNodePixelShiftLines::get_next_row_data(std::uint8_t* out_data)
|
|||
return got_data;
|
||||
}
|
||||
|
||||
ImagePipelineNodePixelShiftColumns::ImagePipelineNodePixelShiftColumns(
|
||||
ImagePipelineNode& source, const std::vector<std::size_t>& shifts) :
|
||||
source_(source),
|
||||
pixel_shifts_{shifts}
|
||||
{
|
||||
width_ = source_.get_width();
|
||||
extra_width_ = compute_pixel_shift_extra_width(width_, pixel_shifts_);
|
||||
if (extra_width_ > width_) {
|
||||
width_ = 0;
|
||||
} else {
|
||||
width_ -= extra_width_;
|
||||
}
|
||||
temp_buffer_.resize(source_.get_row_bytes());
|
||||
}
|
||||
|
||||
bool ImagePipelineNodePixelShiftColumns::get_next_row_data(std::uint8_t* out_data)
|
||||
{
|
||||
if (width_ == 0) {
|
||||
throw SaneException("Attempt to read zero-width line");
|
||||
}
|
||||
bool got_data = source_.get_next_row_data(temp_buffer_.data());
|
||||
|
||||
auto format = get_format();
|
||||
auto shift_count = pixel_shifts_.size();
|
||||
|
||||
for (std::size_t x = 0, width = get_width(); x < width; x += shift_count) {
|
||||
for (std::size_t ishift = 0; ishift < shift_count && x + ishift < width; ishift++) {
|
||||
RawPixel pixel = get_raw_pixel_from_row(temp_buffer_.data(), x + pixel_shifts_[ishift],
|
||||
format);
|
||||
set_raw_pixel_to_row(out_data, x + ishift, pixel, format);
|
||||
}
|
||||
}
|
||||
return got_data;
|
||||
}
|
||||
|
||||
|
||||
std::size_t compute_pixel_shift_extra_width(std::size_t source_width,
|
||||
const std::vector<std::size_t>& shifts)
|
||||
{
|
||||
// we iterate across pixel shifts and find the pixel that needs the maximum shift according to
|
||||
// source_width.
|
||||
int group_size = shifts.size();
|
||||
int non_filled_group = source_width % shifts.size();
|
||||
int extra_width = 0;
|
||||
|
||||
for (int i = 0; i < group_size; ++i) {
|
||||
int shift_groups = shifts[i] / group_size;
|
||||
int shift_rem = shifts[i] % group_size;
|
||||
|
||||
if (shift_rem < non_filled_group) {
|
||||
shift_groups--;
|
||||
}
|
||||
extra_width = std::max(extra_width, shift_groups * group_size + non_filled_group - i);
|
||||
}
|
||||
return extra_width;
|
||||
}
|
||||
|
||||
ImagePipelineNodeExtract::ImagePipelineNodeExtract(ImagePipelineNode& source,
|
||||
std::size_t offset_x, std::size_t offset_y,
|
||||
std::size_t width, std::size_t height) :
|
||||
|
|
|
@ -410,7 +410,8 @@ private:
|
|||
RowBuffer buffer_;
|
||||
};
|
||||
|
||||
// A pipeline node that shifts pixels across lines by the given offsets (performs unstaggering)
|
||||
// A pipeline node that shifts pixels across lines by the given offsets (performs vertical
|
||||
// unstaggering)
|
||||
class ImagePipelineNodePixelShiftLines : public ImagePipelineNode
|
||||
{
|
||||
public:
|
||||
|
@ -435,6 +436,37 @@ private:
|
|||
RowBuffer buffer_;
|
||||
};
|
||||
|
||||
// A pipeline node that shifts pixels across columns by the given offsets. Each row is divided
|
||||
// into pixel groups of shifts.size() pixels. For each output group starting at position xgroup,
|
||||
// the i-th pixel will be set to the input pixel at position xgroup + shifts[i].
|
||||
class ImagePipelineNodePixelShiftColumns : public ImagePipelineNode
|
||||
{
|
||||
public:
|
||||
ImagePipelineNodePixelShiftColumns(ImagePipelineNode& source,
|
||||
const std::vector<std::size_t>& shifts);
|
||||
|
||||
std::size_t get_width() const override { return 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::size_t width_ = 0;
|
||||
std::size_t extra_width_ = 0;
|
||||
|
||||
std::vector<std::size_t> pixel_shifts_;
|
||||
|
||||
std::vector<std::uint8_t> temp_buffer_;
|
||||
};
|
||||
|
||||
// exposed for tests
|
||||
std::size_t compute_pixel_shift_extra_width(std::size_t source_width,
|
||||
const std::vector<std::size_t>& shifts);
|
||||
|
||||
// A pipeline node that extracts a sub-image from the image. Padding and cropping is done as needed.
|
||||
// The class can't pad to the left of the image currently, as only positive offsets are accepted.
|
||||
class ImagePipelineNodeExtract : public ImagePipelineNode
|
||||
|
|
|
@ -551,6 +551,220 @@ void test_node_pixel_shift_lines_4lines()
|
|||
ASSERT_EQ(out_data, expected_data);
|
||||
}
|
||||
|
||||
void test_node_pixel_shift_columns_compute_max_width()
|
||||
{
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(12, {0, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(13, {0, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(14, {0, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(15, {0, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(16, {0, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(17, {0, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(18, {0, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(19, {0, 1, 2, 3}), 0u);
|
||||
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(12, {1, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(13, {1, 1, 2, 3}), 1u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(14, {1, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(15, {1, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(16, {1, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(17, {1, 1, 2, 3}), 1u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(18, {1, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(19, {1, 1, 2, 3}), 0u);
|
||||
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(12, {2, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(13, {2, 1, 2, 3}), 1u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(14, {2, 1, 2, 3}), 2u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(15, {2, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(16, {2, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(17, {2, 1, 2, 3}), 1u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(18, {2, 1, 2, 3}), 2u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(19, {2, 1, 2, 3}), 0u);
|
||||
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(12, {3, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(13, {3, 1, 2, 3}), 1u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(14, {3, 1, 2, 3}), 2u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(15, {3, 1, 2, 3}), 3u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(16, {3, 1, 2, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(17, {3, 1, 2, 3}), 1u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(18, {3, 1, 2, 3}), 2u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(19, {3, 1, 2, 3}), 3u);
|
||||
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(12, {7, 1, 2, 3}), 4u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(13, {7, 1, 2, 3}), 5u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(14, {7, 1, 2, 3}), 6u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(15, {7, 1, 2, 3}), 7u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(16, {7, 1, 2, 3}), 4u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(17, {7, 1, 2, 3}), 5u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(18, {7, 1, 2, 3}), 6u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(19, {7, 1, 2, 3}), 7u);
|
||||
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(12, {0, 1, 3, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(13, {0, 1, 3, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(14, {0, 1, 3, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(15, {0, 1, 3, 3}), 1u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(16, {0, 1, 3, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(17, {0, 1, 3, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(18, {0, 1, 3, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(19, {0, 1, 3, 3}), 1u);
|
||||
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(12, {0, 1, 4, 3}), 2u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(13, {0, 1, 4, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(14, {0, 1, 4, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(15, {0, 1, 4, 3}), 1u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(16, {0, 1, 4, 3}), 2u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(17, {0, 1, 4, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(18, {0, 1, 4, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(19, {0, 1, 4, 3}), 1u);
|
||||
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(12, {0, 1, 5, 3}), 2u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(13, {0, 1, 5, 3}), 3u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(14, {0, 1, 5, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(15, {0, 1, 5, 3}), 1u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(16, {0, 1, 5, 3}), 2u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(17, {0, 1, 5, 3}), 3u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(18, {0, 1, 5, 3}), 0u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(19, {0, 1, 5, 3}), 1u);
|
||||
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(12, {0, 1, 9, 3}), 6u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(13, {0, 1, 9, 3}), 7u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(14, {0, 1, 9, 3}), 4u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(15, {0, 1, 9, 3}), 5u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(16, {0, 1, 9, 3}), 6u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(17, {0, 1, 9, 3}), 7u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(18, {0, 1, 9, 3}), 4u);
|
||||
ASSERT_EQ(compute_pixel_shift_extra_width(19, {0, 1, 9, 3}), 5u);
|
||||
}
|
||||
|
||||
void test_node_pixel_shift_columns_no_switch()
|
||||
{
|
||||
using Data = std::vector<std::uint8_t>;
|
||||
|
||||
Data in_data = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
};
|
||||
|
||||
ImagePipelineStack stack;
|
||||
stack.push_first_node<ImagePipelineNodeArraySource>(12, 2, PixelFormat::I8, in_data);
|
||||
stack.push_node<ImagePipelineNodePixelShiftColumns>(std::vector<std::size_t>{0, 1, 2, 3});
|
||||
|
||||
ASSERT_EQ(stack.get_output_width(), 12u);
|
||||
ASSERT_EQ(stack.get_output_height(), 2u);
|
||||
ASSERT_EQ(stack.get_output_row_bytes(), 12u);
|
||||
ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
|
||||
|
||||
auto out_data = stack.get_all_data();
|
||||
|
||||
ASSERT_EQ(out_data, in_data);
|
||||
}
|
||||
|
||||
void test_node_pixel_shift_columns_group_switch_pixel_multiple()
|
||||
{
|
||||
using Data = std::vector<std::uint8_t>;
|
||||
|
||||
Data in_data = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
};
|
||||
|
||||
ImagePipelineStack stack;
|
||||
stack.push_first_node<ImagePipelineNodeArraySource>(12, 2, PixelFormat::I8, in_data);
|
||||
stack.push_node<ImagePipelineNodePixelShiftColumns>(std::vector<std::size_t>{3, 1, 2, 0});
|
||||
|
||||
ASSERT_EQ(stack.get_output_width(), 12u);
|
||||
ASSERT_EQ(stack.get_output_height(), 2u);
|
||||
ASSERT_EQ(stack.get_output_row_bytes(), 12u);
|
||||
ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
|
||||
|
||||
auto out_data = stack.get_all_data();
|
||||
|
||||
Data expected_data = {
|
||||
0x03, 0x01, 0x02, 0x00, 0x07, 0x05, 0x06, 0x04, 0x0b, 0x09, 0x0a, 0x08,
|
||||
0x13, 0x11, 0x12, 0x10, 0x17, 0x15, 0x16, 0x14, 0x1b, 0x19, 0x1a, 0x18,
|
||||
};
|
||||
ASSERT_EQ(out_data, expected_data);
|
||||
}
|
||||
|
||||
void test_node_pixel_shift_columns_group_switch_pixel_not_multiple()
|
||||
{
|
||||
using Data = std::vector<std::uint8_t>;
|
||||
|
||||
Data in_data = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
|
||||
};
|
||||
|
||||
ImagePipelineStack stack;
|
||||
stack.push_first_node<ImagePipelineNodeArraySource>(13, 2, PixelFormat::I8, in_data);
|
||||
stack.push_node<ImagePipelineNodePixelShiftColumns>(std::vector<std::size_t>{3, 1, 2, 0});
|
||||
|
||||
ASSERT_EQ(stack.get_output_width(), 12u);
|
||||
ASSERT_EQ(stack.get_output_height(), 2u);
|
||||
ASSERT_EQ(stack.get_output_row_bytes(), 12u);
|
||||
ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
|
||||
|
||||
auto out_data = stack.get_all_data();
|
||||
|
||||
Data expected_data = {
|
||||
0x03, 0x01, 0x02, 0x00, 0x07, 0x05, 0x06, 0x04, 0x0b, 0x09, 0x0a, 0x08,
|
||||
0x13, 0x11, 0x12, 0x10, 0x17, 0x15, 0x16, 0x14, 0x1b, 0x19, 0x1a, 0x18,
|
||||
};
|
||||
ASSERT_EQ(out_data, expected_data);
|
||||
}
|
||||
|
||||
void test_node_pixel_shift_columns_group_switch_pixel_large_offsets_multiple()
|
||||
{
|
||||
using Data = std::vector<std::uint8_t>;
|
||||
|
||||
Data in_data = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
};
|
||||
|
||||
ImagePipelineStack stack;
|
||||
stack.push_first_node<ImagePipelineNodeArraySource>(12, 2, PixelFormat::I8, in_data);
|
||||
stack.push_node<ImagePipelineNodePixelShiftColumns>(std::vector<std::size_t>{7, 1, 5, 0});
|
||||
|
||||
ASSERT_EQ(stack.get_output_width(), 8u);
|
||||
ASSERT_EQ(stack.get_output_height(), 2u);
|
||||
ASSERT_EQ(stack.get_output_row_bytes(), 8u);
|
||||
ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
|
||||
|
||||
auto out_data = stack.get_all_data();
|
||||
|
||||
Data expected_data = {
|
||||
0x07, 0x01, 0x05, 0x00, 0x0b, 0x05, 0x09, 0x04,
|
||||
0x17, 0x11, 0x15, 0x10, 0x1b, 0x15, 0x19, 0x14,
|
||||
};
|
||||
ASSERT_EQ(out_data, expected_data);
|
||||
}
|
||||
|
||||
void test_node_pixel_shift_columns_group_switch_pixel_large_offsets_not_multiple()
|
||||
{
|
||||
using Data = std::vector<std::uint8_t>;
|
||||
|
||||
Data in_data = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
|
||||
};
|
||||
|
||||
ImagePipelineStack stack;
|
||||
stack.push_first_node<ImagePipelineNodeArraySource>(13, 2, PixelFormat::I8, in_data);
|
||||
stack.push_node<ImagePipelineNodePixelShiftColumns>(std::vector<std::size_t>{7, 1, 5, 0});
|
||||
|
||||
ASSERT_EQ(stack.get_output_width(), 8u);
|
||||
ASSERT_EQ(stack.get_output_height(), 2u);
|
||||
ASSERT_EQ(stack.get_output_row_bytes(), 8u);
|
||||
ASSERT_EQ(stack.get_output_format(), PixelFormat::I8);
|
||||
|
||||
auto out_data = stack.get_all_data();
|
||||
|
||||
Data expected_data = {
|
||||
0x07, 0x01, 0x05, 0x00, 0x0b, 0x05, 0x09, 0x04,
|
||||
0x17, 0x11, 0x15, 0x10, 0x1b, 0x15, 0x19, 0x14,
|
||||
};
|
||||
ASSERT_EQ(out_data, expected_data);
|
||||
}
|
||||
|
||||
void test_node_calibrate_8bit()
|
||||
{
|
||||
|
@ -640,8 +854,14 @@ void test_image_pipeline()
|
|||
test_node_merge_mono_lines();
|
||||
test_node_split_mono_lines();
|
||||
test_node_component_shift_lines();
|
||||
test_node_pixel_shift_columns_no_switch();
|
||||
test_node_pixel_shift_columns_group_switch_pixel_multiple();
|
||||
test_node_pixel_shift_columns_group_switch_pixel_not_multiple();
|
||||
test_node_pixel_shift_columns_group_switch_pixel_large_offsets_multiple();
|
||||
test_node_pixel_shift_columns_group_switch_pixel_large_offsets_not_multiple();
|
||||
test_node_pixel_shift_lines_2lines();
|
||||
test_node_pixel_shift_lines_4lines();
|
||||
test_node_pixel_shift_columns_compute_max_width();
|
||||
test_node_calibrate_8bit();
|
||||
test_node_calibrate_16bit();
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue