diff --git a/include/boost/gil/algorithm.hpp b/include/boost/gil/algorithm.hpp index e6bf78005e..0fe504781b 100644 --- a/include/boost/gil/algorithm.hpp +++ b/include/boost/gil/algorithm.hpp @@ -1416,11 +1416,12 @@ void view_multiplies_scalar(SrcView const& src_view, Scalar const& scalar, DstVi /// \brief Boundary options for image boundary extension enum class boundary_option { - output_ignore, /// do nothing to the output - output_zero, /// set the output to zero - extend_padded, /// assume the source boundaries to be padded already - extend_zero, /// assume the source boundaries to be zero - extend_constant /// assume the source boundaries to be the boundary value + output_ignore, /// do nothing to the output + output_zero, /// set the output to zero + extend_padded, /// assume the source boundaries to be padded already + extend_zero, /// assume the source boundaries to be zero + extend_constant, /// assume the source boundaries to be the boundary value + extend_reflection /// assumes boundary values as reflection of source row pixels }; namespace detail diff --git a/include/boost/gil/image_processing/convolve.hpp b/include/boost/gil/image_processing/convolve.hpp index a6369c89a6..c7f28763ae 100644 --- a/include/boost/gil/image_processing/convolve.hpp +++ b/include/boost/gil/image_processing/convolve.hpp @@ -2,6 +2,7 @@ // Copyright 2005-2007 Adobe Systems Incorporated // Copyright 2019 Miral Shah // Copyright 2019-2021 Pranam Lashkari +// Copyright 2021 Prathamesh Tagore // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -143,6 +144,18 @@ void correlate_rows_impl( pixel_assigns_t()(src_view.row_end(y)[-1], filler); std::fill_n(it_buffer, kernel.right_size(), filler); } + else if (option == boundary_option::extend_reflection) + { + assign_pixels(src_view.row_begin(y), src_view.row_begin(y) + kernel.left_size(), + it_buffer); + std::reverse(buffer.begin(), buffer.begin() + kernel.left_size()); + it_buffer += kernel.left_size(); + assign_pixels(src_view.row_begin(y), src_view.row_end(y), it_buffer); + it_buffer += width; + assign_pixels(src_view.row_end(y) - kernel.right_size(), src_view.row_end(y), + it_buffer); + std::reverse(buffer.end() - kernel.right_size(), buffer.end()); + } correlator( &buffer.front(), &buffer.front() + width, diff --git a/test/core/image_processing/convolve.cpp b/test/core/image_processing/convolve.cpp index c3ecb431d8..97c76ccd37 100644 --- a/test/core/image_processing/convolve.cpp +++ b/test/core/image_processing/convolve.cpp @@ -1,6 +1,7 @@ // // Copyright 2019-2020 Mateusz Loskot // Copyright 2021 Pranam Lashkari +// Copyright 2021 Prathamesh Tagore // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -111,6 +112,74 @@ struct test_image_5x5_kernel_3x3_identity } }; +struct test_image_5x5_kernel_1x9_boundary_extend_reflection +{ + template + void operator()(Image const&) + { + using image_t = Image; + using pixel_t = typename image_t::value_type; + using channel_t = typename gil::channel_type::type; + auto img = fixture::generate_image(5, 5, fixture::random_value{}); + auto img_view = gil::view(img); + image_t img_out_up_left_offset(img), img_expected_row_up_offset(img); + image_t img_expected_col_left_offset(img), img_out_down_right_offset(img); + image_t img_expected_row_down_offset(img), img_expected_col_right_offset(img); + int kernel_shift_up_left_offset = 2, kernel_shift_down_right_offset = -2; + + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_row_up_offset), + kernel_shift_up_left_offset); + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_row_up_offset), + -1, 0, 1, img_view.height(), 1); + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_row_up_offset), + 1, 0, 0, img_view.height(), 2); + + fixture::col_conv1D_offset_img_generator(gil::view(img_expected_row_up_offset), + gil::view(img_expected_col_left_offset), kernel_shift_up_left_offset); + fixture::col_conv1D_offset_img_generator(gil::view(img_expected_row_up_offset), + gil::view(img_expected_col_left_offset), -1, 1, 0, 1, img_view.width()); + fixture::col_conv1D_offset_img_generator(gil::view(img_expected_row_up_offset), + gil::view(img_expected_col_left_offset), 1, 0, 0, 2, img_view.width()); + + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_row_down_offset), + kernel_shift_down_right_offset, 0, 2, img_view.height(), img_view.width()); + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_row_down_offset), + -1, 0, img_view.width() - 1, img_view.height(), img_view.width()); + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_row_down_offset), + 1, 0, img_view.width() - 2, img_view.height(), img_view.width()); + + fixture::col_conv1D_offset_img_generator(gil::view(img_expected_row_down_offset), + gil::view(img_expected_col_right_offset), kernel_shift_down_right_offset, 2, 0, + img_view.height(), img_view.width()); + fixture::col_conv1D_offset_img_generator(gil::view(img_expected_row_down_offset), + gil::view(img_expected_col_right_offset), -1, img_view.height() - 1, 0, + img_view.height(), img_view.width()); + fixture::col_conv1D_offset_img_generator(gil::view(img_expected_row_down_offset), + gil::view(img_expected_col_right_offset), 1, img_view.height() - 2, 0, + img_view.height(), img_view.width()); + + auto const kernel_up_left_offset = fixture::create_kernel( + {0, 0, 0, 0, 0, 0, 1, 0, 0}); + gil::detail::convolve_1d(gil::const_view(img), kernel_up_left_offset, + gil::view(img_out_up_left_offset), gil::boundary_option::extend_reflection); + + auto const kernel_down_right_offset = fixture::create_kernel( + {0, 0, 1, 0, 0, 0, 0, 0, 0}); + gil::detail::convolve_1d(gil::const_view(img), kernel_down_right_offset, + gil::view(img_out_down_right_offset), gil::boundary_option::extend_reflection); + + BOOST_TEST(gil::equal_pixels(gil::const_view(img_out_up_left_offset), + gil::const_view(img_expected_col_left_offset))); + BOOST_TEST(gil::equal_pixels(gil::const_view(img_out_down_right_offset), + gil::const_view(img_expected_col_right_offset))); + } + static void run() + { + boost::mp11::mp_for_each( + test_image_5x5_kernel_1x9_boundary_extend_reflection{}); + } +}; + int main() { test_image_1x1_kernel_1x1_identity::run(); @@ -118,5 +187,6 @@ int main() test_image_3x3_kernel_3x3_identity::run(); test_image_5x5_kernel_3x3_identity::run(); + test_image_5x5_kernel_1x9_boundary_extend_reflection::run(); return ::boost::report_errors(); } diff --git a/test/core/image_processing/convolve_cols.cpp b/test/core/image_processing/convolve_cols.cpp index c78f181b97..cef5f6b463 100644 --- a/test/core/image_processing/convolve_cols.cpp +++ b/test/core/image_processing/convolve_cols.cpp @@ -1,6 +1,7 @@ // // Copyright 2019-2020 Mateusz Loskot // Copyright 2021 Pranam Lashkari +// Copyright 2021 Prathamesh Tagore // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -65,10 +66,61 @@ struct test_image_1x1_kernel_3x3_identity } }; +struct test_image_5x5_kernel_1x9_boundary_extend_reflection +{ + template + void operator()(Image const&) + { + using image_t = Image; + using pixel_t = typename image_t::value_type; + using channel_t = typename gil::channel_type::type; + auto img = fixture::generate_image(5, 5, fixture::random_value{}); + auto img_view = gil::view(img); + image_t img_out_left_offset(img), img_expected_left_offset(img); + image_t img_out_right_offset(img), img_expected_right_offset(img); + int kernel_shift_left_offset = 2, kernel_shift_right_offset = -2; + + fixture::col_conv1D_offset_img_generator(img_view, gil::view(img_expected_left_offset), + kernel_shift_left_offset); + fixture::col_conv1D_offset_img_generator(img_view, gil::view(img_expected_left_offset), + -1, 1, 0, 1, img_view.width()); + fixture::col_conv1D_offset_img_generator(img_view, gil::view(img_expected_left_offset), + 1, 0, 0, 2, img_view.width()); + + fixture::col_conv1D_offset_img_generator(img_view, gil::view(img_expected_right_offset), + kernel_shift_right_offset, 2, 0, img_view.height(), img_view.width()); + fixture::col_conv1D_offset_img_generator(img_view, gil::view(img_expected_right_offset), + -1, img_view.height() - 1, 0, img_view.height(), img_view.width()); + fixture::col_conv1D_offset_img_generator(img_view, gil::view(img_expected_right_offset), + 1, img_view.height() - 2, 0, img_view.height(), img_view.width()); + + auto const kernel_left_offset = fixture::create_kernel( + {0, 0, 0, 0, 0, 0, 1, 0, 0}); + gil::convolve_cols(gil::const_view(img), kernel_left_offset, + gil::view(img_out_left_offset), gil::boundary_option::extend_reflection); + + auto const kernel_right_offset = fixture::create_kernel( + {0, 0, 1, 0, 0, 0, 0, 0, 0}); + gil::convolve_cols(gil::const_view(img), kernel_right_offset, + gil::view(img_out_right_offset), gil::boundary_option::extend_reflection); + + BOOST_TEST(gil::equal_pixels(gil::const_view(img_out_left_offset), + gil::const_view(img_expected_left_offset))); + BOOST_TEST(gil::equal_pixels(gil::const_view(img_out_right_offset), + gil::const_view(img_expected_right_offset))); + } + static void run() + { + boost::mp11::mp_for_each( + test_image_5x5_kernel_1x9_boundary_extend_reflection{}); + } +}; + int main() { test_image_1x1_kernel_1x1_identity::run(); test_image_1x1_kernel_3x3_identity::run(); + test_image_5x5_kernel_1x9_boundary_extend_reflection::run(); return ::boost::report_errors(); } diff --git a/test/core/image_processing/convolve_rows.cpp b/test/core/image_processing/convolve_rows.cpp index d4a99fcc8a..24525c1744 100644 --- a/test/core/image_processing/convolve_rows.cpp +++ b/test/core/image_processing/convolve_rows.cpp @@ -1,6 +1,7 @@ // // Copyright 2019-2020 Mateusz Loskot // Copyright 2021 Pranam Lashkari +// Copyright 2021 Prathamesh Tagore // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -65,10 +66,61 @@ struct test_image_1x1_kernel_3x3_identity } }; +struct test_image_5x5_kernel_1x9_boundary_extend_reflection +{ + template + void operator()(Image const&) + { + using image_t = Image; + using pixel_t = typename image_t::value_type; + using channel_t = typename gil::channel_type::type; + auto img = fixture::generate_image(5, 5, fixture::random_value{}); + auto img_view = gil::view(img); + image_t img_out_up_offset(img), img_expected_up_offset(img); + image_t img_out_down_offset(img), img_expected_down_offset(img); + int kernel_shift_up_offset = 2, kernel_shift_down_offset = -2; + + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_up_offset), + kernel_shift_up_offset); + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_up_offset), + -1, 0, 1, img_view.height(), 1); + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_up_offset), + 1, 0, 0, img_view.height(), 2); + + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_down_offset), + kernel_shift_down_offset, 0, 2, img_view.height(), img_view.width()); + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_down_offset), + -1, 0, img_view.width() - 1, img_view.height(), img_view.width()); + fixture::row_conv1D_offset_img_generator(img_view, gil::view(img_expected_down_offset), + 1, 0, img_view.width() - 2, img_view.height(), img_view.width()); + + auto const kernel_up_offset = fixture::create_kernel( + {0, 0, 0, 0, 0, 0, 1, 0, 0}); + gil::convolve_rows(gil::const_view(img), kernel_up_offset, + gil::view(img_out_up_offset), gil::boundary_option::extend_reflection); + + auto const kernel_down_offset = fixture::create_kernel( + {0, 0, 1, 0, 0, 0, 0, 0, 0}); + gil::convolve_rows(gil::const_view(img), kernel_down_offset, + gil::view(img_out_down_offset), gil::boundary_option::extend_reflection); + + BOOST_TEST(gil::equal_pixels(gil::const_view(img_out_up_offset), + gil::const_view(img_expected_up_offset))); + BOOST_TEST(gil::equal_pixels(gil::const_view(img_out_down_offset), + gil::const_view(img_expected_down_offset))); + } + static void run() + { + boost::mp11::mp_for_each( + test_image_5x5_kernel_1x9_boundary_extend_reflection{}); + } +}; + int main() { test_image_1x1_kernel_1x1_identity::run(); test_image_1x1_kernel_3x3_identity::run(); + test_image_5x5_kernel_1x9_boundary_extend_reflection::run(); return ::boost::report_errors(); } diff --git a/test/core/image_processing/test_fixture.hpp b/test/core/image_processing/test_fixture.hpp index b49fb6cd76..2777de4f60 100644 --- a/test/core/image_processing/test_fixture.hpp +++ b/test/core/image_processing/test_fixture.hpp @@ -1,6 +1,7 @@ // // Copyright 2019 Mateusz Loskot // Copyright 2021 Pranam Lashkari +// Copyright 2021 Prathamesh Tagore // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -28,4 +29,58 @@ auto create_kernel(std::initializer_list const& values) return kernel; } +// Adds an offset similar to 1D row convolution with kernel {0, 0, 0, 0, 0, 0, 1, 0, 0} +// (for offset = 2) having its anchor point at 5th element and boundary option as "extend_zero". +template +void row_conv1D_offset_img_generator(SrcView src_view, DstView dst_view, int const offset, + std::ptrdiff_t start_row = 0, std::ptrdiff_t start_col = 0, std::ptrdiff_t end_row = -1, + std::ptrdiff_t end_col = -1) +{ + BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions()); + static_assert(color_spaces_are_compatible + < + typename color_space_type::type, + typename color_space_type::type + >::value, "Source and destination views must have pixels with the same color space"); + + if (end_row == -1) + end_row = src_view.height(); + if (end_col == -1) + end_col = src_view.width(); + for (std::ptrdiff_t y = start_row; y < end_row; ++y) + { + auto src_it = src_view.row_begin(y); + auto dst_it = dst_view.row_begin(y); + for (std::ptrdiff_t x = offset + start_col; x < end_col; ++x) + dst_it[x] = src_it[x - offset]; + } +} + +// Adds an offset similar to 1D column convolution with kernel {0, 0, 0, 0, 0, 0, 1, 0, 0} +// (for offset = 2) having its anchor point at 5th element and boundary option as "extend_zero". +template +void col_conv1D_offset_img_generator(SrcView src_view, DstView dst_view, int const offset, + std::ptrdiff_t start_row = 0, std::ptrdiff_t start_col = 0, std::ptrdiff_t end_row = -1, + std::ptrdiff_t end_col = -1) +{ + BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions()); + static_assert(color_spaces_are_compatible + < + typename color_space_type::type, + typename color_space_type::type + >::value, "Source and destination views must have pixels with the same color space"); + + if (end_row == -1) + end_row = src_view.height(); + if (end_col == -1) + end_col = src_view.width(); + for (std::ptrdiff_t x = start_col; x < end_col; ++x) + { + auto src_it = src_view.col_begin(x); + auto dst_it = dst_view.col_begin(x); + for (std::ptrdiff_t y = offset + start_row; y < end_row; ++y) + dst_it[y] = src_it[y - offset]; + } +} + }}}} // namespace boost::gil::test::fixture