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