DVX_GUI/widgets/canvas/widgetCanvas.c

1075 lines
30 KiB
C

#define DVX_WIDGET_IMPL
// widgetCanvas.c -- Drawable canvas widget (freehand draw, PNG save/load)
//
// The canvas widget provides a pixel buffer in the display's native pixel
// format that applications can draw into directly. It stores pixels in
// display format (not always RGB) to avoid per-pixel conversion on every
// repaint -- the paint function just does a straight rectCopy blit from
// the canvas buffer to the display. This is critical on a 486 where
// per-pixel format conversion during repaint would be prohibitively slow.
//
// The tradeoff is that load/save operations must convert between RGB and
// the display format, but those are one-time costs at I/O time rather
// than per-frame costs.
//
// Drawing operations (dot, line, rect, circle) operate directly on the
// canvas buffer using canvasPutPixel for pixel-level writes. The dot
// primitive draws a filled circle using the pen size, and line uses
// Bresenham's algorithm placing dots along the path. This gives smooth
// freehand drawing with variable pen widths.
//
// Canvas coordinates are independent of widget position -- (0,0) is the
// top-left of the canvas content, not the widget. Mouse events translate
// widget-space coordinates to canvas-space by subtracting the border offset.
#include "dvxWgtP.h"
static int32_t sTypeId = -1;
#define CANVAS_BORDER 2
typedef struct {
uint8_t *pixelData; // pixel buffer in display format
int32_t canvasW;
int32_t canvasH;
int32_t canvasPitch;
int32_t canvasBpp; // cached bytes per pixel
uint32_t penColor;
int32_t penSize;
int32_t lastX;
int32_t lastY;
void (*onMouse)(struct WidgetT *w, int32_t cx, int32_t cy, bool drag);
} CanvasDataT;
// ============================================================
// Prototypes
// ============================================================
static void canvasDrawDot(CanvasDataT *cd, int32_t cx, int32_t cy);
static void canvasDrawLine(CanvasDataT *cd, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
// ============================================================
// canvasGetPixel / canvasPutPixel
// ============================================================
//
// Read/write a single pixel at the given address, respecting the display's
// bytes-per-pixel depth (1=8-bit palette, 2=16-bit hicolor, 4=32-bit truecolor).
// These are inline because they're called per-pixel in tight loops (circle fill,
// line draw) -- the function call overhead would dominate on a 486. The bpp
// branch is predictable since it doesn't change within a single draw operation.
static inline uint32_t canvasGetPixel(const uint8_t *src, int32_t bpp) {
if (bpp == 1) {
return *src;
} else if (bpp == 2) {
return *(const uint16_t *)src;
} else {
return *(const uint32_t *)src;
}
}
static inline void canvasPutPixel(uint8_t *dst, uint32_t color, int32_t bpp) {
if (bpp == 1) {
*dst = (uint8_t)color;
} else if (bpp == 2) {
*(uint16_t *)dst = (uint16_t)color;
} else {
*(uint32_t *)dst = color;
}
}
// Forward declarations for BASIC wrapper functions
void wgtCanvasClear(WidgetT *w, uint32_t color);
void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color);
uint32_t wgtCanvasGetPixel(const WidgetT *w, int32_t x, int32_t y);
void wgtCanvasSetPenColor(WidgetT *w, uint32_t color);
void wgtCanvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
// Convert a 0x00RRGGBB color to the display's packed pixel format.
static uint32_t rgbToPacked(WidgetT *w, uint32_t rgb) {
AppContextT *ctx = wgtGetContext(w);
if (!ctx) { return rgb; }
uint8_t r = (rgb >> 16) & 0xFF;
uint8_t g = (rgb >> 8) & 0xFF;
uint8_t b = rgb & 0xFF;
return packColor(&ctx->display, r, g, b);
}
// BASIC-callable wrappers that convert 0x00RRGGBB colors to packed format.
static void basClear(WidgetT *w, int32_t color) {
wgtCanvasClear(w, rgbToPacked(w, (uint32_t)color));
}
static void basSetPixel(WidgetT *w, int32_t x, int32_t y, int32_t color) {
wgtCanvasSetPixel(w, x, y, rgbToPacked(w, (uint32_t)color));
}
static uint32_t basGetPixel(const WidgetT *w, int32_t x, int32_t y) {
uint32_t packed = wgtCanvasGetPixel(w, x, y);
AppContextT *ctx = wgtGetContext((WidgetT *)w);
if (!ctx) { return packed; }
uint8_t r;
uint8_t g;
uint8_t b;
unpackColor(&ctx->display, packed, &r, &g, &b);
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
static void basDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t color) {
wgtCanvasSetPenColor(w, rgbToPacked(w, (uint32_t)color));
wgtCanvasDrawLine(w, x0, y0, x1, y1);
}
static void basDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height, int32_t color) {
wgtCanvasSetPenColor(w, rgbToPacked(w, (uint32_t)color));
wgtCanvasDrawRect(w, x, y, width, height);
}
static void basFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height, int32_t color) {
wgtCanvasSetPenColor(w, rgbToPacked(w, (uint32_t)color));
wgtCanvasFillRect(w, x, y, width, height);
}
static void basFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius, int32_t color) {
wgtCanvasSetPenColor(w, rgbToPacked(w, (uint32_t)color));
wgtCanvasFillCircle(w, cx, cy, radius);
}
// ============================================================
// canvasDrawDot
// ============================================================
//
// Draw a filled circle of diameter penSize at (cx, cy) in canvas coords.
static void canvasDrawDot(CanvasDataT *cd, int32_t cx, int32_t cy) {
int32_t bpp = cd->canvasBpp;
int32_t pitch = cd->canvasPitch;
uint8_t *data = cd->pixelData;
int32_t cw = cd->canvasW;
int32_t ch = cd->canvasH;
uint32_t color = cd->penColor;
int32_t rad = cd->penSize / 2;
if (rad < 1) {
// Single pixel
if (cx >= 0 && cx < cw && cy >= 0 && cy < ch) {
uint8_t *dst = data + cy * pitch + cx * bpp;
canvasPutPixel(dst, color, bpp);
}
return;
}
// Filled circle via per-row horizontal span. For each row (dy offset from
// center), compute the horizontal extent using the circle equation
// dx^2 + dy^2 <= r^2. The horizontal half-span is floor(sqrt(r^2 - dy^2)).
// This approach is faster than checking each pixel individually because
// the inner loop just fills a horizontal run -- no per-pixel distance check.
int32_t r2 = rad * rad;
for (int32_t dy = -rad; dy <= rad; dy++) {
int32_t py = cy + dy;
if (py < 0 || py >= ch) {
continue;
}
// Compute horizontal half-span: dx^2 <= r^2 - dy^2
int32_t dy2 = dy * dy;
int32_t rem = r2 - dy2;
int32_t hspan = 0;
// Integer sqrt via Newton's method (Babylonian method). 8 iterations
// is more than enough to converge for any radius that fits in int32_t.
// Using integer sqrt avoids pulling in the FPU which may not be
// present on 486SX systems, and avoids the float-to-int conversion
// overhead even on systems with an FPU.
if (rem > 0) {
hspan = rad;
for (int32_t i = 0; i < 8; i++) {
hspan = (hspan + rem / hspan) / 2;
}
if (hspan * hspan > rem) {
hspan--;
}
}
int32_t x0 = cx - hspan;
int32_t x1 = cx + hspan;
if (x0 < 0) {
x0 = 0;
}
if (x1 >= cw) {
x1 = cw - 1;
}
if (x0 <= x1) {
uint8_t *dst = data + py * pitch + x0 * bpp;
for (int32_t px = x0; px <= x1; px++) {
canvasPutPixel(dst, color, bpp);
dst += bpp;
}
}
}
}
// ============================================================
// canvasDrawLine
// ============================================================
//
// Bresenham line from (x0,y0) to (x1,y1), placing dots along the path.
// Each point on the line gets a full pen dot (canvasDrawDot), which means
// lines with large pen sizes are smooth rather than aliased. Bresenham was
// chosen over DDA because it's pure integer arithmetic -- no floating point
// needed, which matters on 486SX (no FPU).
static void canvasDrawLine(CanvasDataT *cd, int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
int32_t dx = x1 - x0;
int32_t dy = y1 - y0;
int32_t sx = (dx >= 0) ? 1 : -1;
int32_t sy = (dy >= 0) ? 1 : -1;
if (dx < 0) { dx = -dx; }
if (dy < 0) { dy = -dy; }
int32_t err = dx - dy;
for (;;) {
canvasDrawDot(cd, x0, y0);
if (x0 == x1 && y0 == y1) {
break;
}
int32_t e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
// ============================================================
// wgtCanvasClear
// ============================================================
void wgtCanvasClear(WidgetT *w, uint32_t color) {
if (!w || w->type != sTypeId) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
// Find BlitOps for span fill
WidgetT *root = w;
while (root->parent) {
root = root->parent;
}
AppContextT *ctx = (AppContextT *)root->userData;
if (!ctx) {
return;
}
int32_t pitch = cd->canvasPitch;
int32_t cw = cd->canvasW;
int32_t ch = cd->canvasH;
for (int32_t y = 0; y < ch; y++) {
ctx->blitOps.spanFill(cd->pixelData + y * pitch, color, cw);
}
}
// ============================================================
// wgtCanvasDrawLine
// ============================================================
//
// Draw a line using the current pen color and pen size.
void wgtCanvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
if (!w || w->type != sTypeId) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
canvasDrawLine(cd, x0, y0, x1, y1);
}
// ============================================================
// wgtCanvasDrawRect
// ============================================================
//
// Draw a 1px outlined rectangle using the current pen color.
void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height) {
if (!w || w->type != sTypeId) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
if (width <= 0 || height <= 0) {
return;
}
int32_t bpp = cd->canvasBpp;
int32_t pitch = cd->canvasPitch;
uint8_t *data = cd->pixelData;
int32_t cw = cd->canvasW;
int32_t ch = cd->canvasH;
uint32_t color = cd->penColor;
// Top and bottom edges
for (int32_t px = x; px < x + width; px++) {
if (px < 0 || px >= cw) {
continue;
}
if (y >= 0 && y < ch) {
uint8_t *dst = data + y * pitch + px * bpp;
canvasPutPixel(dst, color, bpp);
}
int32_t by = y + height - 1;
if (by >= 0 && by < ch && by != y) {
uint8_t *dst = data + by * pitch + px * bpp;
canvasPutPixel(dst, color, bpp);
}
}
// Left and right edges (excluding corners already drawn)
for (int32_t py = y + 1; py < y + height - 1; py++) {
if (py < 0 || py >= ch) {
continue;
}
if (x >= 0 && x < cw) {
uint8_t *dst = data + py * pitch + x * bpp;
canvasPutPixel(dst, color, bpp);
}
int32_t rx = x + width - 1;
if (rx >= 0 && rx < cw && rx != x) {
uint8_t *dst = data + py * pitch + rx * bpp;
canvasPutPixel(dst, color, bpp);
}
}
}
// ============================================================
// wgtCanvasFillCircle
// ============================================================
//
// Draw a filled circle using the current pen color.
void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) {
if (!w || w->type != sTypeId) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
if (radius <= 0) {
return;
}
int32_t bpp = cd->canvasBpp;
int32_t pitch = cd->canvasPitch;
uint8_t *data = cd->pixelData;
int32_t cw = cd->canvasW;
int32_t ch = cd->canvasH;
uint32_t color = cd->penColor;
int32_t r2 = radius * radius;
for (int32_t dy = -radius; dy <= radius; dy++) {
int32_t py = cy + dy;
if (py < 0 || py >= ch) {
continue;
}
// Compute horizontal half-span: dx^2 <= r^2 - dy^2
int32_t dy2 = dy * dy;
int32_t rem = r2 - dy2;
int32_t hspan = 0;
// Integer sqrt via Newton's method
if (rem > 0) {
hspan = radius;
for (int32_t i = 0; i < 8; i++) {
hspan = (hspan + rem / hspan) / 2;
}
if (hspan * hspan > rem) {
hspan--;
}
}
int32_t x0 = cx - hspan;
int32_t x1 = cx + hspan;
if (x0 < 0) {
x0 = 0;
}
if (x1 >= cw) {
x1 = cw - 1;
}
if (x0 <= x1) {
uint8_t *dst = data + py * pitch + x0 * bpp;
for (int32_t px = x0; px <= x1; px++) {
canvasPutPixel(dst, color, bpp);
dst += bpp;
}
}
}
}
// ============================================================
// wgtCanvasFillRect
// ============================================================
//
// Draw a filled rectangle using the current pen color.
void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height) {
if (!w || w->type != sTypeId) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
if (width <= 0 || height <= 0) {
return;
}
int32_t bpp = cd->canvasBpp;
int32_t pitch = cd->canvasPitch;
uint8_t *data = cd->pixelData;
int32_t cw = cd->canvasW;
int32_t ch = cd->canvasH;
uint32_t color = cd->penColor;
// Clip to canvas bounds
int32_t x0 = x < 0 ? 0 : x;
int32_t y0 = y < 0 ? 0 : y;
int32_t x1 = x + width > cw ? cw : x + width;
int32_t y1 = y + height > ch ? ch : y + height;
int32_t fillW = x1 - x0;
if (fillW <= 0) {
return;
}
// Find BlitOps for span fill
WidgetT *root = w;
while (root->parent) {
root = root->parent;
}
AppContextT *ctx = (AppContextT *)root->userData;
// Use the optimized spanFill (which uses rep stosl on x86) when available,
// falling back to per-pixel writes if the AppContextT can't be reached.
// The spanFill path is ~4x faster for 32-bit modes because it writes
// 4 bytes per iteration instead of going through the bpp switch.
if (ctx) {
for (int32_t py = y0; py < y1; py++) {
ctx->blitOps.spanFill(data + py * pitch + x0 * bpp, color, fillW);
}
} else {
for (int32_t py = y0; py < y1; py++) {
for (int32_t px = x0; px < x1; px++) {
canvasPutPixel(data + py * pitch + px * bpp, color, bpp);
}
}
}
}
// ============================================================
// wgtCanvasGetPixel
// ============================================================
uint32_t wgtCanvasGetPixel(const WidgetT *w, int32_t x, int32_t y) {
if (!w || w->type != sTypeId) {
return 0;
}
const CanvasDataT *cd = (const CanvasDataT *)w->data;
if (!cd->pixelData) {
return 0;
}
if (x < 0 || x >= cd->canvasW || y < 0 || y >= cd->canvasH) {
return 0;
}
int32_t bpp = cd->canvasBpp;
const uint8_t *src = cd->pixelData + y * cd->canvasPitch + x * bpp;
return canvasGetPixel(src, bpp);
}
// ============================================================
// wgtCanvasSetPixel
// ============================================================
void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color) {
if (!w || w->type != sTypeId) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
if (x < 0 || x >= cd->canvasW || y < 0 || y >= cd->canvasH) {
return;
}
int32_t bpp = cd->canvasBpp;
uint8_t *dst = cd->pixelData + y * cd->canvasPitch + x * bpp;
canvasPutPixel(dst, color, bpp);
}
// ============================================================
// widgetCanvasDestroy
// ============================================================
void widgetCanvasDestroy(WidgetT *w) {
CanvasDataT *cd = (CanvasDataT *)w->data;
free(cd->pixelData);
free(cd);
}
// ============================================================
// widgetCanvasCalcMinSize
// ============================================================
// The canvas requests exactly its pixel dimensions plus the sunken bevel
// border. The font parameter is unused since the canvas has no text content.
// The canvas is not designed to scale -- it reports its exact size as the
// minimum, and the layout engine should respect that.
void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
CanvasDataT *cd = (CanvasDataT *)w->data;
w->calcMinW = cd->canvasW + CANVAS_BORDER * 2;
w->calcMinH = cd->canvasH + CANVAS_BORDER * 2;
}
// ============================================================
// widgetCanvasOnMouse
// ============================================================
// Mouse handler: translates widget-space coordinates to canvas-space (subtracting
// border offset) and invokes the application's mouse callback. The sDrawingCanvas
// global tracks whether this is a drag (mouse was already down on this canvas)
// vs a new click, so the callback can distinguish between starting a new stroke
// and continuing one.
void widgetCanvasOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
CanvasDataT *cd = (CanvasDataT *)hit->data;
if (!cd->onMouse) {
return;
}
// Convert widget coords to canvas coords
int32_t cx = vx - hit->x - CANVAS_BORDER;
int32_t cy = vy - hit->y - CANVAS_BORDER;
sDragWidget = hit;
cd->lastX = -1;
cd->lastY = -1;
cd->lastX = cx;
cd->lastY = cy;
cd->onMouse(hit, cx, cy, false);
wgtInvalidatePaint(hit);
}
// ============================================================
// widgetCanvasOnDragUpdate
// ============================================================
static void widgetCanvasOnDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->onMouse) {
return;
}
int32_t cx = vx - w->x - CANVAS_BORDER;
int32_t cy = vy - w->y - CANVAS_BORDER;
cd->lastX = cx;
cd->lastY = cy;
cd->onMouse(w, cx, cy, true);
wgtInvalidatePaint(w);
}
// ============================================================
// widgetCanvasPaint
// ============================================================
// Paint: draws a sunken bevel border then blits the canvas buffer. Because
// the canvas stores pixels in the display's native format, rectCopy is a
// straight memcpy per scanline -- no per-pixel conversion needed. This makes
// repaint essentially free relative to the display bandwidth.
void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
// Draw a sunken bevel border around the canvas
BevelStyleT sunken;
sunken.highlight = colors->windowShadow;
sunken.shadow = colors->windowHighlight;
sunken.face = 0;
sunken.width = CANVAS_BORDER;
drawBevel(d, ops, w->x, w->y, w->w, w->h, &sunken);
// Blit the canvas data inside the border
int32_t imgW = cd->canvasW;
int32_t imgH = cd->canvasH;
int32_t dx = w->x + CANVAS_BORDER;
int32_t dy = w->y + CANVAS_BORDER;
rectCopy(d, ops, dx, dy,
cd->pixelData, cd->canvasPitch,
0, 0, imgW, imgH);
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassCanvas = {
.version = WGT_CLASS_VERSION,
.flags = 0,
.handlers = {
[WGT_METHOD_PAINT] = (void *)widgetCanvasPaint,
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCanvasCalcMinSize,
[WGT_METHOD_ON_MOUSE] = (void *)widgetCanvasOnMouse,
[WGT_METHOD_ON_DRAG_UPDATE] = (void *)widgetCanvasOnDragUpdate,
[WGT_METHOD_DESTROY] = (void *)widgetCanvasDestroy,
}
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h) {
if (!parent || w <= 0 || h <= 0) {
return NULL;
}
// Find the AppContextT to get display format
WidgetT *root = parent;
while (root->parent) {
root = root->parent;
}
AppContextT *ctx = (AppContextT *)root->userData;
if (!ctx) {
return NULL;
}
const DisplayT *d = &ctx->display;
int32_t bpp = d->format.bytesPerPixel;
int32_t pitch = w * bpp;
uint8_t *data = (uint8_t *)malloc(pitch * h);
if (!data) {
return NULL;
}
// Fill with white using span fill for performance
uint32_t white = packColor(d, 255, 255, 255);
BlitOpsT canvasOps;
drawInit(&canvasOps, d);
for (int32_t y = 0; y < h; y++) {
canvasOps.spanFill(data + y * pitch, white, w);
}
WidgetT *wgt = widgetAlloc(parent, sTypeId);
if (wgt) {
wgt->contentOffX = CANVAS_BORDER;
wgt->contentOffY = CANVAS_BORDER;
CanvasDataT *cd = (CanvasDataT *)calloc(1, sizeof(CanvasDataT));
cd->pixelData = data;
cd->canvasW = w;
cd->canvasH = h;
cd->canvasPitch = pitch;
cd->canvasBpp = bpp;
cd->penColor = packColor(d, 0, 0, 0);
cd->penSize = 1;
cd->lastX = -1;
cd->lastY = -1;
wgt->data = cd;
} else {
free(data);
}
return wgt;
}
void wgtCanvasResize(WidgetT *w, int32_t newW, int32_t newH) {
if (!w || w->type != sTypeId || newW <= 0 || newH <= 0) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd || (cd->canvasW == newW && cd->canvasH == newH)) {
return;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return;
}
const DisplayT *d = &ctx->display;
int32_t bpp = d->format.bytesPerPixel;
int32_t pitch = newW * bpp;
uint8_t *data = (uint8_t *)malloc(pitch * newH);
if (!data) {
return;
}
// Fill with white
uint32_t white = packColor(d, 255, 255, 255);
BlitOpsT canvasOps;
drawInit(&canvasOps, d);
for (int32_t y = 0; y < newH; y++) {
canvasOps.spanFill(data + y * pitch, white, newW);
}
free(cd->pixelData);
cd->pixelData = data;
cd->canvasW = newW;
cd->canvasH = newH;
cd->canvasPitch = pitch;
cd->canvasBpp = bpp;
wgtInvalidatePaint(w);
}
int32_t wgtCanvasLoad(WidgetT *w, const char *path) {
if (!w || w->type != sTypeId || !path) {
return -1;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return -1;
}
int32_t imgW;
int32_t imgH;
int32_t pitch;
uint8_t *data = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
if (!data) {
return -1;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
free(cd->pixelData);
cd->pixelData = data;
cd->canvasW = imgW;
cd->canvasH = imgH;
cd->canvasPitch = pitch;
cd->canvasBpp = ctx->display.format.bytesPerPixel;
return 0;
}
int32_t wgtCanvasSave(WidgetT *w, const char *path) {
if (!w || w->type != sTypeId || !path) {
return -1;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return -1;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return -1;
}
return dvxSaveImage(ctx, cd->pixelData, cd->canvasW, cd->canvasH, cd->canvasPitch, path);
}
void wgtCanvasSetMouseCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag)) {
if (w && w->type == sTypeId) {
CanvasDataT *cd = (CanvasDataT *)w->data;
cd->onMouse = cb;
}
}
void wgtCanvasSetPenColor(WidgetT *w, uint32_t color) {
if (w && w->type == sTypeId) {
CanvasDataT *cd = (CanvasDataT *)w->data;
cd->penColor = color;
}
}
void wgtCanvasSetPenSize(WidgetT *w, int32_t size) {
if (w && w->type == sTypeId && size > 0) {
CanvasDataT *cd = (CanvasDataT *)w->data;
cd->penSize = size;
}
}
void wgtCanvasDrawText(WidgetT *w, int32_t x, int32_t y, const char *text) {
if (!w || w->type != sTypeId || !text) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return;
}
const BitmapFontT *font = &ctx->font;
int32_t cw = font->charWidth;
int32_t ch = font->charHeight;
int32_t bpp = cd->canvasBpp;
for (int32_t ci = 0; text[ci]; ci++) {
int32_t cx = x + ci * cw;
if (cx + cw <= 0 || cx >= cd->canvasW || y + ch <= 0 || y >= cd->canvasH) {
continue;
}
int32_t idx = (uint8_t)text[ci] - font->firstChar;
if (idx < 0 || idx >= font->numChars) {
continue;
}
const uint8_t *glyph = font->glyphData + idx * ch;
for (int32_t row = 0; row < ch; row++) {
int32_t py = y + row;
if (py < 0 || py >= cd->canvasH) {
continue;
}
uint8_t bits = glyph[row];
if (bits == 0) {
continue;
}
for (int32_t col = 0; col < cw; col++) {
int32_t px = cx + col;
if (px < 0 || px >= cd->canvasW) {
continue;
}
if (bits & (0x80 >> col)) {
if (bpp == 2) {
*(uint16_t *)(cd->pixelData + py * cd->canvasPitch + px * 2) = (uint16_t)cd->penColor;
} else if (bpp == 4) {
*(uint32_t *)(cd->pixelData + py * cd->canvasPitch + px * 4) = cd->penColor;
} else {
cd->pixelData[py * cd->canvasPitch + px] = (uint8_t)cd->penColor;
}
}
}
}
}
wgtInvalidatePaint(w);
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, int32_t w, int32_t h);
void (*clear)(WidgetT *w, uint32_t color);
void (*setPenColor)(WidgetT *w, uint32_t color);
void (*setPenSize)(WidgetT *w, int32_t size);
void (*setMouseCallback)(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag));
int32_t (*save)(WidgetT *w, const char *path);
int32_t (*load)(WidgetT *w, const char *path);
void (*drawLine)(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
void (*drawRect)(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
void (*fillRect)(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
void (*fillCircle)(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
void (*setPixel)(WidgetT *w, int32_t x, int32_t y, uint32_t color);
uint32_t (*getPixel)(const WidgetT *w, int32_t x, int32_t y);
void (*drawText)(WidgetT *w, int32_t x, int32_t y, const char *text);
void (*resize)(WidgetT *w, int32_t newW, int32_t newH);
} sApi = {
.create = wgtCanvas,
.clear = wgtCanvasClear,
.setPenColor = wgtCanvasSetPenColor,
.setPenSize = wgtCanvasSetPenSize,
.setMouseCallback = wgtCanvasSetMouseCallback,
.save = wgtCanvasSave,
.load = wgtCanvasLoad,
.drawLine = wgtCanvasDrawLine,
.drawRect = wgtCanvasDrawRect,
.fillRect = wgtCanvasFillRect,
.fillCircle = wgtCanvasFillCircle,
.setPixel = wgtCanvasSetPixel,
.getPixel = wgtCanvasGetPixel,
.drawText = wgtCanvasDrawText,
.resize = wgtCanvasResize
};
static const WgtMethodDescT sMethods[] = {
{ "Clear", WGT_SIG_INT, (void *)basClear },
{ "DrawLine", WGT_SIG_INT5, (void *)basDrawLine },
{ "DrawRect", WGT_SIG_INT5, (void *)basDrawRect },
{ "DrawText", WGT_SIG_INT_INT_STR,(void *)wgtCanvasDrawText },
{ "FillCircle", WGT_SIG_INT4, (void *)basFillCircle },
{ "FillRect", WGT_SIG_INT5, (void *)basFillRect },
{ "GetPixel", WGT_SIG_RET_INT_INT_INT, (void *)basGetPixel },
{ "Load", WGT_SIG_STR, (void *)wgtCanvasLoad },
{ "Save", WGT_SIG_STR, (void *)wgtCanvasSave },
{ "Resize", WGT_SIG_INT_INT, (void *)wgtCanvasResize },
{ "SetPixel", WGT_SIG_INT3, (void *)basSetPixel },
};
static const WgtIfaceT sIface = {
.basName = "PictureBox",
.props = NULL,
.propCount = 0,
.methods = sMethods,
.methodCount = 11,
.events = NULL,
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_INT_INT,
.createArgs = { 64, 64 },
.defaultEvent = "Click",
.namePrefix = "Picture"
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassCanvas);
wgtRegisterApi("canvas", &sApi);
wgtRegisterIface("canvas", &sIface);
}