DVX_GUI/dvx/dvxDraw.c

1047 lines
32 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// dvx_draw.c — Layer 2: Drawing primitives for DVX GUI (optimized)
#include "dvxDraw.h"
#include "platform/dvxPlatform.h"
#include <string.h>
// ============================================================
// Prototypes
// ============================================================
char accelParse(const char *text);
static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *w, int32_t *h);
static inline void putPixel(uint8_t *dst, uint32_t color, int32_t bpp);
// Bit lookup tables — avoids per-pixel shift on 486 (40+ cycle savings per shift)
static const uint8_t sGlyphBit[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
static const uint16_t sMaskBit[16] = {0x8000, 0x4000, 0x2000, 0x1000, 0x0800, 0x0400, 0x0200, 0x0100, 0x0080, 0x0040, 0x0020, 0x0010, 0x0008, 0x0004, 0x0002, 0x0001};
// ============================================================
// accelParse
// ============================================================
char accelParse(const char *text) {
if (!text) {
return 0;
}
while (*text) {
if (*text == '&') {
text++;
if (*text == '&') {
// Escaped && — literal &, not an accelerator
text++;
continue;
}
if (*text && *text != '&') {
char ch = *text;
if (ch >= 'A' && ch <= 'Z') {
return (char)(ch + 32);
}
if (ch >= 'a' && ch <= 'z') {
return ch;
}
if (ch >= '0' && ch <= '9') {
return ch;
}
return ch;
}
break;
}
text++;
}
return 0;
}
// ============================================================
// clipRect
// ============================================================
static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *w, int32_t *h) {
int32_t cx2 = d->clipX + d->clipW;
int32_t cy2 = d->clipY + d->clipH;
int32_t rx1 = *x;
int32_t ry1 = *y;
int32_t rx2 = rx1 + *w;
int32_t ry2 = ry1 + *h;
if (__builtin_expect(rx1 < d->clipX, 0)) { rx1 = d->clipX; }
if (__builtin_expect(ry1 < d->clipY, 0)) { ry1 = d->clipY; }
if (__builtin_expect(rx2 > cx2, 0)) { rx2 = cx2; }
if (__builtin_expect(ry2 > cy2, 0)) { ry2 = cy2; }
*x = rx1;
*y = ry1;
*w = rx2 - rx1;
*h = ry2 - ry1;
}
// ============================================================
// drawBevel
// ============================================================
void drawBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const BevelStyleT *style) {
int32_t bw = style->width;
// Fill interior if requested
if (style->face != 0) {
rectFill(d, ops, x + bw, y + bw, w - bw * 2, h - bw * 2, style->face);
}
// Fast path for the common bevel widths (1 and 2)
// Directly emit spans instead of calling drawHLine->rectFill->clipRect per line
if (bw == 2) {
// Top 2 highlight lines
rectFill(d, ops, x, y, w, 1, style->highlight);
rectFill(d, ops, x + 1, y + 1, w - 2, 1, style->highlight);
// Left 2 highlight columns
rectFill(d, ops, x, y + 1, 1, h - 1, style->highlight);
rectFill(d, ops, x + 1, y + 2, 1, h - 3, style->highlight);
// Bottom 2 shadow lines
rectFill(d, ops, x, y + h - 1, w, 1, style->shadow);
rectFill(d, ops, x + 1, y + h - 2, w - 2, 1, style->shadow);
// Right 2 shadow columns
rectFill(d, ops, x + w - 1, y + 1, 1, h - 2, style->shadow);
rectFill(d, ops, x + w - 2, y + 2, 1, h - 4, style->shadow);
} else if (bw == 1) {
rectFill(d, ops, x, y, w, 1, style->highlight);
rectFill(d, ops, x, y + 1, 1, h - 1, style->highlight);
rectFill(d, ops, x, y + h - 1, w, 1, style->shadow);
rectFill(d, ops, x + w - 1, y + 1, 1, h - 2, style->shadow);
} else {
for (int32_t i = 0; i < bw; i++) {
rectFill(d, ops, x + i, y + i, w - i * 2, 1, style->highlight);
}
for (int32_t i = 0; i < bw; i++) {
rectFill(d, ops, x + i, y + i + 1, 1, h - i * 2 - 1, style->highlight);
}
for (int32_t i = 0; i < bw; i++) {
rectFill(d, ops, x + i, y + h - 1 - i, w - i * 2, 1, style->shadow);
}
for (int32_t i = 0; i < bw; i++) {
rectFill(d, ops, x + w - 1 - i, y + i + 1, 1, h - i * 2 - 2, style->shadow);
}
}
}
// ============================================================
// drawChar
// ============================================================
int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, char ch, uint32_t fg, uint32_t bg, bool opaque) {
int32_t cw = font->charWidth;
int32_t chh = font->charHeight;
// Quick reject: entirely outside clip rect
if (__builtin_expect(x + cw <= d->clipX || x >= d->clipX + d->clipW || y + chh <= d->clipY || y >= d->clipY + d->clipH, 0)) {
return cw;
}
int32_t idx = (uint8_t)ch - font->firstChar;
if (__builtin_expect(idx < 0 || idx >= font->numChars, 0)) {
if (opaque) {
rectFill(d, ops, x, y, cw, chh, bg);
}
return cw;
}
const uint8_t *glyph = font->glyphData + idx * chh;
int32_t bpp = ops->bytesPerPixel;
int32_t pitch = d->pitch;
// Calculate clipped row/col bounds once
int32_t clipX1 = d->clipX;
int32_t clipX2 = d->clipX + d->clipW;
int32_t clipY1 = d->clipY;
int32_t clipY2 = d->clipY + d->clipH;
int32_t rowStart = 0;
int32_t rowEnd = chh;
if (y < clipY1) { rowStart = clipY1 - y; }
if (y + chh > clipY2) { rowEnd = clipY2 - y; }
int32_t colStart = 0;
int32_t colEnd = cw;
if (x < clipX1) { colStart = clipX1 - x; }
if (x + cw > clipX2) { colEnd = clipX2 - x; }
// Unclipped fast path: full 8-pixel character cell with direct bit
// tests eliminates loop overhead and sGlyphBit[] lookups (Item 4)
bool unclipped = (colStart == 0 && colEnd == cw);
if (opaque) {
// Opaque mode: fill entire cell with bg, then overwrite fg pixels
if (unclipped && bpp == 4) {
for (int32_t row = rowStart; row < rowEnd; row++) {
uint32_t *dst32 = (uint32_t *)(d->backBuf + (y + row) * pitch + x * 4);
uint8_t bits = glyph[row];
dst32[0] = (bits & 0x80) ? fg : bg;
dst32[1] = (bits & 0x40) ? fg : bg;
dst32[2] = (bits & 0x20) ? fg : bg;
dst32[3] = (bits & 0x10) ? fg : bg;
dst32[4] = (bits & 0x08) ? fg : bg;
dst32[5] = (bits & 0x04) ? fg : bg;
dst32[6] = (bits & 0x02) ? fg : bg;
dst32[7] = (bits & 0x01) ? fg : bg;
}
} else if (unclipped && bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
uint16_t bg16 = (uint16_t)bg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint16_t *dst16 = (uint16_t *)(d->backBuf + (y + row) * pitch + x * 2);
uint8_t bits = glyph[row];
dst16[0] = (bits & 0x80) ? fg16 : bg16;
dst16[1] = (bits & 0x40) ? fg16 : bg16;
dst16[2] = (bits & 0x20) ? fg16 : bg16;
dst16[3] = (bits & 0x10) ? fg16 : bg16;
dst16[4] = (bits & 0x08) ? fg16 : bg16;
dst16[5] = (bits & 0x04) ? fg16 : bg16;
dst16[6] = (bits & 0x02) ? fg16 : bg16;
dst16[7] = (bits & 0x01) ? fg16 : bg16;
}
} else {
// Clipped path or 8bpp: spanFill bg then overwrite fg
for (int32_t row = rowStart; row < rowEnd; row++) {
int32_t py = y + row;
uint8_t *dst = d->backBuf + py * pitch + (x + colStart) * bpp;
ops->spanFill(dst, bg, colEnd - colStart);
uint8_t bits = glyph[row];
if (bits == 0) {
continue;
}
dst = d->backBuf + py * pitch + x * bpp;
if (bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & sGlyphBit[col]) {
*(uint16_t *)(dst + col * 2) = fg16;
}
}
} else if (bpp == 4) {
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & sGlyphBit[col]) {
*(uint32_t *)(dst + col * 4) = fg;
}
}
} else {
uint8_t fg8 = (uint8_t)fg;
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & sGlyphBit[col]) {
dst[col] = fg8;
}
}
}
}
}
} else {
// Transparent mode: only write foreground pixels
if (unclipped && bpp == 4) {
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) {
continue;
}
uint32_t *dst32 = (uint32_t *)(d->backBuf + (y + row) * pitch + x * 4);
if (bits & 0x80) { dst32[0] = fg; }
if (bits & 0x40) { dst32[1] = fg; }
if (bits & 0x20) { dst32[2] = fg; }
if (bits & 0x10) { dst32[3] = fg; }
if (bits & 0x08) { dst32[4] = fg; }
if (bits & 0x04) { dst32[5] = fg; }
if (bits & 0x02) { dst32[6] = fg; }
if (bits & 0x01) { dst32[7] = fg; }
}
} else if (unclipped && bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) {
continue;
}
uint16_t *dst16 = (uint16_t *)(d->backBuf + (y + row) * pitch + x * 2);
if (bits & 0x80) { dst16[0] = fg16; }
if (bits & 0x40) { dst16[1] = fg16; }
if (bits & 0x20) { dst16[2] = fg16; }
if (bits & 0x10) { dst16[3] = fg16; }
if (bits & 0x08) { dst16[4] = fg16; }
if (bits & 0x04) { dst16[5] = fg16; }
if (bits & 0x02) { dst16[6] = fg16; }
if (bits & 0x01) { dst16[7] = fg16; }
}
} else {
// Clipped path or 8bpp
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) {
continue;
}
int32_t py = y + row;
uint8_t *dst = d->backBuf + py * pitch + x * bpp;
if (bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & sGlyphBit[col]) {
*(uint16_t *)(dst + col * 2) = fg16;
}
}
} else if (bpp == 4) {
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & sGlyphBit[col]) {
*(uint32_t *)(dst + col * 4) = fg;
}
}
} else {
uint8_t fg8 = (uint8_t)fg;
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & sGlyphBit[col]) {
dst[col] = fg8;
}
}
}
}
}
}
return cw;
}
// ============================================================
// drawTextN
// ============================================================
//
// Renders exactly 'count' characters from a buffer in one pass.
// Same idea as drawTermRow but for uniform fg/bg text runs.
// Avoids per-character function call overhead, redundant clip
// calculation, and spanFill startup costs.
void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t count, uint32_t fg, uint32_t bg, bool opaque) {
if (count <= 0) {
return;
}
int32_t cw = font->charWidth;
int32_t ch = font->charHeight;
int32_t bpp = ops->bytesPerPixel;
int32_t pitch = d->pitch;
// Row-level clip: reject if entirely outside vertically
int32_t clipX1 = d->clipX;
int32_t clipX2 = d->clipX + d->clipW;
int32_t clipY1 = d->clipY;
int32_t clipY2 = d->clipY + d->clipH;
if (y + ch <= clipY1 || y >= clipY2) {
return;
}
int32_t totalW = count * cw;
if (x + totalW <= clipX1 || x >= clipX2) {
return;
}
// Vertical clip for glyph scanlines
int32_t rowStart = 0;
int32_t rowEnd = ch;
if (y < clipY1) { rowStart = clipY1 - y; }
if (y + ch > clipY2) { rowEnd = clipY2 - y; }
// Horizontal clip: find first and last visible column (character index)
int32_t firstChar = 0;
int32_t lastChar = count;
if (x < clipX1) {
firstChar = (clipX1 - x) / cw;
}
if (x + totalW > clipX2) {
lastChar = (clipX2 - x + cw - 1) / cw;
if (lastChar > count) { lastChar = count; }
}
// Per-pixel clip for partially visible edge characters
int32_t edgeColStart = 0;
if (x + firstChar * cw < clipX1) {
edgeColStart = clipX1 - (x + firstChar * cw);
}
if (opaque) {
// Opaque: fill background for the entire visible span once per scanline,
// then overlay foreground glyph pixels
int32_t fillX1 = x + firstChar * cw;
int32_t fillX2 = x + lastChar * cw;
if (fillX1 < clipX1) { fillX1 = clipX1; }
if (fillX2 > clipX2) { fillX2 = clipX2; }
int32_t fillW = fillX2 - fillX1;
if (fillW > 0) {
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t *dst = d->backBuf + (y + row) * pitch + fillX1 * bpp;
ops->spanFill(dst, bg, fillW);
}
}
}
// Render glyph foreground pixels
for (int32_t ci = firstChar; ci < lastChar; ci++) {
int32_t cx = x + ci * cw;
int32_t cStart = 0;
int32_t cEnd = cw;
if (ci == firstChar) {
cStart = edgeColStart;
}
if (cx + cw > clipX2) {
cEnd = clipX2 - cx;
}
int32_t idx = (uint8_t)text[ci] - font->firstChar;
const uint8_t *glyph = NULL;
if (idx >= 0 && idx < font->numChars) {
glyph = font->glyphData + idx * ch;
}
if (!glyph) {
continue;
}
if (bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) { continue; }
uint16_t *dst = (uint16_t *)(d->backBuf + (y + row) * pitch + cx * 2);
for (int32_t p = cStart; p < cEnd; p++) {
if (bits & sGlyphBit[p]) {
dst[p] = fg16;
}
}
}
} else if (bpp == 4) {
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) { continue; }
uint32_t *dst = (uint32_t *)(d->backBuf + (y + row) * pitch + cx * 4);
for (int32_t p = cStart; p < cEnd; p++) {
if (bits & sGlyphBit[p]) {
dst[p] = fg;
}
}
}
} else {
uint8_t fg8 = (uint8_t)fg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) { continue; }
uint8_t *dst = d->backBuf + (y + row) * pitch + cx;
for (int32_t p = cStart; p < cEnd; p++) {
if (bits & sGlyphBit[p]) {
dst[p] = fg8;
}
}
}
}
}
}
// ============================================================
// drawFocusRect
// ============================================================
void drawFocusRect(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
int32_t bpp = ops->bytesPerPixel;
int32_t pitch = d->pitch;
int32_t clipX1 = d->clipX;
int32_t clipX2 = d->clipX + d->clipW;
int32_t clipY1 = d->clipY;
int32_t clipY2 = d->clipY + d->clipH;
int32_t x2 = x + w - 1;
int32_t y2 = y + h - 1;
// Top edge
if (y >= clipY1 && y < clipY2) {
for (int32_t px = x; px <= x2; px += 2) {
if (px >= clipX1 && px < clipX2) {
putPixel(d->backBuf + y * pitch + px * bpp, color, bpp);
}
}
}
// Bottom edge
if (y2 >= clipY1 && y2 < clipY2 && y2 != y) {
int32_t parity = (y2 - y) & 1;
for (int32_t px = x + parity; px <= x2; px += 2) {
if (px >= clipX1 && px < clipX2) {
putPixel(d->backBuf + y2 * pitch + px * bpp, color, bpp);
}
}
}
// Left edge (skip corners already drawn)
if (x >= clipX1 && x < clipX2) {
for (int32_t py = y + 2; py < y2; py += 2) {
if (py >= clipY1 && py < clipY2) {
putPixel(d->backBuf + py * pitch + x * bpp, color, bpp);
}
}
}
// Right edge (skip corners already drawn)
if (x2 >= clipX1 && x2 < clipX2 && x2 != x) {
int32_t parity = (x2 - x) & 1;
for (int32_t py = y + 2 - parity; py < y2; py += 2) {
if (py >= clipY1 && py < clipY2) {
putPixel(d->backBuf + py * pitch + x2 * bpp, color, bpp);
}
}
}
}
// ============================================================
// drawHLine
// ============================================================
void drawHLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, uint32_t color) {
rectFill(d, ops, x, y, w, 1, color);
}
// ============================================================
// drawInit
// ============================================================
void drawInit(BlitOpsT *ops, const DisplayT *d) {
ops->bytesPerPixel = d->format.bytesPerPixel;
ops->pitch = d->pitch;
switch (d->format.bytesPerPixel) {
case 1:
ops->spanFill = platformSpanFill8;
ops->spanCopy = platformSpanCopy8;
break;
case 2:
ops->spanFill = platformSpanFill16;
ops->spanCopy = platformSpanCopy16;
break;
case 4:
ops->spanFill = platformSpanFill32;
ops->spanCopy = platformSpanCopy32;
break;
default:
ops->spanFill = platformSpanFill8;
ops->spanCopy = platformSpanCopy8;
break;
}
}
// ============================================================
// drawMaskedBitmap
// ============================================================
void drawMaskedBitmap(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *andMask, const uint16_t *xorData, uint32_t fgColor, uint32_t bgColor) {
int32_t bpp = ops->bytesPerPixel;
int32_t pitch = d->pitch;
// Pre-clip row/col bounds
int32_t clipX1 = d->clipX;
int32_t clipX2 = d->clipX + d->clipW;
int32_t clipY1 = d->clipY;
int32_t clipY2 = d->clipY + d->clipH;
int32_t rowStart = 0;
int32_t rowEnd = h;
if (y < clipY1) { rowStart = clipY1 - y; }
if (y + h > clipY2) { rowEnd = clipY2 - y; }
int32_t colStart = 0;
int32_t colEnd = w;
if (x < clipX1) { colStart = clipX1 - x; }
if (x + w > clipX2) { colEnd = clipX2 - x; }
if (colStart >= colEnd || rowStart >= rowEnd) {
return;
}
// Pre-compute column mask once (loop-invariant)
uint16_t colMask = 0;
for (int32_t col = colStart; col < colEnd; col++) {
colMask |= sMaskBit[col];
}
for (int32_t row = rowStart; row < rowEnd; row++) {
uint16_t mask = andMask[row];
uint16_t data = xorData[row];
// Skip fully transparent rows
if ((mask & colMask) == colMask) {
continue;
}
int32_t py = y + row;
uint8_t *dst = d->backBuf + py * pitch + x * bpp;
if (bpp == 2) {
uint16_t fg16 = (uint16_t)fgColor;
uint16_t bg16 = (uint16_t)bgColor;
for (int32_t col = colStart; col < colEnd; col++) {
uint16_t bit = sMaskBit[col];
if (!(mask & bit)) {
*(uint16_t *)(dst + col * 2) = (data & bit) ? fg16 : bg16;
}
}
} else if (bpp == 4) {
for (int32_t col = colStart; col < colEnd; col++) {
uint16_t bit = sMaskBit[col];
if (!(mask & bit)) {
*(uint32_t *)(dst + col * 4) = (data & bit) ? fgColor : bgColor;
}
}
} else {
uint8_t fg8 = (uint8_t)fgColor;
uint8_t bg8 = (uint8_t)bgColor;
for (int32_t col = colStart; col < colEnd; col++) {
uint16_t bit = sMaskBit[col];
if (!(mask & bit)) {
dst[col] = (data & bit) ? fg8 : bg8;
}
}
}
}
}
// ============================================================
// drawTermRow
// ============================================================
//
// Renders an entire row of terminal character cells in one pass.
// lineData points to (ch, attr) pairs. palette is a 16-entry
// packed-color table. This avoids per-character function call
// overhead, redundant clip calculation, and spanFill startup
// costs that make drawChar expensive when called 80× per row.
void drawTermRow(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, int32_t cols, const uint8_t *lineData, const uint32_t *palette, bool blinkVisible, int32_t cursorCol) {
int32_t cw = font->charWidth;
int32_t ch = font->charHeight;
int32_t bpp = ops->bytesPerPixel;
int32_t pitch = d->pitch;
// Row-level clip: reject if entirely outside vertically
int32_t clipX1 = d->clipX;
int32_t clipX2 = d->clipX + d->clipW;
int32_t clipY1 = d->clipY;
int32_t clipY2 = d->clipY + d->clipH;
if (y + ch <= clipY1 || y >= clipY2) {
return;
}
// Vertical clip for glyph scanlines
int32_t rowStart = 0;
int32_t rowEnd = ch;
if (y < clipY1) { rowStart = clipY1 - y; }
if (y + ch > clipY2) { rowEnd = clipY2 - y; }
// Horizontal clip: find first and last visible column
int32_t rowW = cols * cw;
int32_t firstCol = 0;
int32_t lastCol = cols;
if (x + rowW <= clipX1 || x >= clipX2) {
return;
}
if (x < clipX1) {
firstCol = (clipX1 - x) / cw;
}
if (x + rowW > clipX2) {
lastCol = (clipX2 - x + cw - 1) / cw;
if (lastCol > cols) { lastCol = cols; }
}
// Per-column clip for partially visible edge cells
int32_t edgeColStart = 0;
if (x + firstCol * cw < clipX1) {
edgeColStart = clipX1 - (x + firstCol * cw);
}
// Render each visible cell
for (int32_t col = firstCol; col < lastCol; col++) {
uint8_t gch = lineData[col * 2];
uint8_t attr = lineData[col * 2 + 1];
uint32_t fg = palette[attr & 0x0F];
uint32_t bg = palette[(attr >> 4) & 0x07];
// Blink: hide text during off phase
if ((attr & 0x80) && !blinkVisible) {
fg = bg;
}
// Cursor: invert colors
if (col == cursorCol) {
uint32_t tmp = fg;
fg = bg;
bg = tmp;
}
int32_t cx = x + col * cw;
// Determine per-cell horizontal clip
int32_t cStart = 0;
int32_t cEnd = cw;
if (col == firstCol) {
cStart = edgeColStart;
}
if (cx + cw > clipX2) {
cEnd = clipX2 - cx;
}
// Look up glyph data
int32_t idx = (uint8_t)gch - font->firstChar;
const uint8_t *glyph = NULL;
if (idx >= 0 && idx < font->numChars) {
glyph = font->glyphData + idx * ch;
}
// Render scanlines
if (bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
uint16_t bg16 = (uint16_t)bg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint16_t *dst = (uint16_t *)(d->backBuf + (y + row) * pitch + cx * 2);
uint8_t bits = glyph ? glyph[row] : 0;
for (int32_t p = cStart; p < cEnd; p++) {
dst[p] = (bits & sGlyphBit[p]) ? fg16 : bg16;
}
}
} else if (bpp == 4) {
for (int32_t row = rowStart; row < rowEnd; row++) {
uint32_t *dst = (uint32_t *)(d->backBuf + (y + row) * pitch + cx * 4);
uint8_t bits = glyph ? glyph[row] : 0;
for (int32_t p = cStart; p < cEnd; p++) {
dst[p] = (bits & sGlyphBit[p]) ? fg : bg;
}
}
} else {
uint8_t fg8 = (uint8_t)fg;
uint8_t bg8 = (uint8_t)bg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t *dst = d->backBuf + (y + row) * pitch + cx;
uint8_t bits = glyph ? glyph[row] : 0;
for (int32_t p = cStart; p < cEnd; p++) {
dst[p] = (bits & sGlyphBit[p]) ? fg8 : bg8;
}
}
}
}
}
// ============================================================
// drawText
// ============================================================
void drawText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, uint32_t fg, uint32_t bg, bool opaque) {
int32_t cw = font->charWidth;
int32_t clipX2 = d->clipX + d->clipW;
while (*text) {
// Early out if we've moved past the right clip edge
if (__builtin_expect(x >= clipX2, 0)) {
break;
}
// Skip characters entirely to the left of clip
if (__builtin_expect(x + cw <= d->clipX, 0)) {
x += cw;
text++;
continue;
}
x += drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
text++;
}
}
// ============================================================
// drawTextAccel
// ============================================================
void drawTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, uint32_t fg, uint32_t bg, bool opaque) {
int32_t cw = font->charWidth;
int32_t clipX2 = d->clipX + d->clipW;
while (*text) {
if (__builtin_expect(x >= clipX2, 0)) {
break;
}
if (*text == '&') {
text++;
if (*text == '&') {
// Escaped && — draw literal &
if (x + cw > d->clipX) {
drawChar(d, ops, font, x, y, '&', fg, bg, opaque);
}
x += cw;
text++;
continue;
}
if (*text) {
// Accelerator character — draw it then underline
if (x + cw > d->clipX) {
drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
drawHLine(d, ops, x, y + font->charHeight - 1, cw, fg);
}
x += cw;
text++;
continue;
}
break;
}
if (x + cw > d->clipX) {
drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
}
x += cw;
text++;
}
}
// ============================================================
// drawVLine
// ============================================================
void drawVLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t h, uint32_t color) {
(void)ops;
// Inline single-pixel-wide fill to avoid rectFill overhead for narrow lines
if (__builtin_expect(x < d->clipX || x >= d->clipX + d->clipW, 0)) {
return;
}
int32_t y1 = y;
int32_t y2 = y + h;
if (y1 < d->clipY) { y1 = d->clipY; }
if (y2 > d->clipY + d->clipH) { y2 = d->clipY + d->clipH; }
if (y1 >= y2) { return; }
int32_t bpp = d->format.bytesPerPixel;
uint8_t *dst = d->backBuf + y1 * d->pitch + x * bpp;
int32_t pitch = d->pitch;
if (bpp == 2) {
uint16_t c16 = (uint16_t)color;
for (int32_t i = y1; i < y2; i++) {
*(uint16_t *)dst = c16;
dst += pitch;
}
} else if (bpp == 4) {
for (int32_t i = y1; i < y2; i++) {
*(uint32_t *)dst = color;
dst += pitch;
}
} else {
uint8_t c8 = (uint8_t)color;
for (int32_t i = y1; i < y2; i++) {
*dst = c8;
dst += pitch;
}
}
}
// ============================================================
// putPixel
// ============================================================
static inline void putPixel(uint8_t *dst, uint32_t color, int32_t bpp) {
if (bpp == 2) {
*(uint16_t *)dst = (uint16_t)color;
} else if (bpp == 4) {
*(uint32_t *)dst = color;
} else {
*dst = (uint8_t)color;
}
}
// ============================================================
// rectCopy
// ============================================================
void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h) {
int32_t bpp = ops->bytesPerPixel;
// Clip to display clip rect
int32_t origDstX = dstX;
int32_t origDstY = dstY;
clipRect(d, &dstX, &dstY, &w, &h);
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
return;
}
// Adjust source position by the amount we clipped
srcX += dstX - origDstX;
srcY += dstY - origDstY;
const uint8_t *srcRow = srcBuf + srcY * srcPitch + srcX * bpp;
uint8_t *dstRow = d->backBuf + dstY * d->pitch + dstX * bpp;
int32_t rowBytes = w * bpp;
int32_t dstPitch = d->pitch;
// For full-width copies aligned to pitch, use memcpy (may optimize to rep movsd)
if (rowBytes == dstPitch && rowBytes == srcPitch) {
memcpy(dstRow, srcRow, rowBytes * h);
} else {
for (int32_t i = 0; i < h; i++) {
memcpy(dstRow, srcRow, rowBytes);
srcRow += srcPitch;
dstRow += dstPitch;
}
}
}
// ============================================================
// rectFill
// ============================================================
void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
clipRect(d, &x, &y, &w, &h);
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
return;
}
uint8_t *row = d->backBuf + y * d->pitch + x * d->format.bytesPerPixel;
int32_t pitch = d->pitch;
for (int32_t i = 0; i < h; i++) {
ops->spanFill(row, color, w);
row += pitch;
}
}
// ============================================================
// textWidth
// ============================================================
int32_t textWidth(const BitmapFontT *font, const char *text) {
int32_t w = 0;
while (*text) {
w += font->charWidth;
text++;
}
return w;
}
// ============================================================
// textWidthAccel
// ============================================================
int32_t textWidthAccel(const BitmapFontT *font, const char *text) {
int32_t w = 0;
while (*text) {
if (*text == '&') {
text++;
if (*text == '&') {
// Escaped && — counts as one character
w += font->charWidth;
text++;
continue;
}
if (*text) {
// Accelerator character — counts as one character, & is skipped
w += font->charWidth;
text++;
continue;
}
break;
}
w += font->charWidth;
text++;
}
return w;
}