First version with generated code

This commit is contained in:
2026-04-03 16:13:07 -03:00
parent c0d83963e2
commit be4abb9289
7 changed files with 1295 additions and 10 deletions

View File

@@ -38,13 +38,29 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON) # This enables GNU extensions (gnu++23 instead of c++23)
# Add compile options
add_compile_options(-Wall -Wextra -pedantic)
add_compile_options(-fno-exceptions -fno-rtti -Wall -Wextra -Wpedantic)
# Build with debug symbols by default
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
# --------------------------------------------------------------------------
# Graphics library (platform-independent core)
# --------------------------------------------------------------------------
add_library(gfx_core STATIC
gfx_canvas.cpp
)
target_include_directories(gfx_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
# --------------------------------------------------------------------------
# Linux framebuffer HAL
# --------------------------------------------------------------------------
add_library(gfx_linux_fb STATIC
linux_fb_display.cpp
)
target_link_libraries(gfx_linux_fb PUBLIC gfx_core)
# Use project name for executable
add_executable(${PROJECT_NAME}
main.cpp
@@ -57,8 +73,7 @@ add_executable(${PROJECT_NAME}
# another_file.cpp
# )
# If you need to link libraries (example: pthread)
# target_link_libraries(${PROJECT_NAME} PRIVATE pthread)
target_link_libraries(${PROJECT_NAME} PRIVATE gfx_linux_fb)
# Install rule (optional)
install(TARGETS ${PROJECT_NAME} DESTINATION bin)

574
gfx_canvas.cpp Normal file
View File

@@ -0,0 +1,574 @@
// gfx_canvas.cpp — Drawing algorithm implementations.
#include "gfx_canvas.h"
#include <cstring>
namespace gfx {
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
static void swap16(int16_t& a, int16_t& b) {
int16_t t = a; a = b; b = t;
}
static int16_t abs16(int16_t v) {
return v < 0 ? -v : v;
}
static int16_t min16(int16_t a, int16_t b) { return a < b ? a : b; }
static int16_t max16(int16_t a, int16_t b) { return a > b ? a : b; }
// ---------------------------------------------------------------------------
// Construction
// ---------------------------------------------------------------------------
GfxCanvas::GfxCanvas(int16_t width, int16_t height)
: raw_width_(width), raw_height_(height) {}
// ---------------------------------------------------------------------------
// Geometry (accounts for rotation)
// ---------------------------------------------------------------------------
int16_t GfxCanvas::width() const {
return (rotation_ == Rotation::deg_90 || rotation_ == Rotation::deg_270)
? raw_height_ : raw_width_;
}
int16_t GfxCanvas::height() const {
return (rotation_ == Rotation::deg_90 || rotation_ == Rotation::deg_270)
? raw_width_ : raw_height_;
}
void GfxCanvas::setRotation(Rotation r) { rotation_ = r; }
// ---------------------------------------------------------------------------
// drawPixel — bounds check + rotation → drawPixelImpl
// ---------------------------------------------------------------------------
void GfxCanvas::drawPixel(int16_t x, int16_t y, Color color) {
if (x < 0 || x >= width() || y < 0 || y >= height()) return;
int16_t px = x, py = y;
switch (rotation_) {
case Rotation::deg_0: break;
case Rotation::deg_90: px = raw_height_ - 1 - y; py = x; break;
case Rotation::deg_180: px = raw_width_ - 1 - x; py = raw_height_ - 1 - y; break;
case Rotation::deg_270: px = y; py = raw_width_ - 1 - x; break;
}
drawPixelImpl(px, py, color);
}
// ---------------------------------------------------------------------------
// Horizontal line — clip + rotation → physical HLine or VLine
// ---------------------------------------------------------------------------
void GfxCanvas::drawHLine(int16_t x, int16_t y, int16_t w, Color color) {
if (w <= 0 || y < 0 || y >= height()) return;
if (x < 0) { w += x; x = 0; }
if (x + w > width()) w = width() - x;
if (w <= 0) return;
switch (rotation_) {
case Rotation::deg_0:
drawHLineImpl(x, y, w, color);
break;
case Rotation::deg_90:
drawVLineImpl(raw_height_ - 1 - y, x, w, color);
break;
case Rotation::deg_180:
drawHLineImpl(raw_width_ - x - w, raw_height_ - 1 - y, w, color);
break;
case Rotation::deg_270:
drawVLineImpl(y, raw_width_ - x - w, w, color);
break;
}
}
// ---------------------------------------------------------------------------
// Vertical line — clip + rotation → physical VLine or HLine
// ---------------------------------------------------------------------------
void GfxCanvas::drawVLine(int16_t x, int16_t y, int16_t h, Color color) {
if (h <= 0 || x < 0 || x >= width()) return;
if (y < 0) { h += y; y = 0; }
if (y + h > height()) h = height() - y;
if (h <= 0) return;
switch (rotation_) {
case Rotation::deg_0:
drawVLineImpl(x, y, h, color);
break;
case Rotation::deg_90:
drawHLineImpl(raw_height_ - y - h, x, h, color);
break;
case Rotation::deg_180:
drawVLineImpl(raw_width_ - 1 - x, raw_height_ - y - h, h, color);
break;
case Rotation::deg_270:
drawHLineImpl(y, raw_width_ - 1 - x, h, color);
break;
}
}
// ---------------------------------------------------------------------------
// Arbitrary line — Bresenham
// ---------------------------------------------------------------------------
void GfxCanvas::drawLine(int16_t x0, int16_t y0,
int16_t x1, int16_t y1, Color color) {
if (x0 == x1) {
if (y0 > y1) swap16(y0, y1);
drawVLine(x0, y0, y1 - y0 + 1, color);
return;
}
if (y0 == y1) {
if (x0 > x1) swap16(x0, x1);
drawHLine(x0, y0, x1 - x0 + 1, color);
return;
}
bool steep = abs16(y1 - y0) > abs16(x1 - x0);
if (steep) { swap16(x0, y0); swap16(x1, y1); }
if (x0 > x1) { swap16(x0, x1); swap16(y0, y1); }
int16_t dx = x1 - x0;
int16_t dy = abs16(y1 - y0);
int16_t err = dx / 2;
int16_t ystep = (y0 < y1) ? 1 : -1;
int16_t y = y0;
for (int16_t x = x0; x <= x1; ++x) {
if (steep) drawPixel(y, x, color);
else drawPixel(x, y, color);
err -= dy;
if (err < 0) {
y += ystep;
err += dx;
}
}
}
// ---------------------------------------------------------------------------
// Rectangles
// ---------------------------------------------------------------------------
void GfxCanvas::drawRect(int16_t x, int16_t y,
int16_t w, int16_t h, Color color) {
if (w <= 0 || h <= 0) return;
drawHLine(x, y, w, color);
drawHLine(x, y + h - 1, w, color);
drawVLine(x, y + 1, h - 2, color);
drawVLine(x + w - 1, y + 1, h - 2, color);
}
void GfxCanvas::fillRect(int16_t x, int16_t y,
int16_t w, int16_t h, Color color) {
if (w <= 0 || h <= 0) return;
// Clip to screen bounds.
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x + w > width()) w = width() - x;
if (y + h > height()) h = height() - y;
if (w <= 0 || h <= 0) return;
// Transform the rectangle to physical coordinates.
// After rotation, an axis-aligned rect stays axis-aligned.
int16_t px = x, py = y, pw = w, ph = h;
switch (rotation_) {
case Rotation::deg_0:
break;
case Rotation::deg_90:
px = raw_height_ - y - h;
py = x;
pw = h;
ph = w;
break;
case Rotation::deg_180:
px = raw_width_ - x - w;
py = raw_height_ - y - h;
break;
case Rotation::deg_270:
px = y;
py = raw_width_ - x - w;
pw = h;
ph = w;
break;
}
fillRectImpl(px, py, pw, ph, color);
}
// ---------------------------------------------------------------------------
// Circles — midpoint algorithm
// ---------------------------------------------------------------------------
void GfxCanvas::drawCircle(int16_t cx, int16_t cy,
int16_t r, Color color) {
if (r <= 0) { drawPixel(cx, cy, color); return; }
drawCircleQuadrants(cx, cy, r, 0x0F, color);
}
// Draws selected quadrant arcs using the midpoint circle algorithm.
// bit 0 (0x01): top-right
// bit 1 (0x02): top-left
// bit 2 (0x04): bottom-left
// bit 3 (0x08): bottom-right
void GfxCanvas::drawCircleQuadrants(int16_t cx, int16_t cy, int16_t r,
uint8_t corners, Color color) {
int16_t x = 0, y = r;
int16_t d = 1 - r;
while (x <= y) {
if (corners & 0x01) { drawPixel(cx + x, cy - y, color);
drawPixel(cx + y, cy - x, color); }
if (corners & 0x02) { drawPixel(cx - y, cy - x, color);
drawPixel(cx - x, cy - y, color); }
if (corners & 0x04) { drawPixel(cx - x, cy + y, color);
drawPixel(cx - y, cy + x, color); }
if (corners & 0x08) { drawPixel(cx + y, cy + x, color);
drawPixel(cx + x, cy + y, color); }
if (d < 0) {
d += 2 * x + 3;
} else {
d += 2 * (x - y) + 5;
--y;
}
++x;
}
}
void GfxCanvas::fillCircle(int16_t cx, int16_t cy,
int16_t r, Color color) {
if (r <= 0) { drawPixel(cx, cy, color); return; }
drawVLine(cx, cy - r, 2 * r + 1, color);
fillCircleSpans(cx, cy, r, 0x03, 0, color);
}
// Fills horizontal spans for circle quadrants.
// bit 0: right half (cx to cx+x)
// bit 1: left half (cx-x to cx)
// delta: extra width added to each span (used by fillRoundRect).
void GfxCanvas::fillCircleSpans(int16_t cx, int16_t cy, int16_t r,
uint8_t corners, int16_t delta,
Color color) {
int16_t x = 0, y = r;
int16_t d = 1 - r;
int16_t prev_y = -1; // track to avoid duplicate spans
while (x <= y) {
if (d >= 0) {
if (corners & 0x01) {
drawHLine(cx - x, cy + y, 2 * x + 1 + delta, color);
drawHLine(cx - x, cy - y, 2 * x + 1 + delta, color);
}
if (corners & 0x02) {
drawHLine(cx - x, cy + y, 2 * x + 1 + delta, color);
drawHLine(cx - x, cy - y, 2 * x + 1 + delta, color);
}
d += 2 * (x - y) + 5;
--y;
} else {
d += 2 * x + 3;
}
if (x != prev_y) {
if (corners & 0x01) {
drawHLine(cx - y, cy + x, 2 * y + 1 + delta, color);
drawHLine(cx - y, cy - x, 2 * y + 1 + delta, color);
}
if (corners & 0x02) {
drawHLine(cx - y, cy + x, 2 * y + 1 + delta, color);
drawHLine(cx - y, cy - x, 2 * y + 1 + delta, color);
}
}
prev_y = y;
++x;
}
}
// ---------------------------------------------------------------------------
// Rounded rectangles
// ---------------------------------------------------------------------------
void GfxCanvas::drawRoundRect(int16_t x, int16_t y,
int16_t w, int16_t h,
int16_t r, Color color) {
if (w <= 0 || h <= 0) return;
if (r <= 0) { drawRect(x, y, w, h, color); return; }
int16_t max_r = min16(w, h) / 2;
if (r > max_r) r = max_r;
// Straight edges (between the arc endpoints).
drawHLine(x + r, y, w - 2 * r, color); // top
drawHLine(x + r, y + h - 1, w - 2 * r, color); // bottom
drawVLine(x, y + r, h - 2 * r, color); // left
drawVLine(x + w - 1, y + r, h - 2 * r, color); // right
// Four corner arcs.
drawCircleQuadrants(x + w - 1 - r, y + r, r, 0x01, color); // top-right
drawCircleQuadrants(x + r, y + r, r, 0x02, color); // top-left
drawCircleQuadrants(x + r, y + h - 1 - r, r, 0x04, color); // bottom-left
drawCircleQuadrants(x + w - 1 - r, y + h - 1 - r, r, 0x08, color);// bottom-right
}
void GfxCanvas::fillRoundRect(int16_t x, int16_t y,
int16_t w, int16_t h,
int16_t r, Color color) {
if (w <= 0 || h <= 0) return;
if (r <= 0) { fillRect(x, y, w, h, color); return; }
int16_t max_r = min16(w, h) / 2;
if (r > max_r) r = max_r;
// Central rectangle.
fillRect(x + r, y, w - 2 * r, h, color);
// Side rectangles.
fillRect(x, y + r, r, h - 2 * r, color);
fillRect(x + w - r, y + r, r, h - 2 * r, color);
// Four filled corners using spans.
int16_t cx_l = x + r;
int16_t cx_r = x + w - 1 - r;
int16_t cy_t = y + r;
int16_t cy_b = y + h - 1 - r;
int16_t xi = 0, yi = r;
int16_t d = 1 - r;
while (xi <= yi) {
// Top corners.
drawHLine(cx_l - xi, cy_t - yi, xi + (cx_r - cx_l) + xi + 1, color);
drawHLine(cx_l - yi, cy_t - xi, yi + (cx_r - cx_l) + yi + 1, color);
// Bottom corners.
drawHLine(cx_l - xi, cy_b + yi, xi + (cx_r - cx_l) + xi + 1, color);
drawHLine(cx_l - yi, cy_b + xi, yi + (cx_r - cx_l) + yi + 1, color);
if (d < 0) {
d += 2 * xi + 3;
} else {
d += 2 * (xi - yi) + 5;
--yi;
}
++xi;
}
}
// ---------------------------------------------------------------------------
// Triangles
// ---------------------------------------------------------------------------
void GfxCanvas::drawTriangle(int16_t x0, int16_t y0,
int16_t x1, int16_t y1,
int16_t x2, int16_t y2, Color color) {
drawLine(x0, y0, x1, y1, color);
drawLine(x1, y1, x2, y2, color);
drawLine(x2, y2, x0, y0, color);
}
void GfxCanvas::fillTriangle(int16_t x0, int16_t y0,
int16_t x1, int16_t y1,
int16_t x2, int16_t y2, Color color) {
// Sort by y-coordinate ascending.
if (y0 > y1) { swap16(x0, x1); swap16(y0, y1); }
if (y1 > y2) { swap16(x1, x2); swap16(y1, y2); }
if (y0 > y1) { swap16(x0, x1); swap16(y0, y1); }
// Degenerate: all on same row.
if (y0 == y2) {
int16_t a = min16(min16(x0, x1), x2);
int16_t b = max16(max16(x0, x1), x2);
drawHLine(a, y0, b - a + 1, color);
return;
}
// Edge slopes using fixed-point (16.16) arithmetic.
// Walk from top to bottom, interpolating left and right x boundaries.
int32_t dx02 = x2 - x0, dy02 = y2 - y0;
int32_t dx01 = x1 - x0, dy01 = y1 - y0;
int32_t dx12 = x2 - x1, dy12 = y2 - y1;
// Accumulator for edge 0→2 (spans the full height).
int32_t sa = 0;
// Accumulator for edge 0→1 (top half) then 1→2 (bottom half).
int32_t sb = 0;
// Top half: y0 → y1.
int16_t last_y = (y1 == y2) ? y1 : y1 - 1;
for (int16_t y = y0; y <= last_y; ++y) {
int16_t a = x0 + static_cast<int16_t>(sa / dy02);
int16_t b = x0 + static_cast<int16_t>(sb / dy01);
sa += dx02;
sb += dx01;
if (a > b) swap16(a, b);
drawHLine(a, y, b - a + 1, color);
}
// Bottom half: y1 → y2.
sb = dx12 * (last_y + 1 - y1);
for (int16_t y = last_y + 1; y <= y2; ++y) {
int16_t a = x0 + static_cast<int16_t>(sa / dy02);
int16_t b = x1 + static_cast<int16_t>(sb / dy12);
sa += dx02;
sb += dx12;
if (a > b) swap16(a, b);
drawHLine(a, y, b - a + 1, color);
}
}
// ---------------------------------------------------------------------------
// Screen
// ---------------------------------------------------------------------------
void GfxCanvas::fillScreen(Color color) {
fillRect(0, 0, width(), height(), color);
}
// ---------------------------------------------------------------------------
// Text — configuration
// ---------------------------------------------------------------------------
void GfxCanvas::setFont(const Font& font) { font_ = &font; }
void GfxCanvas::setCursor(int16_t x, int16_t y) {
cursor_x_ = x;
cursor_y_ = y;
}
void GfxCanvas::setTextColor(Color fg) {
text_fg_ = fg;
text_bg_opaque_ = false;
}
void GfxCanvas::setTextColor(Color fg, Color bg) {
text_fg_ = fg;
text_bg_ = bg;
text_bg_opaque_ = true;
}
void GfxCanvas::setTextWrap(bool wrap) { text_wrap_ = wrap; }
// ---------------------------------------------------------------------------
// Text — character rendering (column-major font encoding)
// ---------------------------------------------------------------------------
void GfxCanvas::drawChar(int16_t x, int16_t y, char c,
Color fg, Color bg) const {
if (!font_) return;
if (c < static_cast<char>(font_->first_char) ||
c > static_cast<char>(font_->last_char))
return;
int idx = c - font_->first_char;
const uint8_t* glyph = font_->bitmap + idx * font_->glyph_width;
// Column-major: each byte is one column, bit 0 = top row.
for (int16_t col = 0; col < font_->glyph_width; ++col) {
uint8_t column_bits = glyph[col];
for (int16_t row = 0; row < font_->glyph_height; ++row) {
if (column_bits & (1 << row)) {
const_cast<GfxCanvas*>(this)->drawPixel(
x + col, y + row, fg);
} else if (text_bg_opaque_) {
const_cast<GfxCanvas*>(this)->drawPixel(
x + col, y + row, bg);
}
}
}
}
// ---------------------------------------------------------------------------
// Text — string rendering
// ---------------------------------------------------------------------------
void GfxCanvas::drawString(int16_t x, int16_t y,
const char* str, Color color) {
if (!font_ || !str) return;
while (*str) {
drawChar(x, y, *str, color, text_bg_);
x += font_->x_advance;
++str;
}
}
void GfxCanvas::print(char c) {
if (!font_) return;
if (c == '\n') {
cursor_x_ = 0;
cursor_y_ += font_->y_advance;
return;
}
if (text_wrap_ && (cursor_x_ + font_->x_advance > width())) {
cursor_x_ = 0;
cursor_y_ += font_->y_advance;
}
drawChar(cursor_x_, cursor_y_, c, text_fg_, text_bg_);
cursor_x_ += font_->x_advance;
}
void GfxCanvas::print(const char* str) {
if (!str) return;
while (*str) {
print(*str);
++str;
}
}
// ---------------------------------------------------------------------------
// Text — measurement
// ---------------------------------------------------------------------------
void GfxCanvas::getTextBounds(const char* str, int16_t cx, int16_t cy,
int16_t& bx, int16_t& by,
uint16_t& bw, uint16_t& bh) const {
bx = cx;
by = cy;
bw = 0;
bh = 0;
if (!font_ || !str) return;
int16_t x = cx;
int16_t min_x = cx, max_x = cx;
int16_t min_y = cy, max_y = cy;
while (*str) {
if (*str == '\n') {
x = cx;
cy += font_->y_advance;
} else {
int16_t gx = x;
int16_t gy = cy;
int16_t gx2 = gx + font_->glyph_width - 1;
int16_t gy2 = gy + font_->glyph_height - 1;
if (gx < min_x) min_x = gx;
if (gx2 > max_x) max_x = gx2;
if (gy < min_y) min_y = gy;
if (gy2 > max_y) max_y = gy2;
x += font_->x_advance;
}
++str;
}
if (max_x >= min_x) {
bx = min_x;
by = min_y;
bw = static_cast<uint16_t>(max_x - min_x + 1);
bh = static_cast<uint16_t>(max_y - min_y + 1);
}
}
// ---------------------------------------------------------------------------
// Default Impl methods — loop over drawPixelImpl
// ---------------------------------------------------------------------------
void GfxCanvas::drawHLineImpl(int16_t x, int16_t y, int16_t w, Color color) {
for (int16_t i = 0; i < w; ++i)
drawPixelImpl(x + i, y, color);
}
void GfxCanvas::drawVLineImpl(int16_t x, int16_t y, int16_t h, Color color) {
for (int16_t i = 0; i < h; ++i)
drawPixelImpl(x, y + i, color);
}
void GfxCanvas::fillRectImpl(int16_t x, int16_t y,
int16_t w, int16_t h, Color color) {
for (int16_t row = 0; row < h; ++row)
drawHLineImpl(x, y + row, w, color);
}
} // namespace gfx

176
gfx_canvas.h Normal file
View File

@@ -0,0 +1,176 @@
// gfx_canvas.h — Minimal embedded graphics library.
//
// Zero dynamic allocation. No exceptions. C++23 compatible.
// Compatible with glibc and newlib.
//
// Color model: XRGB8888 (uint32_t) everywhere in the public API.
// The HAL subclass converts to whatever the hardware needs.
#ifndef GFX_CANVAS_H
#define GFX_CANVAS_H
#include <cstddef>
#include <cstdint>
namespace gfx {
// ---------------------------------------------------------------------------
// Color
// ---------------------------------------------------------------------------
using Color = uint32_t; // 0x00RRGGBB
namespace colors {
inline constexpr Color black = 0x00000000;
inline constexpr Color white = 0x00FFFFFF;
inline constexpr Color red = 0x00FF0000;
inline constexpr Color green = 0x0000FF00;
inline constexpr Color blue = 0x000000FF;
inline constexpr Color yellow = 0x00FFFF00;
inline constexpr Color cyan = 0x0000FFFF;
inline constexpr Color magenta = 0x00FF00FF;
} // namespace colors
constexpr Color rgb(uint8_t r, uint8_t g, uint8_t b) {
return (static_cast<uint32_t>(r) << 16) |
(static_cast<uint32_t>(g) << 8) |
static_cast<uint32_t>(b);
}
// ---------------------------------------------------------------------------
// Font — fixed-width, column-major encoding
//
// Each glyph occupies glyph_width consecutive bytes.
// Each byte is one column, bit 0 = topmost row, bit (glyph_height-1) = bottom.
// This is the standard encoding used by SSD1306 fonts, u8g2 legacy fonts,
// and the classic Adafruit 5x7 glcdfont.
// ---------------------------------------------------------------------------
struct Font {
const uint8_t* bitmap; // glyph data, column-major
uint8_t glyph_width; // pixels per glyph (all glyphs same width)
uint8_t glyph_height; // pixels tall
uint8_t first_char; // first codepoint (usually 0x20)
uint8_t last_char; // last codepoint (usually 0x7E)
uint8_t x_advance; // horizontal cursor step (width + spacing)
uint8_t y_advance; // vertical line step (height + spacing)
};
// ---------------------------------------------------------------------------
// Rotation
// ---------------------------------------------------------------------------
enum class Rotation : uint8_t {
deg_0 = 0,
deg_90 = 1,
deg_180 = 2,
deg_270 = 3,
};
// ---------------------------------------------------------------------------
// GfxCanvas — abstract base class
// ---------------------------------------------------------------------------
class GfxCanvas {
public:
GfxCanvas(int16_t width, int16_t height);
virtual ~GfxCanvas() = default;
GfxCanvas(const GfxCanvas&) = delete;
GfxCanvas& operator=(const GfxCanvas&) = delete;
GfxCanvas(GfxCanvas&&) = delete;
GfxCanvas& operator=(GfxCanvas&&) = delete;
// --- Pixel ----------------------------------------------------------
void drawPixel(int16_t x, int16_t y, Color color);
// --- Lines ----------------------------------------------------------
void drawHLine(int16_t x, int16_t y, int16_t w, Color color);
void drawVLine(int16_t x, int16_t y, int16_t h, Color color);
void drawLine(int16_t x0, int16_t y0,
int16_t x1, int16_t y1, Color color);
// --- Rectangles -----------------------------------------------------
void drawRect(int16_t x, int16_t y,
int16_t w, int16_t h, Color color);
void fillRect(int16_t x, int16_t y,
int16_t w, int16_t h, Color color);
// --- Circles --------------------------------------------------------
void drawCircle(int16_t cx, int16_t cy, int16_t r, Color color);
void fillCircle(int16_t cx, int16_t cy, int16_t r, Color color);
// --- Rounded rectangles ---------------------------------------------
void drawRoundRect(int16_t x, int16_t y,
int16_t w, int16_t h,
int16_t r, Color color);
void fillRoundRect(int16_t x, int16_t y,
int16_t w, int16_t h,
int16_t r, Color color);
// --- Triangles ------------------------------------------------------
void drawTriangle(int16_t x0, int16_t y0,
int16_t x1, int16_t y1,
int16_t x2, int16_t y2, Color color);
void fillTriangle(int16_t x0, int16_t y0,
int16_t x1, int16_t y1,
int16_t x2, int16_t y2, Color color);
// --- Screen ---------------------------------------------------------
void fillScreen(Color color);
// --- Text -----------------------------------------------------------
void setFont(const Font& font);
void setCursor(int16_t x, int16_t y);
void setTextColor(Color fg);
void setTextColor(Color fg, Color bg);
void setTextWrap(bool wrap);
void drawChar(int16_t x, int16_t y, char c, Color fg, Color bg) const;
void drawString(int16_t x, int16_t y, const char* str, Color color);
void print(const char* str);
void print(char c);
void getTextBounds(const char* str, int16_t cx, int16_t cy,
int16_t& bx, int16_t& by,
uint16_t& bw, uint16_t& bh) const;
// --- Rotation -------------------------------------------------------
void setRotation(Rotation r);
Rotation rotation() const { return rotation_; }
// --- Geometry -------------------------------------------------------
int16_t width() const;
int16_t height() const;
int16_t rawWidth() const { return raw_width_; }
int16_t rawHeight() const { return raw_height_; }
protected:
// === HAL — subclass must implement drawPixelImpl ===================
virtual void drawPixelImpl(int16_t x, int16_t y, Color color) = 0;
// === HAL — optional fast-path overrides ============================
virtual void drawHLineImpl(int16_t x, int16_t y, int16_t w, Color color);
virtual void drawVLineImpl(int16_t x, int16_t y, int16_t h, Color color);
virtual void fillRectImpl(int16_t x, int16_t y,
int16_t w, int16_t h, Color color);
private:
int16_t raw_width_;
int16_t raw_height_;
int16_t cursor_x_ = 0;
int16_t cursor_y_ = 0;
Color text_fg_ = colors::white;
Color text_bg_ = colors::black;
bool text_bg_opaque_ = false;
bool text_wrap_ = true;
Rotation rotation_ = Rotation::deg_0;
const Font* font_ = nullptr;
// Internal helpers for circle quadrant drawing.
void drawCircleQuadrants(int16_t cx, int16_t cy, int16_t r,
uint8_t corners, Color color);
void fillCircleSpans(int16_t cx, int16_t cy, int16_t r,
uint8_t corners, int16_t delta, Color color);
};
} // namespace gfx
#endif // GFX_CANVAS_H

123
gfx_font_5x7.h Normal file
View File

@@ -0,0 +1,123 @@
// gfx_font_5x7.h — Built-in 5x7 monospace bitmap font.
//
// Column-major encoding: 5 bytes per glyph, each byte is one column,
// bit 0 = topmost row. Covers printable ASCII 0x200x7E (95 glyphs).
#ifndef GFX_FONT_5X7_H
#define GFX_FONT_5X7_H
#include "gfx_canvas.h"
namespace gfx {
inline constexpr uint8_t font_5x7_bitmap[] = {
0x00,0x00,0x00,0x00,0x00, // 0x20 ' '
0x00,0x00,0x5F,0x00,0x00, // 0x21 '!'
0x00,0x07,0x00,0x07,0x00, // 0x22 '"'
0x14,0x7F,0x14,0x7F,0x14, // 0x23 '#'
0x24,0x2A,0x7F,0x2A,0x12, // 0x24 '$'
0x23,0x13,0x08,0x64,0x62, // 0x25 '%'
0x36,0x49,0x55,0x22,0x50, // 0x26 '&'
0x00,0x05,0x03,0x00,0x00, // 0x27 '''
0x00,0x1C,0x22,0x41,0x00, // 0x28 '('
0x00,0x41,0x22,0x1C,0x00, // 0x29 ')'
0x14,0x08,0x3E,0x08,0x14, // 0x2A '*'
0x08,0x08,0x3E,0x08,0x08, // 0x2B '+'
0x00,0x50,0x30,0x00,0x00, // 0x2C ','
0x08,0x08,0x08,0x08,0x08, // 0x2D '-'
0x00,0x60,0x60,0x00,0x00, // 0x2E '.'
0x20,0x10,0x08,0x04,0x02, // 0x2F '/'
0x3E,0x51,0x49,0x45,0x3E, // 0x30 '0'
0x00,0x42,0x7F,0x40,0x00, // 0x31 '1'
0x42,0x61,0x51,0x49,0x46, // 0x32 '2'
0x21,0x41,0x45,0x4B,0x31, // 0x33 '3'
0x18,0x14,0x12,0x7F,0x10, // 0x34 '4'
0x27,0x45,0x45,0x45,0x39, // 0x35 '5'
0x3C,0x4A,0x49,0x49,0x30, // 0x36 '6'
0x01,0x71,0x09,0x05,0x03, // 0x37 '7'
0x36,0x49,0x49,0x49,0x36, // 0x38 '8'
0x06,0x49,0x49,0x29,0x1E, // 0x39 '9'
0x00,0x36,0x36,0x00,0x00, // 0x3A ':'
0x00,0x56,0x36,0x00,0x00, // 0x3B ';'
0x08,0x14,0x22,0x41,0x00, // 0x3C '<'
0x14,0x14,0x14,0x14,0x14, // 0x3D '='
0x00,0x41,0x22,0x14,0x08, // 0x3E '>'
0x02,0x01,0x51,0x09,0x06, // 0x3F '?'
0x32,0x49,0x79,0x41,0x3E, // 0x40 '@'
0x7E,0x11,0x11,0x11,0x7E, // 0x41 'A'
0x7F,0x49,0x49,0x49,0x36, // 0x42 'B'
0x3E,0x41,0x41,0x41,0x22, // 0x43 'C'
0x7F,0x41,0x41,0x22,0x1C, // 0x44 'D'
0x7F,0x49,0x49,0x49,0x41, // 0x45 'E'
0x7F,0x09,0x09,0x09,0x01, // 0x46 'F'
0x3E,0x41,0x49,0x49,0x7A, // 0x47 'G'
0x7F,0x08,0x08,0x08,0x7F, // 0x48 'H'
0x00,0x41,0x7F,0x41,0x00, // 0x49 'I'
0x20,0x40,0x41,0x3F,0x01, // 0x4A 'J'
0x7F,0x08,0x14,0x22,0x41, // 0x4B 'K'
0x7F,0x40,0x40,0x40,0x40, // 0x4C 'L'
0x7F,0x02,0x0C,0x02,0x7F, // 0x4D 'M'
0x7F,0x04,0x08,0x10,0x7F, // 0x4E 'N'
0x3E,0x41,0x41,0x41,0x3E, // 0x4F 'O'
0x7F,0x09,0x09,0x09,0x06, // 0x50 'P'
0x3E,0x41,0x51,0x21,0x5E, // 0x51 'Q'
0x7F,0x09,0x19,0x29,0x46, // 0x52 'R'
0x46,0x49,0x49,0x49,0x31, // 0x53 'S'
0x01,0x01,0x7F,0x01,0x01, // 0x54 'T'
0x3F,0x40,0x40,0x40,0x3F, // 0x55 'U'
0x1F,0x20,0x40,0x20,0x1F, // 0x56 'V'
0x3F,0x40,0x38,0x40,0x3F, // 0x57 'W'
0x63,0x14,0x08,0x14,0x63, // 0x58 'X'
0x07,0x08,0x70,0x08,0x07, // 0x59 'Y'
0x61,0x51,0x49,0x45,0x43, // 0x5A 'Z'
0x00,0x7F,0x41,0x41,0x00, // 0x5B '['
0x02,0x04,0x08,0x10,0x20, // 0x5C '\'
0x00,0x41,0x41,0x7F,0x00, // 0x5D ']'
0x04,0x02,0x01,0x02,0x04, // 0x5E '^'
0x40,0x40,0x40,0x40,0x40, // 0x5F '_'
0x00,0x01,0x02,0x04,0x00, // 0x60 '`'
0x20,0x54,0x54,0x54,0x78, // 0x61 'a'
0x7F,0x48,0x44,0x44,0x38, // 0x62 'b'
0x38,0x44,0x44,0x44,0x20, // 0x63 'c'
0x38,0x44,0x44,0x48,0x7F, // 0x64 'd'
0x38,0x54,0x54,0x54,0x18, // 0x65 'e'
0x08,0x7E,0x09,0x01,0x02, // 0x66 'f'
0x0C,0x52,0x52,0x52,0x3E, // 0x67 'g'
0x7F,0x08,0x04,0x04,0x78, // 0x68 'h'
0x00,0x44,0x7D,0x40,0x00, // 0x69 'i'
0x20,0x40,0x44,0x3D,0x00, // 0x6A 'j'
0x7F,0x10,0x28,0x44,0x00, // 0x6B 'k'
0x00,0x41,0x7F,0x40,0x00, // 0x6C 'l'
0x7C,0x04,0x18,0x04,0x78, // 0x6D 'm'
0x7C,0x08,0x04,0x04,0x78, // 0x6E 'n'
0x38,0x44,0x44,0x44,0x38, // 0x6F 'o'
0x7C,0x14,0x14,0x14,0x08, // 0x70 'p'
0x08,0x14,0x14,0x18,0x7C, // 0x71 'q'
0x7C,0x08,0x04,0x04,0x08, // 0x72 'r'
0x48,0x54,0x54,0x54,0x20, // 0x73 's'
0x04,0x3F,0x44,0x40,0x20, // 0x74 't'
0x3C,0x40,0x40,0x20,0x7C, // 0x75 'u'
0x1C,0x20,0x40,0x20,0x1C, // 0x76 'v'
0x3C,0x40,0x30,0x40,0x3C, // 0x77 'w'
0x44,0x28,0x10,0x28,0x44, // 0x78 'x'
0x0C,0x50,0x50,0x50,0x3C, // 0x79 'y'
0x44,0x64,0x54,0x4C,0x44, // 0x7A 'z'
0x00,0x08,0x36,0x41,0x00, // 0x7B '{'
0x00,0x00,0x7F,0x00,0x00, // 0x7C '|'
0x00,0x41,0x36,0x08,0x00, // 0x7D '}'
0x10,0x08,0x08,0x10,0x08, // 0x7E '~'
};
inline constexpr Font font_5x7 = {
.bitmap = font_5x7_bitmap,
.glyph_width = 5,
.glyph_height = 7,
.first_char = 0x20,
.last_char = 0x7E,
.x_advance = 6, // 5px glyph + 1px spacing
.y_advance = 9, // 7px glyph + 2px spacing
};
} // namespace gfx
#endif // GFX_FONT_5X7_H

214
linux_fb_display.cpp Normal file
View File

@@ -0,0 +1,214 @@
// linux_fb_display.cpp — Linux framebuffer HAL implementation.
#include "linux_fb_display.h"
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
namespace gfx {
// ---------------------------------------------------------------------------
// Construction / destruction
// ---------------------------------------------------------------------------
LinuxFBDisplay::LinuxFBDisplay(int16_t width, int16_t height)
: GfxCanvas(width, height) {}
LinuxFBDisplay::~LinuxFBDisplay() { close(); }
// ---------------------------------------------------------------------------
// Open / close
// ---------------------------------------------------------------------------
bool LinuxFBDisplay::open(const char* device) {
fd_ = ::open(device, O_RDWR);
if (fd_ < 0) {
std::perror("LinuxFBDisplay::open");
return false;
}
if (ioctl(fd_, FBIOGET_VSCREENINFO, &vinfo_) < 0) {
std::perror("FBIOGET_VSCREENINFO");
close();
return false;
}
if (ioctl(fd_, FBIOGET_FSCREENINFO, &finfo_) < 0) {
std::perror("FBIOGET_FSCREENINFO");
close();
return false;
}
bpp_ = vinfo_.bits_per_pixel;
line_len_ = finfo_.line_length;
map_size_ = finfo_.smem_len;
buf_ = static_cast<uint8_t*>(
mmap(nullptr, map_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0));
if (buf_ == MAP_FAILED) {
std::perror("mmap");
buf_ = nullptr;
close();
return false;
}
return true;
}
void LinuxFBDisplay::close() {
if (buf_) {
munmap(buf_, map_size_);
buf_ = nullptr;
}
if (fd_ >= 0) {
::close(fd_);
fd_ = -1;
}
}
// ---------------------------------------------------------------------------
// Pixel format conversion
// ---------------------------------------------------------------------------
void LinuxFBDisplay::writePixel(uint8_t* dst, Color color) const {
if (bpp_ == 32) {
// XRGB8888 — write directly.
std::memcpy(dst, &color, 4);
} else if (bpp_ == 16) {
// RGB565
uint16_t r = (color >> 19) & 0x1F;
uint16_t g = (color >> 10) & 0x3F;
uint16_t b = (color >> 3) & 0x1F;
uint16_t pixel = (r << 11) | (g << 5) | b;
std::memcpy(dst, &pixel, 2);
} else if (bpp_ == 8) {
// Grayscale — simple luminance approximation.
uint8_t r = (color >> 16) & 0xFF;
uint8_t g = (color >> 8) & 0xFF;
uint8_t b = color & 0xFF;
dst[0] = static_cast<uint8_t>((r * 77 + g * 150 + b * 29) >> 8);
}
}
// ---------------------------------------------------------------------------
// drawPixelImpl
// ---------------------------------------------------------------------------
void LinuxFBDisplay::drawPixelImpl(int16_t x, int16_t y, Color color) {
if (!buf_) return;
uint32_t offset = static_cast<uint32_t>(y) * line_len_
+ static_cast<uint32_t>(x) * (bpp_ / 8);
writePixel(buf_ + offset, color);
}
// ---------------------------------------------------------------------------
// Accelerated horizontal line — memset for monochrome, loop for color
// ---------------------------------------------------------------------------
void LinuxFBDisplay::drawHLineImpl(int16_t x, int16_t y,
int16_t w, Color color) {
if (!buf_ || w <= 0) return;
uint32_t bytes_pp = bpp_ / 8;
uint8_t* row = buf_ + static_cast<uint32_t>(y) * line_len_
+ static_cast<uint32_t>(x) * bytes_pp;
if (bpp_ == 32) {
// For XRGB8888, use 32-bit fill.
auto* p = reinterpret_cast<uint32_t*>(row);
for (int16_t i = 0; i < w; ++i)
p[i] = color;
} else if (bpp_ == 16) {
uint16_t r = (color >> 19) & 0x1F;
uint16_t g = (color >> 10) & 0x3F;
uint16_t b = (color >> 3) & 0x1F;
uint16_t pixel = (r << 11) | (g << 5) | b;
auto* p = reinterpret_cast<uint16_t*>(row);
for (int16_t i = 0; i < w; ++i)
p[i] = pixel;
} else if (bpp_ == 8) {
uint8_t r_ch = (color >> 16) & 0xFF;
uint8_t g_ch = (color >> 8) & 0xFF;
uint8_t b_ch = color & 0xFF;
uint8_t gray = static_cast<uint8_t>(
(r_ch * 77 + g_ch * 150 + b_ch * 29) >> 8);
std::memset(row, gray, static_cast<size_t>(w));
}
}
// ---------------------------------------------------------------------------
// Accelerated vertical line
// ---------------------------------------------------------------------------
void LinuxFBDisplay::drawVLineImpl(int16_t x, int16_t y,
int16_t h, Color color) {
if (!buf_ || h <= 0) return;
uint32_t bytes_pp = bpp_ / 8;
uint8_t* p = buf_ + static_cast<uint32_t>(y) * line_len_
+ static_cast<uint32_t>(x) * bytes_pp;
// Pre-encode the pixel once.
uint8_t pixel_buf[4];
writePixel(pixel_buf, color);
for (int16_t i = 0; i < h; ++i) {
std::memcpy(p, pixel_buf, bytes_pp);
p += line_len_;
}
}
// ---------------------------------------------------------------------------
// Accelerated filled rectangle
// ---------------------------------------------------------------------------
void LinuxFBDisplay::fillRectImpl(int16_t x, int16_t y,
int16_t w, int16_t h, Color color) {
if (!buf_ || w <= 0 || h <= 0) return;
// For 32-bpp with color == black or white, we can use memset for the
// entire rectangle. Otherwise, fill the first row with drawHLineImpl
// and memcpy it to subsequent rows.
uint32_t bytes_pp = bpp_ / 8;
uint32_t row_bytes = static_cast<uint32_t>(w) * bytes_pp;
uint8_t* first_row = buf_ + static_cast<uint32_t>(y) * line_len_
+ static_cast<uint32_t>(x) * bytes_pp;
// Optimized path: if all bytes of the pixel are the same value,
// the entire rect can be filled with memset.
bool can_memset = false;
uint8_t fill_byte = 0;
if (bpp_ == 32) {
// 0x00000000 → all bytes 0x00. 0x00FFFFFF → not uniform.
// Only pure black (all 0x00) qualifies.
if (color == 0x00000000) { can_memset = true; fill_byte = 0x00; }
} else if (bpp_ == 8) {
uint8_t r_ch = (color >> 16) & 0xFF;
uint8_t g_ch = (color >> 8) & 0xFF;
uint8_t b_ch = color & 0xFF;
fill_byte = static_cast<uint8_t>(
(r_ch * 77 + g_ch * 150 + b_ch * 29) >> 8);
can_memset = true;
}
if (can_memset) {
uint8_t* row = first_row;
for (int16_t r = 0; r < h; ++r) {
std::memset(row, fill_byte, row_bytes);
row += line_len_;
}
return;
}
// General path: fill first row with pixel values, then memcpy.
drawHLineImpl(x, y, w, color);
uint8_t* src = first_row;
uint8_t* dst = first_row + line_len_;
for (int16_t r = 1; r < h; ++r) {
std::memcpy(dst, src, row_bytes);
dst += line_len_;
}
}
} // namespace gfx

58
linux_fb_display.h Normal file
View File

@@ -0,0 +1,58 @@
// linux_fb_display.h — GfxCanvas subclass for Linux framebuffer devices.
//
// Supports XRGB8888 (32 bpp), RGB565 (16 bpp), and 8-bit grayscale.
// Uses mmap for zero-copy access to the kernel framebuffer.
#ifndef LINUX_FB_DISPLAY_H
#define LINUX_FB_DISPLAY_H
#include "gfx_canvas.h"
#include <cstdint>
#include <linux/fb.h>
namespace gfx {
class LinuxFBDisplay : public GfxCanvas {
public:
// Construct with the physical panel dimensions.
LinuxFBDisplay(int16_t width, int16_t height);
~LinuxFBDisplay() override;
// Open the framebuffer device. Returns true on success.
[[nodiscard]] bool open(const char* device);
// Release the mmap and close the file descriptor.
void close();
// Is the framebuffer currently open and mapped?
bool isOpen() const { return buf_ != nullptr; }
protected:
// Mandatory HAL: single pixel write.
void drawPixelImpl(int16_t x, int16_t y, Color color) override;
// Accelerated HAL overrides.
void drawHLineImpl(int16_t x, int16_t y, int16_t w, Color color) override;
void drawVLineImpl(int16_t x, int16_t y, int16_t h, Color color) override;
void fillRectImpl(int16_t x, int16_t y,
int16_t w, int16_t h, Color color) override;
private:
int fd_ = -1;
uint8_t* buf_ = nullptr;
size_t map_size_ = 0;
uint32_t bpp_ = 0;
uint32_t line_len_ = 0;
fb_var_screeninfo vinfo_{};
fb_fix_screeninfo finfo_{};
// Convert XRGB8888 color to the native pixel value for this fb.
// Returns the value and its byte width.
void writePixel(uint8_t* dst, Color color) const;
};
} // namespace gfx
#endif // LINUX_FB_DISPLAY_H

139
main.cpp
View File

@@ -1,9 +1,134 @@
#include <iostream>
// main.cpp — Info display for SSD1306 128x64 using gfx::LinuxFBDisplay.
//
// Build: see CMakeLists.txt
// Run: ./gfx_info_display # defaults to /dev/fb1
// ./gfx_info_display /dev/fb0 # override
int main(int argc, char** argv)
{
(void)argc;
(void)argv;
std::cout << "Hello RPI" << std::endl;
return 0;
#include "gfx_canvas.h"
#include "gfx_font_5x7.h"
#include "linux_fb_display.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <string>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
// ---------------------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------------------
constexpr int16_t SCREEN_W = 128;
constexpr int16_t SCREEN_H = 64;
constexpr const char* ETH_IFACE = "eth0";
constexpr const char* WLAN_IFACE = "wlan0";
// ---------------------------------------------------------------------------
// Network helper
// ---------------------------------------------------------------------------
static std::string get_ip(const char* iface_name) {
struct ifaddrs* ifaddr = nullptr;
if (getifaddrs(&ifaddr) == -1) return "err";
std::string result = "down";
for (auto* ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
if (!ifa->ifa_addr) continue;
if (ifa->ifa_addr->sa_family != AF_INET) continue;
if (std::strcmp(ifa->ifa_name, iface_name) != 0) continue;
char buf[INET_ADDRSTRLEN];
auto* sa = reinterpret_cast<sockaddr_in*>(ifa->ifa_addr);
inet_ntop(AF_INET, &sa->sin_addr, buf, sizeof(buf));
result = buf;
break;
}
freeifaddrs(ifaddr);
return result;
}
// ---------------------------------------------------------------------------
// Text centering helper
// ---------------------------------------------------------------------------
static void draw_centered(gfx::GfxCanvas& disp, int16_t y, const char* text) {
int16_t bx, by;
uint16_t bw, bh;
disp.getTextBounds(text, 0, 0, bx, by, bw, bh);
disp.drawString((SCREEN_W - static_cast<int16_t>(bw)) / 2, y, text,
gfx::colors::white);
}
// ---------------------------------------------------------------------------
// Signal handling
// ---------------------------------------------------------------------------
static volatile sig_atomic_t g_running = 1;
static void signal_handler(int) { g_running = 0; }
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
int main(int argc, char* argv[]) {
const char* fb_device = "/dev/fb0";
if (argc > 1) fb_device = argv[1];
gfx::LinuxFBDisplay display(SCREEN_W, SCREEN_H);
if (!display.open(fb_device)) return EXIT_FAILURE;
display.setFont(gfx::font_5x7);
display.setTextColor(gfx::colors::white);
display.setTextWrap(false);
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
char line_buf[64];
while (g_running) {
display.fillScreen(gfx::colors::black);
// --- Network info ---
std::string eth_ip = get_ip(ETH_IFACE);
std::string wlan_ip = get_ip(WLAN_IFACE);
std::snprintf(line_buf, sizeof(line_buf), "ETH: %s", eth_ip.c_str());
display.setCursor(0, 1);
display.print(line_buf);
std::snprintf(line_buf, sizeof(line_buf), "WLAN: %s", wlan_ip.c_str());
display.setCursor(0, 11);
display.print(line_buf);
// --- Separator ---
display.drawHLine(0, 23, SCREEN_W, gfx::colors::white);
// --- Date and time ---
std::time_t now = std::time(nullptr);
std::tm* tm = std::localtime(&now);
std::snprintf(line_buf, sizeof(line_buf), "%04d-%02d-%02d",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
draw_centered(display, 29, line_buf);
std::snprintf(line_buf, sizeof(line_buf), "%02d:%02d:%02d",
tm->tm_hour, tm->tm_min, tm->tm_sec);
draw_centered(display, 41, line_buf);
// --- Sleep aligned to next whole second ---
struct timespec ts{};
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 1;
ts.tv_nsec = 0;
clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, nullptr);
}
display.fillScreen(gfx::colors::black);
std::printf("Exiting.\n");
return EXIT_SUCCESS;
}