idfxx 1.0.0
Modern C++23 components for ESP-IDF
Loading...
Searching...
No Matches
gfx.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Chris Leishman
3
4#pragma once
5
38#include <idfxx/error.hpp>
39#include <idfxx/font.hpp>
40
41#include <algorithm>
42#include <concepts>
43#include <cstddef>
44#include <cstdint>
45#include <cstdlib>
46#include <string_view>
47#include <utility>
48
53namespace idfxx::gfx {
54
70template<typename S>
71concept pixel_surface = requires(S& s, const S& cs, size_t x, size_t y, typename S::pixel_type pixel) {
72 typename S::pixel_type;
73 { s.set_pixel(x, y, pixel) } noexcept;
74 { cs.width() } -> std::convertible_to<size_t>;
75 { cs.height() } -> std::convertible_to<size_t>;
76};
77
93template<pixel_surface Surface>
95 Surface& surface,
96 size_t x,
97 size_t y,
98 size_t width,
99 size_t height,
100 typename Surface::pixel_type ink
101) noexcept {
102 const size_t surface_width = surface.width();
103 const size_t surface_height = surface.height();
104 if (x >= surface_width || y >= surface_height) {
105 return;
106 }
107 const size_t x_end = x + std::min(width, surface_width - x);
108 const size_t y_end = y + std::min(height, surface_height - y);
109 for (size_t py = y; py < y_end; ++py) {
110 for (size_t px = x; px < x_end; ++px) {
111 surface.set_pixel(px, py, ink);
112 }
113 }
114}
115
129template<pixel_surface Surface>
130void draw_hline(Surface& surface, size_t x, size_t y, size_t length, typename Surface::pixel_type ink) noexcept {
131 fill_rect(surface, x, y, length, 1, ink);
132}
133
147template<pixel_surface Surface>
148void draw_vline(Surface& surface, size_t x, size_t y, size_t length, typename Surface::pixel_type ink) noexcept {
149 fill_rect(surface, x, y, 1, length, ink);
150}
151
167template<pixel_surface Surface>
169 Surface& surface,
170 size_t x,
171 size_t y,
172 size_t width,
173 size_t height,
174 typename Surface::pixel_type ink
175) noexcept {
176 if (width == 0 || height == 0) {
177 return;
178 }
179 if (width <= 2 || height <= 2) {
180 fill_rect(surface, x, y, width, height, ink);
181 return;
182 }
183 draw_hline(surface, x, y, width, ink);
184 draw_hline(surface, x, y + height - 1, width, ink);
185 draw_vline(surface, x, y + 1, height - 2, ink);
186 draw_vline(surface, x + width - 1, y + 1, height - 2, ink);
187}
188
203template<pixel_surface Surface>
205 Surface& surface,
206 size_t x0,
207 size_t y0,
208 size_t x1,
209 size_t y1,
210 typename Surface::pixel_type ink
211) noexcept {
212 if (std::min(x0, x1) >= surface.width() || std::min(y0, y1) >= surface.height()) {
213 return; // the endpoints' bounding box lies entirely off the surface
214 }
215 // Bresenham's algorithm; the walk stays within the endpoints' bounding
216 // box, so intermediate coordinates never go negative.
217 ptrdiff_t px = static_cast<ptrdiff_t>(x0);
218 ptrdiff_t py = static_cast<ptrdiff_t>(y0);
219 const ptrdiff_t ex = static_cast<ptrdiff_t>(x1);
220 const ptrdiff_t ey = static_cast<ptrdiff_t>(y1);
221 const ptrdiff_t dx = std::abs(ex - px);
222 const ptrdiff_t dy = -std::abs(ey - py);
223 const ptrdiff_t sx = px < ex ? 1 : -1;
224 const ptrdiff_t sy = py < ey ? 1 : -1;
225 ptrdiff_t err = dx + dy;
226 while (true) {
227 surface.set_pixel(static_cast<size_t>(px), static_cast<size_t>(py), ink);
228 if (px == ex && py == ey) {
229 break;
230 }
231 const ptrdiff_t e2 = 2 * err;
232 if (e2 >= dy) {
233 err += dy;
234 px += sx;
235 }
236 if (e2 <= dx) {
237 err += dx;
238 py += sy;
239 }
240 }
241}
242
264template<pixel_surface Surface>
266 Surface& surface,
267 const font::mono_font& font,
268 size_t x,
269 size_t y,
270 std::string_view text,
271 typename Surface::pixel_type ink,
272 unsigned scale = 1
273) noexcept {
274 if (scale == 0) {
275 scale = 1;
276 }
277 if (y >= surface.height()) {
278 return; // every glyph row would clip
279 }
280 const size_t bpr = font.bytes_per_row();
281 size_t cell_x = x;
282
283 for (char c : text) {
284 if (cell_x >= surface.width()) {
285 break; // rendering is left-to-right; nothing further can draw
286 }
287 if (font.contains(c)) {
288 const uint8_t* glyph = font.glyph(c);
289 for (size_t row = 0; row < font.height; ++row) {
290 const uint8_t* row_bits = glyph + row * bpr;
291 uint8_t bits = 0;
292 for (size_t col = 0; col < font.width; ++col, bits <<= 1) {
293 if (col % 8 == 0) {
294 bits = row_bits[col / 8];
295 }
296 if (!(bits & 0x80u)) {
297 continue;
298 }
299 const size_t px = cell_x + col * scale;
300 const size_t py = y + row * scale;
301 for (unsigned sy = 0; sy < scale; ++sy) {
302 for (unsigned sx = 0; sx < scale; ++sx) {
303 surface.set_pixel(px + sx, py + sy, ink);
304 }
305 }
306 }
307 }
308 }
309 cell_x += font.advance(c) * scale;
310 }
311}
312
327template<pixel_surface Surface>
328 requires std::same_as<typename Surface::pixel_type, bool>
329void draw_text(Surface& surface, const font::mono_font& font, size_t x, size_t y, std::string_view text) noexcept {
330 draw_text(surface, font, x, y, text, true);
331}
332
392template<pixel_surface Surface>
393class canvas {
394public:
396 using pixel_type = typename Surface::pixel_type;
397
407 explicit canvas(Surface& surface) noexcept
408 : _surface(&surface)
409 , _dx(0)
410 , _dy(0)
411 , _width(surface.width())
412 , _height(surface.height()) {}
413
429 canvas(Surface& surface, size_t x, size_t y) noexcept
430 : _surface(&surface)
431 , _dx(static_cast<ptrdiff_t>(x))
432 , _dy(static_cast<ptrdiff_t>(y))
433 , _width(x + surface.width())
434 , _height(y + surface.height()) {}
435
437 [[nodiscard]] Surface& surface() const noexcept { return *_surface; }
438
447 [[nodiscard]] size_t width() const noexcept { return _width; }
448
457 [[nodiscard]] size_t height() const noexcept { return _height; }
458
481 [[nodiscard]] canvas window(size_t x, size_t y, size_t width, size_t height) const noexcept {
482 canvas sub(*this);
483 sub._dx = _dx - static_cast<ptrdiff_t>(x);
484 sub._dy = _dy - static_cast<ptrdiff_t>(y);
485 sub._width = x < _width ? std::min(width, _width - x) : 0;
486 sub._height = y < _height ? std::min(height, _height - y) : 0;
487 return sub;
488 }
489
500 void set_pixel(size_t x, size_t y, pixel_type ink) noexcept {
501 if (x >= _width || y >= _height) {
502 return;
503 }
504 // Coordinates left of or above the surface wrap to huge values and
505 // are ignored by the surface's own bounds check.
506 _surface->set_pixel(
507 static_cast<size_t>(static_cast<ptrdiff_t>(x) - _dx),
508 static_cast<size_t>(static_cast<ptrdiff_t>(y) - _dy),
509 ink
510 );
511 }
512
523 void fill(pixel_type ink) noexcept {
524 if constexpr (requires(Surface& s, pixel_type i) {
525 { s.fill(i) } noexcept;
526 }) {
527 if (_covers_surface()) {
528 _surface->fill(ink);
529 return;
530 }
531 }
532 fill_rect(0, 0, _width, _height, ink);
533 }
534
545 void clear() noexcept {
546 if constexpr (requires(Surface& s) {
547 { s.clear() } noexcept;
548 }) {
549 if (_covers_surface()) {
550 _surface->clear();
551 return;
552 }
553 }
554 fill(pixel_type{});
555 }
556
573 template<typename... Args>
574 requires requires(Surface& s) { s.flush(std::declval<Args>()...); }
575 decltype(auto) flush(Args&&... args) const {
576 return _surface->flush(std::forward<Args>(args)...);
577 }
578
593 template<typename... Args>
594 requires requires(Surface& s) { s.try_flush(std::declval<Args>()...); }
595 [[nodiscard]] decltype(auto) try_flush(Args&&... args) const {
596 return _surface->try_flush(std::forward<Args>(args)...);
597 }
598
612 void fill_rect(size_t x, size_t y, size_t width, size_t height, pixel_type ink) noexcept {
613 // Clamp the start to the drawable region in canvas coordinates so
614 // the translated start is never left of or above the surface; the
615 // surface-level fill_rect clips the far edges.
616 const size_t sx = std::max(x, _dx > 0 ? static_cast<size_t>(_dx) : size_t{0});
617 const size_t sy = std::max(y, _dy > 0 ? static_cast<size_t>(_dy) : size_t{0});
618 if (sx >= _width || sy >= _height || sx - x >= width || sy - y >= height) {
619 return;
620 }
622 *_surface,
623 static_cast<size_t>(static_cast<ptrdiff_t>(sx) - _dx),
624 static_cast<size_t>(static_cast<ptrdiff_t>(sy) - _dy),
625 std::min(width - (sx - x), _width - sx),
626 std::min(height - (sy - y), _height - sy),
627 ink
628 );
629 }
630
642 void draw_hline(size_t x, size_t y, size_t length, pixel_type ink) noexcept { fill_rect(x, y, length, 1, ink); }
643
655 void draw_vline(size_t x, size_t y, size_t length, pixel_type ink) noexcept { fill_rect(x, y, 1, length, ink); }
656
670 void draw_rect(size_t x, size_t y, size_t width, size_t height, pixel_type ink) noexcept {
671 gfx::draw_rect(*this, x, y, width, height, ink);
672 }
673
686 void draw_line(size_t x0, size_t y0, size_t x1, size_t y1, pixel_type ink) noexcept {
687 gfx::draw_line(*this, x0, y0, x1, y1, ink);
688 }
689
710 const font::mono_font& font,
711 size_t x,
712 size_t y,
713 std::string_view text,
714 pixel_type ink,
715 unsigned scale = 1
716 ) noexcept {
717 gfx::draw_text(*this, font, x, y, text, ink, scale);
718 }
719
731 void draw_text(const font::mono_font& font, size_t x, size_t y, std::string_view text) noexcept
732 requires std::same_as<pixel_type, bool>
733 {
734 gfx::draw_text(*this, font, x, y, text);
735 }
736
737private:
738 // True when the drawable region spans the entire surface (the identity
739 // and whole-band cases), enabling the surface's native fill/clear.
740 [[nodiscard]] bool _covers_surface() const noexcept {
741 return _dx >= 0 && _dy >= 0 && static_cast<size_t>(_dx) + _surface->width() <= _width &&
742 static_cast<size_t>(_dy) + _surface->height() <= _height;
743 }
744
745 Surface* _surface;
746 ptrdiff_t _dx; // canvas-coordinate position of the surface's (0, 0)
747 ptrdiff_t _dy;
748 size_t _width; // canvas-coordinate clip bounds
749 size_t _height;
750};
751
758template<typename Surface, typename Dest, typename DrawFn>
759concept banded_renderable = pixel_surface<Surface> && requires(canvas<Surface>& c, Dest& dest, DrawFn& draw) {
760 draw(c);
761 { c.try_flush(dest, size_t{}, size_t{}) } -> std::same_as<result<void>>;
762};
766template<pixel_surface Surface, typename Dest, typename DrawFn>
767 requires banded_renderable<Surface, Dest, DrawFn>
768[[nodiscard]] result<void> try_render_banded(Surface& band, Dest& dest, size_t frame_height, DrawFn&& draw);
771#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
809template<pixel_surface Surface, typename Dest, typename DrawFn>
810 requires banded_renderable<Surface, Dest, DrawFn>
811void render_banded(Surface& band, Dest& dest, size_t frame_height, DrawFn&& draw) {
812 unwrap(try_render_banded(band, dest, frame_height, draw));
813}
814#endif
815
845template<pixel_surface Surface, typename Dest, typename DrawFn>
846 requires banded_renderable<Surface, Dest, DrawFn>
847[[nodiscard]] result<void> try_render_banded(Surface& band, Dest& dest, size_t frame_height, DrawFn&& draw) {
848 const size_t band_height = band.height();
849 if (band_height == 0 || frame_height == 0 || frame_height % band_height != 0) {
850 return error(errc::invalid_arg);
851 }
852 for (size_t y = 0; y < frame_height; y += band_height) {
853 canvas c(band, 0, y);
854 c.clear();
855 draw(c);
856 if (auto flushed = c.try_flush(dest, size_t{0}, y); !flushed) {
857 return flushed;
858 }
859 }
860 return {};
861}
862
// end of idfxx_gfx
864
865} // namespace idfxx::gfx
A drawing view bundling a pixel surface with the drawing primitives.
Definition gfx.hpp:393
canvas(Surface &surface) noexcept
Creates a canvas drawing on the given surface.
Definition gfx.hpp:407
void draw_text(const font::mono_font &font, size_t x, size_t y, std::string_view text, pixel_type ink, unsigned scale=1) noexcept
Draws text with the given ink.
Definition gfx.hpp:709
size_t height() const noexcept
Returns the height of the canvas's coordinate space, in pixels.
Definition gfx.hpp:457
decltype(auto) flush(Args &&... args) const
Pushes the surface's content onward, on surfaces that support it.
Definition gfx.hpp:575
canvas window(size_t x, size_t y, size_t width, size_t height) const noexcept
Returns a canvas for a sub-region of this one.
Definition gfx.hpp:481
void clear() noexcept
Sets every drawable pixel to the default value (equivalent to fill(pixel_type{})).
Definition gfx.hpp:545
void fill(pixel_type ink) noexcept
Sets every drawable pixel to the given ink.
Definition gfx.hpp:523
void draw_hline(size_t x, size_t y, size_t length, pixel_type ink) noexcept
Draws a horizontal line with the given ink.
Definition gfx.hpp:642
void draw_line(size_t x0, size_t y0, size_t x1, size_t y1, pixel_type ink) noexcept
Draws a straight line between two points with the given ink.
Definition gfx.hpp:686
canvas(Surface &surface, size_t x, size_t y) noexcept
Creates a canvas whose surface sits at (x, y) in the canvas's coordinates.
Definition gfx.hpp:429
void draw_rect(size_t x, size_t y, size_t width, size_t height, pixel_type ink) noexcept
Outlines a rectangle with the given ink.
Definition gfx.hpp:670
typename Surface::pixel_type pixel_type
The pixel value type of the underlying surface.
Definition gfx.hpp:396
size_t width() const noexcept
Returns the width of the canvas's coordinate space, in pixels.
Definition gfx.hpp:447
void draw_text(const font::mono_font &font, size_t x, size_t y, std::string_view text) noexcept
Draws text on a monochrome surface, setting glyph ink pixels.
Definition gfx.hpp:731
void fill_rect(size_t x, size_t y, size_t width, size_t height, pixel_type ink) noexcept
Fills a rectangle with the given ink.
Definition gfx.hpp:612
void set_pixel(size_t x, size_t y, pixel_type ink) noexcept
Sets a single pixel to the given ink.
Definition gfx.hpp:500
Surface & surface() const noexcept
Returns the underlying surface.
Definition gfx.hpp:437
decltype(auto) try_flush(Args &&... args) const
Pushes the surface's content onward, on surfaces that support it.
Definition gfx.hpp:595
void draw_vline(size_t x, size_t y, size_t length, pixel_type ink) noexcept
Draws a vertical line with the given ink.
Definition gfx.hpp:655
A drawable two-dimensional pixel surface.
Definition gfx.hpp:71
Bitmap font representation and text metrics.
result< void > try_render_banded(Surface &band, Dest &dest, size_t frame_height, DrawFn &&draw)
Renders a frame taller than its framebuffer, band by band.
Definition gfx.hpp:847
void render_banded(Surface &band, Dest &dest, size_t frame_height, DrawFn &&draw)
Renders a frame taller than its framebuffer, band by band.
Definition gfx.hpp:811
void draw_hline(Surface &surface, size_t x, size_t y, size_t length, typename Surface::pixel_type ink) noexcept
Draws a horizontal line with the given ink.
Definition gfx.hpp:130
void draw_text(Surface &surface, const font::mono_font &font, size_t x, size_t y, std::string_view text, typename Surface::pixel_type ink, unsigned scale=1) noexcept
Draws text with the given ink.
Definition gfx.hpp:265
void fill_rect(Surface &surface, size_t x, size_t y, size_t width, size_t height, typename Surface::pixel_type ink) noexcept
Fills a rectangle with the given ink.
Definition gfx.hpp:94
void draw_vline(Surface &surface, size_t x, size_t y, size_t length, typename Surface::pixel_type ink) noexcept
Draws a vertical line with the given ink.
Definition gfx.hpp:148
void draw_rect(Surface &surface, size_t x, size_t y, size_t width, size_t height, typename Surface::pixel_type ink) noexcept
Outlines a rectangle with the given ink.
Definition gfx.hpp:168
void draw_line(Surface &surface, size_t x0, size_t y0, size_t x1, size_t y1, typename Surface::pixel_type ink) noexcept
Draws a straight line between two points with the given ink.
Definition gfx.hpp:204
IDFXX error handling.
Drawing primitives and the pixel surface concept.
Definition gfx.hpp:53
constexpr std::unexpected< std::error_code > error(E e) noexcept
Creates an unexpected error from an error code enum.
Definition error.hpp:187
T unwrap(result< T > result)
Throws a std::system_error if the result is an error.
Definition error.hpp:307
@ invalid_arg
Invalid argument.
std::expected< T, std::error_code > result
result type wrapping a value or error code.
Definition error.hpp:120
A fixed-cell monochrome bitmap font.
Definition font.hpp:41
constexpr size_t advance(char) const noexcept
Returns the horizontal distance the cursor moves after c.
Definition font.hpp:90
constexpr const uint8_t * glyph(char c) const noexcept
Returns a pointer to the glyph bitmap for c.
Definition font.hpp:62
uint8_t width
Glyph cell width in pixels.
Definition font.hpp:42
constexpr size_t bytes_per_row() const noexcept
Returns the number of bytes in one glyph row.
Definition font.hpp:49
constexpr bool contains(char c) const noexcept
Returns true if the font has a glyph for c.
Definition font.hpp:55
uint8_t height
Glyph cell height in pixels.
Definition font.hpp:43