generated from Projetos/RaspberryPi_app
First version with generated code
This commit is contained in:
@@ -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
574
gfx_canvas.cpp
Normal 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
176
gfx_canvas.h
Normal 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
123
gfx_font_5x7.h
Normal 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 0x20–0x7E (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
214
linux_fb_display.cpp
Normal 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
58
linux_fb_display.h
Normal 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
139
main.cpp
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user