1 //
2 // Copyright 2005-2007 Adobe Systems Incorporated
3 //
4 // Distributed under the Boost Software License, Version 1.0
5 // See accompanying file LICENSE_1_0.txt or copy at
6 // http://www.boost.org/LICENSE_1_0.txt
7 //
8 #ifndef BOOST_GIL_IMAGE_HPP
9 #define BOOST_GIL_IMAGE_HPP
10 
11 #include <boost/gil/algorithm.hpp>
12 #include <boost/gil/image_view.hpp>
13 #include <boost/gil/metafunctions.hpp>
14 #include <boost/gil/detail/mp11.hpp>
15 
16 #include <boost/assert.hpp>
17 #include <boost/core/exchange.hpp>
18 
19 #include <cstddef>
20 #include <memory>
21 #include <utility>
22 #include <type_traits>
23 
24 namespace boost { namespace gil {
25 
26 ////////////////////////////////////////////////////////////////////////////////////////
27 /// \ingroup ImageModel PixelBasedModel
28 /// \brief container interface over image view. Models ImageConcept, PixelBasedConcept
29 ///
30 /// A 2D container whose elements are pixels. It is templated over the pixel type, a boolean
31 /// indicating whether it should be planar, and an optional allocator.
32 ///
33 /// Note that its element type does not have to be a pixel. \p image can be instantiated with any Regular element,
34 /// in which case it models the weaker RandomAccess2DImageConcept and does not model PixelBasedConcept
35 ///
36 /// When recreating an image of the same or smaller size the memory will be reused if possible.
37 ///
38 ////////////////////////////////////////////////////////////////////////////////////////
39 
40 template< typename Pixel, bool IsPlanar = false, typename Alloc=std::allocator<unsigned char> >
41 class image
42 {
43 public:
44 #if defined(BOOST_NO_CXX11_ALLOCATOR)
45     using allocator_type = typename Alloc::template rebind<unsigned char>::other;
46 #else
47     using allocator_type = typename std::allocator_traits<Alloc>::template rebind_alloc<unsigned char>;
48 #endif
49     using view_t = typename view_type_from_pixel<Pixel, IsPlanar>::type;
50     using const_view_t = typename view_t::const_t;
51     using point_t = typename view_t::point_t;
52     using coord_t = typename view_t::coord_t;
53     using value_type = typename view_t::value_type;
54     using x_coord_t = coord_t;
55     using y_coord_t = coord_t;
56 
dimensions() const57     const point_t&          dimensions()            const { return _view.dimensions(); }
width() const58     x_coord_t               width()                 const { return _view.width(); }
height() const59     y_coord_t               height()                const { return _view.height(); }
60 
image(std::size_t alignment=0,const Alloc alloc_in=Alloc ())61     explicit image(std::size_t alignment=0,
62                    const Alloc alloc_in = Alloc()) :
63         _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in), _allocated_bytes( 0 ) {}
64 
65     // Create with dimensions and optional initial value and alignment
image(const point_t & dimensions,std::size_t alignment=0,const Alloc alloc_in=Alloc ())66     image(const point_t& dimensions,
67           std::size_t alignment=0,
68           const Alloc alloc_in = Alloc()) : _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in)
69                                           , _allocated_bytes( 0 )
70     {
71         allocate_and_default_construct(dimensions);
72     }
73 
image(x_coord_t width,y_coord_t height,std::size_t alignment=0,const Alloc alloc_in=Alloc ())74     image(x_coord_t width, y_coord_t height,
75           std::size_t alignment=0,
76           const Alloc alloc_in = Alloc()) : _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in)
77                                           , _allocated_bytes( 0 )
78     {
79         allocate_and_default_construct(point_t(width,height));
80     }
81 
image(const point_t & dimensions,const Pixel & p_in,std::size_t alignment=0,const Alloc alloc_in=Alloc ())82     image(const point_t& dimensions,
83           const Pixel& p_in,
84           std::size_t alignment = 0,
85           const Alloc alloc_in = Alloc())  : _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in)
86                                            , _allocated_bytes( 0 )
87     {
88         allocate_and_fill(dimensions, p_in);
89     }
90 
image(x_coord_t width,y_coord_t height,const Pixel & p_in,std::size_t alignment=0,const Alloc alloc_in=Alloc ())91     image(x_coord_t width, y_coord_t height,
92           const Pixel& p_in,
93           std::size_t alignment = 0,
94           const Alloc alloc_in = Alloc())  : _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in)
95                                            , _allocated_bytes ( 0 )
96     {
97         allocate_and_fill(point_t(width,height),p_in);
98     }
99 
image(const image & img)100     image(const image& img) : _memory(nullptr), _align_in_bytes(img._align_in_bytes), _alloc(img._alloc)
101                             , _allocated_bytes( img._allocated_bytes )
102     {
103         allocate_and_copy(img.dimensions(),img._view);
104     }
105 
106     template <typename P2, bool IP2, typename Alloc2>
image(const image<P2,IP2,Alloc2> & img)107     image(const image<P2,IP2,Alloc2>& img) : _memory(nullptr), _align_in_bytes(img._align_in_bytes), _alloc(img._alloc)
108                                            , _allocated_bytes( img._allocated_bytes )
109     {
110        allocate_and_copy(img.dimensions(),img._view);
111     }
112 
113     // TODO Optimization: use noexcept (requires _view to be nothrow copy constructible)
image(image && img)114     image(image&& img) :
115       _view(img._view),
116       _memory(img._memory),
117       _align_in_bytes(img._align_in_bytes),
118       _alloc(std::move(img._alloc)),
119       _allocated_bytes(img._allocated_bytes)
120     {
121         img._view = view_t();
122         img._memory = nullptr;
123         img._align_in_bytes = 0;
124         img._allocated_bytes = 0;
125     }
126 
operator =(const image & img)127     image& operator=(const image& img)
128     {
129         if (dimensions() == img.dimensions())
130             copy_pixels(img._view,_view);
131         else
132         {
133             image tmp(img);
134             swap(tmp);
135         }
136         return *this;
137     }
138 
139     template <typename Img>
operator =(const Img & img)140     image& operator=(const Img& img)
141     {
142         if (dimensions() == img.dimensions())
143             copy_pixels(img._view,_view);
144         else
145         {
146             image tmp(img);
147             swap(tmp);
148         }
149         return *this;
150     }
151 
152   private:
153       using propagate_allocators = std::true_type;
154       using no_propagate_allocators = std::false_type;
155 
156       template <class Alloc2>
157       using choose_pocma = typename std::conditional<
158           // TODO: Use std::allocator_traits<Allocator>::is_always_equal if available
159           std::is_empty<Alloc2>::value,
160           std::true_type,
161           typename std::allocator_traits<Alloc2>::propagate_on_container_move_assignment::type
162       >::type;
163 
exchange_memory(image & lhs,image & rhs)164       static void exchange_memory(image& lhs, image& rhs)
165       {
166           lhs._memory = boost::exchange(rhs._memory, nullptr);
167           lhs._align_in_bytes = boost::exchange(rhs._align_in_bytes, 0);
168           lhs._allocated_bytes = boost::exchange(rhs._allocated_bytes, 0);
169           lhs._view = boost::exchange(rhs._view, image::view_t{});
170       };
171 
move_assign(image & img,propagate_allocators)172       void move_assign(image& img, propagate_allocators) noexcept {
173           // non-sticky allocator, can adopt the memory, fast
174           destruct_pixels(_view);
175           this->deallocate();
176           this->_alloc = img._alloc;
177           exchange_memory(*this, img);
178       }
179 
move_assign(image & img,no_propagate_allocators)180       void move_assign(image& img, no_propagate_allocators) {
181           if (_alloc == img._alloc) {
182               // allocator stuck to the rhs, but it's equivalent of ours, we can still adopt the memory
183               destruct_pixels(_view);
184               this->deallocate();
185               exchange_memory(*this, img);
186           } else {
187               // cannot propagate the allocator and cannot adopt the memory
188               if (img._memory)
189               {
190                   allocate_and_copy(img.dimensions(), img._view);
191                   destruct_pixels(img._view);
192                   img.deallocate();
193                   img._view = image::view_t{};
194               }
195               else
196               {
197                   destruct_pixels(this->_view);
198                   this->deallocate();
199                   this->_view = view_t{};
200               }
201           }
202       }
203 
204   public:
205       // TODO: Use noexcept(noexcept(move_assign(img, choose_pocma<allocator_type>{})))
206       // But https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52869 prevents it (fixed in GCC > 9)
operator =(image && img)207       image& operator=(image&& img) {
208           if (this != std::addressof(img))
209               // Use rebinded alloc to choose pocma
210               move_assign(img, choose_pocma<allocator_type>{});
211 
212           return *this;
213       }
214 
~image()215     ~image()
216     {
217         destruct_pixels(_view);
218         deallocate();
219     }
220 
allocator()221     Alloc&       allocator() { return _alloc; }
allocator() const222     Alloc const& allocator() const { return _alloc; }
223 
swap(image & img)224     void swap(image& img) // required by MutableContainerConcept
225     {
226         using std::swap;
227         swap(_align_in_bytes,  img._align_in_bytes);
228         swap(_memory,          img._memory);
229         swap(_view,            img._view);
230         swap(_alloc,           img._alloc);
231         swap(_allocated_bytes, img._allocated_bytes );
232     }
233 
234     /////////////////////
235     // recreate
236     /////////////////////
237 
238     // without Allocator
recreate(const point_t & dims,std::size_t alignment=0)239     void recreate(const point_t& dims, std::size_t alignment = 0)
240     {
241         if (dims == _view.dimensions() && _align_in_bytes == alignment)
242             return;
243 
244         _align_in_bytes = alignment;
245 
246         if (_allocated_bytes >= total_allocated_size_in_bytes(dims))
247         {
248             destruct_pixels(_view);
249             create_view(dims, std::integral_constant<bool, IsPlanar>());
250             default_construct_pixels(_view);
251         }
252         else
253         {
254             image tmp(dims, alignment);
255             swap(tmp);
256         }
257     }
258 
recreate(x_coord_t width,y_coord_t height,std::size_t alignment=0)259     void recreate(x_coord_t width, y_coord_t height, std::size_t alignment = 0)
260     {
261         recreate(point_t(width, height), alignment);
262     }
263 
recreate(const point_t & dims,const Pixel & p_in,std::size_t alignment=0)264     void recreate(const point_t& dims, const Pixel& p_in, std::size_t alignment = 0)
265     {
266         if (dims == _view.dimensions() && _align_in_bytes == alignment)
267             return;
268 
269         _align_in_bytes = alignment;
270 
271         if (_allocated_bytes >= total_allocated_size_in_bytes(dims))
272         {
273             destruct_pixels(_view);
274             create_view(dims, typename std::integral_constant<bool, IsPlanar>());
275             uninitialized_fill_pixels(_view, p_in);
276         }
277         else
278         {
279             image tmp(dims, p_in, alignment);
280             swap(tmp);
281         }
282     }
283 
recreate(x_coord_t width,y_coord_t height,const Pixel & p_in,std::size_t alignment=0)284     void recreate( x_coord_t width, y_coord_t height, const Pixel& p_in, std::size_t alignment = 0 )
285     {
286         recreate( point_t( width, height ), p_in, alignment );
287     }
288 
289     // with Allocator
recreate(const point_t & dims,std::size_t alignment,const Alloc alloc_in)290     void recreate(const point_t& dims, std::size_t alignment, const Alloc alloc_in)
291     {
292         if (dims == _view.dimensions() && _align_in_bytes == alignment && alloc_in == _alloc)
293             return;
294 
295         _align_in_bytes = alignment;
296 
297         if (_allocated_bytes >= total_allocated_size_in_bytes(dims))
298         {
299             destruct_pixels(_view);
300             create_view(dims, std::integral_constant<bool, IsPlanar>());
301             default_construct_pixels(_view);
302         }
303         else
304         {
305             image tmp(dims, alignment, alloc_in);
306             swap(tmp);
307         }
308     }
309 
recreate(x_coord_t width,y_coord_t height,std::size_t alignment,const Alloc alloc_in)310     void recreate(x_coord_t width, y_coord_t height, std::size_t alignment, const Alloc alloc_in)
311     {
312         recreate(point_t(width, height), alignment, alloc_in);
313     }
314 
recreate(const point_t & dims,const Pixel & p_in,std::size_t alignment,const Alloc alloc_in)315     void recreate(const point_t& dims, const Pixel& p_in, std::size_t alignment, const Alloc alloc_in)
316     {
317         if (dims == _view.dimensions() && _align_in_bytes == alignment && alloc_in == _alloc)
318             return;
319 
320         _align_in_bytes = alignment;
321 
322         if (_allocated_bytes >= total_allocated_size_in_bytes(dims))
323         {
324             destruct_pixels(_view);
325             create_view(dims, std::integral_constant<bool, IsPlanar>());
326             uninitialized_fill_pixels(_view, p_in);
327         }
328         else
329         {
330             image tmp(dims, p_in, alignment, alloc_in);
331             swap(tmp);
332         }
333     }
334 
recreate(x_coord_t width,y_coord_t height,const Pixel & p_in,std::size_t alignment,const Alloc alloc_in)335     void recreate(x_coord_t width, y_coord_t height, const Pixel& p_in, std::size_t alignment, const Alloc alloc_in )
336     {
337         recreate(point_t(width, height), p_in, alignment, alloc_in);
338     }
339 
340     view_t       _view;      // contains pointer to the pixels, the image size and ways to navigate pixels
341 
342     // for construction from other type
343     template <typename P2, bool IP2, typename Alloc2> friend class image;
344 private:
345     unsigned char* _memory;
346     std::size_t    _align_in_bytes;
347     allocator_type _alloc;
348 
349     std::size_t _allocated_bytes;
350 
allocate_and_default_construct(point_t const & dimensions)351     void allocate_and_default_construct(point_t const& dimensions)
352     {
353         try
354         {
355             allocate_(dimensions, std::integral_constant<bool, IsPlanar>());
356             default_construct_pixels(_view);
357         }
358         catch (...) { deallocate(); throw; }
359     }
360 
allocate_and_fill(const point_t & dimensions,Pixel const & p_in)361     void allocate_and_fill(const point_t& dimensions, Pixel const& p_in)
362     {
363         try
364         {
365             allocate_(dimensions, std::integral_constant<bool, IsPlanar>());
366             uninitialized_fill_pixels(_view, p_in);
367         }
368         catch(...) { deallocate(); throw; }
369     }
370 
371     template <typename View>
allocate_and_copy(const point_t & dimensions,View const & v)372     void allocate_and_copy(const point_t& dimensions, View const& v)
373     {
374         try
375         {
376             allocate_(dimensions, std::integral_constant<bool, IsPlanar>());
377             uninitialized_copy_pixels(v, _view);
378         }
379         catch(...) { deallocate(); throw; }
380     }
381 
deallocate()382     void deallocate()
383     {
384         if (_memory && _allocated_bytes > 0)
385             _alloc.deallocate(_memory, _allocated_bytes);
386     }
387 
is_planar_impl(std::size_t const size_in_units,std::size_t const channels_in_image,std::true_type) const388     std::size_t is_planar_impl(
389         std::size_t const size_in_units,
390         std::size_t const channels_in_image,
391         std::true_type) const
392     {
393         return size_in_units * channels_in_image;
394     }
395 
is_planar_impl(std::size_t const size_in_units,std::size_t const,std::false_type) const396     std::size_t is_planar_impl(
397         std::size_t const size_in_units,
398         std::size_t const,
399         std::false_type) const
400     {
401         return size_in_units;
402     }
403 
total_allocated_size_in_bytes(point_t const & dimensions) const404     std::size_t total_allocated_size_in_bytes(point_t const& dimensions) const
405     {
406         using x_iterator = typename view_t::x_iterator;
407 
408         // when value_type is a non-pixel, like int or float, num_channels< ... > doesn't work.
409         constexpr std::size_t _channels_in_image =
410             std::conditional
411             <
412                 is_pixel<value_type>::value,
413                 num_channels<view_t>,
414                 std::integral_constant<std::size_t, 1>
415             >::type::value;
416 
417         std::size_t size_in_units = is_planar_impl(
418             get_row_size_in_memunits(dimensions.x) * dimensions.y,
419             _channels_in_image,
420             std::integral_constant<bool, IsPlanar>());
421 
422         // return the size rounded up to the nearest byte
423         return ( size_in_units + byte_to_memunit< x_iterator >::value - 1 )
424             / byte_to_memunit<x_iterator>::value
425             + ( _align_in_bytes > 0 ? _align_in_bytes - 1 : 0 ); // add extra padding in case we need to align the first image pixel
426     }
427 
get_row_size_in_memunits(x_coord_t width) const428     std::size_t get_row_size_in_memunits(x_coord_t width) const {   // number of units per row
429         std::size_t size_in_memunits = width*memunit_step(typename view_t::x_iterator());
430         if (_align_in_bytes>0) {
431             std::size_t alignment_in_memunits=_align_in_bytes*byte_to_memunit<typename view_t::x_iterator>::value;
432             return align(size_in_memunits, alignment_in_memunits);
433         }
434         return size_in_memunits;
435     }
436 
allocate_(point_t const & dimensions,std::false_type)437     void allocate_(point_t const& dimensions, std::false_type)
438     {
439         // if it throws and _memory!=0 the client must deallocate _memory
440         _allocated_bytes = total_allocated_size_in_bytes(dimensions);
441         _memory=_alloc.allocate( _allocated_bytes );
442 
443         unsigned char* tmp=(_align_in_bytes>0) ? (unsigned char*)align((std::size_t)_memory,_align_in_bytes) : _memory;
444         _view=view_t(dimensions,typename view_t::locator(typename view_t::x_iterator(tmp), get_row_size_in_memunits(dimensions.x)));
445 
446         BOOST_ASSERT(_view.width() == dimensions.x);
447         BOOST_ASSERT(_view.height() == dimensions.y);
448     }
449 
allocate_(point_t const & dimensions,std::true_type)450     void allocate_(point_t const& dimensions, std::true_type)
451     {
452         // if it throws and _memory!=0 the client must deallocate _memory
453         std::size_t row_size=get_row_size_in_memunits(dimensions.x);
454         std::size_t plane_size=row_size*dimensions.y;
455 
456         _allocated_bytes = total_allocated_size_in_bytes( dimensions );
457 
458         _memory = _alloc.allocate( _allocated_bytes );
459 
460         unsigned char* tmp=(_align_in_bytes>0) ? (unsigned char*)align((std::size_t)_memory,_align_in_bytes) : _memory;
461         typename view_t::x_iterator first;
462         for (std::size_t i = 0; i < num_channels<view_t>::value; ++i)
463         {
464             dynamic_at_c(first, i) = (typename channel_type<view_t>::type*)tmp;
465             memunit_advance(dynamic_at_c(first, i), static_cast<std::ptrdiff_t>(plane_size * i));
466         }
467         _view=view_t(dimensions, typename view_t::locator(first, row_size));
468 
469         BOOST_ASSERT(_view.width() == dimensions.x);
470         BOOST_ASSERT(_view.height() == dimensions.y);
471     }
472 
create_view(point_t const & dims,std::true_type)473     void create_view(point_t const& dims, std::true_type) // is planar
474     {
475         std::size_t row_size=get_row_size_in_memunits(dims.x);
476         std::size_t plane_size=row_size*dims.y;
477 
478         unsigned char* tmp = ( _align_in_bytes > 0 ) ? (unsigned char*) align( (std::size_t) _memory
479                                                                              ,_align_in_bytes
480                                                                              )
481                                                      : _memory;
482         typename view_t::x_iterator first;
483 
484         for (std::size_t i = 0; i < num_channels<view_t>::value; ++i)
485         {
486             dynamic_at_c(first, i) = (typename channel_type<view_t>::type*)tmp;
487             memunit_advance(dynamic_at_c(first, i), static_cast<std::ptrdiff_t>(plane_size * i));
488         }
489 
490         _view = view_t(dims, typename view_t::locator(first, row_size));
491 
492         BOOST_ASSERT(_view.width() == dims.x);
493         BOOST_ASSERT(_view.height() == dims.y);
494     }
495 
create_view(point_t const & dims,std::false_type)496     void create_view(point_t const& dims, std::false_type) // is planar
497     {
498         unsigned char* tmp = ( _align_in_bytes > 0 ) ? ( unsigned char* ) align( (std::size_t) _memory
499                                                                                , _align_in_bytes
500                                                                                )
501                                                      : _memory;
502 
503         _view = view_t( dims
504                       , typename view_t::locator( typename view_t::x_iterator( tmp )
505                                                 , get_row_size_in_memunits( dims.x )
506                                                 )
507                       );
508 
509         BOOST_ASSERT(_view.width() == dims.x);
510         BOOST_ASSERT(_view.height() == dims.y);
511     }
512 };
513 
514 template <typename Pixel, bool IsPlanar, typename Alloc>
swap(image<Pixel,IsPlanar,Alloc> & im1,image<Pixel,IsPlanar,Alloc> & im2)515 void swap(image<Pixel, IsPlanar, Alloc>& im1,image<Pixel, IsPlanar, Alloc>& im2)
516 {
517     im1.swap(im2);
518 }
519 
520 template <typename Pixel1, bool IsPlanar1, typename Alloc1, typename Pixel2, bool IsPlanar2, typename Alloc2>
operator ==(const image<Pixel1,IsPlanar1,Alloc1> & im1,const image<Pixel2,IsPlanar2,Alloc2> & im2)521 bool operator==(const image<Pixel1,IsPlanar1,Alloc1>& im1,const image<Pixel2,IsPlanar2,Alloc2>& im2)
522 {
523     if ((void*)(&im1)==(void*)(&im2)) return true;
524     if (const_view(im1).dimensions()!=const_view(im2).dimensions()) return false;
525     return equal_pixels(const_view(im1),const_view(im2));
526 }
527 template <typename Pixel1, bool IsPlanar1, typename Alloc1, typename Pixel2, bool IsPlanar2, typename Alloc2>
operator !=(const image<Pixel1,IsPlanar1,Alloc1> & im1,const image<Pixel2,IsPlanar2,Alloc2> & im2)528 bool operator!=(const image<Pixel1,IsPlanar1,Alloc1>& im1,const image<Pixel2,IsPlanar2,Alloc2>& im2) {return !(im1==im2);}
529 
530 ///@{
531 /// \name view, const_view
532 /// \brief Get an image view from an image
533 
534 /// \ingroup ImageModel
535 
536 /// \brief Returns the non-constant-pixel view of an image
537 template <typename Pixel, bool IsPlanar, typename Alloc> inline
view(image<Pixel,IsPlanar,Alloc> & img)538 const typename image<Pixel,IsPlanar,Alloc>::view_t& view(image<Pixel,IsPlanar,Alloc>& img) { return img._view; }
539 
540 /// \brief Returns the constant-pixel view of an image
541 template <typename Pixel, bool IsPlanar, typename Alloc> inline
const_view(const image<Pixel,IsPlanar,Alloc> & img)542 const typename image<Pixel,IsPlanar,Alloc>::const_view_t const_view(const image<Pixel,IsPlanar,Alloc>& img)
543 {
544     return static_cast<const typename image<Pixel,IsPlanar,Alloc>::const_view_t>(img._view);
545 }
546 ///@}
547 
548 /////////////////////////////
549 //  PixelBasedConcept
550 /////////////////////////////
551 
552 template <typename Pixel, bool IsPlanar, typename Alloc>
553 struct channel_type<image<Pixel, IsPlanar, Alloc>> : channel_type<Pixel> {};
554 
555 template <typename Pixel, bool IsPlanar, typename Alloc>
556 struct color_space_type<image<Pixel, IsPlanar, Alloc>> : color_space_type<Pixel> {};
557 
558 template <typename Pixel, bool IsPlanar, typename Alloc>
559 struct channel_mapping_type<image<Pixel, IsPlanar, Alloc>> : channel_mapping_type<Pixel> {};
560 
561 template <typename Pixel, bool IsPlanar, typename Alloc>
562 struct is_planar<image<Pixel, IsPlanar, Alloc>> : std::integral_constant<bool, IsPlanar> {};
563 
564 }}  // namespace boost::gil
565 
566 #endif
567