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