libepaper 2.0.0
A C++23 library for controlling Waveshare e-paper displays on Raspberry Pi, featuring transparent sleep/wake management and a fluent builder API.
Loading...
Searching...
No Matches
graphics.hpp
Go to the documentation of this file.
1#pragma once
2
7#include <cmath>
8#include <cstddef>
9#include <string_view>
10
11namespace epaper {
12
66class Graphics {
67public:
79 template <FramebufferLike FB>
80 static auto draw_line(FB &fb, Point start, Point end, LineStyle style, Color color, Orientation orientation) -> void;
81
94 template <FramebufferLike FB>
95 static auto draw_rectangle(FB &fb, Point top_left, Point bottom_right, LineStyle style, Color color, DrawFill fill,
96 Orientation orientation) -> void;
97
110 template <FramebufferLike FB>
111 static auto draw_circle(FB &fb, Point center, std::size_t radius, LineStyle style, Color color, DrawFill fill,
112 Orientation orientation) -> void;
113
126 template <FramebufferLike FB>
127 static auto draw_text(FB &fb, Point pos, std::string_view text, const Font &font, Color foreground, Color background,
128 Orientation orientation) -> void;
129
143 template <FramebufferLike FB>
144 static auto draw_bitmap(FB &fb, Point pos, std::span<const std::uint8_t> data, std::size_t w, std::size_t h,
145 std::size_t target_w, std::size_t target_h, Orientation orientation) -> void;
146};
147
148// ========== Template Implementations ==========
149
170template <FramebufferLike FB>
171auto Graphics::draw_line(FB &fb, Point start, Point end, LineStyle style, Color color, Orientation orientation)
172 -> void {
173 // Initialize Bresenham variables
174 int x0 = static_cast<int>(start.x);
175 int y0 = static_cast<int>(start.y);
176 int x1 = static_cast<int>(end.x);
177 int y1 = static_cast<int>(end.y);
178
179 int dx = std::abs(x1 - x0); // Absolute delta X
180 int dy = std::abs(y1 - y0); // Absolute delta Y
181 int sx = (x0 < x1) ? 1 : -1; // Step direction X
182 int sy = (y0 < y1) ? 1 : -1; // Step direction Y
183 int err = dx - dy; // Error accumulator
184
185 int step_count = 0;
186 while (true) {
187 // Draw pixel only if solid line, or if dotted and on even step
188 if (style == LineStyle::Solid || (step_count % 2 == 0)) {
189 fb.set_pixel(static_cast<std::size_t>(x0), static_cast<std::size_t>(y0), color, orientation);
190 }
191
192 // Reached endpoint - line complete
193 if (x0 == x1 && y0 == y1) {
194 break;
195 }
196
197 // Bresenham error adjustment and step
198 int e2 = 2 * err;
199 if (e2 > -dy) {
200 err -= dy;
201 x0 += sx; // Step in X direction
202 }
203 if (e2 < dx) {
204 err += dx;
205 y0 += sy; // Step in Y direction
206 }
207 ++step_count; // Track step for dotted pattern
208 }
209}
210
225template <FramebufferLike FB>
226auto Graphics::draw_rectangle(FB &fb, Point top_left, Point bottom_right, LineStyle style, Color color, DrawFill fill,
227 Orientation orientation) -> void {
228 // Draw four edges
229 draw_line(fb, top_left, {bottom_right.x, top_left.y}, style, color, orientation); // Top edge
230 draw_line(fb, {bottom_right.x, top_left.y}, bottom_right, style, color, orientation); // Right edge
231 draw_line(fb, bottom_right, {top_left.x, bottom_right.y}, style, color, orientation); // Bottom edge
232 draw_line(fb, {top_left.x, bottom_right.y}, top_left, style, color, orientation); // Left edge
233
234 // Fill interior with horizontal scan lines (skips edge pixels to avoid overdraw)
235 if (fill == DrawFill::Full) {
236 for (std::size_t y = top_left.y + 1; y < bottom_right.y; ++y) {
237 draw_line(fb, {top_left.x + 1, y}, {bottom_right.x - 1, y}, style, color, orientation);
238 }
239 }
240}
241
264template <FramebufferLike FB>
265auto Graphics::draw_circle(FB &fb, Point center, std::size_t radius, LineStyle /*style*/, Color color, DrawFill fill,
266 Orientation orientation) -> void {
267 int x = static_cast<int>(radius);
268 int y = 0;
269 int err = 0; // Error accumulator for midpoint decision
270
271 // Lambda to plot all 8 octants using symmetry
272 // Given (x,y) relative to center, plot (±x,±y) and (±y,±x)
273 auto plot_points = [&](int cx, int cy, int px, int py) {
274 // Quadrant I and II (y-axis symmetry)
275 fb.set_pixel(static_cast<std::size_t>(cx + px), static_cast<std::size_t>(cy + py), color, orientation);
276 fb.set_pixel(static_cast<std::size_t>(cx - px), static_cast<std::size_t>(cy + py), color, orientation);
277 // Quadrant III and IV (x-axis symmetry)
278 fb.set_pixel(static_cast<std::size_t>(cx + px), static_cast<std::size_t>(cy - py), color, orientation);
279 fb.set_pixel(static_cast<std::size_t>(cx - px), static_cast<std::size_t>(cy - py), color, orientation);
280 // 45-degree rotated points (octant reflection)
281 fb.set_pixel(static_cast<std::size_t>(cx + py), static_cast<std::size_t>(cy + px), color, orientation);
282 fb.set_pixel(static_cast<std::size_t>(cx - py), static_cast<std::size_t>(cy + px), color, orientation);
283 fb.set_pixel(static_cast<std::size_t>(cx + py), static_cast<std::size_t>(cy - px), color, orientation);
284 fb.set_pixel(static_cast<std::size_t>(cx - py), static_cast<std::size_t>(cy - px), color, orientation);
285 };
286
287 // Lambda to fill horizontal scan lines for solid circle
288 // For each Y level (py), draw horizontal line from -px to +px
289 // Also fill the 90-degree rotated spans (using py as X range)
290 auto fill_horizontal_line = [&](int cx, int cy, int px, int py) {
291 if (fill == DrawFill::Full) {
292 // Fill horizontal spans at y = ±py
293 // Range: x ∈ [-px, +px] (covers full width at this Y level)
294 for (int fx = -px; fx <= px; ++fx) {
295 fb.set_pixel(static_cast<std::size_t>(cx + fx), static_cast<std::size_t>(cy + py), color, orientation);
296 fb.set_pixel(static_cast<std::size_t>(cx + fx), static_cast<std::size_t>(cy - py), color, orientation);
297 }
298 // Fill horizontal spans at y = ±px (rotated 90 degrees)
299 // Range: x ∈ [-py, +py] (covers narrower width at steeper Y)
300 for (int fx = -py; fx <= py; ++fx) {
301 fb.set_pixel(static_cast<std::size_t>(cx + fx), static_cast<std::size_t>(cy + px), color, orientation);
302 fb.set_pixel(static_cast<std::size_t>(cx + fx), static_cast<std::size_t>(cy - px), color, orientation);
303 }
304 }
305 };
306
307 // Midpoint circle algorithm main loop
308 // Start at (radius, 0) and walk counterclockwise to 45° line (x == y)
309 // Only compute one octant; symmetry gives remaining 7 octants
310 while (x >= y) {
311 plot_points(static_cast<int>(center.x), static_cast<int>(center.y), x, y);
312 fill_horizontal_line(static_cast<int>(center.x), static_cast<int>(center.y), x, y);
313
314 // Decision parameter for next pixel
315 // err represents distance from ideal circle: err ≈ x² + y² - r²
316 if (err <= 0) {
317 y += 1; // Move up (increasing Y)
318 err += 2 * y + 1; // Update error: Δerr = (y+1)² - y² = 2y + 1
319 }
320 if (err > 0) {
321 x -= 1; // Move left (decreasing X)
322 err -= 2 * x + 1; // Update error: Δerr = x² - (x-1)² = 2x - 1
323 }
324 }
325}
326
327template <FramebufferLike FB>
328auto Graphics::draw_text(FB &fb, Point pos, std::string_view text, const Font &font, Color foreground, Color background,
329 Orientation orientation) -> void {
330 std::size_t cursor_x = pos.x; // Current X position for next character
331 std::size_t cursor_y = pos.y; // Baseline Y position
332
333 // Font metrics define character cell dimensions
334 const auto &metrics = font.metrics();
335 const auto font_width = metrics.width; // Character width in pixels
336 const auto font_height = metrics.height; // Character height in pixels
337
338 // Calculate bytes per row for character bitmap (MSB-first packing)
339 // Example: 12-pixel width needs 2 bytes (12/8 rounded up)
340 const auto width_bytes = (font_width % 8 == 0) ? (font_width / 8) : ((font_width / 8) + 1);
341
342 // Render each character sequentially
343 for (char c : text) {
344 // Fetch character bitmap (may be empty for unsupported chars)
345 auto bitmap = font.char_data(c);
346 if (bitmap.empty()) {
347 continue; // Skip unsupported characters
348 }
349
350 // Rasterize character bitmap row-by-row, pixel-by-pixel
351 for (std::size_t j = 0; j < font_height; ++j) {
352 for (std::size_t i = 0; i < font_width; ++i) {
353 // Calculate byte index for MSB-first bitmap layout
354 // Bitmap format: [row0_byte0, row0_byte1, ..., row1_byte0, ...]
355 std::size_t byte_idx = (j * width_bytes) + (i / 8);
356 if (byte_idx >= bitmap.size()) {
357 break; // Malformed font data - abort this character
358 }
359
360 // Extract bit from bitmap byte (MSB-first: bit 7 = leftmost pixel)
361 std::uint8_t byte = bitmap[byte_idx];
362 bool is_set = (byte & (0x80 >> (i % 8))) != 0;
363
364 // Map bitmap bit to foreground/background color
365 // 1 = foreground (ink), 0 = background (paper)
366 Color pixel_color = is_set ? foreground : background;
367 fb.set_pixel(cursor_x + i, cursor_y + j, pixel_color, orientation);
368 }
369 }
370
371 cursor_x += font_width; // Advance to next character cell
372 }
373}
374
375template <FramebufferLike FB>
376auto Graphics::draw_bitmap(FB &fb, Point pos, std::span<const std::uint8_t> data, std::size_t w, std::size_t h,
377 std::size_t target_w, std::size_t target_h, Orientation orientation) -> void {
378 // Determine target dimensions (default to source size if not specified)
379 std::size_t tw = (target_w > 0) ? target_w : w;
380 std::size_t th = (target_h > 0) ? target_h : h;
381
382 // Calculate scaling factors (nearest-neighbor sampling)
383 // scale_x/y represent source pixels per target pixel
384 // Example: source=200px, target=100px → scale=2.0 (sample every 2nd pixel)
385 float scale_x = static_cast<float>(w) / static_cast<float>(tw);
386 float scale_y = static_cast<float>(h) / static_cast<float>(th);
387
388 // Iterate over target (output) dimensions
389 for (std::size_t y = 0; y < th; ++y) {
390 for (std::size_t x = 0; x < tw; ++x) {
391 // Nearest-neighbor resampling: map target coordinate to source coordinate
392 // Truncates to nearest integer (no interpolation)
393 auto src_x = static_cast<std::size_t>(static_cast<float>(x) * scale_x);
394 auto src_y = static_cast<std::size_t>(static_cast<float>(y) * scale_y);
395
396 // Calculate linear index in source bitmap (row-major layout)
397 std::size_t idx = (src_y * w) + src_x;
398
399 if (idx < data.size()) {
400 // Simple binary threshold: 0=Black, non-zero=White
401 // TODO: Support grayscale/color bitmaps via ColorManager
402 Color color = (data[idx] == 0) ? Color::Black : Color::White;
403 fb.set_pixel(pos.x + x, pos.y + y, color, orientation);
404 }
405 }
406 }
407}
408
409} // namespace epaper
Definition font.hpp:142
Definition graphics.hpp:66
static auto draw_line(FB &fb, Point start, Point end, LineStyle style, Color color, Orientation orientation) -> void
Draw a line on the framebuffer.
Definition graphics.hpp:171
static auto draw_circle(FB &fb, Point center, std::size_t radius, LineStyle style, Color color, DrawFill fill, Orientation orientation) -> void
Draw a circle on the framebuffer.
Definition graphics.hpp:265
static auto draw_rectangle(FB &fb, Point top_left, Point bottom_right, LineStyle style, Color color, DrawFill fill, Orientation orientation) -> void
Draw a rectangle on the framebuffer.
Definition graphics.hpp:226
static auto draw_bitmap(FB &fb, Point pos, std::span< const std::uint8_t > data, std::size_t w, std::size_t h, std::size_t target_w, std::size_t target_h, Orientation orientation) -> void
Draw a bitmap on the framebuffer.
Definition graphics.hpp:376
static auto draw_text(FB &fb, Point pos, std::string_view text, const Font &font, Color foreground, Color background, Orientation orientation) -> void
Draw text string on the framebuffer.
Definition graphics.hpp:328
Bitmap font rendering for e-paper displays.
Definition color.hpp:5
DrawFill
Definition types.hpp:151
@ Full
Draw filled shape (solid interior)
Orientation
Definition types.hpp:66
Color
Definition types.hpp:32
@ White
White (or lightest gray in grayscale modes)
@ Black
Black (or darkest gray in grayscale modes)
LineStyle
Definition types.hpp:120
@ Solid
Continuous solid line.
Definition geometry.hpp:37