HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
pixmap.hpp
Go to the documentation of this file.
1// Copyright Take Vos 2023.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
9#pragma once
10
11#include "../utility/utility.hpp"
12#include "../macros.hpp"
13#include <cstddef>
14#include <memory>
15#include <span>
16
17hi_export_module(hikogui.image.pixmap);
18
19hi_warning_push();
20// C26439: This kind of function should not throw. Declare it 'noexcept' (f.6)
21// move assignment can throw because allocation may be needed due to proper allocator implementation.
22hi_warning_ignore_msvc(26439);
23// C26459: You called an STL function 'std::unitialized_move' with a raw pointer... (stl.1)
24// Writing iterators instead of using raw pointers will require a lot of code without any added safety.
25hi_warning_ignore_msvc(26459);
26
27hi_export namespace hi { inline namespace v1 {
28template<typename T>
29class pixmap_span;
30
37template<typename T, typename Allocator = std::allocator<T>>
38class pixmap {
39public:
42 using value_type = T;
43
46 using allocator_type = Allocator;
47
50 using size_type = size_t;
51 using pointer = value_type *;
52 using const_pointer = value_type const *;
53 using reference = value_type&;
54 using const_reference = value_type const&;
55 using interator = pointer;
56 using const_iterator = const_pointer;
57
60 using row_type = std::span<value_type>;
61
64 using const_row_type = std::span<value_type const>;
65
66 template<typename Pixmap>
67 struct row_iterator {
68 Pixmap *_ptr;
69 size_t _y;
70
71 // clang-format off
72 constexpr row_iterator(row_iterator const &) noexcept = default;
73 constexpr row_iterator(row_iterator &&) noexcept = default;
74 constexpr row_iterator &operator=(row_iterator const &) noexcept = default;
75 constexpr row_iterator &operator=(row_iterator &&) noexcept = default;
76 [[nodiscard]] constexpr row_iterator(Pixmap *ptr, size_t y) noexcept : _ptr(ptr), _y(y) {}
77 [[nodiscard]] constexpr friend bool operator==(row_iterator const &, row_iterator const &) noexcept = default;
78 constexpr row_iterator &operator++() noexcept { ++_y; return *this; }
79 constexpr row_iterator &operator++(int) noexcept { auto tmp = *this; ++_y; return tmp; }
80 constexpr row_iterator &operator--() noexcept { --_y; return *this; }
81 constexpr row_iterator &operator--(int) noexcept { auto tmp = *this; --_y; return tmp; }
82 [[nodiscard]] constexpr auto operator*() const noexcept { return (*_ptr)[_y]; }
83 // clang-format on
84 };
85
86 template<typename Pixmap>
87 struct row_range {
88 Pixmap *_ptr;
89
90 // clang-format off
91 constexpr row_range(row_range const &) noexcept = default;
92 constexpr row_range(row_range &&) noexcept = default;
93 constexpr row_range &operator=(row_range const &) noexcept = default;
94 constexpr row_range &operator=(row_range &&) noexcept = default;
95 [[nodiscard]] constexpr row_range(Pixmap *ptr) noexcept : _ptr(ptr) {}
96 [[nodiscard]] constexpr auto begin() const noexcept { return row_iterator{_ptr, 0_uz}; }
97 [[nodiscard]] constexpr auto end() const noexcept { return row_iterator{_ptr, _ptr->height()}; }
98 // clang-format on
99 };
100
101 ~pixmap()
102 {
103 std::destroy(this->begin(), this->end());
104 if (_data != nullptr) {
105 std::allocator_traits<allocator_type>::deallocate(_allocator, _data, _capacity);
106 }
107 }
108
118 constexpr pixmap(pixmap const& other) :
119 _capacity(other.size()),
120 _width(other._width),
121 _height(other._height),
122 _allocator(std::allocator_traits<allocator_type>::select_on_container_copy_construction(other._allocator))
123 {
124 _data = std::allocator_traits<allocator_type>::allocate(_allocator, _capacity);
125 std::uninitialized_copy(other.begin(), other.end(), this->begin());
126 }
127
134 constexpr pixmap(pixmap&& other) noexcept :
135 _data(std::exchange(other._data, nullptr)),
136 _capacity(std::exchange(other._capacity, 0)),
137 _width(std::exchange(other._width, 0)),
138 _height(std::exchange(other._height, 0)),
139 _allocator(std::exchange(other._allocator, {}))
140 {
141 }
142
150 constexpr pixmap& operator=(pixmap const& other)
151 {
153
154 auto const use_this_allocator = this->_allocator == other._allocator or not propogate_allocator;
155
156 if (&other == this) {
157 return *this;
158
159 } else if (this->_capacity >= other.size() and use_this_allocator) {
160 static_assert(std::is_nothrow_copy_constructible_v<value_type>);
161
162 // Reuse allocation.
163 clear();
164 _width = other._width;
165 _height = other._height;
166 std::uninitialized_copy(other.begin(), other.end(), this->begin());
167 return *this;
168
169 } else {
170 auto& new_allocator = propogate_allocator ? const_cast<allocator_type&>(other._allocator) : this->_allocator;
171 auto const new_capacity = other.size();
172
173 value_type *new_data = nullptr;
174 try {
175 new_data = std::allocator_traits<allocator_type>::allocate(new_allocator, other.size());
176 std::uninitialized_copy(other.begin(), other.end(), new_data);
177 } catch (...) {
178 std::allocator_traits<allocator_type>::deallocate(_allocator, new_data, new_capacity);
179 throw;
180 }
181
182 try {
183 clear();
184 shrink_to_fit();
185 } catch (...) {
186 std::destroy_n(new_data, new_capacity);
187 std::allocator_traits<allocator_type>::deallocate(_allocator, new_data, new_capacity);
188 throw;
189 }
190
191 _data = new_data;
192 _capacity = new_capacity;
193 _width = other._width;
194 _height = other._height;
195 _allocator = new_allocator;
196 return *this;
197 }
198 }
199
208 {
210
211 if (&other == this) {
212 return *this;
213
214 } else if (_allocator == other._allocator or propogate_allocator) {
215 clear();
216 shrink_to_fit();
217
218 _data = std::exchange(other._data, nullptr);
219 _capacity = std::exchange(other._capacity, 0);
220 _width = std::exchange(other._width, 0);
221 _height = std::exchange(other._height, 0);
222 _allocator = other._allocator;
223 return *this;
224
225 } else if (_capacity >= other.size()) {
226 // Reuse allocation.
227 clear();
228 _width = other._width;
229 _height = other._height;
230
231 std::uninitialized_move(other.begin(), other.end(), this->begin());
232
233 // Clear, but leave the allocation intact, so that it can be reused.
234 other.clear();
235 return *this;
236
237 } else {
238 auto const new_capacity = other.size();
239 value_type *new_data = nullptr;
240 try {
241 new_data = std::allocator_traits<allocator_type>::allocate(_allocator, new_capacity);
242 std::uninitialized_move(other.begin(), other.end(), new_data);
243 } catch (...) {
244 std::allocator_traits<allocator_type>::deallocate(_allocator, new_data, new_capacity);
245 throw;
246 }
247
248 try {
249 clear();
250 shrink_to_fit();
251 } catch (...) {
252 std::destroy_n(new_data, new_capacity);
253 std::allocator_traits<allocator_type>::deallocate(_allocator, new_data, new_capacity);
254 throw;
255 }
256
257 _data = new_data;
258 _capacity = new_capacity;
259 _width = other._width;
260 _height = other._height;
261 _data = std::allocator_traits<allocator_type>::allocate(_allocator, _capacity);
262
263 // Clear, but leave the allocation intact, so that it can be reused.
264 other.clear();
265 return *this;
266 }
267 }
268
269 [[nodiscard]] constexpr pixmap() noexcept = default;
270
273 [[nodiscard]] constexpr pixmap(size_type width, size_type height, allocator_type allocator = allocator_type{}) :
274 _data(std::allocator_traits<allocator_type>::allocate(allocator, width * height)),
275 _capacity(width * height),
276 _width(width),
277 _height(height),
278 _allocator(allocator)
279 {
280 std::uninitialized_value_construct(begin(), end());
281 }
282
283 template<std::convertible_to<value_type> O>
284 [[nodiscard]] constexpr pixmap(
285 O *hi_restrict data,
286 size_type width,
287 size_type height,
288 size_type stride,
289 allocator_type allocator = allocator_type{}) :
290 _data(std::allocator_traits<allocator_type>::allocate(allocator, width * height)),
291 _capacity(width * height),
292 _width(width),
293 _height(height),
294 _allocator(allocator)
295 {
296 if (width == stride) {
297 try {
298 std::uninitialized_copy(data, data + width * height, begin());
299 } catch (...) {
300 std::allocator_traits<allocator_type>::deallocate(_allocator, _data, _capacity);
301 throw;
302 }
303
304 } else {
305 auto src = data;
306 auto dst = begin();
307 auto dst_end = end();
308
309 try {
310 while (dst != dst_end) {
311 std::uninitialized_copy(src, src + width, dst);
312 dst += width;
313 src += stride;
314 }
315 } catch (...) {
316 std::destroy(begin(), dst);
317 std::allocator_traits<allocator_type>::deallocate(_allocator, _data, _capacity);
318 throw;
319 }
320 }
321 }
322
323 template<std::convertible_to<value_type> O>
324 [[nodiscard]] constexpr pixmap(
325 O *hi_restrict data,
326 size_type width,
327 size_type height,
328 allocator_type allocator = allocator_type{}) noexcept :
329 pixmap(data, width, height, width, allocator)
330 {
331 }
332
333 template<std::convertible_to<value_type> O>
334 [[nodiscard]] constexpr explicit pixmap(pixmap<O> const& other, allocator_type allocator = allocator_type{}) :
335 pixmap(other.data(), other.width(), other.height(), allocator)
336 {
337 }
338
339 template<std::convertible_to<value_type> O>
340 [[nodiscard]] constexpr explicit pixmap(pixmap_span<O> const& other, allocator_type allocator = allocator_type{}) :
341 pixmap(other.data(), other.width(), other.height(), other.stride(), allocator)
342 {
343 }
344
345 template<std::same_as<value_type const> O>
346 [[nodiscard]] constexpr operator pixmap_span<O>() const noexcept
347 {
348 return pixmap_span<O>{_data, _width, _height};
349 }
350
351 [[nodiscard]] constexpr friend bool operator==(pixmap const& lhs, pixmap const& rhs) noexcept
352 {
353 if (lhs._width != rhs._width or lhs._height != rhs._height) {
354 return false;
355 }
356 return std::equal(lhs.begin(), lhs.end(), rhs.begin());
357 }
358
359 [[nodiscard]] constexpr allocator_type get_allocator() const noexcept
360 {
361 return _allocator;
362 }
363
364 [[nodiscard]] constexpr size_type width() const noexcept
365 {
366 return _width;
367 }
368
369 [[nodiscard]] constexpr size_type height() const noexcept
370 {
371 return _height;
372 }
373
376 [[nodiscard]] constexpr size_type size() const noexcept
377 {
378 return _width * _height;
379 }
380
383 [[nodiscard]] constexpr size_type capacity() const noexcept
384 {
385 return _capacity;
386 }
387
388 [[nodiscard]] constexpr bool empty() const noexcept
389 {
390 return _width == 0 and _height == 0;
391 }
392
393 [[nodiscard]] constexpr pointer data() noexcept
394 {
395 return _data;
396 }
397
398 [[nodiscard]] constexpr const_pointer data() const noexcept
399 {
400 return _data;
401 }
402
403 [[nodiscard]] constexpr interator begin() noexcept
404 {
405 return _data;
406 }
407
408 [[nodiscard]] constexpr const_iterator begin() const noexcept
409 {
410 return _data;
411 }
412
413 [[nodiscard]] constexpr const_iterator cbegin() const noexcept
414 {
415 return _data;
416 }
417
418 [[nodiscard]] constexpr interator end() noexcept
419 {
420 return _data + size();
421 }
422
423 [[nodiscard]] constexpr const_iterator end() const noexcept
424 {
425 return _data + size();
426 }
427
428 [[nodiscard]] constexpr const_iterator cend() const noexcept
429 {
430 return _data + size();
431 }
432
433 constexpr reference operator()(size_type x, size_type y) noexcept
434 {
435 hi_axiom(x < _width);
436 hi_axiom(y < _height);
437 return _data[y * _width + x];
438 }
439
440 constexpr const_reference operator()(size_type x, size_type y) const noexcept
441 {
442 hi_axiom(x < _width);
443 hi_axiom(y < _height);
444 return _data[y * _width + x];
445 }
446
447 [[nodiscard]] constexpr row_type operator[](size_type y) noexcept
448 {
449 hi_axiom(y < _height);
450 return {_data + y * _width, _width};
451 }
452
453 [[nodiscard]] constexpr const_row_type operator[](size_type y) const noexcept
454 {
455 hi_axiom(y < height());
456 return {_data + y * _width, _width};
457 }
458
459 [[nodiscard]] constexpr auto rows() noexcept
460 {
461 return row_range{this};
462 }
463
464 [[nodiscard]] constexpr auto rows() const noexcept
465 {
466 return row_range{this};
467 }
468
469 [[nodiscard]] constexpr pixmap
470 subimage(size_type x, size_type y, size_type new_width, size_type new_height, allocator_type allocator) const noexcept
471 {
472 hi_axiom(x + new_width <= _width);
473 hi_axiom(y + new_height <= _height);
474
475 auto const p = _data + y * _width + x;
476 return {p, new_width, new_height, _width, allocator};
477 }
478
479 [[nodiscard]] constexpr pixmap subimage(size_type x, size_type y, size_type new_width, size_type new_height) const noexcept
480 {
481 return subimage(x, y, new_width, new_height, _allocator);
482 }
483
484 constexpr void clear() noexcept
485 {
486 std::destroy(begin(), end());
487 _width = 0;
488 _height = 0;
489 }
490
491 constexpr void shrink_to_fit()
492 {
493 if (empty()) {
494 if (_data != nullptr) {
495 std::allocator_traits<allocator_type>::deallocate(_allocator, _data, _capacity);
496 _data = nullptr;
497 _capacity = 0;
498 }
499 return;
500 }
501
502 auto const new_capacity = size();
503 value_type *new_data = nullptr;
504 try {
505 new_data = std::allocator_traits<allocator_type>::allocate(_allocator, new_capacity);
506 std::uninitialized_move(begin(), end(), new_data);
507 } catch (...) {
508 std::allocator_traits<allocator_type>::deallocate(_allocator, new_data, new_capacity);
509 throw;
510 }
511
512 std::destroy(begin(), end());
513 auto const old_capacity = std::exchange(_capacity, new_capacity);
514 auto const old_data = std::exchange(_data, new_data);
515 std::allocator_traits<allocator_type>::deallocate(_allocator, old_data, old_capacity);
516 }
517
518 constexpr friend void fill(pixmap &dst, value_type value = value_type{}) noexcept
519 {
520 std::fill(dst.begin(), dst.end(), value);
521 }
522
523private:
524 value_type *_data = nullptr;
525 size_type _capacity = 0;
526 size_type _width = 0;
527 size_type _height = 0;
528 [[no_unique_address]] allocator_type _allocator = {};
529};
530
531template<typename T>
532pixmap(pixmap_span<T> const& other) -> pixmap<std::remove_const_t<T>, std::allocator<std::remove_const_t<T>>>;
533
534template<typename T, typename Allocator>
535pixmap(pixmap_span<T> const& other, Allocator allocator) -> pixmap<std::remove_const_t<T>, Allocator>;
536
537}} // namespace hi::v1
538
539hi_warning_pop();
@ other
The gui_event does not have associated data.
STL namespace.
The HikoGUI namespace.
Definition array_generic.hpp:20
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
A 2D pixel-based image.
Definition pixmap.hpp:38
constexpr pixmap & operator=(pixmap &&other)
Move assignment.
Definition pixmap.hpp:207
T value_type
The pixel format type.
Definition pixmap.hpp:42
constexpr pixmap & operator=(pixmap const &other)
Copy assignment.
Definition pixmap.hpp:150
std::span< value_type const > const_row_type
The type for a row of pixels.
Definition pixmap.hpp:64
std::span< value_type > row_type
The type for a row of pixels.
Definition pixmap.hpp:60
constexpr pixmap(pixmap const &other)
Copy constructor.
Definition pixmap.hpp:118
size_t size_type
The size type.
Definition pixmap.hpp:50
constexpr size_type capacity() const noexcept
The number of pixels of capacity allocated.
Definition pixmap.hpp:383
constexpr size_type size() const noexcept
The number of pixels (width * height) in this image.
Definition pixmap.hpp:376
Allocator allocator_type
The allocator to use for allocating the array.
Definition pixmap.hpp:46
constexpr pixmap(pixmap &&other) noexcept
Move constructor.
Definition pixmap.hpp:134
Definition pixmap.hpp:67
Definition pixmap.hpp:87
T allocate(T... args)
T deallocate(T... args)
T equal(T... args)
T fill(T... args)
T uninitialized_copy(T... args)