// // Copyright 2005-2007 Adobe Systems Incorporated // Copyright 2021 Pranam Lashkari // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // #ifndef BOOST_GIL_ALGORITHM_HPP #define BOOST_GIL_ALGORITHM_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace gil { //forward declarations template struct planar_pixel_iterator; template class memory_based_step_iterator; template class memory_based_2d_locator; // a tag denoting incompatible arguments struct error_t {}; /// \defgroup ImageViewSTLAlgorithms STL-like Algorithms /// \ingroup ImageViewAlgorithm /// \brief Image view-equivalents of STL algorithms /// /// Image views provide 1D iteration of their pixels via \p begin() and \p end() methods, /// which makes it possible to use STL algorithms with them. However, using nested loops /// over X and Y is in many cases more efficient. The algorithms in this section resemble /// STL algorithms, but they abstract away the nested loops and take views (as opposed to ranges) as input. /// /// Most algorithms check whether the image views are 1D-traversable. A 1D-traversable image view has no gaps /// at the end of the rows. In other words, if an x_iterator of that view is advanced past the last pixel in a row /// it will move to the first pixel of the next row. When image views are 1D-traversable, the algorithms use /// a single loop and run more efficiently. If one or more of the input views are not 1D-traversable, the algorithms /// fall-back to an X-loop nested inside a Y-loop. /// /// The algorithms typically delegate the work to their corresponding STL algorithms. For example, \p copy_pixels calls /// \p std::copy either for each row, or, when the images are 1D-traversable, once for all pixels. /// /// In addition, overloads are sometimes provided for the STL algorithms. For example, std::copy for planar iterators /// is overloaded to perform \p std::copy for each of the planes. \p std::copy over bitwise-copiable pixels results in /// std::copy over unsigned char, which STL typically implements via \p memmove. /// /// As a result \p copy_pixels may result in a single call to \p memmove for interleaved 1D-traversable views, /// or one per each plane of planar 1D-traversable views, or one per each row of interleaved non-1D-traversable images, etc. /// \defgroup STLOptimizations Performance overloads of STL algorithms /// \ingroup ImageViewAlgorithm /// \brief overloads of STL algorithms allowing more efficient implementation when used with GIL constructs /// \brief A generic binary operation on views /// \ingroup ImageViewSTLAlgorithms /// /// Use this class as a convenience superclass when defining an operation for any image views. /// Many operations have different behavior when the two views are compatible. This class checks /// for compatibility and invokes apply_compatible(V1,V2) or apply_incompatible(V1,V2) of the subclass. /// You must provide apply_compatible(V1,V2) method in your subclass, but apply_incompatible(V1,V2) /// is not required and the default throws std::bad_cast. template struct binary_operation_obj { using result_type = Result; template BOOST_FORCEINLINE auto operator()(const std::pair& p) const -> result_type { return apply(*p.first, *p.second, typename views_are_compatible::type()); } template BOOST_FORCEINLINE auto operator()(const V1& v1, const V2& v2) const -> result_type { return apply(v1, v2, typename views_are_compatible::type()); } auto operator()(const error_t&) const -> result_type { throw std::bad_cast(); } private: // dispatch from apply overload to a function with distinct name template BOOST_FORCEINLINE auto apply(V1 const& v1, V2 const& v2, std::false_type) const -> result_type { return ((const Derived*)this)->apply_incompatible(v1, v2); } // dispatch from apply overload to a function with distinct name template BOOST_FORCEINLINE auto apply(V1 const& v1, V2 const& v2, std::true_type) const -> result_type { return ((const Derived*)this)->apply_compatible(v1, v2); } // function with distinct name - it can be overloaded by subclasses template BOOST_FORCEINLINE auto apply_incompatible(V1 const& /*v1*/, V2 const& /*v2*/) const -> result_type { throw std::bad_cast(); } }; }} // namespace boost::gil ////////////////////////////////////////////////////////////////////////////////////// // std::copy and gil::copy_pixels ////////////////////////////////////////////////////////////////////////////////////// /// \defgroup ImageViewSTLAlgorithmsCopyPixels copy_pixels /// \ingroup ImageViewSTLAlgorithms /// \brief std::copy for image views namespace std { /// \ingroup STLOptimizations /// \brief Copy when both src and dst are interleaved and of the same type can be just memmove template BOOST_FORCEINLINE auto copy( boost::gil::pixel* first, boost::gil::pixel* last, boost::gil::pixel* dst) -> boost::gil::pixel* { auto p = std::copy((unsigned char*)first, (unsigned char*)last, (unsigned char*)dst); return reinterpret_cast*>(p); } /// \ingroup STLOptimizations /// \brief Copy when both src and dst are interleaved and of the same type can be just memmove template BOOST_FORCEINLINE auto copy(const boost::gil::pixel* first, const boost::gil::pixel* last, boost::gil::pixel* dst) -> boost::gil::pixel* { return (boost::gil::pixel*)std::copy((unsigned char*)first,(unsigned char*)last, (unsigned char*)dst); } } // namespace std namespace boost { namespace gil { namespace detail { template struct copy_fn { BOOST_FORCEINLINE I operator()(I first, I last, O dst) const { return std::copy(first,last,dst); } }; } // namespace detail } } // namespace boost::gil namespace std { /// \ingroup STLOptimizations /// \brief Copy when both src and dst are planar pointers is copy for each channel template BOOST_FORCEINLINE auto copy(boost::gil::planar_pixel_iterator first, boost::gil::planar_pixel_iterator last, boost::gil::planar_pixel_iterator dst) -> boost::gil::planar_pixel_iterator { boost::gil::gil_function_requires::value_type,typename std::iterator_traits::value_type>>(); static_for_each(first,last,dst,boost::gil::detail::copy_fn()); return dst+(last-first); } } // namespace std namespace boost { namespace gil { namespace detail { /// Does a copy-n. If the inputs contain image iterators, performs a copy at each row using the row iterators /// \ingroup CopyPixels template struct copier_n { BOOST_FORCEINLINE void operator()(I src, typename std::iterator_traits::difference_type n, O dst) const { std::copy(src,src+n, dst); } }; /// Source range is delimited by image iterators template // IL Models ConstPixelLocatorConcept, O Models PixelIteratorConcept struct copier_n,O> { using diff_t = typename std::iterator_traits>::difference_type; BOOST_FORCEINLINE void operator()(iterator_from_2d src, diff_t n, O dst) const { gil_function_requires>(); gil_function_requires>(); while (n>0) { diff_t l=src.width()-src.x_pos(); diff_t numToCopy=(n // I Models ConstPixelIteratorConcept, OL Models PixelLocatorConcept struct copier_n> { using diff_t = typename std::iterator_traits::difference_type; BOOST_FORCEINLINE void operator()(I src, diff_t n, iterator_from_2d
    dst) const { gil_function_requires>(); gil_function_requires>(); while (n>0) { diff_t l=dst.width()-dst.x_pos(); diff_t numToCopy=(n struct copier_n,iterator_from_2d
      > { using diff_t = typename iterator_from_2d::difference_type; BOOST_FORCEINLINE void operator()(iterator_from_2d src, diff_t n, iterator_from_2d
        dst) const { gil_function_requires>(); gil_function_requires>(); if (src.x_pos()!=dst.x_pos() || src.width()!=dst.width()) { while(n-->0) { *dst++=*src++; } } while (n>0) { diff_t l=dst.width()-dst.x_pos(); diff_t numToCopy=(n BOOST_FORCEINLINE auto copy_with_2d_iterators(SrcIterator first, SrcIterator last, DstIterator dst) -> DstIterator { using src_x_iterator = typename SrcIterator::x_iterator; using dst_x_iterator = typename DstIterator::x_iterator; typename SrcIterator::difference_type n = last - first; if (first.is_1d_traversable()) { if (dst.is_1d_traversable()) copier_n()(first.x(),n, dst.x()); else copier_n()(first.x(),n, dst); } else { if (dst.is_1d_traversable()) copier_n()(first,n, dst.x()); else copier_n()(first,n,dst); } return dst+n; } } // namespace detail } } // namespace boost::gil namespace std { /// \ingroup STLOptimizations /// \brief std::copy(I1,I1,I2) with I1 and I2 being a iterator_from_2d template BOOST_FORCEINLINE auto copy1(boost::gil::iterator_from_2d first, boost::gil::iterator_from_2d last, boost::gil::iterator_from_2d
          dst) -> boost::gil::iterator_from_2d
            { return boost::gil::detail::copy_with_2d_iterators(first,last,dst); } } // namespace std namespace boost { namespace gil { /// \ingroup ImageViewSTLAlgorithmsCopyPixels /// \brief std::copy for image views template BOOST_FORCEINLINE void copy_pixels(const View1& src, const View2& dst) { BOOST_ASSERT(src.dimensions() == dst.dimensions()); detail::copy_with_2d_iterators(src.begin(),src.end(),dst.begin()); } ////////////////////////////////////////////////////////////////////////////////////// // copy_and_convert_pixels ////////////////////////////////////////////////////////////////////////////////////// /// \defgroup ImageViewSTLAlgorithmsCopyAndConvertPixels copy_and_convert_pixels /// \ingroup ImageViewSTLAlgorithms /// \brief copies src view into dst view, color converting if necessary. /// /// Versions taking static and runtime views are provided. Versions taking user-defined color convered are provided. namespace detail { template class copy_and_convert_pixels_fn : public binary_operation_obj> { private: CC _cc; public: using result_type = typename binary_operation_obj>::result_type; copy_and_convert_pixels_fn() {} copy_and_convert_pixels_fn(CC cc_in) : _cc(cc_in) {} // when the two color spaces are incompatible, a color conversion is performed template BOOST_FORCEINLINE auto apply_incompatible(const V1& src, const V2& dst) const -> result_type { copy_pixels(color_converted_view(src,_cc),dst); } // If the two color spaces are compatible, copy_and_convert is just copy template BOOST_FORCEINLINE auto apply_compatible(const V1& src, const V2& dst) const -> result_type { copy_pixels(src,dst); } }; } // namespace detail /// \ingroup ImageViewSTLAlgorithmsCopyAndConvertPixels template BOOST_FORCEINLINE void copy_and_convert_pixels(const V1& src, const V2& dst,CC cc) { detail::copy_and_convert_pixels_fn ccp(cc); ccp(src,dst); } struct default_color_converter; /// \ingroup ImageViewSTLAlgorithmsCopyAndConvertPixels template BOOST_FORCEINLINE void copy_and_convert_pixels(const View1& src, const View2& dst) { detail::copy_and_convert_pixels_fn ccp; ccp(src,dst); } } } // namespace boost::gil ////////////////////////////////////////////////////////////////////////////////////// // std::fill and gil::fill_pixels ////////////////////////////////////////////////////////////////////////////////////// /// \defgroup ImageViewSTLAlgorithmsFillPixels fill_pixels /// \ingroup ImageViewSTLAlgorithms /// \brief std::fill for image views namespace std { /// \ingroup STLOptimizations /// \brief std::fill(I,I,V) with I being a iterator_from_2d /// /// Invoked when one calls std::fill(I,I,V) with I being a iterator_from_2d (which is /// a 1D iterator over the pixels in an image). For contiguous images (i.e. images that have /// no alignment gap at the end of each row) it is more efficient to use the underlying /// pixel iterator that does not check for the end of rows. For non-contiguous images fill /// resolves to fill of each row using the underlying pixel iterator, which is still faster template void fill(boost::gil::iterator_from_2d first, boost::gil::iterator_from_2d last, const V& val) { boost::gil::gil_function_requires>(); if (first.is_1d_traversable()) { std::fill(first.x(), last.x(), val); } else { // fill row by row std::ptrdiff_t n=last-first; while (n>0) { std::ptrdiff_t numToDo=std::min(n,(std::ptrdiff_t)(first.width()-first.x_pos())); std::fill_n(first.x(), numToDo, val); first+=numToDo; n-=numToDo; } } } } // namespace std namespace boost { namespace gil { namespace detail { /// struct to do std::fill struct std_fill_t { template void operator()(It first, It last, const P& p_in) { std::fill(first,last,p_in); } }; /// std::fill for planar iterators template BOOST_FORCEINLINE void fill_aux(It first, It last, P const& p, std::true_type) { static_for_each(first, last, p, std_fill_t()); } /// std::fill for interleaved iterators template BOOST_FORCEINLINE void fill_aux(It first, It last, P const& p, std::false_type) { std::fill(first, last, p); } } // namespace detail /// \ingroup ImageViewSTLAlgorithmsFillPixels /// \brief std::fill for image views template BOOST_FORCEINLINE void fill_pixels(View const& view, Value const& value) { if (view.is_1d_traversable()) { detail::fill_aux( view.begin().x(), view.end().x(), value, is_planar()); } else { for (std::ptrdiff_t y = 0; y < view.height(); ++y) detail::fill_aux( view.row_begin(y), view.row_end(y), value, is_planar()); } } ////////////////////////////////////////////////////////////////////////////////////// // destruct_pixels ////////////////////////////////////////////////////////////////////////////////////// /// \defgroup ImageViewSTLAlgorithmsDestructPixels destruct_pixels /// \ingroup ImageViewSTLAlgorithms /// \brief invokes the destructor on every pixel of an image view namespace detail { template BOOST_FORCEINLINE void destruct_range_impl(Iterator first, Iterator last, typename std::enable_if < mp11::mp_and < std::is_pointer, mp11::mp_not < detail::is_trivially_destructible::value_type> > >::value >::type* /*ptr*/ = 0) { while (first != last) { first->~value_t(); ++first; } } template BOOST_FORCEINLINE void destruct_range_impl(Iterator /*first*/, Iterator /*last*/, typename std::enable_if < mp11::mp_or < mp11::mp_not>, detail::is_trivially_destructible::value_type> >::value >::type* /* ptr */ = nullptr) { } template BOOST_FORCEINLINE void destruct_range(Iterator first, Iterator last) { destruct_range_impl(first, last); } struct std_destruct_t { template void operator()(Iterator first, Iterator last) const { destruct_range(first,last); } }; /// destruct for planar iterators template BOOST_FORCEINLINE void destruct_aux(It first, It last, std::true_type) { static_for_each(first,last,std_destruct_t()); } /// destruct for interleaved iterators template BOOST_FORCEINLINE void destruct_aux(It first, It last, std::false_type) { destruct_range(first,last); } } // namespace detail /// \ingroup ImageViewSTLAlgorithmsDestructPixels /// \brief Invokes the in-place destructor on every pixel of the view template BOOST_FORCEINLINE void destruct_pixels(View const& view) { if (view.is_1d_traversable()) { detail::destruct_aux( view.begin().x(), view.end().x(), is_planar()); } else { for (std::ptrdiff_t y = 0; y < view.height(); ++y) detail::destruct_aux( view.row_begin(y), view.row_end(y), is_planar()); } } ////////////////////////////////////////////////////////////////////////////////////// // uninitialized_fill_pixels ////////////////////////////////////////////////////////////////////////////////////// /// \defgroup ImageViewSTLAlgorithmsUninitializedFillPixels uninitialized_fill_pixels /// \ingroup ImageViewSTLAlgorithms /// \brief std::uninitialized_fill for image views namespace detail { /// std::uninitialized_fill for planar iterators /// If an exception is thrown destructs any in-place copy-constructed objects template BOOST_FORCEINLINE void uninitialized_fill_aux(It first, It last, P const& p, std::true_type) { std::size_t channel = 0; try { using pixel_t = typename std::iterator_traits::value_type; while (channel < num_channels::value) { std::uninitialized_fill( dynamic_at_c(first,channel), dynamic_at_c(last,channel), dynamic_at_c(p,channel)); ++channel; } } catch (...) { for (std::size_t c = 0; c < channel; ++c) destruct_range(dynamic_at_c(first, c), dynamic_at_c(last, c)); throw; } } /// std::uninitialized_fill for interleaved iterators /// If an exception is thrown destructs any in-place copy-constructed objects template BOOST_FORCEINLINE void uninitialized_fill_aux(It first, It last, P const& p, std::false_type) { std::uninitialized_fill(first,last,p); } } // namespace detail /// \ingroup ImageViewSTLAlgorithmsUninitializedFillPixels /// \brief std::uninitialized_fill for image views. /// Does not support planar heterogeneous views. /// If an exception is thrown destructs any in-place copy-constructed pixels template void uninitialized_fill_pixels(const View& view, const Value& val) { if (view.is_1d_traversable()) detail::uninitialized_fill_aux(view.begin().x(), view.end().x(), val,is_planar()); else { typename View::y_coord_t y = 0; try { for (y=0; y()); } catch(...) { for (typename View::y_coord_t y0=0; y0()); throw; } } } ////////////////////////////////////////////////////////////////////////////////////// // default_construct_pixels ////////////////////////////////////////////////////////////////////////////////////// /// \defgroup ImageViewSTLAlgorithmsDefaultConstructPixels default_construct_pixels /// \ingroup ImageViewSTLAlgorithms /// \brief invokes the default constructor on every pixel of an image view namespace detail { template BOOST_FORCEINLINE void default_construct_range_impl(It first, It last, std::true_type) { It first1 = first; try { using value_t = typename std::iterator_traits::value_type; while (first != last) { new (first) value_t(); ++first; } } catch (...) { destruct_range(first1, first); throw; } } template BOOST_FORCEINLINE void default_construct_range_impl(It, It, std::false_type) {} template BOOST_FORCEINLINE void default_construct_range(It first, It last) { default_construct_range_impl(first, last, typename std::is_pointer::type()); } /// uninitialized_default_construct for planar iterators template BOOST_FORCEINLINE void default_construct_aux(It first, It last, std::true_type) { std::size_t channel = 0; try { using pixel_t = typename std::iterator_traits::value_type; while (channel < num_channels::value) { default_construct_range(dynamic_at_c(first, channel), dynamic_at_c(last, channel)); ++channel; } } catch (...) { for (std::size_t c = 0; c < channel; ++c) destruct_range(dynamic_at_c(first, c), dynamic_at_c(last, c)); throw; } } /// uninitialized_default_construct for interleaved iterators template BOOST_FORCEINLINE void default_construct_aux(It first, It last, std::false_type) { default_construct_range(first, last); } template struct has_trivial_pixel_constructor : detail::is_trivially_default_constructible {}; template struct has_trivial_pixel_constructor : detail::is_trivially_default_constructible::type> {}; template BOOST_FORCEINLINE void default_construct_pixels_impl( View const& view, std::enable_if* /*ptr*/ = nullptr) { if (view.is_1d_traversable()) { detail::default_construct_aux( view.begin().x(), view.end().x(), is_planar()); } else { typename View::y_coord_t y = 0; try { for( y = 0; y < view.height(); ++y ) detail::default_construct_aux( view.row_begin(y), view.row_end(y), is_planar()); } catch(...) { for (typename View::y_coord_t y0 = 0; y0 < y; ++y0 ) detail::destruct_aux( view.row_begin(y0), view.row_end(y0), is_planar()); throw; } } } } // namespace detail /// \ingroup ImageViewSTLAlgorithmsDefaultConstructPixels /// \brief Invokes the in-place default constructor on every pixel of the (uninitialized) view. /// Does not support planar heterogeneous views. /// If an exception is thrown destructs any in-place default-constructed pixels template void default_construct_pixels(View const& view) { detail::default_construct_pixels_impl < View, detail::has_trivial_pixel_constructor < View, is_planar::value >::value >(view); } ////////////////////////////////////////////////////////////////////////////////////// // uninitialized_copy_pixels ////////////////////////////////////////////////////////////////////////////////////// /// \defgroup ImageViewSTLAlgorithmsUninitializedCopyPixels uninitialized_copy_pixels /// \ingroup ImageViewSTLAlgorithms /// \brief std::uninitialized_copy for image views namespace detail { enum class copy_planarity_condition { planar_to_planar, interleaved_to_planar, mixed_to_interleaved }; using planar_to_planar_type = std::integral_constant < copy_planarity_condition, copy_planarity_condition::planar_to_planar >; using interleaved_to_planar_type = std::integral_constant < copy_planarity_condition, copy_planarity_condition::interleaved_to_planar >; using mixed_to_interleaved_type = std::integral_constant < copy_planarity_condition, copy_planarity_condition::mixed_to_interleaved >; /// std::uninitialized_copy for pairs of planar iterators template BOOST_FORCEINLINE void uninitialized_copy_aux(It1 first1, It1 last1, It2 first2, It2 last2, planar_to_planar_type) { std::size_t channel=0; try { using pixel_t = typename std::iterator_traits::value_type; while (channel < num_channels::value) { std::uninitialized_copy( dynamic_at_c(first1, channel), dynamic_at_c(last1, channel), dynamic_at_c(first2, channel)); ++channel; } } catch (...) { It2 last2 = first2; std::advance(last2, std::distance(first1, last1)); for (std::size_t c = 0; c < channel; ++c) destruct_range(dynamic_at_c(first2, c), dynamic_at_c(last2, c)); throw; } } /// std::uninitialized_copy for interleaved or mixed(planar into interleaved) iterators template BOOST_FORCEINLINE void uninitialized_copy_aux(It1 first1, It1 last1, It2 first2, It2, mixed_to_interleaved_type) { std::uninitialized_copy(first1, last1, first2); } /// std::uninitialized_copy for interleaved to planar iterators template BOOST_FORCEINLINE void uninitialized_copy_aux(It1 first1, It1, It2 first2, It2 last2, interleaved_to_planar_type) { default_construct_aux(first2, last2, std::true_type()); typename It2::difference_type n = last2 - first2; copier_n()(first1, n, first2); } } // namespace detail /// \ingroup ImageViewSTLAlgorithmsUninitializedCopyPixels /// \brief std::uninitialized_copy for image views. /// Does not support planar heterogeneous views. /// If an exception is thrown destructs any in-place copy-constructed objects template void uninitialized_copy_pixels(View1 const& view1, View2 const& view2) { using copy_planarity_condition = detail::copy_planarity_condition; using copy_planarity_condition_type = std::integral_constant < copy_planarity_condition, !is_planar::value ? copy_planarity_condition::mixed_to_interleaved : (is_planar::value ? copy_planarity_condition::planar_to_planar : copy_planarity_condition::interleaved_to_planar) >; BOOST_ASSERT(view1.dimensions() == view2.dimensions()); if (view1.is_1d_traversable() && view2.is_1d_traversable()) { detail::uninitialized_copy_aux( view1.begin().x(), view1.end().x(), view2.begin().x(), view2.end().x(), copy_planarity_condition_type()); } else { typename View1::y_coord_t y = 0; try { for (y = 0; y < view1.height(); ++y) detail::uninitialized_copy_aux( view1.row_begin(y), view1.row_end(y), view2.row_begin(y), view2.row_end(y), copy_planarity_condition_type()); } catch(...) { for (typename View1::y_coord_t y0 = 0; y0 < y; ++y0) detail::destruct_aux(view2.row_begin(y0), view2.row_end(y0), is_planar()); throw; } } } ////////////////////////////////////////////////////////////////////////////////////// // for_each_pixel ////////////////////////////////////////////////////////////////////////////////////// /// \defgroup ImageViewSTLAlgorithmsForEachPixel for_each_pixel /// \ingroup ImageViewSTLAlgorithms /// \brief std::for_each for image views /// /// For contiguous images (i.e. images that have no alignment gap at the end of each row) it is /// more efficient to use the underlying pixel iterator that does not check for the end of rows. /// For non-contiguous images for_each_pixel resolves to for_each of each row using the underlying /// pixel iterator, which is still faster /// \ingroup ImageViewSTLAlgorithmsForEachPixel template F for_each_pixel(View const& view, F fun) { if (view.is_1d_traversable()) { return std::for_each(view.begin().x(), view.end().x(), fun); } else { for (std::ptrdiff_t y = 0; y < view.height(); ++y) for (auto begin = view.row_begin(y), end = view.row_end(y); begin != end; ++begin) fun(*begin); return fun; } } /// \defgroup ImageViewSTLAlgorithmsForEachPixelPosition for_each_pixel_position /// \ingroup ImageViewSTLAlgorithms /// \brief adobe::for_each_position for image views (passes locators, instead of pixel references, to the function object) /// \ingroup ImageViewSTLAlgorithmsForEachPixelPosition template F for_each_pixel_position(View const& view, F fun) { typename View::xy_locator loc = view.xy_at(0, 0); for (std::ptrdiff_t y = 0; y < view.height(); ++y) { for (std::ptrdiff_t x = 0; x < view.width(); ++x, ++loc.x()) fun(loc); loc.x() -= view.width(); ++loc.y(); } return fun; } ////////////////////////////////////////////////////////////////////////////////////// // generate_pixels ////////////////////////////////////////////////////////////////////////////////////// /// \defgroup ImageViewSTLAlgorithmsGeneratePixels generate_pixels /// \ingroup ImageViewSTLAlgorithms /// \brief std::generate for image views /// \ingroup ImageViewSTLAlgorithmsGeneratePixels /// \brief std::generate for image views template void generate_pixels(View const& view, F fun) { if (view.is_1d_traversable()) { std::generate(view.begin().x(), view.end().x(), fun); } else { for (std::ptrdiff_t y = 0; y < view.height(); ++y) std::generate(view.row_begin(y), view.row_end(y), fun); } } ////////////////////////////////////////////////////////////////////////////////////// // std::equal and gil::equal_pixels for GIL constructs ////////////////////////////////////////////////////////////////////////////////////// /// \defgroup ImageViewSTLAlgorithmsEqualPixels equal_pixels /// \ingroup ImageViewSTLAlgorithms /// \brief std::equal for image views template BOOST_FORCEINLINE bool equal_n(I1 i1, std::ptrdiff_t n, I2 i2); namespace detail { template struct equal_n_fn { BOOST_FORCEINLINE bool operator()(I1 i1, std::ptrdiff_t n, I2 i2) const { return std::equal(i1, i1 + n, i2); } }; /// Equal when both ranges are interleaved and of the same type. /// GIL pixels are bitwise comparable, so memcmp is used. User-defined pixels that are not bitwise comparable need to provide an overload template struct equal_n_fn const*, pixel const*> { BOOST_FORCEINLINE bool operator()(pixel const* i1, std::ptrdiff_t n, pixel const* i2) const { return memcmp(i1, i2, n * sizeof(pixel)) == 0; } }; template struct equal_n_fn*, pixel*> : equal_n_fn const*, pixel const*> {}; /// EqualPixels /// Equal when both ranges are planar pointers of the same type. memcmp is invoked for each channel plane /// User-defined channels that are not bitwise comparable need to provide an overload template struct equal_n_fn, planar_pixel_iterator> { BOOST_FORCEINLINE bool operator()(planar_pixel_iterator const i1, std::ptrdiff_t n, planar_pixel_iterator const i2) const { // FIXME: ptrdiff_t vs size_t constexpr std::ptrdiff_t byte_size = n * sizeof(typename std::iterator_traits::value_type); for (std::ptrdiff_t i = 0; i < mp11::mp_size::value; ++i) { if (memcmp(dynamic_at_c(i1, i), dynamic_at_c(i2, i), byte_size) != 0) return false; } return true; } }; /// Source range is delimited by image iterators /// \tparam Loc Models ConstPixelLocatorConcept /// \tparam It Models PixelIteratorConcept template struct equal_n_fn, It> { BOOST_FORCEINLINE bool operator()(boost::gil::iterator_from_2d i1, std::ptrdiff_t n, It i2) const { gil_function_requires>(); gil_function_requires>(); while (n > 0) { std::ptrdiff_t const num = std::min(n, i1.width() - i1.x_pos()); if (!equal_n(i1.x(), num, i2)) return false; i1 += num; i2 += num; n -= num; } return true; } }; /// Destination range is delimited by image iterators /// \tparam It Models PixelIteratorConcept /// \tparam Loc Models PixelLocatorConcept template struct equal_n_fn> { BOOST_FORCEINLINE bool operator()(It i1, std::ptrdiff_t n, boost::gil::iterator_from_2d i2) const { gil_function_requires>(); gil_function_requires>(); while (n > 0) { std::ptrdiff_t const num = std::min(n, i2.width() - i2.x_pos()); if (!equal_n(i1, num, i2.x())) return false; i1 += num; i2 += num; n -= num; } return true; } }; /// Both source and destination ranges are delimited by image iterators template struct equal_n_fn,boost::gil::iterator_from_2d> { BOOST_FORCEINLINE bool operator()(boost::gil::iterator_from_2d i1, std::ptrdiff_t n, boost::gil::iterator_from_2d i2) const { gil_function_requires>(); gil_function_requires>(); if (i1.x_pos()!=i2.x_pos() || i1.width()!=i2.width()) { while(n-->0) { if (*i1++!=*i2++) return false; } } while (n>0) { std::ptrdiff_t num=std::min(n,i2.width()-i2.x_pos()); if (!equal_n(i1.x(), num, i2.x())) return false; i1+=num; i2+=num; n-=num; } return true; } }; } // namespace detail template BOOST_FORCEINLINE bool equal_n(I1 i1, std::ptrdiff_t n, I2 i2) { return detail::equal_n_fn()(i1,n,i2); } } } // namespace boost::gil namespace std { /// \ingroup STLOptimizations /// \brief std::equal(I1,I1,I2) with I1 and I2 being a iterator_from_2d /// /// Invoked when one calls std::equal(I1,I1,I2) with I1 and I2 being a iterator_from_2d (which is /// a 1D iterator over the pixels in an image). Attempts to demote the source and destination /// iterators to simpler/faster types if the corresponding range is contiguous. /// For contiguous images (i.e. images that have /// no alignment gap at the end of each row) it is more efficient to use the underlying /// pixel iterator that does not check for the end of rows. If the underlying pixel iterator /// happens to be a fundamental planar/interleaved pointer, the call may further resolve /// to memcmp. Otherwise it resolves to copying each row using the underlying pixel iterator template BOOST_FORCEINLINE bool equal(boost::gil::iterator_from_2d first, boost::gil::iterator_from_2d last, boost::gil::iterator_from_2d first2) { boost::gil::gil_function_requires>(); boost::gil::gil_function_requires>(); std::ptrdiff_t n=last-first; if (first.is_1d_traversable()) { if (first2.is_1d_traversable()) return boost::gil::detail::equal_n_fn()(first.x(),n, first2.x()); else return boost::gil::detail::equal_n_fn>()(first.x(),n, first2); } else { if (first2.is_1d_traversable()) return boost::gil::detail::equal_n_fn,typename Loc2::x_iterator>()(first,n, first2.x()); else return boost::gil::detail::equal_n_fn,boost::gil::iterator_from_2d>()(first,n,first2); } } } // namespace std namespace boost { namespace gil { /// \ingroup ImageViewSTLAlgorithmsEqualPixels /// \brief std::equal for image views template BOOST_FORCEINLINE bool equal_pixels(const View1& v1, const View2& v2) { BOOST_ASSERT(v1.dimensions() == v2.dimensions()); return std::equal(v1.begin(),v1.end(),v2.begin()); // std::equal has overloads with GIL iterators for optimal performance } ////////////////////////////////////////////////////////////////////////////////////// /// /// transform_pixels /// ////////////////////////////////////////////////////////////////////////////////////// /// \defgroup ImageViewSTLAlgorithmsTransformPixels transform_pixels /// \ingroup ImageViewSTLAlgorithms /// \brief std::transform for image views /// \ingroup ImageViewSTLAlgorithmsTransformPixels /// \brief std::transform for image views template BOOST_FORCEINLINE F transform_pixels(const View1& src,const View2& dst, F fun) { BOOST_ASSERT(src.dimensions() == dst.dimensions()); for (std::ptrdiff_t y=0; y BOOST_FORCEINLINE F transform_pixels(const View1& src1, const View2& src2,const View3& dst, F fun) { for (std::ptrdiff_t y=0; y BOOST_FORCEINLINE F transform_pixel_positions(const View1& src,const View2& dst, F fun) { BOOST_ASSERT(src.dimensions() == dst.dimensions()); typename View1::xy_locator loc=src.xy_at(0,0); for (std::ptrdiff_t y=0; y BOOST_FORCEINLINE F transform_pixel_positions(const View1& src1,const View2& src2,const View3& dst, F fun) { BOOST_ASSERT(src1.dimensions() == dst.dimensions()); BOOST_ASSERT(src2.dimensions() == dst.dimensions()); typename View1::xy_locator loc1=src1.xy_at(0,0); typename View2::xy_locator loc2=src2.xy_at(0,0); for (std::ptrdiff_t y=0; y /// \brief Reference proxy associated with a type that has a \p "reference" member type alias. /// /// The reference proxy is the reference type, but with stripped-out C++ reference. /// Models PixelConcept. template struct pixel_proxy : std::remove_reference {}; /// \brief std::for_each for a pair of iterators template BinaryFunction for_each(Iterator1 first1, Iterator1 last1, Iterator2 first2, BinaryFunction f) { while (first1 != last1) f(*first1++, *first2++); return f; } template inline auto assign_pixels(SrcIterator src, SrcIterator src_end, DstIterator dst) -> DstIterator { for_each(src, src_end, dst, pixel_assigns_t < typename pixel_proxy::value_type>::type, typename pixel_proxy::value_type>::type >()); return dst + (src_end - src); } namespace detail { template struct inner_product_k_t { template < class InputIterator1, class InputIterator2, class T, class BinaryOperation1, class BinaryOperation2 > static T apply( InputIterator1 first1, InputIterator2 first2, T init, BinaryOperation1 binary_op1, BinaryOperation2 binary_op2) { init = binary_op1(init, binary_op2(*first1, *first2)); return inner_product_k_t::template apply( first1 + 1, first2 + 1, init, binary_op1, binary_op2); } }; template <> struct inner_product_k_t<0> { template < class InputIterator1, class InputIterator2, class T, class BinaryOperation1, class BinaryOperation2 > static T apply( InputIterator1 first1, InputIterator2 first2, T init, BinaryOperation1 binary_op1, BinaryOperation2 binary_op2) { return init; } }; } // namespace detail /// static version of std::inner_product template < std::size_t Size, class InputIterator1, class InputIterator2, class T, class BinaryOperation1, class BinaryOperation2 > BOOST_FORCEINLINE T inner_product_k( InputIterator1 first1, InputIterator2 first2, T init, BinaryOperation1 binary_op1, BinaryOperation2 binary_op2) { return detail::inner_product_k_t::template apply( first1, first2, init, binary_op1, binary_op2); } /// \brief 1D un-guarded cross-correlation with a variable-size kernel template < typename PixelAccum, typename SrcIterator, typename KernelIterator, typename Size, typename DstIterator > inline auto correlate_pixels_n( SrcIterator src_begin, SrcIterator src_end, KernelIterator kernel_begin, Size kernel_size, DstIterator dst_begin) -> DstIterator { using src_pixel_ref_t = typename pixel_proxy < typename std::iterator_traits::value_type >::type; using dst_pixel_ref_t = typename pixel_proxy < typename std::iterator_traits::value_type >::type; using kernel_value_t = typename std::iterator_traits::value_type; PixelAccum accum_zero; pixel_zeros_t()(accum_zero); while (src_begin != src_end) { pixel_assigns_t()( std::inner_product( src_begin, src_begin + kernel_size, kernel_begin, accum_zero, pixel_plus_t(), pixel_multiplies_scalar_t()), *dst_begin); ++src_begin; ++dst_begin; } return dst_begin; } /// \brief 1D un-guarded cross-correlation with a fixed-size kernel template < std::size_t Size, typename PixelAccum, typename SrcIterator, typename KernelIterator, typename DstIterator > inline auto correlate_pixels_k( SrcIterator src_begin, SrcIterator src_end, KernelIterator kernel_begin, DstIterator dst_begin) -> DstIterator { using src_pixel_ref_t = typename pixel_proxy < typename std::iterator_traits::value_type >::type; using dst_pixel_ref_t = typename pixel_proxy < typename std::iterator_traits::value_type >::type; using kernel_type = typename std::iterator_traits::value_type; PixelAccum accum_zero; pixel_zeros_t()(accum_zero); while (src_begin != src_end) { pixel_assigns_t()( inner_product_k( src_begin, kernel_begin, accum_zero, pixel_plus_t(), pixel_multiplies_scalar_t()), *dst_begin); ++src_begin; ++dst_begin; } return dst_begin; } /// \brief destination is set to be product of the source and a scalar /// \tparam PixelAccum - TODO /// \tparam SrcView Models ImageViewConcept /// \tparam DstView Models MutableImageViewConcept template inline void view_multiplies_scalar(SrcView const& src_view, Scalar const& scalar, DstView const& dst_view) { static_assert(std::is_scalar::value, "Scalar is not scalar"); BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions()); using src_pixel_ref_t = typename pixel_proxy::type; using dst_pixel_ref_t = typename pixel_proxy::type; using y_coord_t = typename SrcView::y_coord_t; y_coord_t const height = src_view.height(); for (y_coord_t y = 0; y < height; ++y) { typename SrcView::x_iterator it_src = src_view.row_begin(y); typename DstView::x_iterator it_dst = dst_view.row_begin(y); typename SrcView::x_iterator it_src_end = src_view.row_end(y); while (it_src != it_src_end) { pixel_assigns_t()( pixel_multiplies_scalar_t()(*it_src, scalar), *it_dst); ++it_src; ++it_dst; } } } /// \ingroup ImageAlgorithms /// \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 }; namespace detail { template void extend_row_impl( SrcView const& src_view, RltView result_view, std::size_t extend_count, boundary_option option) { std::ptrdiff_t extend_count_ = static_cast(extend_count); if (option == boundary_option::extend_constant) { for (std::ptrdiff_t i = 0; i < result_view.height(); i++) { if(i >= extend_count_ && i < extend_count_ + src_view.height()) { assign_pixels( src_view.row_begin(i - extend_count_), src_view.row_end(i - extend_count_), result_view.row_begin(i) ); } else if(i < extend_count_) { assign_pixels(src_view.row_begin(0), src_view.row_end(0), result_view.row_begin(i)); } else { assign_pixels( src_view.row_begin(src_view.height() - 1), src_view.row_end(src_view.height() - 1), result_view.row_begin(i) ); } } } else if (option == boundary_option::extend_zero) { typename SrcView::value_type acc_zero; pixel_zeros_t()(acc_zero); for (std::ptrdiff_t i = 0; i < result_view.height(); i++) { if (i >= extend_count_ && i < extend_count_ + src_view.height()) { assign_pixels( src_view.row_begin(i - extend_count_), src_view.row_end(i - extend_count_), result_view.row_begin(i) ); } else { std::fill_n(result_view.row_begin(i), result_view.width(), acc_zero); } } } else if (option == boundary_option::extend_padded) { auto original_view = subimage_view( src_view, 0, -extend_count, src_view.width(), src_view.height() + (2 * extend_count) ); for (std::ptrdiff_t i = 0; i < result_view.height(); i++) { assign_pixels( original_view.row_begin(i), original_view.row_end(i), result_view.row_begin(i) ); } } else { BOOST_ASSERT_MSG(false, "Invalid boundary option"); } } } //namespace detail /// \brief adds new row at top and bottom. /// Image padding introduces new pixels around the edges of an image. /// The border provides space for annotations or acts as a boundary when using advanced filtering techniques. /// \tparam SrcView Models ImageViewConcept /// \tparam extend_count number of rows to be added each side /// \tparam option - TODO template auto extend_row( SrcView const& src_view, std::size_t extend_count, boundary_option option ) -> typename gil::image { typename gil::image result_img(src_view.width(), src_view.height() + (2 * extend_count)); auto result_view = view(result_img); detail::extend_row_impl(src_view, result_view, extend_count, option); return result_img; } /// \brief adds new column at left and right. /// Image padding introduces new pixels around the edges of an image. /// The border provides space for annotations or acts as a boundary when using advanced filtering techniques. /// \tparam SrcView Models ImageViewConcept /// \tparam extend_count number of columns to be added each side /// \tparam option - TODO template auto extend_col( SrcView const& src_view, std::size_t extend_count, boundary_option option ) -> typename gil::image { auto src_view_rotate = rotated90cw_view(src_view); typename gil::image result_img(src_view.width() + (2 * extend_count), src_view.height()); auto result_view = rotated90cw_view(view(result_img)); detail::extend_row_impl(src_view_rotate, result_view, extend_count, option); return result_img; } /// \brief adds new row and column at all sides. /// Image padding introduces new pixels around the edges of an image. /// The border provides space for annotations or acts as a boundary when using advanced filtering techniques. /// \tparam SrcView Models ImageViewConcept /// \tparam extend_count number of rows/column to be added each side /// \tparam option - TODO template auto extend_boundary( SrcView const& src_view, std::size_t extend_count, boundary_option option ) -> typename gil::image { if (option == boundary_option::extend_padded) { typename gil::image result_img(src_view.width()+(2 * extend_count), src_view.height()+(2 * extend_count)); typename gil::image::view_t result_view = view(result_img); auto original_view = subimage_view( src_view, -extend_count, -extend_count, src_view.width() + (2 * extend_count), src_view.height() + (2 * extend_count) ); for (std::ptrdiff_t i = 0; i < result_view.height(); i++) { assign_pixels( original_view.row_begin(i), original_view.row_end(i), result_view.row_begin(i) ); } return result_img; } auto auxilary_img = extend_col(src_view, extend_count, option); return extend_row(view(auxilary_img), extend_count, option); } } } // namespace boost::gil #endif